diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 8b83f83a..ef21d365 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: Bug report description: Create a report to help us improve title: "[Bug] " +labels: ["bug"] body: - type: checkboxes id: ensure @@ -12,8 +13,8 @@ Please verify that you've followed these steps " options: - label: " -确保你使用的是**本仓库**最新的的 clash 或 clash Alpha 版本 -Ensure you are using the latest version of Clash or Clash Premium from **this repository**. +确保你使用的是**本仓库**最新的的 mihomo 或 mihomo Alpha 版本 +Ensure you are using the latest version of Mihomo or Mihomo Alpha from **this repository**. " required: true - label: " @@ -37,14 +38,14 @@ I have read the [documentation](https://wiki.metacubex.one/) and was unable to s " required: true - label: " -这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题 -This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash. +这是 Mihomo 核心的问题,并非我所使用的 Mihomo 衍生版本(如 OpenMihomo、KoolMihomo 等)的特定问题 +This is an issue of the Mihomo core *per se*, not to the derivatives of Mihomo, like OpenMihomo or KoolMihomo. " required: true - type: input attributes: - label: Clash version - description: "use `clash -v`" + label: Mihomo version + description: "use `mihomo -v`" validations: required: true - type: dropdown @@ -60,20 +61,20 @@ This is an issue of the Clash core *per se*, not to the derivatives of Clash, li - type: textarea attributes: render: yaml - label: "Clash config" + label: "Mihomo config" description: " -在下方附上 Clash core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等) -Paste the Clash core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port) +在下方附上 Mihomo core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等) +Paste the Mihomo core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port) " validations: required: true - type: textarea attributes: render: shell - label: Clash log + label: Mihomo log description: " -在下方附上 Clash Core 的日志,log level 使用 DEBUG -Paste the Clash core log below with the log level set to `DEBUG`. +在下方附上 Mihomo Core 的日志,log level 使用 DEBUG +Paste the Mihomo core log below with the log level set to `DEBUG`. " - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..75d37b63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: mihomo Community Support + url: https://github.com/MetaCubeX/mihomo/discussions + about: Please ask and answer questions about mihomo here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c8f70b19..7987526c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Feature request description: Suggest an idea for this project title: "[Feature] " +labels: ["enhancement"] body: - type: checkboxes id: ensure @@ -24,7 +25,7 @@ I have read the [documentation](https://wiki.metacubex.one/) and was unable to s - type: textarea attributes: label: Description - description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽? + description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Mihomo Core 的行为是什麽? validations: required: true - type: textarea diff --git a/.github/rename-cgo.sh b/.github/rename-cgo.sh index a0d736de..2bfdb3c6 100644 --- a/.github/rename-cgo.sh +++ b/.github/rename-cgo.sh @@ -5,25 +5,25 @@ 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 + mv $FILENAME mihomo-darwin-arm64-cgo elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then echo "rename darwin-10.16-amd64 $FILENAME" - mv $FILENAME clash.meta-darwin-amd64-cgo + mv $FILENAME mihomo-darwin-amd64-cgo elif [[ $FILENAME =~ "windows-4.0-386" ]];then echo "rename windows 386 $FILENAME" - mv $FILENAME clash.meta-windows-386-cgo.exe + mv $FILENAME mihomo-windows-386-cgo.exe elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then echo "rename windows amd64 $FILENAME" - mv $FILENAME clash.meta-windows-amd64-cgo.exe - elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then - echo "rename clash.meta-linux-arm-5 $FILENAME" - mv $FILENAME clash.meta-linux-armv5-cgo - elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then - echo "rename clash.meta-linux-arm-6 $FILENAME" - mv $FILENAME clash.meta-linux-armv6-cgo - elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then - echo "rename clash.meta-linux-arm-7 $FILENAME" - mv $FILENAME clash.meta-linux-armv7-cgo + mv $FILENAME mihomo-windows-amd64-cgo.exe + elif [[ $FILENAME =~ "mihomo-linux-arm-5" ]];then + echo "rename mihomo-linux-arm-5 $FILENAME" + mv $FILENAME mihomo-linux-armv5-cgo + elif [[ $FILENAME =~ "mihomo-linux-arm-6" ]];then + echo "rename mihomo-linux-arm-6 $FILENAME" + mv $FILENAME mihomo-linux-armv6-cgo + elif [[ $FILENAME =~ "mihomo-linux-arm-7" ]];then + echo "rename mihomo-linux-arm-7 $FILENAME" + mv $FILENAME mihomo-linux-armv7-cgo elif [[ $FILENAME =~ "linux" ]];then echo "rename linux $FILENAME" mv $FILENAME $FILENAME-cgo diff --git a/.github/rename-go120.sh b/.github/rename-go120.sh new file mode 100644 index 00000000..eddb1769 --- /dev/null +++ b/.github/rename-go120.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +FILENAMES=$(ls) +for FILENAME in $FILENAMES +do + if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then + mv $FILENAME ${FILENAME}-go120 + elif [[ $FILENAME =~ ".exe" ]];then + mv $FILENAME ${FILENAME%.*}-go120.exe + else echo "skip $FILENAME" + fi +done \ No newline at end of file diff --git a/.github/workflows/Delete.yml b/.github/workflows/Delete.yml new file mode 100644 index 00000000..31f4a265 --- /dev/null +++ b/.github/workflows/Delete.yml @@ -0,0 +1,16 @@ +name: Delete old workflow runs +on: + schedule: + - cron: '0 0 1 * *' +# Run monthly, at 00:00 on the 1st day of month. + +jobs: + del_runs: + runs-on: ubuntu-latest + steps: + - name: Delete workflow runs + uses: GitRML/delete-workflow-runs@main + with: + token: ${{ secrets.AUTH_PAT }} + repository: ${{ github.repository }} + retain_days: 30 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1283400c..94163a94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ on: - Alpha concurrency: - group: ${{ github.ref }}-${{ github.workflow }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: @@ -48,7 +48,7 @@ jobs: target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat", id: "4", } - - { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" } + - { type: "WithoutCGO", target: "linux-386 linux-riscv64 linux-loong64", id: "5" } - { type: "WithoutCGO", target: "freebsd-386 freebsd-amd64 freebsd-arm64", @@ -69,17 +69,23 @@ jobs: target: "darwin-amd64 darwin-arm64 android-arm64", id: "9", } - - { type: "WithCGO", target: "windows/*", id: "1" } - - { type: "WithCGO", target: "linux/386", id: "2" } - - { type: "WithCGO", target: "linux/amd64", id: "3" } - - { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" } - - { type: "WithCGO", target: "linux/arm,", id: "5" } - - { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" } - - { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" } - - { type: "WithCGO", target: "linux/mips64", id: "8" } - - { type: "WithCGO", target: "linux/mips64le", id: "9" } - - { type: "WithCGO", target: "darwin-10.16/*", id: "10" } - - { type: "WithCGO", target: "android", id: "11" } + # only for test + - { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" } + # Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016. + - { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" } + # Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later. + - { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" } +# - { 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 @@ -107,34 +113,39 @@ jobs: - name: Set ENV run: | sudo timedatectl set-timezone "Asia/Shanghai" - echo "NAME=clash.meta" >> $GITHUB_ENV - echo "REPO=${{ github.repository }}" >> $GITHUB_ENV - echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV echo "BUILDTIME=$(date)" >> $GITHUB_ENV - echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV shell: bash - name: Set ENV run: | echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV - echo "LDFLAGS=-X 'github.com/Dreamacro/clash/constant.Version=${VERSION}' -X 'github.com/Dreamacro/clash/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV + echo "LDFLAGS=-X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV + echo "GOTOOLCHAIN=local" >> $GITHUB_ENV shell: bash - name: Setup Go + if: ${{ matrix.job.type!='WithoutCGO-GO120' }} uses: actions/setup-go@v4 with: go-version: "1.21" check-latest: true + - name: Setup Go + if: ${{ matrix.job.type=='WithoutCGO-GO120' }} + uses: actions/setup-go@v4 + with: + go-version: "1.20" + check-latest: true + - name: Test - if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }} + if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }} run: | go test ./... - name: Build WithoutCGO - if: ${{ matrix.job.type=='WithoutCGO' }} + if: ${{ matrix.job.type!='WithCGO' }} env: - NAME: Clash.Meta + NAME: mihomo BINDIR: bin run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }} @@ -142,9 +153,8 @@ jobs: if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} id: setup-ndk with: - ndk-version: r26 - add-to-path: false - local-cache: true + ndk-version: r26b + add-to-path: true - name: Build Android if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} @@ -180,6 +190,17 @@ jobs: ls -la cd .. + - name: Rename + if: ${{ matrix.job.type=='WithoutCGO-GO120' }} + run: | + cd bin + ls -la + cp ../.github/rename-go120.sh ./ + bash ./rename-go120.sh + rm ./rename-go120.sh + ls -la + cd .. + - name: Zip if: ${{ success() }} run: | @@ -204,7 +225,7 @@ jobs: Upload-Prerelease: permissions: write-all - if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }} + if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }} needs: [Build] runs-on: ubuntu-latest steps: @@ -241,7 +262,7 @@ jobs: Release created at ${{ env.BUILDTIME }} Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
- [我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/Clash.Meta/wiki/FAQ) + [我应该下载哪个文件? / Which file should I download?](https://github.com/MetaCubeX/mihomo/wiki/FAQ) [查看文档 / Docs](https://metacubex.github.io/Meta-Docs/) EOF @@ -298,7 +319,7 @@ jobs: body_path: release.md Docker: - if: ${{ github.event_name != 'pull_request' }} + if: ${{ !startsWith(github.event_name, 'pull_request') }} permissions: write-all needs: [Build] runs-on: ubuntu-latest @@ -331,14 +352,15 @@ jobs: id: meta uses: docker/metadata-action@v4 with: - images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}} + images: ${{ env.REGISTRY }}/${{ github.repository }} + - name: Show files run: | ls . ls bin/ - - name: Log into registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + + - name: login to docker REGISTRY + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKER_HUB_USER }} @@ -348,7 +370,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile @@ -356,8 +378,7 @@ jobs: platforms: | linux/386 linux/amd64 - linux/arm64/v8 + linux/arm64 linux/arm/v7 - # linux/riscv64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/trigger-cmfa-update.yml b/.github/workflows/trigger-cmfa-update.yml new file mode 100644 index 00000000..d767657e --- /dev/null +++ b/.github/workflows/trigger-cmfa-update.yml @@ -0,0 +1,33 @@ +name: Trigger CMFA Update +on: + workflow_dispatch: + push: + paths-ignore: + - "docs/**" + - "README.md" + - ".github/ISSUE_TEMPLATE/**" + branches: + - Alpha + tags: + - "v*" + pull_request_target: + branches: + - Alpha + +jobs: + # Send "core-updated" to MetaCubeX/MihomoForAndroid to trigger update-dependencies + trigger-CMFA-update: + runs-on: ubuntu-latest + steps: + - uses: tibdex/github-app-token@v1 + id: generate-token + with: + app_id: ${{ secrets.MAINTAINER_APPID }} + private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }} + + - name: Trigger update-dependencies + run: | + curl -X POST https://api.github.com/repos/MetaCubeX/MihomoForAndroid/dispatches \ + -H "Accept: application/vnd.github.everest-preview+json" \ + -H "Authorization: token ${{ steps.generate-token.outputs.token }}" \ + -d '{"event_type": "core-updated"}' \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml index f5b67397..1de71ad8 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -11,7 +11,7 @@ linters-settings: custom-order: true sections: - standard - - prefix(github.com/Dreamacro/clash) + - prefix(github.com/metacubex/mihomo) - default staticcheck: go: '1.19' diff --git a/Dockerfile b/Dockerfile index 6c5a91f9..c9cd56b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,25 +3,25 @@ ARG TARGETPLATFORM RUN echo "I'm building for $TARGETPLATFORM" RUN apk add --no-cache gzip && \ - mkdir /clash-config && \ - wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \ - wget -O /clash-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \ - wget -O /clash-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat + mkdir /mihomo-config && \ + wget -O /mihomo-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \ + wget -O /mihomo-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \ + wget -O /mihomo-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat -COPY docker/file-name.sh /clash/file-name.sh -WORKDIR /clash +COPY docker/file-name.sh /mihomo/file-name.sh +WORKDIR /mihomo COPY bin/ bin/ RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \ - mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test + mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test FROM alpine:latest -LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta" +LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo" RUN apk add --no-cache ca-certificates tzdata iptables -VOLUME ["/root/.config/clash/"] +VOLUME ["/root/.config/mihomo/"] -COPY --from=builder /clash-config/ /root/.config/clash/ -COPY --from=builder /clash/clash /clash -RUN chmod +x /clash -ENTRYPOINT [ "/clash" ] +COPY --from=builder /mihomo-config/ /root/.config/mihomo/ +COPY --from=builder /mihomo/mihomo /mihomo +RUN chmod +x /mihomo +ENTRYPOINT [ "/mihomo" ] diff --git a/Makefile b/Makefile index 028b986c..f6ffcae5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -NAME=clash.meta +NAME=mihomo BINDIR=bin BRANCH=$(shell git branch --show-current) ifeq ($(BRANCH),Alpha) @@ -12,8 +12,8 @@ VERSION=$(shell git rev-parse --short HEAD) endif BUILDTIME=$(shell date -u) -GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ - -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ +GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/metacubex/mihomo/constant.Version=$(VERSION)" \ + -X "github.com/metacubex/mihomo/constant.BuildTime=$(BUILDTIME)" \ -w -s -buildid=' PLATFORM_LIST = \ diff --git a/README.md b/README.md index 51cecc2d..8c82536c 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,17 @@
Meta Kernel
-

Another Clash Kernel.

+

Another Mihomo Kernel.

- - + + - - - + + + - +

@@ -21,270 +21,52 @@ ## Features - Local HTTP/HTTPS/SOCKS server with authentication support -- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections +- VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. - Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency - Remote providers, allowing users to get node lists remotely instead of hard-coding in config -- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. +- Netfilter TCP redirecting. Deploy Mihomo on your Internet gateway with `iptables`. - Comprehensive HTTP RESTful API controller ## Dashboard -We made an official web dashboard providing first class support for this project, check it out -at [metacubexd](https://github.com/MetaCubeX/metacubexd) +A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd). -## Wiki +## Configration example -Configuration examples can be found -at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be -found [Clash.Meta Wiki](https://clash-meta.wiki). +Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/mihomo/blob/Alpha/docs/config.yaml). -## Build +## Docs -You should install [golang](https://go.dev) first. +Documentation can be found in [mihomo Docs](https://wiki.metacubex.one/). -Then get the source code of Clash.Meta: +## For development + +Requirements: +[Go 1.20 or newer](https://go.dev/dl/) + +Build mihomo: ```shell -git clone https://github.com/MetaCubeX/Clash.Meta.git -cd Clash.Meta && go mod download +git clone https://github.com/MetaCubeX/mihomo.git +cd mihomo && go mod download +go build ``` -If you can't visit GitHub, you should set proxy first: +Set go proxy if a connection to GitHub is not possible: ```shell go env -w GOPROXY=https://goproxy.io,direct ``` -Now you can build it: - -```shell -go build -``` - -If you need gvisor for tun stack, build with: +Build with gvisor tun stack: ```shell go build -tags with_gvisor ``` - - - - ### IPTABLES configuration Work on Linux OS which supported `iptables` @@ -298,61 +80,9 @@ iptables: inbound-interface: eth0 # detect the inbound interface, default is 'lo' ``` -### General installation guide for Linux - -- 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 - -Run Meta Kernel by user `clash-meta` as a daemon. - -Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`: - -``` -[Unit] -Description=Clash-Meta Daemon, Another Clash Kernel. -After=network.target NetworkManager.service systemd-networkd.service iwd.service - -[Service] -Type=simple -User=clash-meta -Group=clash-meta -LimitNPROC=500 -LimitNOFILE=1000000 -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE -Restart=always -ExecStartPre=/usr/bin/sleep 1s -ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta - -[Install] -WantedBy=multi-user.target -``` - -Launch clash-meta daemon on system startup with: - -```shell -$ systemctl enable Clash-Meta -``` - -Launch clash-meta daemon immediately with: - -```shell -$ systemctl start Clash-Meta -``` - -## Development - -If you want to build an application that uses clash as a library, check out -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 +Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug API. ## Credits @@ -368,4 +98,4 @@ API. This software is released under the GPL-3.0 license. -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large) diff --git a/adapter/adapter.go b/adapter/adapter.go index e9ce59bb..da6171f1 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -12,14 +12,12 @@ import ( "strconv" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/queue" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - - "github.com/puzpuzpuz/xsync/v2" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/queue" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" + "github.com/puzpuzpuz/xsync/v3" ) var UnifiedDelay = atomic.NewBool(false) @@ -28,22 +26,16 @@ const ( defaultHistoriesNum = 10 ) -type extraProxyState struct { +type internalProxyState struct { + alive atomic.Bool history *queue.Queue[C.DelayHistory] - alive *atomic.Bool } type Proxy struct { C.ProxyAdapter + alive atomic.Bool history *queue.Queue[C.DelayHistory] - alive *atomic.Bool - url string - extra *xsync.MapOf[string, *extraProxyState] -} - -// Alive implements C.Proxy -func (p *Proxy) Alive() bool { - return p.alive.Load() + extra *xsync.MapOf[string, *internalProxyState] } // AliveForTestUrl implements C.Proxy @@ -88,7 +80,6 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { for _, item := range queueM { histories = append(histories, item) } - return histories } @@ -99,11 +90,6 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { if state, ok := p.extra.Load(url); ok { queueM = state.history.Copy() } - - if queueM == nil { - queueM = p.history.Copy() - } - histories := []C.DelayHistory{} for _, item := range queueM { histories = append(histories, item) @@ -111,61 +97,46 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { return histories } -func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory { - extraHistory := map[string][]C.DelayHistory{} - - p.extra.Range(func(k string, v *extraProxyState) bool { +// ExtraDelayHistories return all delay histories for each test URL +// implements C.Proxy +func (p *Proxy) ExtraDelayHistories() map[string]C.ProxyState { + histories := map[string]C.ProxyState{} + p.extra.Range(func(k string, v *internalProxyState) bool { testUrl := k state := v - histories := []C.DelayHistory{} queueM := state.history.Copy() + var history []C.DelayHistory for _, item := range queueM { - histories = append(histories, item) + history = append(history, item) } - extraHistory[testUrl] = histories - + histories[testUrl] = C.ProxyState{ + Alive: state.alive.Load(), + History: history, + } return true }) - return extraHistory + return histories } -// LastDelay return last history record. if proxy is not alive, return the max value of uint16. +// LastDelayForTestUrl return last history record of the specified URL. if proxy is not alive, return the max value of uint16. // implements C.Proxy -func (p *Proxy) LastDelay() (delay uint16) { - var max uint16 = 0xffff - if !p.alive.Load() { - return max - } - - history := p.history.Last() - if history.Delay == 0 { - return max - } - return history.Delay -} - -// LastDelayForTestUrl implements C.Proxy func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) { - var max uint16 = 0xffff + var maxDelay uint16 = 0xffff - alive := p.alive.Load() - history := p.history.Last() + alive := false + var history C.DelayHistory if state, ok := p.extra.Load(url); ok { alive = state.alive.Load() history = state.history.Last() } - if !alive { - return max - } - - if history.Delay == 0 { - return max + if !alive || history.Delay == 0 { + return maxDelay } return history.Delay } @@ -180,8 +151,8 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping := map[string]any{} _ = json.Unmarshal(inner, &mapping) mapping["history"] = p.DelayHistory() - mapping["extra"] = p.ExtraDelayHistory() - mapping["alive"] = p.Alive() + mapping["extra"] = p.ExtraDelayHistories() + mapping["alive"] = p.alive.Load() mapping["name"] = p.Name() mapping["udp"] = p.SupportUDP() mapping["xudp"] = p.SupportXUDP() @@ -191,54 +162,35 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { // URLTest get the delay for the specified URL // implements C.Proxy -func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) { +func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) { defer func() { alive := err == nil - store = p.determineFinalStoreType(store, url) - - switch store { - case C.OriginalHistory: - p.alive.Store(alive) - record := C.DelayHistory{Time: time.Now()} - if alive { - record.Delay = t - } - p.history.Put(record) - if p.history.Len() > defaultHistoriesNum { - p.history.Pop() - } - - // test URL configured by the proxy provider - if len(p.url) == 0 { - p.url = url - } - case C.ExtraHistory: - record := C.DelayHistory{Time: time.Now()} - if alive { - record.Delay = t - } - p.history.Put(record) - if p.history.Len() > defaultHistoriesNum { - p.history.Pop() - } - - state, ok := p.extra.Load(url) - if !ok { - state = &extraProxyState{ - history: queue.New[C.DelayHistory](defaultHistoriesNum), - alive: atomic.NewBool(true), - } - p.extra.Store(url, state) - } - - state.alive.Store(alive) - state.history.Put(record) - if state.history.Len() > defaultHistoriesNum { - state.history.Pop() - } - default: - log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t) + record := C.DelayHistory{Time: time.Now()} + if alive { + record.Delay = t } + + p.alive.Store(alive) + p.history.Put(record) + if p.history.Len() > defaultHistoriesNum { + p.history.Pop() + } + + state, ok := p.extra.Load(url) + if !ok { + state = &internalProxyState{ + history: queue.New[C.DelayHistory](defaultHistoriesNum), + alive: atomic.NewBool(true), + } + p.extra.Store(url, state) + } + + state.alive.Store(alive) + state.history.Put(record) + if state.history.Len() > defaultHistoriesNum { + state.history.Pop() + } + }() unifiedDelay := UnifiedDelay.Load() @@ -315,8 +267,7 @@ func NewProxy(adapter C.ProxyAdapter) *Proxy { ProxyAdapter: adapter, history: queue.New[C.DelayHistory](defaultHistoriesNum), alive: atomic.NewBool(true), - url: "", - extra: xsync.NewMapOf[*extraProxyState]()} + extra: xsync.NewMapOf[string, *internalProxyState]()} } func urlToMetadata(rawURL string) (addr C.Metadata, err error) { @@ -349,24 +300,3 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) { } return } - -func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType { - if store != C.DropHistory { - return store - } - - if len(p.url) == 0 || url == p.url { - return C.OriginalHistory - } - - if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum { - return C.ExtraHistory - } - - _, ok := p.extra.Load(url) - if ok { - return C.ExtraHistory - } - - return store -} diff --git a/adapter/inbound/addition.go b/adapter/inbound/addition.go index 47307ed7..a9896c8c 100644 --- a/adapter/inbound/addition.go +++ b/adapter/inbound/addition.go @@ -1,13 +1,17 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" + "net" + + C "github.com/metacubex/mihomo/constant" ) type Addition func(metadata *C.Metadata) -func (a Addition) Apply(metadata *C.Metadata) { - a(metadata) +func ApplyAdditions(metadata *C.Metadata, additions ...Addition) { + for _, addition := range additions { + addition(metadata) + } } func WithInName(name string) Addition { @@ -33,3 +37,29 @@ func WithSpecialProxy(specialProxy string) Addition { metadata.SpecialProxy = specialProxy } } + +func WithDstAddr(addr net.Addr) Addition { + return func(metadata *C.Metadata) { + _ = metadata.SetRemoteAddr(addr) + } +} + +func WithSrcAddr(addr net.Addr) Addition { + return func(metadata *C.Metadata) { + m := C.Metadata{} + if err := m.SetRemoteAddr(addr);err ==nil{ + metadata.SrcIP = m.DstIP + metadata.SrcPort = m.DstPort + } + } +} + +func WithInAddr(addr net.Addr) Addition { + return func(metadata *C.Metadata) { + m := C.Metadata{} + if err := m.SetRemoteAddr(addr);err ==nil{ + metadata.InIP = m.DstIP + metadata.InPort = m.DstPort + } + } +} diff --git a/adapter/inbound/auth.go b/adapter/inbound/auth.go new file mode 100644 index 00000000..984c9bd6 --- /dev/null +++ b/adapter/inbound/auth.go @@ -0,0 +1,45 @@ +package inbound + +import ( + "net" + "net/netip" + + C "github.com/metacubex/mihomo/constant" +) + +var skipAuthPrefixes []netip.Prefix + +func SetSkipAuthPrefixes(prefixes []netip.Prefix) { + skipAuthPrefixes = prefixes +} + +func SkipAuthPrefixes() []netip.Prefix { + return skipAuthPrefixes +} + +func SkipAuthRemoteAddr(addr net.Addr) bool { + m := C.Metadata{} + if err := m.SetRemoteAddr(addr); err != nil { + return false + } + return skipAuth(m.AddrPort().Addr()) +} + +func SkipAuthRemoteAddress(addr string) bool { + m := C.Metadata{} + if err := m.SetRemoteAddress(addr); err != nil { + return false + } + return skipAuth(m.AddrPort().Addr()) +} + +func skipAuth(addr netip.Addr) bool { + if addr.IsValid() { + for _, prefix := range skipAuthPrefixes { + if prefix.Contains(addr.Unmap()) { + return true + } + } + } + return false +} diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go index b1b881ce..8f912fbe 100644 --- a/adapter/inbound/http.go +++ b/adapter/inbound/http.go @@ -3,26 +3,18 @@ package inbound import ( "net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) // NewHTTP receive normal http request and return HTTPContext -func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext { +func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { metadata := parseSocksAddr(target) metadata.NetWork = C.TCP metadata.Type = C.HTTP - for _, addition := range additions { - addition.Apply(metadata) - } - if ip, port, err := parseAddr(source); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } - if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { - metadata.InIP = ip - metadata.InPort = port - } - return context.NewConnContext(conn, metadata) + metadata.RawSrcAddr = srcConn.RemoteAddr() + metadata.RawDstAddr = srcConn.LocalAddr() + ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr())) + ApplyAdditions(metadata, additions...) + return conn, metadata } diff --git a/adapter/inbound/https.go b/adapter/inbound/https.go index 485e72bb..55f6731a 100644 --- a/adapter/inbound/https.go +++ b/adapter/inbound/https.go @@ -4,24 +4,14 @@ import ( "net" "net/http" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" + C "github.com/metacubex/mihomo/constant" ) // NewHTTPS receive CONNECT request and return ConnContext -func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext { +func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { metadata := parseHTTPAddr(request) metadata.Type = C.HTTPS - for _, addition := range additions { - addition.Apply(metadata) - } - if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } - if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { - metadata.InIP = ip - metadata.InPort = port - } - return context.NewConnContext(conn, metadata) + ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) + ApplyAdditions(metadata, additions...) + return conn, metadata } diff --git a/adapter/inbound/ipfilter.go b/adapter/inbound/ipfilter.go new file mode 100644 index 00000000..7fa218c1 --- /dev/null +++ b/adapter/inbound/ipfilter.go @@ -0,0 +1,57 @@ +package inbound + +import ( + "net" + "net/netip" + + C "github.com/metacubex/mihomo/constant" +) + +var lanAllowedIPs []netip.Prefix +var lanDisAllowedIPs []netip.Prefix + +func SetAllowedIPs(prefixes []netip.Prefix) { + lanAllowedIPs = prefixes +} + +func SetDisAllowedIPs(prefixes []netip.Prefix) { + lanDisAllowedIPs = prefixes +} + +func AllowedIPs() []netip.Prefix { + return lanAllowedIPs +} + +func DisAllowedIPs() []netip.Prefix { + return lanDisAllowedIPs +} + +func IsRemoteAddrDisAllowed(addr net.Addr) bool { + m := C.Metadata{} + if err := m.SetRemoteAddr(addr); err != nil { + return false + } + return isAllowed(m.AddrPort().Addr().Unmap()) && !isDisAllowed(m.AddrPort().Addr().Unmap()) +} + +func isAllowed(addr netip.Addr) bool { + if addr.IsValid() { + for _, prefix := range lanAllowedIPs { + if prefix.Contains(addr) { + return true + } + } + } + return false +} + +func isDisAllowed(addr netip.Addr) bool { + if addr.IsValid() { + for _, prefix := range lanDisAllowedIPs { + if prefix.Contains(addr) { + return true + } + } + } + return false +} diff --git a/adapter/inbound/packet.go b/adapter/inbound/packet.go index 44e5e1a7..a10d402e 100644 --- a/adapter/inbound/packet.go +++ b/adapter/inbound/packet.go @@ -1,42 +1,22 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) -// PacketAdapter is a UDP Packet adapter for socks/redir/tun -type PacketAdapter struct { - C.UDPPacket - metadata *C.Metadata -} - -// Metadata returns destination metadata -func (s *PacketAdapter) Metadata() *C.Metadata { - return s.metadata -} - // NewPacket is PacketAdapter generator -func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter { +func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) { metadata := parseSocksAddr(target) metadata.NetWork = C.UDP metadata.Type = source - for _, addition := range additions { - addition.Apply(metadata) - } - if ip, port, err := parseAddr(packet.LocalAddr()); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } + metadata.RawSrcAddr = packet.LocalAddr() + metadata.RawDstAddr = metadata.UDPAddr() + ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr())) if p, ok := packet.(C.UDPPacketInAddr); ok { - if ip, port, err := parseAddr(p.InAddr()); err == nil { - metadata.InIP = ip - metadata.InPort = port - } + ApplyAdditions(metadata, WithInAddr(p.InAddr())) } + ApplyAdditions(metadata, additions...) - return &PacketAdapter{ - packet, - metadata, - } + return packet, metadata } diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index d75901f1..8cd301f7 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -2,51 +2,17 @@ package inbound import ( "net" - "net/netip" - "strconv" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) // NewSocket receive TCP inbound and return ConnContext -func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext { +func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) { metadata := parseSocksAddr(target) metadata.NetWork = C.TCP metadata.Type = source - for _, addition := range additions { - addition.Apply(metadata) - } - - if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } - if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { - metadata.InIP = ip - metadata.InPort = port - } - - return context.NewConnContext(conn, metadata) -} - -func NewInner(conn net.Conn, address string) *context.ConnContext { - metadata := &C.Metadata{} - metadata.NetWork = C.TCP - metadata.Type = C.INNER - metadata.DNSMode = C.DNSNormal - metadata.Process = C.ClashName - if h, port, err := net.SplitHostPort(address); err == nil { - if port, err := strconv.ParseUint(port, 10, 16); err == nil { - metadata.DstPort = uint16(port) - } - if ip, err := netip.ParseAddr(h); err == nil { - metadata.DstIP = ip - } else { - metadata.Host = h - } - } - - return context.NewConnContext(conn, metadata) + ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) + ApplyAdditions(metadata, additions...) + return conn, metadata } diff --git a/adapter/inbound/util.go b/adapter/inbound/util.go index 626687c0..743337fc 100644 --- a/adapter/inbound/util.go +++ b/adapter/inbound/util.go @@ -1,16 +1,15 @@ package inbound import ( - "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" + "github.com/metacubex/mihomo/common/nnip" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) func parseSocksAddr(target socks5.Addr) *C.Metadata { @@ -62,29 +61,3 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { return metadata } - -func parseAddr(addr net.Addr) (netip.Addr, uint16, error) { - // Filter when net.Addr interface is nil - if addr == nil { - return netip.Addr{}, 0, 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{}, 0, err - } - - var uint16Port uint16 - if port, err := strconv.ParseUint(port, 10, 16); err == nil { - uint16Port = uint16(port) - } - - ip, err := netip.ParseAddr(host) - return ip, uint16Port, err -} diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index ba991bfc..ae8c4651 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -7,10 +7,10 @@ import ( "strings" "syscall" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" ) type Base struct { diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 75e999a6..7def7b20 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,16 +3,18 @@ package outbound import ( "context" "errors" + "fmt" "net/netip" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" ) type Direct struct { *Base + loopBack *loopBackDetector } type DirectOption struct { @@ -22,17 +24,23 @@ type DirectOption struct { // DialContext implements C.ProxyAdapter func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + if d.loopBack.CheckConn(metadata.SourceAddrPort()) { + return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + } opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) if err != nil { return nil, err } N.TCPKeepAlive(c) - return NewConn(c, d), nil + return d.loopBack.NewConn(NewConn(c, d)), nil } // ListenPacketContext implements C.ProxyAdapter func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) { + return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + } // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver) @@ -45,7 +53,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, if err != nil { return nil, err } - return newPacketConn(pc, d), nil + return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil } func NewDirectWithOption(option DirectOption) *Direct { @@ -60,6 +68,7 @@ func NewDirectWithOption(option DirectOption) *Direct { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, + loopBack: newLoopBackDetector(), } } @@ -71,6 +80,7 @@ func NewDirect() *Direct { udp: true, prefer: C.DualStack, }, + loopBack: newLoopBackDetector(), } } @@ -82,5 +92,6 @@ func NewCompatible() *Direct { udp: true, prefer: C.DualStack, }, + loopBack: newLoopBackDetector(), } } diff --git a/adapter/outbound/direct_loopback_detect.go b/adapter/outbound/direct_loopback_detect.go new file mode 100644 index 00000000..410d5a2f --- /dev/null +++ b/adapter/outbound/direct_loopback_detect.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "net/netip" + + "github.com/metacubex/mihomo/common/callback" + C "github.com/metacubex/mihomo/constant" + + "github.com/puzpuzpuz/xsync/v3" +) + +type loopBackDetector struct { + connMap *xsync.MapOf[netip.AddrPort, struct{}] + packetConnMap *xsync.MapOf[netip.AddrPort, struct{}] +} + +func newLoopBackDetector() *loopBackDetector { + return &loopBackDetector{ + connMap: xsync.NewMapOf[netip.AddrPort, struct{}](), + packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](), + } +} + +func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + l.connMap.Store(connAddr, struct{}{}) + return callback.NewCloseCallbackConn(conn, func() { + l.connMap.Delete(connAddr) + }) +} + +func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + l.packetConnMap.Store(connAddr, struct{}{}) + return callback.NewCloseCallbackPacketConn(conn, func() { + l.packetConnMap.Delete(connAddr) + }) +} + +func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool { + if !connAddr.IsValid() { + return false + } + _, ok := l.connMap.Load(connAddr) + return ok +} + +func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool { + if !connAddr.IsValid() { + return false + } + _, ok := l.packetConnMap.Load(connAddr) + return ok +} diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index 19074bb3..b837e49a 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -13,11 +13,11 @@ import ( "net/http" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" ) type Http struct { diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 8a9d6258..dacffd10 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -14,17 +14,17 @@ import ( "github.com/metacubex/quic-go/congestion" M "github.com/sagernet/sing/common/metadata" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion" - "github.com/Dreamacro/clash/transport/hysteria/core" - "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" - "github.com/Dreamacro/clash/transport/hysteria/transport" - "github.com/Dreamacro/clash/transport/hysteria/utils" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion" + "github.com/metacubex/mihomo/transport/hysteria/core" + "github.com/metacubex/mihomo/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix" + "github.com/metacubex/mihomo/transport/hysteria/transport" + "github.com/metacubex/mihomo/transport/hysteria/utils" ) const ( @@ -46,7 +46,7 @@ type Hysteria struct { } func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...)) + tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...)) if err != nil { return nil, err } diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go index 57c15a12..ddd5ccea 100644 --- a/adapter/outbound/hysteria2.go +++ b/adapter/outbound/hysteria2.go @@ -9,12 +9,12 @@ import ( "runtime" "strconv" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - tuicCommon "github.com/Dreamacro/clash/transport/tuic/common" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/sing-quic/hysteria2" @@ -55,7 +55,7 @@ type Hysteria2Option struct { func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { options := h.Base.DialOptions(opts...) h.dialer.SetDialer(dialer.NewDialer(options...)) - c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress())) + c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err } diff --git a/adapter/outbound/reality.go b/adapter/outbound/reality.go index 23314e5f..6711f378 100644 --- a/adapter/outbound/reality.go +++ b/adapter/outbound/reality.go @@ -1,13 +1,13 @@ package outbound import ( + "crypto/ecdh" "encoding/base64" "encoding/hex" "errors" + "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" - - "golang.org/x/crypto/curve25519" + tlsC "github.com/metacubex/mihomo/component/tls" ) type RealityOptions struct { @@ -19,10 +19,16 @@ func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) { if o.PublicKey != "" { config := new(tlsC.RealityConfig) - n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey)) - if err != nil || n != curve25519.ScalarSize { + const x25519ScalarSize = 32 + var publicKey [x25519ScalarSize]byte + n, err := base64.RawURLEncoding.Decode(publicKey[:], []byte(o.PublicKey)) + if err != nil || n != x25519ScalarSize { return nil, errors.New("invalid REALITY public key") } + config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey[:]) + if err != nil { + return nil, fmt.Errorf("fail to create REALITY public key: %w", err) + } n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID)) if err != nil || n > tlsC.RealityMaxShortIDLen { diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index a72dc377..62f4aaa5 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -6,23 +6,41 @@ import ( "net" "time" - "github.com/Dreamacro/clash/common/buf" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/buf" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" ) type Reject struct { *Base + drop bool +} + +type RejectOption struct { + Name string `proxy:"name"` } // DialContext implements C.ProxyAdapter func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + if r.drop { + return NewConn(dropConn{}, r), nil + } return NewConn(nopConn{}, r), nil } // ListenPacketContext implements C.ProxyAdapter func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - return newPacketConn(nopPacketConn{}, r), nil + return newPacketConn(&nopPacketConn{}, r), nil +} + +func NewRejectWithOption(option RejectOption) *Reject { + return &Reject{ + Base: &Base{ + name: option.Name, + tp: C.Direct, + udp: true, + }, + } } func NewReject() *Reject { @@ -36,6 +54,18 @@ func NewReject() *Reject { } } +func NewRejectDrop() *Reject { + return &Reject{ + Base: &Base{ + name: "REJECT-DROP", + tp: C.RejectDrop, + udp: true, + prefer: C.DualStack, + }, + drop: true, + } +} + func NewPass() *Reject { return &Reject{ Base: &Base{ @@ -49,35 +79,29 @@ func NewPass() *Reject { type nopConn struct{} -func (rw nopConn) Read(b []byte) (int, error) { - return 0, io.EOF -} +func (rw nopConn) Read(b []byte) (int, error) { return 0, io.EOF } -func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { - return io.EOF -} +func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF } -func (rw nopConn) Write(b []byte) (int, error) { - return 0, io.EOF -} - -func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { - return io.EOF -} - -func (rw nopConn) Close() error { return nil } -func (rw nopConn) LocalAddr() net.Addr { return nil } -func (rw nopConn) RemoteAddr() net.Addr { return nil } -func (rw nopConn) SetDeadline(time.Time) error { return nil } -func (rw nopConn) SetReadDeadline(time.Time) error { return nil } -func (rw nopConn) SetWriteDeadline(time.Time) error { return nil } +func (rw nopConn) Write(b []byte) (int, error) { return 0, io.EOF } +func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF } +func (rw nopConn) Close() error { return nil } +func (rw nopConn) LocalAddr() net.Addr { return nil } +func (rw nopConn) RemoteAddr() net.Addr { return nil } +func (rw nopConn) SetDeadline(time.Time) error { return nil } +func (rw nopConn) SetReadDeadline(time.Time) error { return nil } +func (rw nopConn) SetWriteDeadline(time.Time) error { return nil } var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0} type nopPacketConn struct{} -func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } -func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } +func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + return len(b), nil +} +func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + return 0, nil, io.EOF +} func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { return nil, nil, nil, io.EOF } @@ -86,3 +110,19 @@ func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4U func (npc nopPacketConn) SetDeadline(time.Time) error { return nil } func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil } func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil } + +type dropConn struct{} + +func (rw dropConn) Read(b []byte) (int, error) { return 0, io.EOF } +func (rw dropConn) ReadBuffer(buffer *buf.Buffer) error { + time.Sleep(C.DefaultDropTime) + return io.EOF +} +func (rw dropConn) Write(b []byte) (int, error) { return 0, io.EOF } +func (rw dropConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF } +func (rw dropConn) Close() error { return nil } +func (rw dropConn) LocalAddr() net.Addr { return nil } +func (rw dropConn) RemoteAddr() net.Addr { return nil } +func (rw dropConn) SetDeadline(time.Time) error { return nil } +func (rw dropConn) SetReadDeadline(time.Time) error { return nil } +func (rw dropConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index f744ec53..98932f0c 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -7,19 +7,20 @@ import ( "net" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/structure" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/restls" - obfs "github.com/Dreamacro/clash/transport/simple-obfs" - shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls" - v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/restls" + obfs "github.com/metacubex/mihomo/transport/simple-obfs" + shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" + v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin" restlsC "github.com/3andne/restls-client-go" shadowsocks "github.com/metacubex/sing-shadowsocks2" + "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/uot" ) @@ -58,14 +59,16 @@ type simpleObfsOption struct { } type v2rayObfsOption struct { - Mode string `obfs:"mode"` - Host string `obfs:"host,omitempty"` - Path string `obfs:"path,omitempty"` - TLS bool `obfs:"tls,omitempty"` - Fingerprint string `obfs:"fingerprint,omitempty"` - Headers map[string]string `obfs:"headers,omitempty"` - SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` - Mux bool `obfs:"mux,omitempty"` + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` + Path string `obfs:"path,omitempty"` + TLS bool `obfs:"tls,omitempty"` + Fingerprint string `obfs:"fingerprint,omitempty"` + Headers map[string]string `obfs:"headers,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Mux bool `obfs:"mux,omitempty"` + V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"` + V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` } type shadowTLSOption struct { @@ -123,9 +126,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada } } if useEarly { - return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil + return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil } else { - return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) } } @@ -185,7 +188,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial if err != nil { return nil, err } - pc = ss.method.DialPacketConn(N.NewBindPacketConn(pc, addr)) + pc = ss.method.DialPacketConn(bufio.NewBindPacketConn(pc, addr)) return newPacketConn(pc, ss), nil } @@ -208,9 +211,9 @@ func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, destination := M.SocksaddrFromNet(metadata.UDPAddr()) if ss.option.UDPOverTCPVersion == uot.LegacyVersion { - return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil } else { - return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), ss), nil } } return nil, C.ErrNotSupport @@ -259,10 +262,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } obfsMode = opts.Mode v2rayOption = &v2rayObfs.Option{ - Host: opts.Host, - Path: opts.Path, - Headers: opts.Headers, - Mux: opts.Mux, + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + V2rayHttpUpgrade: opts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: opts.V2rayHttpUpgradeFastOpen, } if opts.TLS { diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 0f03f86d..07d78047 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -7,16 +7,16 @@ import ( "net" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/shadowsocks/core" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowstream" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/ssr/obfs" - "github.com/Dreamacro/clash/transport/ssr/protocol" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/shadowsocks/core" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowstream" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/ssr/obfs" + "github.com/metacubex/mihomo/transport/ssr/protocol" ) type ShadowSocksR struct { @@ -125,7 +125,7 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { // SSR protocol compatibility - // https://github.com/Dreamacro/clash/pull/2056 + // https://github.com/metacubex/mihomo/pull/2056 if option.Cipher == "none" { option.Cipher = "dummy" } diff --git a/adapter/outbound/singmux.go b/adapter/outbound/singmux.go index c9f50ce9..67267744 100644 --- a/adapter/outbound/singmux.go +++ b/adapter/outbound/singmux.go @@ -5,11 +5,12 @@ import ( "errors" "runtime" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" mux "github.com/sagernet/sing-mux" E "github.com/sagernet/sing/common/exceptions" @@ -25,14 +26,21 @@ type SingMux struct { } type SingMuxOption struct { - Enabled bool `proxy:"enabled,omitempty"` - Protocol string `proxy:"protocol,omitempty"` - MaxConnections int `proxy:"max-connections,omitempty"` - MinStreams int `proxy:"min-streams,omitempty"` - MaxStreams int `proxy:"max-streams,omitempty"` - Padding bool `proxy:"padding,omitempty"` - Statistic bool `proxy:"statistic,omitempty"` - OnlyTcp bool `proxy:"only-tcp,omitempty"` + Enabled bool `proxy:"enabled,omitempty"` + Protocol string `proxy:"protocol,omitempty"` + MaxConnections int `proxy:"max-connections,omitempty"` + MinStreams int `proxy:"min-streams,omitempty"` + MaxStreams int `proxy:"max-streams,omitempty"` + Padding bool `proxy:"padding,omitempty"` + Statistic bool `proxy:"statistic,omitempty"` + OnlyTcp bool `proxy:"only-tcp,omitempty"` + BrutalOpts BrutalOption `proxy:"brutal-opts,omitempty"` +} + +type BrutalOption struct { + Enabled bool `proxy:"enabled,omitempty"` + Up string `proxy:"up,omitempty"` + Down string `proxy:"down,omitempty"` } type ProxyBase interface { @@ -42,7 +50,7 @@ type ProxyBase interface { func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { options := s.base.DialOptions(opts...) s.dialer.SetDialer(dialer.NewDialer(options...)) - c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress())) + c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err } @@ -94,14 +102,23 @@ func closeSingMux(s *SingMux) { } func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) { + // TODO + // "TCP Brutal is only supported on Linux-based systems" + singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic) client, err := mux.NewClient(mux.Options{ Dialer: singDialer, + Logger: log.SingLogger, Protocol: option.Protocol, MaxConnections: option.MaxConnections, MinStreams: option.MinStreams, MaxStreams: option.MaxStreams, Padding: option.Padding, + Brutal: mux.BrutalOptions{ + Enabled: option.BrutalOpts.Enabled, + SendBPS: StringToBps(option.BrutalOpts.Up), + ReceiveBPS: StringToBps(option.BrutalOpts.Down), + }, }) if err != nil { return nil, err diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 16405fcf..76ed4be9 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -6,13 +6,13 @@ import ( "net" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/structure" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - obfs "github.com/Dreamacro/clash/transport/simple-obfs" - "github.com/Dreamacro/clash/transport/snell" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + obfs "github.com/metacubex/mihomo/transport/simple-obfs" + "github.com/metacubex/mihomo/transport/snell" ) type Snell struct { diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 864500c5..c17ee6a7 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -7,14 +7,15 @@ import ( "fmt" "io" "net" + "net/netip" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type Socks5 struct { @@ -136,7 +137,8 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, } } - bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) + udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0)) + bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user) if err != nil { err = fmt.Errorf("client hanshake error: %w", err) return diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 337f2a38..b14761a4 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -8,14 +8,14 @@ import ( "net/http" "strconv" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - tlsC "github.com/Dreamacro/clash/component/tls" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/gun" - "github.com/Dreamacro/clash/transport/trojan" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + tlsC "github.com/metacubex/mihomo/component/tls" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/gun" + "github.com/metacubex/mihomo/transport/trojan" ) type Trojan struct { @@ -53,9 +53,12 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) if t.option.Network == "ws" { host, port, _ := net.SplitHostPort(t.addr) wsOpts := &trojan.WebsocketOption{ - Host: host, - Port: port, - Path: t.option.WSOpts.Path, + Host: host, + Port: port, + Path: t.option.WSOpts.Path, + V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen, + Headers: http.Header{}, } if t.option.SNI != "" { @@ -63,11 +66,9 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) } if len(t.option.WSOpts.Headers) != 0 { - header := http.Header{} for key, value := range t.option.WSOpts.Headers { - header.Add(key, value) + wsOpts.Headers.Add(key, value) } - wsOpts.Headers = header } return t.instance.StreamWebsocketConn(ctx, c, wsOpts) diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index 93e49dc7..666e72fa 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -10,12 +10,12 @@ import ( "strconv" "time" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/tuic" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/tuic" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index b048cd8b..2c85c7c8 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -11,9 +11,9 @@ import ( "strconv" "sync" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) var ( @@ -140,24 +140,25 @@ func StringToBps(s string) uint64 { if m == nil { return 0 } - var n uint64 + var n uint64 = 1 switch m[2] { - case "K": - n = 1 << 10 - case "M": - n = 1 << 20 - case "G": - n = 1 << 30 case "T": - n = 1 << 40 - default: - n = 1 + n *= 1000 + fallthrough + case "G": + n *= 1000 + fallthrough + case "M": + n *= 1000 + fallthrough + case "K": + n *= 1000 } v, _ := strconv.ParseUint(m[1], 10, 64) - n = v * n + n *= v if m[3] == "b" { // Bits, need to convert to bytes - n = n >> 3 + n /= 8 } return n } diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 037f3367..ceeb52a5 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -12,20 +12,20 @@ import ( "strconv" "sync" - "github.com/Dreamacro/clash/common/convert" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - tlsC "github.com/Dreamacro/clash/component/tls" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/gun" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/vless" - "github.com/Dreamacro/clash/transport/vmess" + "github.com/metacubex/mihomo/common/convert" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + tlsC "github.com/metacubex/mihomo/component/tls" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/gun" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/vless" + "github.com/metacubex/mihomo/transport/vmess" vmessSing "github.com/metacubex/sing-vmess" "github.com/metacubex/sing-vmess/packetaddr" @@ -88,13 +88,15 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M case "ws": host, port, _ := net.SplitHostPort(v.addr) wsOpts := &vmess.WebsocketConfig{ - Host: host, - Port: port, - Path: v.option.WSOpts.Path, - MaxEarlyData: v.option.WSOpts.MaxEarlyData, - EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, - ClientFingerprint: v.option.ClientFingerprint, - Headers: http.Header{}, + Host: host, + Port: port, + Path: v.option.WSOpts.Path, + MaxEarlyData: v.option.WSOpts.MaxEarlyData, + EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, + ClientFingerprint: v.option.ClientFingerprint, + Headers: http.Header{}, } if len(v.option.WSOpts.Headers) != 0 { diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index db654580..c1c981ce 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -11,17 +11,17 @@ import ( "strings" "sync" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - tlsC "github.com/Dreamacro/clash/component/tls" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/ntp" - "github.com/Dreamacro/clash/transport/gun" - clashVMess "github.com/Dreamacro/clash/transport/vmess" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + tlsC "github.com/metacubex/mihomo/component/tls" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/ntp" + "github.com/metacubex/mihomo/transport/gun" + mihomoVMess "github.com/metacubex/mihomo/transport/vmess" vmess "github.com/metacubex/sing-vmess" "github.com/metacubex/sing-vmess/packetaddr" @@ -87,10 +87,12 @@ type GrpcOptions struct { } type WSOptions struct { - Path string `proxy:"path,omitempty"` - Headers map[string]string `proxy:"headers,omitempty"` - MaxEarlyData int `proxy:"max-early-data,omitempty"` - EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` + Path string `proxy:"path,omitempty"` + Headers map[string]string `proxy:"headers,omitempty"` + MaxEarlyData int `proxy:"max-early-data,omitempty"` + EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"` + V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"` + V2rayHttpUpgradeFastOpen bool `proxy:"v2ray-http-upgrade-fast-open,omitempty"` } // StreamConnContext implements C.ProxyAdapter @@ -104,14 +106,16 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M switch v.option.Network { case "ws": host, port, _ := net.SplitHostPort(v.addr) - wsOpts := &clashVMess.WebsocketConfig{ - Host: host, - Port: port, - Path: v.option.WSOpts.Path, - MaxEarlyData: v.option.WSOpts.MaxEarlyData, - EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, - ClientFingerprint: v.option.ClientFingerprint, - Headers: http.Header{}, + wsOpts := &mihomoVMess.WebsocketConfig{ + Host: host, + Port: port, + Path: v.option.WSOpts.Path, + MaxEarlyData: v.option.WSOpts.MaxEarlyData, + EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, + ClientFingerprint: v.option.ClientFingerprint, + Headers: http.Header{}, } if len(v.option.WSOpts.Headers) != 0 { @@ -139,12 +143,12 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M wsOpts.TLSConfig.ServerName = host } } - c, err = clashVMess.StreamWebsocketConn(ctx, c, wsOpts) + c, err = mihomoVMess.StreamWebsocketConn(ctx, c, wsOpts) case "http": // readability first, so just copy default TLS logic if v.option.TLS { host, _, _ := net.SplitHostPort(v.addr) - tlsOpts := &clashVMess.TLSConfig{ + tlsOpts := &mihomoVMess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, ClientFingerprint: v.option.ClientFingerprint, @@ -155,24 +159,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M if v.option.ServerName != "" { tlsOpts.Host = v.option.ServerName } - c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts) + c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts) if err != nil { return nil, err } } host, _, _ := net.SplitHostPort(v.addr) - httpOpts := &clashVMess.HTTPConfig{ + httpOpts := &mihomoVMess.HTTPConfig{ Host: host, Method: v.option.HTTPOpts.Method, Path: v.option.HTTPOpts.Path, Headers: v.option.HTTPOpts.Headers, } - c = clashVMess.StreamHTTPConn(c, httpOpts) + c = mihomoVMess.StreamHTTPConn(c, httpOpts) case "h2": host, _, _ := net.SplitHostPort(v.addr) - tlsOpts := clashVMess.TLSConfig{ + tlsOpts := mihomoVMess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, NextProtos: []string{"h2"}, @@ -184,24 +188,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M tlsOpts.Host = v.option.ServerName } - c, err = clashVMess.StreamTLSConn(ctx, c, &tlsOpts) + c, err = mihomoVMess.StreamTLSConn(ctx, c, &tlsOpts) if err != nil { return nil, err } - h2Opts := &clashVMess.H2Config{ + h2Opts := &mihomoVMess.H2Config{ Hosts: v.option.HTTP2Opts.Host, Path: v.option.HTTP2Opts.Path, } - c, err = clashVMess.StreamH2Conn(c, h2Opts) + c, err = mihomoVMess.StreamH2Conn(c, h2Opts) case "grpc": c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) default: // handle TLS if v.option.TLS { host, _, _ := net.SplitHostPort(v.addr) - tlsOpts := &clashVMess.TLSConfig{ + tlsOpts := &mihomoVMess.TLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, ClientFingerprint: v.option.ClientFingerprint, @@ -213,7 +217,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M tlsOpts.Host = v.option.ServerName } - c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts) + c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts) } } @@ -260,10 +264,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err } else { if N.NeedHandshake(c) { conn = v.client.DialEarlyConn(c, - M.ParseSocksaddr(metadata.RemoteAddress())) + M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) } else { conn, err = v.client.DialConn(c, - M.ParseSocksaddr(metadata.RemoteAddress())) + M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) } } if err != nil { @@ -284,7 +288,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d safeConnClose(c, err) }(c) - c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) + c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err } diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index 6a11a234..9af1751b 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -13,13 +13,13 @@ import ( "strings" "sync" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/dns" - "github.com/Dreamacro/clash/log" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/dns" + "github.com/metacubex/mihomo/log" wireguard "github.com/metacubex/sing-wireguard" diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 899b9a9b..488c2d34 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -6,13 +6,13 @@ import ( "errors" "time" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/callback" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/callback" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" ) type Fallback struct { @@ -84,11 +84,12 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ - "type": f.Type().String(), - "now": f.Now(), - "all": all, - "testUrl": f.testUrl, - "expected": f.expectedStatus, + "type": f.Type().String(), + "now": f.Now(), + "all": all, + "testUrl": f.testUrl, + "expectedStatus": f.expectedStatus, + "fixed": f.selected, }) } @@ -102,13 +103,11 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy { proxies := f.GetProxies(touch) for _, proxy := range proxies { if len(f.selected) == 0 { - // if proxy.Alive() { if proxy.AliveForTestUrl(f.testUrl) { return proxy } } else { if proxy.Name() == f.selected { - // if proxy.Alive() { if proxy.AliveForTestUrl(f.testUrl) { return proxy } else { @@ -135,12 +134,11 @@ func (f *Fallback) Set(name string) error { } f.selected = name - // if !p.Alive() { if !p.AliveForTestUrl(f.testUrl) { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) defer cancel() expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus) - _, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory) + _, _ = p.URLTest(ctx, f.testUrl, expectedStatus) } return nil diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 66776bf5..0ea3685b 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -7,14 +7,14 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" - types "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" + types "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel" "github.com/dlclark/regexp2" ) @@ -28,7 +28,7 @@ type GroupBase struct { failedTestMux sync.Mutex failedTimes int failedTime time.Time - failedTesting *atomic.Bool + failedTesting atomic.Bool proxies [][]C.Proxy versions []atomic.Uint32 } @@ -202,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti proxy := proxy wg.Add(1) go func() { - delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory) + delay, err := proxy.URLTest(ctx, url, expectedStatus) if err == nil { lock.Lock() mp[proxy.Name()] = delay @@ -222,7 +222,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti } func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) { - if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass { + if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass || adapterType == C.RejectDrop { return } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index e336c5f0..79b18948 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -9,14 +9,14 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/common/callback" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/callback" + "github.com/metacubex/mihomo/common/lru" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" "golang.org/x/net/publicsuffix" ) @@ -150,7 +150,6 @@ func strategyRoundRobin(url string) strategyFn { for ; i < length; i++ { id := (idx + i) % length proxy := proxies[id] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { i++ return proxy @@ -169,7 +168,6 @@ func strategyConsistentHashing(url string) strategyFn { for i := 0; i < maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) proxy := proxies[idx] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { return proxy } @@ -177,7 +175,6 @@ func strategyConsistentHashing(url string) strategyFn { // when availability is poor, traverse the entire list to get the available nodes for _, proxy := range proxies { - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { return proxy } @@ -190,9 +187,9 @@ func strategyConsistentHashing(url string) strategyFn { func strategyStickySessions(url string) strategyFn { ttl := time.Minute * 10 maxRetry := 5 - lruCache := cache.New[uint64, int]( - cache.WithAge[uint64, int](int64(ttl.Seconds())), - cache.WithSize[uint64, int](1000)) + lruCache := lru.New[uint64, int]( + lru.WithAge[uint64, int](int64(ttl.Seconds())), + lru.WithSize[uint64, int](1000)) return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { key := utils.MapHash(getKeyWithSrcAndDst(metadata)) length := len(proxies) @@ -204,7 +201,6 @@ func strategyStickySessions(url string) strategyFn { nowIdx := idx for i := 1; i < maxRetry; i++ { proxy := proxies[nowIdx] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { if nowIdx != idx { lruCache.Delete(key) diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index a8bdc557..3f7f9770 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -5,12 +5,12 @@ import ( "fmt" "strings" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/adapter/provider" - "github.com/Dreamacro/clash/common/structure" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - types "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/adapter/provider" + "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + types "github.com/metacubex/mihomo/constant/provider" ) var ( @@ -22,21 +22,24 @@ var ( type GroupCommonOption struct { outbound.BasicOption - Name string `group:"name"` - Type string `group:"type"` - Proxies []string `group:"proxies,omitempty"` - Use []string `group:"use,omitempty"` - URL string `group:"url,omitempty"` - Interval int `group:"interval,omitempty"` - Lazy bool `group:"lazy,omitempty"` - DisableUDP bool `group:"disable-udp,omitempty"` - Filter string `group:"filter,omitempty"` - ExcludeFilter string `group:"exclude-filter,omitempty"` - ExcludeType string `group:"exclude-type,omitempty"` - ExpectedStatus string `group:"expected-status,omitempty"` + Name string `group:"name"` + Type string `group:"type"` + Proxies []string `group:"proxies,omitempty"` + Use []string `group:"use,omitempty"` + URL string `group:"url,omitempty"` + Interval int `group:"interval,omitempty"` + Lazy bool `group:"lazy,omitempty"` + DisableUDP bool `group:"disable-udp,omitempty"` + Filter string `group:"filter,omitempty"` + ExcludeFilter string `group:"exclude-filter,omitempty"` + ExcludeType string `group:"exclude-type,omitempty"` + ExpectedStatus string `group:"expected-status,omitempty"` + IncludeAll bool `group:"include-all,omitempty"` + IncludeAllProxies bool `group:"include-all-proxies,omitempty"` + IncludeAllProviders bool `group:"include-all-providers,omitempty"` } -func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { +func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) { decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) groupOption := &GroupCommonOption{ @@ -54,7 +57,24 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providers := []types.ProxyProvider{} - if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { + if groupOption.IncludeAll { + groupOption.IncludeAllProviders = true + groupOption.IncludeAllProxies = true + } + var GroupUse []string + var GroupProxies []string + if groupOption.IncludeAllProviders { + GroupUse = append(GroupUse, AllProviders...) + } else { + GroupUse = groupOption.Use + } + if groupOption.IncludeAllProxies { + GroupProxies = append(groupOption.Proxies, AllProxies...) + } else { + GroupProxies = groupOption.Proxies + } + + if len(GroupProxies) == 0 && len(GroupUse) == 0 { return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) } @@ -70,8 +90,13 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide groupOption.ExpectedStatus = status testUrl := groupOption.URL - if len(groupOption.Proxies) != 0 { - ps, err := getProxies(proxyMap, groupOption.Proxies) + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + testUrl = groupOption.URL + } + + if len(GroupProxies) != 0 { + ps, err := getProxies(proxyMap, GroupProxies) if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) } @@ -80,24 +105,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) } - var url string - var interval uint - // select don't need health check if groupOption.Type != "select" && groupOption.Type != "relay" { - if groupOption.URL == "" { - groupOption.URL = "https://cp.cloudflare.com/generate_204" - } - if groupOption.Interval == 0 { groupOption.Interval = 300 } - - url = groupOption.URL - interval = uint(groupOption.Interval) } - hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus) + hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.Interval), groupOption.Lazy, expectedStatus) + pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) @@ -107,8 +123,8 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providersMap[groupName] = pd } - if len(groupOption.Use) != 0 { - list, err := getProviders(providersMap, groupOption.Use) + if len(GroupUse) != 0 { + list, err := getProviders(providersMap, GroupUse) if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) } diff --git a/adapter/outboundgroup/patch_android.go b/adapter/outboundgroup/patch_android.go new file mode 100644 index 00000000..c6566814 --- /dev/null +++ b/adapter/outboundgroup/patch_android.go @@ -0,0 +1,64 @@ +//go:build android && cmfa + +package outboundgroup + +import ( + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" +) + +type ProxyGroup interface { + C.ProxyAdapter + + Providers() []provider.ProxyProvider + Proxies() []C.Proxy + Now() string +} + +func (f *Fallback) Providers() []provider.ProxyProvider { + return f.providers +} + +func (lb *LoadBalance) Providers() []provider.ProxyProvider { + return lb.providers +} + +func (f *Fallback) Proxies() []C.Proxy { + return f.GetProxies(false) +} + +func (lb *LoadBalance) Proxies() []C.Proxy { + return lb.GetProxies(false) +} + +func (lb *LoadBalance) Now() string { + return "" +} + +func (r *Relay) Providers() []provider.ProxyProvider { + return r.providers +} + +func (r *Relay) Proxies() []C.Proxy { + return r.GetProxies(false) +} + +func (r *Relay) Now() string { + return "" +} + +func (s *Selector) Providers() []provider.ProxyProvider { + return s.providers +} + +func (s *Selector) Proxies() []C.Proxy { + return s.GetProxies(false) +} + +func (u *URLTest) Providers() []provider.ProxyProvider { + return u.providers +} + +func (u *URLTest) Proxies() []C.Proxy { + return u.GetProxies(false) +} diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index ba733616..2b1be8a5 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -3,11 +3,11 @@ package outboundgroup import ( "context" "encoding/json" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" ) type Relay struct { diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 96934f0c..4d06c544 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -5,10 +5,10 @@ import ( "encoding/json" "errors" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" ) type Selector struct { diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 3f6c6ab0..2f9456fa 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -4,15 +4,18 @@ import ( "context" "encoding/json" "errors" + "fmt" + "sync" "time" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/callback" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/singledo" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/callback" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/singledo" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" ) type urlTestOption func(*URLTest) @@ -101,7 +104,7 @@ func (u *URLTest) fast(touch bool) C.Proxy { proxies := u.GetProxies(touch) if u.selected != "" { for _, proxy := range proxies { - if !proxy.Alive() { + if !proxy.AliveForTestUrl(u.testUrl) { continue } if proxy.Name() == u.selected { @@ -113,8 +116,7 @@ func (u *URLTest) fast(touch bool) C.Proxy { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { fast := proxies[0] - // min := fast.LastDelay() - min := fast.LastDelayForTestUrl(u.testUrl) + minDelay := fast.LastDelayForTestUrl(u.testUrl) fastNotExist := true for _, proxy := range proxies[1:] { @@ -122,21 +124,18 @@ func (u *URLTest) fast(touch bool) C.Proxy { fastNotExist = false } - // if !proxy.Alive() { if !proxy.AliveForTestUrl(u.testUrl) { continue } - // delay := proxy.LastDelay() delay := proxy.LastDelayForTestUrl(u.testUrl) - if delay < min { + if delay < minDelay { fast = proxy - min = delay + minDelay = delay } } // tolerance - // if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance { u.fastNode = fast } @@ -169,14 +168,43 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { all = append(all, proxy.Name()) } return json.Marshal(map[string]any{ - "type": u.Type().String(), - "now": u.Now(), - "all": all, - "testUrl": u.testUrl, - "expected": u.expectedStatus, + "type": u.Type().String(), + "now": u.Now(), + "all": all, + "testUrl": u.testUrl, + "expectedStatus": u.expectedStatus, + "fixed": u.selected, }) } +func (u *URLTest) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) { + var wg sync.WaitGroup + var lock sync.Mutex + mp := map[string]uint16{} + proxies := u.GetProxies(false) + for _, proxy := range proxies { + proxy := proxy + wg.Add(1) + go func() { + delay, err := proxy.URLTest(ctx, u.testUrl, expectedStatus) + if err == nil { + lock.Lock() + mp[proxy.Name()] = delay + lock.Unlock() + } + + wg.Done() + }() + } + wg.Wait() + + if len(mp) == 0 { + return mp, fmt.Errorf("get delay: all proxies timeout") + } else { + return mp, nil + } +} + func parseURLTestOption(config map[string]any) []urlTestOption { opts := []urlTestOption{} diff --git a/adapter/parser.go b/adapter/parser.go index eeb0fd59..1d363c1f 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -3,11 +3,11 @@ package adapter import ( "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" + tlsC "github.com/metacubex/mihomo/component/tls" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/common/structure" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/structure" + C "github.com/metacubex/mihomo/constant" ) func ParseProxy(mapping map[string]any) (C.Proxy, error) { @@ -120,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { break } proxy = outbound.NewDirectWithOption(*directOption) + case "reject": + rejectOption := &outbound.RejectOption{} + err = decoder.Decode(mapping, rejectOption) + if err != nil { + break + } + proxy = outbound.NewRejectWithOption(*rejectOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index 35327b1c..12e1df42 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -6,12 +6,12 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/batch" - "github.com/Dreamacro/clash/common/singledo" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/batch" + "github.com/metacubex/mihomo/common/singledo" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/dlclark/regexp2" ) @@ -34,12 +34,12 @@ type HealthCheck struct { url string extra map[string]*extraOption mu sync.Mutex - started *atomic.Bool + started atomic.Bool proxies []C.Proxy - interval uint + interval time.Duration lazy bool expectedStatus utils.IntRanges[uint16] - lastTouch *atomic.Int64 + lastTouch atomic.TypedValue[time.Time] done chan struct{} singleDo *singledo.Single[struct{}] } @@ -50,13 +50,14 @@ func (hc *HealthCheck) process() { return } - ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) + ticker := time.NewTicker(hc.interval) hc.start() for { select { case <-ticker.C: - now := time.Now().Unix() - if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { + lastTouch := hc.lastTouch.Load() + since := time.Since(lastTouch) + if !hc.lazy || since < hc.interval { hc.check() } else { log.Debugln("Skip once health check because we are lazy") @@ -85,7 +86,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils. // if the provider has not set up health checks, then modify it to be the same as the group's interval if hc.interval == 0 { - hc.interval = interval + hc.interval = time.Duration(interval) * time.Second } if hc.extra == nil { @@ -103,12 +104,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils. return } - // due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing - if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum { - log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum) - return - } - option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus} splitAndAddFiltersToExtra(filter, option) hc.extra[url] = option @@ -135,7 +130,7 @@ func (hc *HealthCheck) auto() bool { } func (hc *HealthCheck) touch() { - hc.lastTouch.Store(time.Now().Unix()) + hc.lastTouch.Store(time.Now()) } func (hc *HealthCheck) start() { @@ -147,6 +142,10 @@ func (hc *HealthCheck) stop() { } func (hc *HealthCheck) check() { + if len(hc.proxies) == 0 { + return + } + _, _, _ = hc.singleDo.Do(func() (struct{}, error) { id := utils.NewUUIDV4().String() log.Debugln("Start New Health Checking {%s}", id) @@ -176,13 +175,8 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex } var filterReg *regexp2.Regexp - var store = C.OriginalHistory var expectedStatus utils.IntRanges[uint16] if option != nil { - if url != hc.url { - store = C.ExtraHistory - } - expectedStatus = option.expectedStatus if len(option.filters) != 0 { filters := make([]string, 0, len(option.filters)) @@ -207,7 +201,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) defer cancel() log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) - _, _ = p.URLTest(ctx, url, expectedStatus, store) + _, _ = p.URLTest(ctx, url, expectedStatus) log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid) return false, nil }) @@ -219,20 +213,18 @@ func (hc *HealthCheck) close() { } func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck { - if len(url) == 0 { - interval = 0 - expectedStatus = nil + if url == "" { + // expectedStatus = nil + url = C.DefaultTestURL } return &HealthCheck{ proxies: proxies, url: url, extra: map[string]*extraOption{}, - started: atomic.NewBool(false), - interval: interval, + interval: time.Duration(interval) * time.Second, lazy: lazy, expectedStatus: expectedStatus, - lastTouch: atomic.NewInt64(0), done: make(chan struct{}, 1), singleDo: singledo.NewSingle[struct{}](time.Second), } diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index d885a546..c78a9d39 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -5,11 +5,12 @@ import ( "fmt" "time" - "github.com/Dreamacro/clash/common/structure" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/resource" - C "github.com/Dreamacro/clash/constant" - types "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/resource" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + types "github.com/metacubex/mihomo/constant/provider" ) var ( @@ -25,16 +26,29 @@ type healthCheckSchema struct { ExpectedStatus string `provider:"expected-status,omitempty"` } +type OverrideSchema struct { + UDP *bool `provider:"udp,omitempty"` + Up *string `provider:"up,omitempty"` + Down *string `provider:"down,omitempty"` + DialerProxy *string `provider:"dialer-proxy,omitempty"` + SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"` + Interface *string `provider:"interface-name,omitempty"` + RoutingMark *int `provider:"routing-mark,omitempty"` + IPVersion *string `provider:"ip-version,omitempty"` +} + type proxyProviderSchema struct { - Type string `provider:"type"` - Path string `provider:"path,omitempty"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` - Filter string `provider:"filter,omitempty"` - ExcludeFilter string `provider:"exclude-filter,omitempty"` - ExcludeType string `provider:"exclude-type,omitempty"` - DialerProxy string `provider:"dialer-proxy,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Type string `provider:"type"` + Path string `provider:"path,omitempty"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` + Filter string `provider:"filter,omitempty"` + ExcludeFilter string `provider:"exclude-filter,omitempty"` + ExcludeType string `provider:"exclude-type,omitempty"` + DialerProxy string `provider:"dialer-proxy,omitempty"` + + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Override OverrideSchema `provider:"override,omitempty"` } func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) { @@ -56,6 +70,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide var hcInterval uint if schema.HealthCheck.Enable { + if schema.HealthCheck.Interval == 0 { + schema.HealthCheck.Interval = 300 + } hcInterval = uint(schema.HealthCheck.Interval) } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus) @@ -68,7 +85,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide case "http": if schema.Path != "" { path := C.Path.Resolve(schema.Path) - if !C.Path.IsSafePath(path) { + if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } vehicle = resource.NewHTTPVehicle(schema.URL, path) @@ -85,6 +102,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide excludeFilter := schema.ExcludeFilter excludeType := schema.ExcludeType dialerProxy := schema.DialerProxy + override := schema.Override - return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc) + return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, override, vehicle, hc) } diff --git a/adapter/provider/patch_android.go b/adapter/provider/patch_android.go new file mode 100644 index 00000000..e9042bda --- /dev/null +++ b/adapter/provider/patch_android.go @@ -0,0 +1,36 @@ +//go:build android && cmfa + +package provider + +import ( + "time" +) + +var ( + suspended bool +) + +type UpdatableProvider interface { + UpdatedAt() time.Time +} + +func (pp *proxySetProvider) UpdatedAt() time.Time { + return pp.Fetcher.UpdatedAt +} + +func (pp *proxySetProvider) Close() error { + pp.healthCheck.close() + pp.Fetcher.Destroy() + + return nil +} + +func (cp *compatibleProvider) Close() error { + cp.healthCheck.close() + + return nil +} + +func Suspend(s bool) { + suspended = s +} diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index d547dcb7..d710d3a4 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -10,15 +10,15 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/common/convert" - "github.com/Dreamacro/clash/common/utils" - clashHttp "github.com/Dreamacro/clash/component/http" - "github.com/Dreamacro/clash/component/resource" - C "github.com/Dreamacro/clash/constant" - types "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/metacubex/mihomo/adapter" + "github.com/metacubex/mihomo/common/convert" + "github.com/metacubex/mihomo/common/utils" + mihomoHttp "github.com/metacubex/mihomo/component/http" + "github.com/metacubex/mihomo/component/resource" + C "github.com/metacubex/mihomo/constant" + types "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel/statistic" "github.com/dlclark/regexp2" "gopkg.in/yaml.v3" @@ -46,12 +46,18 @@ type proxySetProvider struct { } func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { + expectedStatus := "*" + if pp.healthCheck.expectedStatus != nil { + expectedStatus = pp.healthCheck.expectedStatus.ToString() + } + return json.Marshal(map[string]any{ "name": pp.Name(), "type": pp.Type().String(), "vehicleType": pp.VehicleType().String(), "proxies": pp.Proxies(), "testUrl": pp.healthCheck.url, + "expectedStatus": expectedStatus, "updatedAt": pp.UpdatedAt, "subscriptionInfo": pp.subscriptionInfo, }) @@ -119,8 +125,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() { go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), - http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), + http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return } @@ -128,7 +134,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() { userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) if userInfoStr == "" { - resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), + resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil) if err != nil { return @@ -163,7 +169,7 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.Fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) if err != nil { return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) @@ -191,7 +197,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc healthCheck: hc, } - fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd)) + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd)) pd.Fetcher = fetcher wrapper := &ProxySetProvider{pd} runtime.SetFinalizer(wrapper, stopProxyProvider) @@ -211,12 +217,18 @@ type compatibleProvider struct { } func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { + expectedStatus := "*" + if cp.healthCheck.expectedStatus != nil { + expectedStatus = cp.healthCheck.expectedStatus.ToString() + } + return json.Marshal(map[string]any{ - "name": cp.Name(), - "type": cp.Type().String(), - "vehicleType": cp.VehicleType().String(), - "proxies": cp.Proxies(), - "testUrl": cp.healthCheck.url, + "name": cp.Name(), + "type": cp.Type().String(), + "vehicleType": cp.VehicleType().String(), + "proxies": cp.Proxies(), + "testUrl": cp.healthCheck.url, + "expectedStatus": expectedStatus, }) } @@ -237,6 +249,9 @@ func (cp *compatibleProvider) Update() error { } func (cp *compatibleProvider) Initial() error { + if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" { + cp.HealthCheck() + } return nil } @@ -292,7 +307,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -355,13 +370,41 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray if _, ok := proxiesSet[name]; ok { continue } + if len(dialerProxy) > 0 { mapping["dialer-proxy"] = dialerProxy } + + if override.UDP != nil { + mapping["udp"] = *override.UDP + } + if override.Up != nil { + mapping["up"] = *override.Up + } + if override.Down != nil { + mapping["down"] = *override.Down + } + if override.DialerProxy != nil { + mapping["dialer-proxy"] = *override.DialerProxy + } + if override.SkipCertVerify != nil { + mapping["skip-cert-verify"] = *override.SkipCertVerify + } + if override.Interface != nil { + mapping["interface-name"] = *override.Interface + } + if override.RoutingMark != nil { + mapping["routing-mark"] = *override.RoutingMark + } + if override.IPVersion != nil { + mapping["ip-version"] = *override.IPVersion + } + proxy, err := adapter.ParseProxy(mapping) if err != nil { return nil, fmt.Errorf("proxy %d error: %w", idx, err) } + proxiesSet[name] = struct{}{} proxies = append(proxies, proxy) } diff --git a/adapter/provider/subscription_info.go b/adapter/provider/subscription_info.go index fc6992e2..3a9e2d72 100644 --- a/adapter/provider/subscription_info.go +++ b/adapter/provider/subscription_info.go @@ -1,7 +1,6 @@ package provider import ( - "github.com/dlclark/regexp2" "strconv" "strings" ) @@ -13,45 +12,28 @@ type SubscriptionInfo struct { Expire int64 } -func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) { - si = &SubscriptionInfo{} - str = strings.ToLower(str) - reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0) - reExpire := regexp2.MustCompile("expire=(\\d+)", 0) - - match, err := reTraffic.FindStringMatch(str) - if err != nil || match == nil { - return nil, err - } - group := match.Groups() - si.Upload, err = str2uint64(group[1].String()) - if err != nil { - return nil, err - } - - si.Download, err = str2uint64(group[2].String()) - if err != nil { - return nil, err - } - - si.Total, err = str2uint64(group[3].String()) - if err != nil { - return nil, err - } - - match, _ = reExpire.FindStringMatch(str) - if match != nil { - group = match.Groups() - si.Expire, err = str2uint64(group[1].String()) +func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) { + userinfo = strings.ToLower(userinfo) + userinfo = strings.ReplaceAll(userinfo, " ", "") + si = new(SubscriptionInfo) + for _, field := range strings.Split(userinfo, ";") { + switch name, value, _ := strings.Cut(field, "="); name { + case "upload": + si.Upload, err = strconv.ParseInt(value, 10, 64) + case "download": + si.Download, err = strconv.ParseInt(value, 10, 64) + case "total": + si.Total, err = strconv.ParseInt(value, 10, 64) + case "expire": + if value == "" { + si.Expire = 0 + } else { + si.Expire, err = strconv.ParseInt(value, 10, 64) + } + } if err != nil { - return nil, err + return } } - return } - -func str2uint64(str string) (int64, error) { - i, err := strconv.ParseInt(str, 10, 64) - return i, err -} diff --git a/common/arc/arc.go b/common/arc/arc.go new file mode 100644 index 00000000..da78b1c1 --- /dev/null +++ b/common/arc/arc.go @@ -0,0 +1,235 @@ +package arc + +import ( + "sync" + "time" + + list "github.com/bahlo/generic-list-go" + "github.com/samber/lo" +) + +//modify from https://github.com/alexanderGugel/arc + +// Option is part of Functional Options Pattern +type Option[K comparable, V any] func(*ARC[K, V]) + +func WithSize[K comparable, V any](maxSize int) Option[K, V] { + return func(a *ARC[K, V]) { + a.c = maxSize + } +} + +type ARC[K comparable, V any] struct { + p int + c int + t1 *list.List[*entry[K, V]] + b1 *list.List[*entry[K, V]] + t2 *list.List[*entry[K, V]] + b2 *list.List[*entry[K, V]] + mutex sync.Mutex + len int + cache map[K]*entry[K, V] +} + +// New returns a new Adaptive Replacement Cache (ARC). +func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] { + arc := &ARC[K, V]{ + p: 0, + t1: list.New[*entry[K, V]](), + b1: list.New[*entry[K, V]](), + t2: list.New[*entry[K, V]](), + b2: list.New[*entry[K, V]](), + len: 0, + cache: make(map[K]*entry[K, V]), + } + + for _, option := range options { + option(arc) + } + return arc +} + +// Set inserts a new key-value pair into the cache. +// This optimizes future access to this entry (side effect). +func (a *ARC[K, V]) Set(key K, value V) { + a.mutex.Lock() + defer a.mutex.Unlock() + + a.set(key, value) +} + +func (a *ARC[K, V]) set(key K, value V) { + a.setWithExpire(key, value, time.Unix(0, 0)) +} + +// SetWithExpire stores any representation of a response for a given key and given expires. +// The expires time will round to second. +func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) { + a.mutex.Lock() + defer a.mutex.Unlock() + + a.setWithExpire(key, value, expires) +} + +func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) { + ent, ok := a.cache[key] + if !ok { + a.len++ + ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()} + a.req(ent) + a.cache[key] = ent + return + } + + if ent.ghost { + a.len++ + } + + ent.value = value + ent.ghost = false + ent.expires = expires.Unix() + a.req(ent) +} + +// Get retrieves a previously via Set inserted entry. +// This optimizes future access to this entry (side effect). +func (a *ARC[K, V]) Get(key K) (value V, ok bool) { + a.mutex.Lock() + defer a.mutex.Unlock() + + ent, ok := a.get(key) + if !ok { + return lo.Empty[V](), false + } + return ent.value, true +} + +func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) { + ent, ok := a.cache[key] + if !ok { + return ent, false + } + a.req(ent) + return ent, !ent.ghost +} + +// GetWithExpire returns any representation of a cached response, +// a time.Time Give expected expires, +// and a bool set to true if the key was found. +// This method will NOT update the expires. +func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) { + a.mutex.Lock() + defer a.mutex.Unlock() + + ent, ok := a.get(key) + if !ok { + return lo.Empty[V](), time.Time{}, false + } + + return ent.value, time.Unix(ent.expires, 0), true +} + +// Len determines the number of currently cached entries. +// This method is side-effect free in the sense that it does not attempt to optimize random cache access. +func (a *ARC[K, V]) Len() int { + a.mutex.Lock() + defer a.mutex.Unlock() + + return a.len +} + +func (a *ARC[K, V]) req(ent *entry[K, V]) { + switch { + case ent.ll == a.t1 || ent.ll == a.t2: + // Case I + ent.setMRU(a.t2) + case ent.ll == a.b1: + // Case II + // Cache Miss in t1 and t2 + + // Adaptation + var d int + if a.b1.Len() >= a.b2.Len() { + d = 1 + } else { + d = a.b2.Len() / a.b1.Len() + } + a.p = min(a.p+d, a.c) + + a.replace(ent) + ent.setMRU(a.t2) + case ent.ll == a.b2: + // Case III + // Cache Miss in t1 and t2 + + // Adaptation + var d int + if a.b2.Len() >= a.b1.Len() { + d = 1 + } else { + d = a.b1.Len() / a.b2.Len() + } + a.p = max(a.p-d, 0) + + a.replace(ent) + ent.setMRU(a.t2) + case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c: + // Case IV A + if a.t1.Len() < a.c { + a.delLRU(a.b1) + a.replace(ent) + } else { + a.delLRU(a.t1) + } + ent.setMRU(a.t1) + case ent.ll == nil && a.t1.Len()+a.b1.Len() < a.c: + // Case IV B + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { + a.delLRU(a.b2) + } + a.replace(ent) + } + ent.setMRU(a.t1) + case ent.ll == nil: + // Case IV, not A nor B + ent.setMRU(a.t1) + } +} + +func (a *ARC[K, V]) delLRU(list *list.List[*entry[K, V]]) { + lru := list.Back() + list.Remove(lru) + a.len-- + delete(a.cache, lru.Value.key) +} + +func (a *ARC[K, V]) replace(ent *entry[K, V]) { + if a.t1.Len() > 0 && ((a.t1.Len() > a.p) || (ent.ll == a.b2 && a.t1.Len() == a.p)) { + lru := a.t1.Back().Value + lru.value = lo.Empty[V]() + lru.ghost = true + a.len-- + lru.setMRU(a.b1) + } else { + lru := a.t2.Back().Value + lru.value = lo.Empty[V]() + lru.ghost = true + a.len-- + lru.setMRU(a.b2) + } +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a int, b int) int { + if a < b { + return b + } + return a +} diff --git a/common/arc/arc_test.go b/common/arc/arc_test.go new file mode 100644 index 00000000..a9d8a0c1 --- /dev/null +++ b/common/arc/arc_test.go @@ -0,0 +1,105 @@ +package arc + +import ( + "testing" +) + +func TestInsertion(t *testing.T) { + cache := New[string, string](WithSize[string, string](3)) + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) + } + + const ( + k1 = "Hello" + k2 = "Hallo" + k3 = "Ciao" + k4 = "Salut" + + v1 = "World" + v2 = "Worlds" + v3 = "Welt" + ) + + // Insert the first value + cache.Set(k1, v1) + if got, want := cache.Len(), 1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v1 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v1) + } + + // Replace existing value for a given key + cache.Set(k1, v2) + if got, want := cache.Len(), 1; got != want { + t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("re-insertion: cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) + } + + // Add a second different key + cache.Set(k2, v3) + if got, want := cache.Len(), 2; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) + } + if got, ok := cache.Get(k2); !ok || got != v3 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k2, got, ok, v3) + } + + // Fill cache + cache.Set(k3, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + + // Exceed size, this should not exceed size: + cache.Set(k4, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key out of size: cache.Len(): got %d want %d", cache.Len(), want) + } +} + +func TestEviction(t *testing.T) { + size := 3 + cache := New[string, string](WithSize[string, string](size)) + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) + } + + tests := []struct { + k, v string + }{ + {"k1", "v1"}, + {"k2", "v2"}, + {"k3", "v3"}, + {"k4", "v4"}, + } + for i, tt := range tests[:size] { + cache.Set(tt.k, tt.v) + if got, want := cache.Len(), i+1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + } + + // Exceed size and check we don't outgrow it: + cache.Set(tests[size].k, tests[size].v) + if got := cache.Len(); got != size { + t.Errorf("insertion of overflow key #%d: cache.Len(): got %d want %d", 4, cache.Len(), size) + } + + // Check that LRU got evicted: + if got, ok := cache.Get(tests[0].k); ok || got != "" { + t.Errorf("cache.Get(%v): got (%v,%t) want (,true)", tests[0].k, got, ok) + } + + for _, tt := range tests[1:] { + if got, ok := cache.Get(tt.k); !ok || got != tt.v { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", tt.k, got, ok, tt.v) + } + } +} diff --git a/common/arc/entry.go b/common/arc/entry.go new file mode 100644 index 00000000..679cbc1c --- /dev/null +++ b/common/arc/entry.go @@ -0,0 +1,32 @@ +package arc + +import ( + list "github.com/bahlo/generic-list-go" +) + +type entry[K comparable, V any] struct { + key K + value V + ll *list.List[*entry[K, V]] + el *list.Element[*entry[K, V]] + ghost bool + expires int64 +} + +func (e *entry[K, V]) setLRU(list *list.List[*entry[K, V]]) { + e.detach() + e.ll = list + e.el = e.ll.PushBack(e) +} + +func (e *entry[K, V]) setMRU(list *list.List[*entry[K, V]]) { + e.detach() + e.ll = list + e.el = e.ll.PushFront(e) +} + +func (e *entry[K, V]) detach() { + if e.ll != nil { + e.ll.Remove(e.el) + } +} diff --git a/common/atomic/type.go b/common/atomic/type.go index f1549235..71695c63 100644 --- a/common/atomic/type.go +++ b/common/atomic/type.go @@ -11,10 +11,9 @@ type Bool struct { atomic.Bool } -func NewBool(val bool) *Bool { - i := &Bool{} +func NewBool(val bool) (i Bool) { i.Store(val) - return i + return } func (i *Bool) MarshalJSON() ([]byte, error) { @@ -39,12 +38,11 @@ type Pointer[T any] struct { atomic.Pointer[T] } -func NewPointer[T any](v *T) *Pointer[T] { - var p Pointer[T] +func NewPointer[T any](v *T) (p Pointer[T]) { if v != nil { p.Store(v) } - return &p + return } func (p *Pointer[T]) MarshalJSON() ([]byte, error) { @@ -68,10 +66,9 @@ type Int32 struct { atomic.Int32 } -func NewInt32(val int32) *Int32 { - i := &Int32{} +func NewInt32(val int32) (i Int32) { i.Store(val) - return i + return } func (i *Int32) MarshalJSON() ([]byte, error) { @@ -96,10 +93,9 @@ type Int64 struct { atomic.Int64 } -func NewInt64(val int64) *Int64 { - i := &Int64{} +func NewInt64(val int64) (i Int64) { i.Store(val) - return i + return } func (i *Int64) MarshalJSON() ([]byte, error) { @@ -124,10 +120,9 @@ type Uint32 struct { atomic.Uint32 } -func NewUint32(val uint32) *Uint32 { - i := &Uint32{} +func NewUint32(val uint32) (i Uint32) { i.Store(val) - return i + return } func (i *Uint32) MarshalJSON() ([]byte, error) { @@ -152,10 +147,9 @@ type Uint64 struct { atomic.Uint64 } -func NewUint64(val uint64) *Uint64 { - i := &Uint64{} +func NewUint64(val uint64) (i Uint64) { i.Store(val) - return i + return } func (i *Uint64) MarshalJSON() ([]byte, error) { @@ -180,10 +174,9 @@ type Uintptr struct { atomic.Uintptr } -func NewUintptr(val uintptr) *Uintptr { - i := &Uintptr{} +func NewUintptr(val uintptr) (i Uintptr) { i.Store(val) - return i + return } func (i *Uintptr) MarshalJSON() ([]byte, error) { diff --git a/common/atomic/value.go b/common/atomic/value.go index ca0eb631..cbc6c5b8 100644 --- a/common/atomic/value.go +++ b/common/atomic/value.go @@ -11,6 +11,7 @@ func DefaultValue[T any]() T { } type TypedValue[T any] struct { + _ noCopy value atomic.Value } @@ -51,8 +52,13 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error { return nil } -func NewTypedValue[T any](t T) *TypedValue[T] { - v := &TypedValue[T]{} +func NewTypedValue[T any](t T) (v TypedValue[T]) { v.Store(t) - return v + return } + +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} +func (*noCopy) Unlock() {} diff --git a/common/buf/sing.go b/common/buf/sing.go index d204ba11..0907a95c 100644 --- a/common/buf/sing.go +++ b/common/buf/sing.go @@ -10,6 +10,7 @@ const BufferSize = buf.BufferSize type Buffer = buf.Buffer var New = buf.New +var NewPacket = buf.NewPacket var NewSize = buf.NewSize var With = buf.With var As = buf.As diff --git a/common/callback/callback.go b/common/callback/callback.go index fe76dc67..9ae0f94a 100644 --- a/common/callback/callback.go +++ b/common/callback/callback.go @@ -1,9 +1,9 @@ package callback import ( - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" ) type firstWriteCallBackConn struct { diff --git a/common/callback/close_callback.go b/common/callback/close_callback.go new file mode 100644 index 00000000..630ee5d7 --- /dev/null +++ b/common/callback/close_callback.go @@ -0,0 +1,61 @@ +package callback + +import ( + "sync" + + C "github.com/metacubex/mihomo/constant" +) + +type closeCallbackConn struct { + C.Conn + closeFunc func() + closeOnce sync.Once +} + +func (w *closeCallbackConn) Close() error { + w.closeOnce.Do(w.closeFunc) + return w.Conn.Close() +} + +func (w *closeCallbackConn) ReaderReplaceable() bool { + return true +} + +func (w *closeCallbackConn) WriterReplaceable() bool { + return true +} + +func (w *closeCallbackConn) Upstream() any { + return w.Conn +} + +func NewCloseCallbackConn(conn C.Conn, callback func()) C.Conn { + return &closeCallbackConn{Conn: conn, closeFunc: callback} +} + +type closeCallbackPacketConn struct { + C.PacketConn + closeFunc func() + closeOnce sync.Once +} + +func (w *closeCallbackPacketConn) Close() error { + w.closeOnce.Do(w.closeFunc) + return w.PacketConn.Close() +} + +func (w *closeCallbackPacketConn) ReaderReplaceable() bool { + return true +} + +func (w *closeCallbackPacketConn) WriterReplaceable() bool { + return true +} + +func (w *closeCallbackPacketConn) Upstream() any { + return w.PacketConn +} + +func NewCloseCallbackPacketConn(conn C.PacketConn, callback func()) C.PacketConn { + return &closeCallbackPacketConn{PacketConn: conn, closeFunc: callback} +} diff --git a/common/collections/stack.go b/common/collections/stack.go deleted file mode 100644 index 74673f9a..00000000 --- a/common/collections/stack.go +++ /dev/null @@ -1,56 +0,0 @@ -package collections - -import "sync" - -type ( - stack struct { - top *node - length int - lock *sync.RWMutex - } - - node struct { - value interface{} - prev *node - } -) - -// NewStack Create a new stack -func NewStack() *stack { - return &stack{nil, 0, &sync.RWMutex{}} -} - -// Len Return the number of items in the stack -func (this *stack) Len() int { - return this.length -} - -// Peek View the top item on the stack -func (this *stack) Peek() interface{} { - if this.length == 0 { - return nil - } - return this.top.value -} - -// Pop the top item of the stack and return it -func (this *stack) Pop() interface{} { - this.lock.Lock() - defer this.lock.Unlock() - if this.length == 0 { - return nil - } - n := this.top - this.top = n.prev - this.length-- - return n.value -} - -// Push a value onto the top of the stack -func (this *stack) Push(value interface{}) { - this.lock.Lock() - defer this.lock.Unlock() - n := &node{value, this.top} - this.top = n - this.length++ -} diff --git a/common/convert/converter.go b/common/convert/converter.go index 5a618f42..bf5bcbd7 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -9,10 +9,10 @@ import ( "strconv" "strings" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" ) -// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config +// ConvertsV2Ray convert V2Ray subscribe proxies data to mihomo proxies config func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { data := DecodeBase64(buf) @@ -142,6 +142,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { tuic["udp-relay-mode"] = udpRelayMode } + proxies = append(proxies, tuic) + case "trojan": urlTrojan, err := url.Parse(line) if err != nil { diff --git a/common/convert/util.go b/common/convert/util.go index 0ec35acd..a715b556 100644 --- a/common/convert/util.go +++ b/common/convert/util.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/zhangyunhao116/fastrand" diff --git a/common/generics/list/list.go b/common/generics/list/list.go deleted file mode 100644 index 04d84180..00000000 --- a/common/generics/list/list.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// 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. -type Element[T any] struct { - // Next and previous pointers in the doubly-linked list of elements. - // To simplify the implementation, internally a list l is implemented - // as a ring, such that &l.root is both the next element of the last - // list element (l.Back()) and the previous element of the first list - // element (l.Front()). - next, prev *Element[T] - - // The list to which this element belongs. - list *List[T] - - // The value stored with this element. - Value T -} - -// Next returns the next list element or nil. -func (e *Element[T]) Next() *Element[T] { - if p := e.next; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// Prev returns the previous list element or nil. -func (e *Element[T]) Prev() *Element[T] { - if p := e.prev; e.list != nil && p != &e.list.root { - return p - } - return nil -} - -// List represents a doubly linked list. -// The zero value for List is an empty list ready to use. -type List[T any] struct { - root Element[T] // sentinel list element, only &root, root.prev, and root.next are used - len int // current list length excluding (this) sentinel element -} - -// Init initializes or clears list l. -func (l *List[T]) Init() *List[T] { - l.root.next = &l.root - l.root.prev = &l.root - l.len = 0 - return l -} - -// New returns an initialized list. -func New[T any]() *List[T] { return new(List[T]).Init() } - -// Len returns the number of elements of list l. -// The complexity is O(1). -func (l *List[T]) Len() int { return l.len } - -// Front returns the first element of list l or nil if the list is empty. -func (l *List[T]) Front() *Element[T] { - if l.len == 0 { - return nil - } - return l.root.next -} - -// Back returns the last element of list l or nil if the list is empty. -func (l *List[T]) Back() *Element[T] { - if l.len == 0 { - return nil - } - return l.root.prev -} - -// lazyInit lazily initializes a zero List value. -func (l *List[T]) lazyInit() { - if l.root.next == nil { - l.Init() - } -} - -// insert inserts e after at, increments l.len, and returns e. -func (l *List[T]) insert(e, at *Element[T]) *Element[T] { - e.prev = at - e.next = at.next - e.prev.next = e - e.next.prev = e - e.list = l - l.len++ - return e -} - -// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). -func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { - return l.insert(&Element[T]{Value: v}, at) -} - -// remove removes e from its list, decrements l.len -func (l *List[T]) remove(e *Element[T]) { - e.prev.next = e.next - e.next.prev = e.prev - e.next = nil // avoid memory leaks - e.prev = nil // avoid memory leaks - e.list = nil - l.len-- -} - -// move moves e to next to at. -func (l *List[T]) move(e, at *Element[T]) { - if e == at { - return - } - e.prev.next = e.next - e.next.prev = e.prev - - e.prev = at - e.next = at.next - e.prev.next = e - e.next.prev = e -} - -// Remove removes e from l if e is an element of list l. -// It returns the element value e.Value. -// The element must not be nil. -func (l *List[T]) Remove(e *Element[T]) T { - if e.list == l { - // if e.list == l, l must have been initialized when e was inserted - // in l or l == nil (e is a zero Element) and l.remove will crash - l.remove(e) - } - return e.Value -} - -// PushFront inserts a new element e with value v at the front of list l and returns e. -func (l *List[T]) PushFront(v T) *Element[T] { - l.lazyInit() - return l.insertValue(v, &l.root) -} - -// PushBack inserts a new element e with value v at the back of list l and returns e. -func (l *List[T]) PushBack(v T) *Element[T] { - l.lazyInit() - return l.insertValue(v, l.root.prev) -} - -// InsertBefore inserts a new element e with value v immediately before mark and returns e. -// If mark is not an element of l, the list is not modified. -// The mark must not be nil. -func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { - if mark.list != l { - return nil - } - // see comment in List.Remove about initialization of l - return l.insertValue(v, mark.prev) -} - -// InsertAfter inserts a new element e with value v immediately after mark and returns e. -// If mark is not an element of l, the list is not modified. -// The mark must not be nil. -func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { - if mark.list != l { - return nil - } - // see comment in List.Remove about initialization of l - return l.insertValue(v, mark) -} - -// MoveToFront moves element e to the front of list l. -// If e is not an element of l, the list is not modified. -// The element must not be nil. -func (l *List[T]) MoveToFront(e *Element[T]) { - if e.list != l || l.root.next == e { - return - } - // see comment in List.Remove about initialization of l - l.move(e, &l.root) -} - -// MoveToBack moves element e to the back of list l. -// If e is not an element of l, the list is not modified. -// The element must not be nil. -func (l *List[T]) MoveToBack(e *Element[T]) { - if e.list != l || l.root.prev == e { - return - } - // see comment in List.Remove about initialization of l - l.move(e, l.root.prev) -} - -// MoveBefore moves element e to its new position before mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -// The element and mark must not be nil. -func (l *List[T]) MoveBefore(e, mark *Element[T]) { - if e.list != l || e == mark || mark.list != l { - return - } - l.move(e, mark.prev) -} - -// MoveAfter moves element e to its new position after mark. -// If e or mark is not an element of l, or e == mark, the list is not modified. -// The element and mark must not be nil. -func (l *List[T]) MoveAfter(e, mark *Element[T]) { - if e.list != l || e == mark || mark.list != l { - return - } - l.move(e, mark) -} - -// PushBackList inserts a copy of another list at the back of list l. -// The lists l and other may be the same. They must not be nil. -func (l *List[T]) PushBackList(other *List[T]) { - l.lazyInit() - for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { - l.insertValue(e.Value, l.root.prev) - } -} - -// PushFrontList inserts a copy of another list at the front of list l. -// The lists l and other may be the same. They must not be nil. -func (l *List[T]) PushFrontList(other *List[T]) { - l.lazyInit() - for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { - l.insertValue(e.Value, &l.root) - } -} diff --git a/common/cache/lrucache.go b/common/lru/lrucache.go similarity index 93% rename from common/cache/lrucache.go rename to common/lru/lrucache.go index 2f9d3e79..6f32ed18 100644 --- a/common/cache/lrucache.go +++ b/common/lru/lrucache.go @@ -1,4 +1,4 @@ -package cache +package lru // Modified by https://github.com/die-net/lrucache @@ -6,8 +6,7 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/generics/list" - + list "github.com/bahlo/generic-list-go" "github.com/samber/lo" ) @@ -81,7 +80,7 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { return lc } -// Get returns the any representation of a cached response and a bool +// Get returns any representation of a cached response and a bool // set to true if the key was found. func (c *LruCache[K, V]) Get(key K) (V, bool) { c.mu.Lock() @@ -111,7 +110,7 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) { return value, true } -// GetWithExpire returns the any representation of a cached response, +// GetWithExpire returns any representation of a cached response, // a time.Time Give expected expires, // and a bool set to true if the key was found. // This method will NOT check the maxAge of element and will NOT update the expires. @@ -136,7 +135,7 @@ func (c *LruCache[K, V]) Exist(key K) bool { return ok } -// Set stores the any representation of a response for a given key. +// Set stores any representation of a response for a given key. func (c *LruCache[K, V]) Set(key K, value V) { c.mu.Lock() defer c.mu.Unlock() @@ -152,7 +151,7 @@ func (c *LruCache[K, V]) set(key K, value V) { c.setWithExpire(key, value, time.Unix(expires, 0)) } -// SetWithExpire stores the any representation of a response for a given key and given expires. +// SetWithExpire stores any representation of a response for a given key and given expires. // The expires time will round to second. func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) { c.mu.Lock() diff --git a/common/cache/lrucache_test.go b/common/lru/lrucache_test.go similarity index 99% rename from common/cache/lrucache_test.go rename to common/lru/lrucache_test.go index 4cbc1ff8..340b3da3 100644 --- a/common/cache/lrucache_test.go +++ b/common/lru/lrucache_test.go @@ -1,4 +1,4 @@ -package cache +package lru import ( "testing" diff --git a/common/net/bufconn.go b/common/net/bufconn.go index 6da2d9d1..b7e98e04 100644 --- a/common/net/bufconn.go +++ b/common/net/bufconn.go @@ -4,7 +4,7 @@ import ( "bufio" "net" - "github.com/Dreamacro/clash/common/buf" + "github.com/metacubex/mihomo/common/buf" ) var _ ExtendedConn = (*BufferedConn)(nil) @@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn { return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false} } +func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn { + if br != nil && br.Buffered() > 0 { + if bc, ok := c.(*BufferedConn); ok && bc.r == br { + return bc + } + return &BufferedConn{br, NewExtendedConn(c), true} + } + return c +} + // Reader returns the internal bufio.Reader. func (c *BufferedConn) Reader() *bufio.Reader { return c.r @@ -74,9 +84,9 @@ func (c *BufferedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.C length := c.r.Buffered() b, _ := c.r.Peek(length) _, _ = c.r.Discard(length) - c.r = nil // drop bufio.Reader to let gc can clean up its internal buf return buf.As(b) } + c.r = nil // drop bufio.Reader to let gc can clean up its internal buf return nil } diff --git a/common/net/bufconn_unsafe.go b/common/net/bufconn_unsafe.go new file mode 100644 index 00000000..349321df --- /dev/null +++ b/common/net/bufconn_unsafe.go @@ -0,0 +1,34 @@ +package net + +import ( + "io" + "unsafe" +) + +// bufioReader copy from stdlib bufio/bufio.go +// This structure has remained unchanged from go1.5 to go1.21. +type bufioReader struct { + buf []byte + rd io.Reader // reader provided by the client + r, w int // buf read and write positions + err error + lastByte int // last byte read for UnreadByte; -1 means invalid + lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid +} + +func (c *BufferedConn) AppendData(buf []byte) (ok bool) { + b := (*bufioReader)(unsafe.Pointer(c.r)) + pos := len(b.buf) - b.w - len(buf) + if pos >= -b.r { // len(b.buf)-(b.w - b.r) >= len(buf) + if pos < 0 { // len(b.buf)-b.w < len(buf) + // Slide existing data to beginning. + copy(b.buf, b.buf[b.r:b.w]) + b.w -= b.r + b.r = 0 + } + + b.w += copy(b.buf[b.w:], buf) + return true + } + return false +} diff --git a/common/net/cached.go b/common/net/cached.go new file mode 100644 index 00000000..fb605b74 --- /dev/null +++ b/common/net/cached.go @@ -0,0 +1,49 @@ +package net + +import ( + "net" + + "github.com/metacubex/mihomo/common/buf" +) + +var _ ExtendedConn = (*CachedConn)(nil) + +type CachedConn struct { + ExtendedConn + data []byte +} + +func NewCachedConn(c net.Conn, data []byte) *CachedConn { + return &CachedConn{NewExtendedConn(c), data} +} + +func (c *CachedConn) Read(b []byte) (n int, err error) { + if len(c.data) > 0 { + n = copy(b, c.data) + c.data = c.data[n:] + return + } + return c.ExtendedConn.Read(b) +} + +func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy + if len(c.data) > 0 { + return buf.As(c.data) + } + return nil +} + +func (c *CachedConn) Upstream() any { + return c.ExtendedConn +} + +func (c *CachedConn) ReaderReplaceable() bool { + if len(c.data) > 0 { + return false + } + return true +} + +func (c *CachedConn) WriterReplaceable() bool { + return true +} diff --git a/common/net/context.go b/common/net/context.go new file mode 100644 index 00000000..917028d1 --- /dev/null +++ b/common/net/context.go @@ -0,0 +1,31 @@ +package net + +import ( + "context" + "net" +) + +// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine. +func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) { + var ( + quit = make(chan struct{}) + interrupt = make(chan error, 1) + ) + go func() { + select { + case <-quit: + interrupt <- nil + case <-ctx.Done(): + // Close the connection, discarding the error + _ = conn.Close() + interrupt <- ctx.Err() + } + }() + return func(inputErr *error) { + close(quit) + if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil { + // Return context error to user. + inputErr = &ctxErr + } + } +} diff --git a/common/net/deadline/conn.go b/common/net/deadline/conn.go new file mode 100644 index 00000000..e8446ce2 --- /dev/null +++ b/common/net/deadline/conn.go @@ -0,0 +1,149 @@ +package deadline + +import ( + "net" + "os" + "time" + + "github.com/metacubex/mihomo/common/atomic" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/network" +) + +type connReadResult struct { + buffer []byte + err error +} + +type Conn struct { + network.ExtendedConn + deadline atomic.TypedValue[time.Time] + pipeDeadline pipeDeadline + disablePipe atomic.Bool + inRead atomic.Bool + resultCh chan *connReadResult +} + +func NewConn(conn net.Conn) *Conn { + c := &Conn{ + ExtendedConn: bufio.NewExtendedConn(conn), + pipeDeadline: makePipeDeadline(), + resultCh: make(chan *connReadResult, 1), + } + c.resultCh <- nil + return c +} + +func (c *Conn) Read(p []byte) (n int, err error) { + select { + case result := <-c.resultCh: + if result != nil { + n = copy(p, result.buffer) + err = result.err + if n >= len(result.buffer) { + c.resultCh <- nil // finish cache read + } else { + result.buffer = result.buffer[n:] + c.resultCh <- result // push back for next call + } + return + } else { + c.resultCh <- nil + break + } + case <-c.pipeDeadline.wait(): + return 0, os.ErrDeadlineExceeded + } + + if c.disablePipe.Load() { + return c.ExtendedConn.Read(p) + } else if c.deadline.Load().IsZero() { + c.inRead.Store(true) + defer c.inRead.Store(false) + return c.ExtendedConn.Read(p) + } + + <-c.resultCh + go c.pipeRead(len(p)) + + return c.Read(p) +} + +func (c *Conn) pipeRead(size int) { + buffer := make([]byte, size) + n, err := c.ExtendedConn.Read(buffer) + buffer = buffer[:n] + c.resultCh <- &connReadResult{ + buffer: buffer, + err: err, + } +} + +func (c *Conn) ReadBuffer(buffer *buf.Buffer) (err error) { + select { + case result := <-c.resultCh: + if result != nil { + n, _ := buffer.Write(result.buffer) + err = result.err + + if n >= len(result.buffer) { + c.resultCh <- nil // finish cache read + } else { + result.buffer = result.buffer[n:] + c.resultCh <- result // push back for next call + } + return + } else { + c.resultCh <- nil + break + } + case <-c.pipeDeadline.wait(): + return os.ErrDeadlineExceeded + } + + if c.disablePipe.Load() { + return c.ExtendedConn.ReadBuffer(buffer) + } else if c.deadline.Load().IsZero() { + c.inRead.Store(true) + defer c.inRead.Store(false) + return c.ExtendedConn.ReadBuffer(buffer) + } + + <-c.resultCh + go c.pipeRead(buffer.FreeLen()) + + return c.ReadBuffer(buffer) +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + if c.disablePipe.Load() { + return c.ExtendedConn.SetReadDeadline(t) + } else if c.inRead.Load() { + c.disablePipe.Store(true) + return c.ExtendedConn.SetReadDeadline(t) + } + c.deadline.Store(t) + c.pipeDeadline.set(t) + return nil +} + +func (c *Conn) ReaderReplaceable() bool { + select { + case result := <-c.resultCh: + c.resultCh <- result + if result != nil { + return false // cache reading + } else { + break + } + default: + return false // pipe reading + } + return c.disablePipe.Load() || c.deadline.Load().IsZero() +} + +func (c *Conn) Upstream() any { + return c.ExtendedConn +} diff --git a/common/net/deadline/packet.go b/common/net/deadline/packet.go index bcf2db9d..67043198 100644 --- a/common/net/deadline/packet.go +++ b/common/net/deadline/packet.go @@ -6,8 +6,8 @@ import ( "runtime" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/net/packet" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/net/packet" ) type readResult struct { diff --git a/common/net/deadline/packet_enhance.go b/common/net/deadline/packet_enhance.go index 5b7d767f..3e314fb8 100644 --- a/common/net/deadline/packet_enhance.go +++ b/common/net/deadline/packet_enhance.go @@ -5,7 +5,7 @@ import ( "os" "runtime" - "github.com/Dreamacro/clash/common/net/packet" + "github.com/metacubex/mihomo/common/net/packet" ) type EnhancePacketConn struct { diff --git a/common/net/deadline/packet_sing.go b/common/net/deadline/packet_sing.go index f41f3f5b..d54748b0 100644 --- a/common/net/deadline/packet_sing.go +++ b/common/net/deadline/packet_sing.go @@ -4,7 +4,8 @@ import ( "os" "runtime" - "github.com/Dreamacro/clash/common/net/packet" + "github.com/metacubex/mihomo/common/net/packet" + "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" @@ -121,17 +122,18 @@ type singPacketReadWaiter struct { type singWaitReadResult singReadResult -func (c *singPacketReadWaiter) InitializeReadWaiter(newBuffer func() *buf.Buffer) { - c.packetReadWaiter.InitializeReadWaiter(newBuffer) +func (c *singPacketReadWaiter) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + return c.packetReadWaiter.InitializeReadWaiter(options) } -func (c *singPacketReadWaiter) WaitReadPacket() (destination M.Socksaddr, err error) { +func (c *singPacketReadWaiter) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) { FOR: for { select { case result := <-c.netPacketConn.resultCh: if result != nil { if result, ok := result.(*singWaitReadResult); ok { + buffer = result.buffer destination = result.destination err = result.err c.netPacketConn.resultCh <- nil // finish cache read @@ -145,7 +147,7 @@ FOR: break FOR } case <-c.netPacketConn.pipeDeadline.wait(): - return M.Socksaddr{}, os.ErrDeadlineExceeded + return nil, M.Socksaddr{}, os.ErrDeadlineExceeded } } @@ -154,8 +156,7 @@ FOR: } else if c.netPacketConn.deadline.Load().IsZero() { c.netPacketConn.inRead.Store(true) defer c.netPacketConn.inRead.Store(false) - destination, err = c.packetReadWaiter.WaitReadPacket() - return + return c.packetReadWaiter.WaitReadPacket() } <-c.netPacketConn.resultCh @@ -165,8 +166,9 @@ FOR: } func (c *singPacketReadWaiter) pipeWaitReadPacket() { - destination, err := c.packetReadWaiter.WaitReadPacket() + buffer, destination, err := c.packetReadWaiter.WaitReadPacket() result := &singWaitReadResult{} + result.buffer = buffer result.destination = destination result.err = err c.netPacketConn.resultCh <- result diff --git a/common/net/earlyconn.go b/common/net/earlyconn.go new file mode 100644 index 00000000..c9a42819 --- /dev/null +++ b/common/net/earlyconn.go @@ -0,0 +1,67 @@ +package net + +import ( + "net" + "sync" + "sync/atomic" + "unsafe" + + "github.com/metacubex/mihomo/common/buf" +) + +type earlyConn struct { + ExtendedConn // only expose standard N.ExtendedConn function to outside + resFunc func() error + resOnce sync.Once + resErr error +} + +func (conn *earlyConn) Response() error { + conn.resOnce.Do(func() { + conn.resErr = conn.resFunc() + }) + return conn.resErr +} + +func (conn *earlyConn) Read(b []byte) (n int, err error) { + err = conn.Response() + if err != nil { + return 0, err + } + return conn.ExtendedConn.Read(b) +} + +func (conn *earlyConn) ReadBuffer(buffer *buf.Buffer) (err error) { + err = conn.Response() + if err != nil { + return err + } + return conn.ExtendedConn.ReadBuffer(buffer) +} + +func (conn *earlyConn) Upstream() any { + return conn.ExtendedConn +} + +func (conn *earlyConn) Success() bool { + // atomic visit sync.Once.done + return atomic.LoadUint32((*uint32)(unsafe.Pointer(&conn.resOnce))) == 1 && conn.resErr == nil +} + +func (conn *earlyConn) ReaderReplaceable() bool { + return conn.Success() +} + +func (conn *earlyConn) ReaderPossiblyReplaceable() bool { + return !conn.Success() +} + +func (conn *earlyConn) WriterReplaceable() bool { + return true +} + +var _ ExtendedConn = (*earlyConn)(nil) + +func NewEarlyConn(c net.Conn, f func() error) net.Conn { + return &earlyConn{ExtendedConn: NewExtendedConn(c), resFunc: f} +} diff --git a/common/net/packet.go b/common/net/packet.go index fc562c42..fd03b4f8 100644 --- a/common/net/packet.go +++ b/common/net/packet.go @@ -1,8 +1,8 @@ package net import ( - "github.com/Dreamacro/clash/common/net/deadline" - "github.com/Dreamacro/clash/common/net/packet" + "github.com/metacubex/mihomo/common/net/deadline" + "github.com/metacubex/mihomo/common/net/packet" ) type EnhancePacketConn = packet.EnhancePacketConn diff --git a/common/net/packet/packet.go b/common/net/packet/packet.go index 6c9542c1..0cdbccae 100644 --- a/common/net/packet/packet.go +++ b/common/net/packet/packet.go @@ -3,7 +3,7 @@ package packet import ( "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) type WaitReadFrom interface { diff --git a/common/net/packet/packet_posix.go b/common/net/packet/packet_posix.go index 2861482f..2073e35d 100644 --- a/common/net/packet/packet_posix.go +++ b/common/net/packet/packet_posix.go @@ -7,7 +7,7 @@ import ( "strconv" "syscall" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) type enhanceUDPConn struct { diff --git a/common/net/packet/packet_sing.go b/common/net/packet/packet_sing.go index cfcf5ed0..6e25eb4d 100644 --- a/common/net/packet/packet_sing.go +++ b/common/net/packet/packet_sing.go @@ -24,16 +24,16 @@ type enhanceSingPacketConn struct { func (c *enhanceSingPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { var buff *buf.Buffer var dest M.Socksaddr - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff - } + rwOptions := N.ReadWaitOptions{} if c.packetReadWaiter != nil { - c.packetReadWaiter.InitializeReadWaiter(newBuffer) - defer c.packetReadWaiter.InitializeReadWaiter(nil) - dest, err = c.packetReadWaiter.WaitReadPacket() + c.packetReadWaiter.InitializeReadWaiter(rwOptions) + buff, dest, err = c.packetReadWaiter.WaitReadPacket() } else { - dest, err = c.SingPacketConn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = c.SingPacketConn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if dest.IsFqdn() { addr = dest @@ -41,9 +41,7 @@ func (c *enhanceSingPacketConn) WaitReadFrom() (data []byte, put func(), addr ne addr = dest.UDPAddr() } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() return } if buff == nil { diff --git a/common/net/packet/packet_windows.go b/common/net/packet/packet_windows.go index cb4c518b..3c467c6d 100644 --- a/common/net/packet/packet_windows.go +++ b/common/net/packet/packet_windows.go @@ -4,12 +4,72 @@ package packet import ( "net" + "strconv" + "syscall" + + "github.com/metacubex/mihomo/common/pool" + + "golang.org/x/sys/windows" ) type enhanceUDPConn struct { *net.UDPConn + rawConn syscall.RawConn } func (c *enhanceUDPConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { - return waitReadFrom(c.UDPConn) + if c.rawConn == nil { + c.rawConn, _ = c.UDPConn.SyscallConn() + } + var readErr error + hasData := false + err = c.rawConn.Read(func(fd uintptr) (done bool) { + if !hasData { + hasData = true + // golang's internal/poll.FD.RawRead will Use a zero-byte read as a way to get notified when this + // socket is readable if we return false. So the `recvfrom` syscall will not block the system thread. + return false + } + readBuf := pool.Get(pool.UDPBufferSize) + put = func() { + _ = pool.Put(readBuf) + } + var readFrom windows.Sockaddr + var readN int + readN, readFrom, readErr = windows.Recvfrom(windows.Handle(fd), readBuf, 0) + if readN > 0 { + data = readBuf[:readN] + } else { + put() + put = nil + data = nil + } + if readErr == windows.WSAEWOULDBLOCK { + return false + } + if readFrom != nil { + switch from := readFrom.(type) { + case *windows.SockaddrInet4: + ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 4 bytes + addr = &net.UDPAddr{IP: ip[:], Port: from.Port} + case *windows.SockaddrInet6: + ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 16 bytes + addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: strconv.FormatInt(int64(from.ZoneId), 10)} + } + } + // udp should not convert readN == 0 to io.EOF + //if readN == 0 { + // readErr = io.EOF + //} + hasData = false + return true + }) + if err != nil { + return + } + if readErr != nil { + err = readErr + return + } + return } diff --git a/common/net/refconn.go b/common/net/refconn.go index 5caaebc8..6d0dde98 100644 --- a/common/net/refconn.go +++ b/common/net/refconn.go @@ -5,7 +5,7 @@ import ( "runtime" "time" - "github.com/Dreamacro/clash/common/buf" + "github.com/metacubex/mihomo/common/buf" ) type refConn struct { diff --git a/common/net/relay.go b/common/net/relay.go index 6191e76b..f2a1b146 100644 --- a/common/net/relay.go +++ b/common/net/relay.go @@ -12,7 +12,7 @@ package net // // go func() { // // Wrapping to avoid using *net.TCPConn.(ReadFrom) -// // See also https://github.com/Dreamacro/clash/pull/1209 +// // See also https://github.com/metacubex/mihomo/pull/1209 // _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) // leftConn.SetReadDeadline(time.Now()) // ch <- err diff --git a/common/net/sing.go b/common/net/sing.go index c92008ba..f8698620 100644 --- a/common/net/sing.go +++ b/common/net/sing.go @@ -5,9 +5,10 @@ import ( "net" "runtime" + "github.com/metacubex/mihomo/common/net/deadline" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" "github.com/sagernet/sing/common/network" ) @@ -19,8 +20,10 @@ type ExtendedConn = network.ExtendedConn type ExtendedWriter = network.ExtendedWriter type ExtendedReader = network.ExtendedReader +var WriteBuffer = bufio.WriteBuffer + func NewDeadlineConn(conn net.Conn) ExtendedConn { - return deadline.NewFallbackConn(conn) + return deadline.NewConn(conn) } func NeedHandshake(conn any) bool { diff --git a/common/net/tls.go b/common/net/tls.go index e51324f7..b2865503 100644 --- a/common/net/tls.go +++ b/common/net/tls.go @@ -10,7 +10,11 @@ import ( "math/big" ) -func ParseCert(certificate, privateKey string) (tls.Certificate, error) { +type Path interface { + Resolve(path string) string +} + +func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) { if certificate == "" && privateKey == "" { return newRandomTLSKeyPair() } @@ -19,6 +23,8 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) { return cert, nil } + certificate = path.Resolve(certificate) + privateKey = path.Resolve(privateKey) cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) if loadErr != nil { return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index 5a02273d..d263cb94 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/common/atomic" + "github.com/metacubex/mihomo/common/atomic" "github.com/stretchr/testify/assert" ) diff --git a/common/pool/alloc.go b/common/pool/alloc.go index 5722b047..80927a2c 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -12,22 +12,28 @@ var defaultAllocator = NewAllocator() // Allocator for incoming frames, optimized to prevent overwriting after zeroing type Allocator struct { - buffers []sync.Pool + buffers [11]sync.Pool } // NewAllocator initiates a []byte allocator for frames less than 65536 bytes, // the waste(memory fragmentation) of space allocation is guaranteed to be // no more than 50%. func NewAllocator() *Allocator { - alloc := new(Allocator) - alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K - for k := range alloc.buffers { - i := k - alloc.buffers[k].New = func() any { - return make([]byte, 1< 64K + {New: func() any { return new([1 << 6]byte) }}, + {New: func() any { return new([1 << 7]byte) }}, + {New: func() any { return new([1 << 8]byte) }}, + {New: func() any { return new([1 << 9]byte) }}, + {New: func() any { return new([1 << 10]byte) }}, + {New: func() any { return new([1 << 11]byte) }}, + {New: func() any { return new([1 << 12]byte) }}, + {New: func() any { return new([1 << 13]byte) }}, + {New: func() any { return new([1 << 14]byte) }}, + {New: func() any { return new([1 << 15]byte) }}, + {New: func() any { return new([1 << 16]byte) }}, + }, } - return alloc } // Get a []byte from pool with most appropriate cap @@ -40,12 +46,42 @@ func (alloc *Allocator) Get(size int) []byte { case size > 65536: return make([]byte, size) default: - bits := msb(size) - if size == 1< 64 { + index = msb(size) + if size != 1< 65536 { return nil } - + bits := msb(cap(buf)) if cap(buf) != 1<data; void *data_end = (void *)(long)skb->data_end; @@ -264,7 +264,7 @@ int tc_redir_ingress_func(struct __sk_buff *skb) { return TC_ACT_OK; } -SEC("tc_clash_auto_redir_egress") +SEC("tc_mihomo_auto_redir_egress") int tc_redir_egress_func(struct __sk_buff *skb) { void *data = (void *)(long)skb->data; void *data_end = (void *)(long)skb->data_end; @@ -276,10 +276,10 @@ int tc_redir_egress_func(struct __sk_buff *skb) { if (eth->h_proto != bpf_htons(ETH_P_IP)) return TC_ACT_OK; - __u32 key = 0, *redir_ip, *redir_port; // *clash_mark + __u32 key = 0, *redir_ip, *redir_port; // *mihomo_mark -// clash_mark = bpf_map_lookup_elem(&redir_params_map, &key); -// if (clash_mark && *clash_mark != 0 && *clash_mark == skb->mark) +// mihomo_mark = bpf_map_lookup_elem(&redir_params_map, &key); +// if (mihomo_mark && *mihomo_mark != 0 && *mihomo_mark == skb->mark) // return TC_ACT_OK; struct iphdr *iph = (struct iphdr *)(eth + 1); diff --git a/component/ebpf/bpf/tc.c b/component/ebpf/bpf/tc.c index 4eebf41c..3513bf04 100644 --- a/component/ebpf/bpf/tc.c +++ b/component/ebpf/bpf/tc.c @@ -38,7 +38,7 @@ static __always_inline bool is_lan_ip(__be32 addr) { return false; } -SEC("tc_clash_redirect_to_tun") +SEC("tc_mihomo_redirect_to_tun") int tc_tun_func(struct __sk_buff *skb) { void *data = (void *)(long)skb->data; void *data_end = (void *)(long)skb->data_end; @@ -50,13 +50,13 @@ int tc_tun_func(struct __sk_buff *skb) { if (eth->h_proto == bpf_htons(ETH_P_ARP)) return TC_ACT_OK; - __u32 key = 0, *clash_mark, *tun_ifindex; + __u32 key = 0, *mihomo_mark, *tun_ifindex; - clash_mark = bpf_map_lookup_elem(&tc_params_map, &key); - if (!clash_mark) + mihomo_mark = bpf_map_lookup_elem(&tc_params_map, &key); + if (!mihomo_mark) return TC_ACT_OK; - if (skb->mark == *clash_mark) + if (skb->mark == *mihomo_mark) return TC_ACT_OK; if (eth->h_proto == bpf_htons(ETH_P_IP)) { diff --git a/component/ebpf/byteorder/byteorder_littleendian.go b/component/ebpf/byteorder/byteorder_littleendian.go index 216a5e5a..d40f3517 100644 --- a/component/ebpf/byteorder/byteorder_littleendian.go +++ b/component/ebpf/byteorder/byteorder_littleendian.go @@ -1,4 +1,4 @@ -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 package byteorder diff --git a/component/ebpf/ebpf.go b/component/ebpf/ebpf.go index 6257675c..b0f5a65f 100644 --- a/component/ebpf/ebpf.go +++ b/component/ebpf/ebpf.go @@ -3,8 +3,8 @@ package ebpf import ( "net/netip" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type TcEBpfProgram struct { diff --git a/component/ebpf/ebpf_linux.go b/component/ebpf/ebpf_linux.go index 2ffd4bd5..304f32fe 100644 --- a/component/ebpf/ebpf_linux.go +++ b/component/ebpf/ebpf_linux.go @@ -6,11 +6,11 @@ import ( "fmt" "net/netip" - "github.com/Dreamacro/clash/common/cmd" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/ebpf/redir" - "github.com/Dreamacro/clash/component/ebpf/tc" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/cmd" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/ebpf/redir" + "github.com/metacubex/mihomo/component/ebpf/tc" + C "github.com/metacubex/mihomo/constant" "github.com/sagernet/netlink" ) @@ -47,7 +47,7 @@ func NewTcEBpfProgram(ifaceNames []string, tunName string) (*TcEBpfProgram, erro tunIndex := uint32(tunIface.Attrs().Index) - dialer.DefaultRoutingMark.Store(C.ClashTrafficMark) + dialer.DefaultRoutingMark.Store(C.MihomoTrafficMark) ifMark := uint32(dialer.DefaultRoutingMark.Load()) diff --git a/component/ebpf/redir/auto_redirect.go b/component/ebpf/redir/auto_redirect.go index 4fd8b785..57c99616 100644 --- a/component/ebpf/redir/auto_redirect.go +++ b/component/ebpf/redir/auto_redirect.go @@ -16,9 +16,9 @@ import ( "github.com/sagernet/netlink" "golang.org/x/sys/unix" - "github.com/Dreamacro/clash/component/ebpf/byteorder" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/component/ebpf/byteorder" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/redir.c @@ -131,7 +131,7 @@ func (e *EBpfRedirect) Start() error { filter := &netlink.BpfFilter{ FilterAttrs: filterAttrs, Fd: objs.bpfPrograms.TcRedirIngressFunc.FD(), - Name: "clash-redir-ingress-" + e.ifName, + Name: "mihomo-redir-ingress-" + e.ifName, DirectAction: true, } @@ -153,7 +153,7 @@ func (e *EBpfRedirect) Start() error { filterEgress := &netlink.BpfFilter{ FilterAttrs: filterAttrsEgress, Fd: objs.bpfPrograms.TcRedirEgressFunc.FD(), - Name: "clash-redir-egress-" + e.ifName, + Name: "mihomo-redir-egress-" + e.ifName, DirectAction: true, } diff --git a/component/ebpf/redir/bpf_bpfel.go b/component/ebpf/redir/bpf_bpfel.go index 936b84eb..1fe3454a 100644 --- a/component/ebpf/redir/bpf_bpfel.go +++ b/component/ebpf/redir/bpf_bpfel.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 +// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64 package redir diff --git a/component/ebpf/tc/bpf_bpfel.go b/component/ebpf/tc/bpf_bpfel.go index 07daba12..3bfa1655 100644 --- a/component/ebpf/tc/bpf_bpfel.go +++ b/component/ebpf/tc/bpf_bpfel.go @@ -1,6 +1,6 @@ -// Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +// +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 +// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64 package tc diff --git a/component/ebpf/tc/redirect_to_tun.go b/component/ebpf/tc/redirect_to_tun.go index 1edc1781..d7be64af 100644 --- a/component/ebpf/tc/redirect_to_tun.go +++ b/component/ebpf/tc/redirect_to_tun.go @@ -14,8 +14,8 @@ import ( "github.com/sagernet/netlink" "golang.org/x/sys/unix" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf ../bpf/tc.c @@ -115,7 +115,7 @@ func (e *EBpfTC) Start() error { filter := &netlink.BpfFilter{ FilterAttrs: filterAttrs, Fd: objs.bpfPrograms.TcTunFunc.FD(), - Name: "clash-tc-" + e.ifName, + Name: "mihomo-tc-" + e.ifName, DirectAction: true, } diff --git a/component/fakeip/cachefile.go b/component/fakeip/cachefile.go index c31d751f..6f0cc48b 100644 --- a/component/fakeip/cachefile.go +++ b/component/fakeip/cachefile.go @@ -3,7 +3,7 @@ package fakeip import ( "net/netip" - "github.com/Dreamacro/clash/component/profile/cachefile" + "github.com/metacubex/mihomo/component/profile/cachefile" ) type cachefileStore struct { diff --git a/component/fakeip/memory.go b/component/fakeip/memory.go index 249c5e2a..00eff810 100644 --- a/component/fakeip/memory.go +++ b/component/fakeip/memory.go @@ -3,12 +3,12 @@ package fakeip import ( "net/netip" - "github.com/Dreamacro/clash/common/cache" + "github.com/metacubex/mihomo/common/lru" ) type memoryStore struct { - cacheIP *cache.LruCache[string, netip.Addr] - cacheHost *cache.LruCache[netip.Addr, string] + cacheIP *lru.LruCache[string, netip.Addr] + cacheHost *lru.LruCache[netip.Addr, string] } // GetByHost implements store.GetByHost @@ -73,7 +73,7 @@ func (m *memoryStore) FlushFakeIP() error { func newMemoryStore(size int) *memoryStore { return &memoryStore{ - cacheIP: cache.New[string, netip.Addr](cache.WithSize[string, netip.Addr](size)), - cacheHost: cache.New[netip.Addr, string](cache.WithSize[netip.Addr, string](size)), + cacheIP: lru.New[string, netip.Addr](lru.WithSize[string, netip.Addr](size)), + cacheHost: lru.New[netip.Addr, string](lru.WithSize[netip.Addr, string](size)), } } diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index ee11fedd..2b06fc0b 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,9 +6,9 @@ import ( "strings" "sync" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/component/profile/cachefile" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/common/nnip" + "github.com/metacubex/mihomo/component/profile/cachefile" + "github.com/metacubex/mihomo/component/trie" ) const ( @@ -36,7 +36,7 @@ type Pool struct { cycle bool mux sync.Mutex host *trie.DomainTrie[struct{}] - ipnet *netip.Prefix + ipnet netip.Prefix store store } @@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr { } // IPNet return raw ipnet -func (p *Pool) IPNet() *netip.Prefix { +func (p *Pool) IPNet() netip.Prefix { return p.ipnet } @@ -153,7 +153,7 @@ func (p *Pool) restoreState() { } type Options struct { - IPNet *netip.Prefix + IPNet netip.Prefix Host *trie.DomainTrie[struct{}] // Size sets the maximum number of entries in memory @@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) { hostAddr = options.IPNet.Masked().Addr() gateway = hostAddr.Next() first = gateway.Next().Next().Next() // default start with 198.18.0.4 - last = nnip.UnMasked(*options.IPNet) + last = nnip.UnMasked(options.IPNet) ) if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) { diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index ae343f96..cc50fcf7 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -7,11 +7,11 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/component/profile/cachefile" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/component/profile/cachefile" + "github.com/metacubex/mihomo/component/trie" + "github.com/sagernet/bbolt" "github.com/stretchr/testify/assert" - "go.etcd.io/bbolt" ) func createPools(options Options) ([]*Pool, string, error) { @@ -32,7 +32,7 @@ func createCachefileStore(options Options) (*Pool, string, error) { if err != nil { return nil, "", err } - f, err := os.CreateTemp("", "clash") + f, err := os.CreateTemp("", "mihomo") if err != nil { return nil, "", err } @@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) { func TestPool_Basic(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.0/28") pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) assert.Nil(t, err) @@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) { func TestPool_BasicV6(t *testing.T) { ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118") pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) assert.Nil(t, err) @@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) { func TestPool_Case_Insensitive(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/29") pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) assert.Nil(t, err) @@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) { func TestPool_CycleUsed(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.16/28") pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) assert.Nil(t, err) @@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) { tree := trie.New[struct{}]() tree.Insert("example.com", struct{}{}) pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, Host: tree, }) @@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) { func TestPool_MaxCacheSize(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 2, }) @@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 2, }) @@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) { func TestPool_Clone(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/24") pool, _ := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 2, }) @@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) { assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) newPool, _ := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 2, }) newPool.CloneFrom(pool) @@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) { func TestPool_Error(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/31") _, err := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) @@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) { func TestPool_FlushFileCache(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/28") pools, tempfile, err := createPools(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) assert.Nil(t, err) @@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) { func TestPool_FlushMemoryCache(t *testing.T) { ipnet := netip.MustParsePrefix("192.168.0.1/28") pool, _ := New(Options{ - IPNet: &ipnet, + IPNet: ipnet, Size: 10, }) diff --git a/component/geodata/attr.go b/component/geodata/attr.go index e35a25ca..a9742aca 100644 --- a/component/geodata/attr.go +++ b/component/geodata/attr.go @@ -3,7 +3,7 @@ package geodata import ( "strings" - "github.com/Dreamacro/clash/component/geodata/router" + "github.com/metacubex/mihomo/component/geodata/router" ) type AttributeList struct { diff --git a/component/geodata/geodata.go b/component/geodata/geodata.go index 9d0b0df0..a6ef146a 100644 --- a/component/geodata/geodata.go +++ b/component/geodata/geodata.go @@ -3,8 +3,8 @@ package geodata import ( "fmt" - "github.com/Dreamacro/clash/component/geodata/router" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/component/geodata/router" + C "github.com/metacubex/mihomo/constant" ) type loader struct { diff --git a/component/geodata/geodataproto.go b/component/geodata/geodataproto.go index 34bdad70..0f1ce4d2 100644 --- a/component/geodata/geodataproto.go +++ b/component/geodata/geodataproto.go @@ -1,7 +1,7 @@ package geodata import ( - "github.com/Dreamacro/clash/component/geodata/router" + "github.com/metacubex/mihomo/component/geodata/router" ) type LoaderImplementation interface { diff --git a/component/geodata/init.go b/component/geodata/init.go index acae1a34..842efcc5 100644 --- a/component/geodata/init.go +++ b/component/geodata/init.go @@ -8,10 +8,10 @@ import ( "os" "time" - clashHttp "github.com/Dreamacro/clash/component/http" - "github.com/Dreamacro/clash/component/mmdb" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + mihomoHttp "github.com/metacubex/mihomo/component/http" + "github.com/metacubex/mihomo/component/mmdb" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) var initGeoSite bool @@ -44,7 +44,7 @@ func InitGeoSite() error { func downloadGeoSite(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return } @@ -63,7 +63,7 @@ func downloadGeoSite(path string) (err error) { func downloadGeoIP(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return } diff --git a/component/geodata/memconservative/cache.go b/component/geodata/memconservative/cache.go index ca78d19d..ef76a42c 100644 --- a/component/geodata/memconservative/cache.go +++ b/component/geodata/memconservative/cache.go @@ -5,9 +5,9 @@ import ( "os" "strings" - "github.com/Dreamacro/clash/component/geodata/router" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/geodata/router" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "google.golang.org/protobuf/proto" ) diff --git a/component/geodata/memconservative/memc.go b/component/geodata/memconservative/memc.go index 88d3b4e5..30d89f10 100644 --- a/component/geodata/memconservative/memc.go +++ b/component/geodata/memconservative/memc.go @@ -5,8 +5,8 @@ import ( "fmt" "runtime" - "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/geodata/router" + "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata/router" ) type memConservativeLoader struct { diff --git a/component/geodata/router/condition.go b/component/geodata/router/condition.go index 4e0ad46c..73bc88d4 100644 --- a/component/geodata/router/condition.go +++ b/component/geodata/router/condition.go @@ -7,7 +7,8 @@ import ( "sort" "strings" - "github.com/Dreamacro/clash/component/geodata/strmatcher" + "github.com/metacubex/mihomo/component/geodata/strmatcher" + "github.com/metacubex/mihomo/component/trie" ) var matcherTypeMap = map[Domain_Type]strmatcher.Type{ @@ -31,12 +32,69 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) { return matcher, nil } -type DomainMatcher struct { +type DomainMatcher interface { + ApplyDomain(string) bool +} + +type succinctDomainMatcher struct { + set *trie.DomainSet + otherMatchers []strmatcher.Matcher + not bool +} + +func (m *succinctDomainMatcher) ApplyDomain(domain string) bool { + isMatched := m.set.Has(domain) + if !isMatched { + for _, matcher := range m.otherMatchers { + isMatched = matcher.Match(domain) + if isMatched { + break + } + } + } + if m.not { + isMatched = !isMatched + } + return isMatched +} + +func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) { + t := trie.New[struct{}]() + m := &succinctDomainMatcher{ + not: not, + } + for _, d := range domains { + switch d.Type { + case Domain_Plain, Domain_Regex: + matcher, err := matcherTypeMap[d.Type].New(d.Value) + if err != nil { + return nil, err + } + m.otherMatchers = append(m.otherMatchers, matcher) + + case Domain_Domain: + err := t.Insert("+."+d.Value, struct{}{}) + if err != nil { + return nil, err + } + + case Domain_Full: + err := t.Insert(d.Value, struct{}{}) + if err != nil { + return nil, err + } + } + } + m.set = t.NewDomainSet() + return m, nil +} + +type v2rayDomainMatcher struct { matchers strmatcher.IndexMatcher not bool } -func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) { +func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) { g := strmatcher.NewMphMatcherGroup() for _, d := range domains { matcherType, f := matcherTypeMap[d.Type] @@ -49,30 +107,13 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) { } } g.Build() - return &DomainMatcher{ + return &v2rayDomainMatcher{ matchers: g, not: not, }, nil } -// NewDomainMatcher new domain matcher. -func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) { - g := new(strmatcher.MatcherGroup) - for _, d := range domains { - m, err := domainToMatcher(d) - if err != nil { - return nil, err - } - g.Add(m) - } - - return &DomainMatcher{ - matchers: g, - not: not, - }, nil -} - -func (m *DomainMatcher) ApplyDomain(domain string) bool { +func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool { isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0 if m.not { isMatched = !isMatched diff --git a/component/geodata/router/config.pb.go b/component/geodata/router/config.pb.go index 7c3af22a..59d90c7a 100644 --- a/component/geodata/router/config.pb.go +++ b/component/geodata/router/config.pb.go @@ -84,7 +84,7 @@ type Domain struct { unknownFields protoimpl.UnknownFields // Domain matching type. - Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=clash.component.geodata.router.Domain_Type" json:"type,omitempty"` + Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=mihomo.component.geodata.router.Domain_Type" json:"type,omitempty"` // Domain value. Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // Attributes of this domain. May be used for filtering. @@ -585,22 +585,22 @@ func file_component_geodata_router_config_proto_rawDescGZIP() []byte { var file_component_geodata_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_component_geodata_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_component_geodata_router_config_proto_goTypes = []interface{}{ - (Domain_Type)(0), // 0: clash.component.geodata.router.Domain.Type - (*Domain)(nil), // 1: clash.component.geodata.router.Domain - (*CIDR)(nil), // 2: clash.component.geodata.router.CIDR - (*GeoIP)(nil), // 3: clash.component.geodata.router.GeoIP - (*GeoIPList)(nil), // 4: clash.component.geodata.router.GeoIPList - (*GeoSite)(nil), // 5: clash.component.geodata.router.GeoSite - (*GeoSiteList)(nil), // 6: clash.component.geodata.router.GeoSiteList - (*Domain_Attribute)(nil), // 7: clash.component.geodata.router.Domain.Attribute + (Domain_Type)(0), // 0: mihomo.component.geodata.router.Domain.Type + (*Domain)(nil), // 1: mihomo.component.geodata.router.Domain + (*CIDR)(nil), // 2: mihomo.component.geodata.router.CIDR + (*GeoIP)(nil), // 3: mihomo.component.geodata.router.GeoIP + (*GeoIPList)(nil), // 4: mihomo.component.geodata.router.GeoIPList + (*GeoSite)(nil), // 5: mihomo.component.geodata.router.GeoSite + (*GeoSiteList)(nil), // 6: mihomo.component.geodata.router.GeoSiteList + (*Domain_Attribute)(nil), // 7: mihomo.component.geodata.router.Domain.Attribute } var file_component_geodata_router_config_proto_depIdxs = []int32{ - 0, // 0: clash.component.geodata.router.Domain.type:type_name -> clash.component.geodata.router.Domain.Type - 7, // 1: clash.component.geodata.router.Domain.attribute:type_name -> clash.component.geodata.router.Domain.Attribute - 2, // 2: clash.component.geodata.router.GeoIP.cidr:type_name -> clash.component.geodata.router.CIDR - 3, // 3: clash.component.geodata.router.GeoIPList.entry:type_name -> clash.component.geodata.router.GeoIP - 1, // 4: clash.component.geodata.router.GeoSite.domain:type_name -> clash.component.geodata.router.Domain - 5, // 5: clash.component.geodata.router.GeoSiteList.entry:type_name -> clash.component.geodata.router.GeoSite + 0, // 0: mihomo.component.geodata.router.Domain.type:type_name -> mihomo.component.geodata.router.Domain.Type + 7, // 1: mihomo.component.geodata.router.Domain.attribute:type_name -> mihomo.component.geodata.router.Domain.Attribute + 2, // 2: mihomo.component.geodata.router.GeoIP.cidr:type_name -> mihomo.component.geodata.router.CIDR + 3, // 3: mihomo.component.geodata.router.GeoIPList.entry:type_name -> mihomo.component.geodata.router.GeoIP + 1, // 4: mihomo.component.geodata.router.GeoSite.domain:type_name -> mihomo.component.geodata.router.Domain + 5, // 5: mihomo.component.geodata.router.GeoSiteList.entry:type_name -> mihomo.component.geodata.router.GeoSite 6, // [6:6] is the sub-list for method output_type 6, // [6:6] is the sub-list for method input_type 6, // [6:6] is the sub-list for extension type_name diff --git a/component/geodata/router/config.proto b/component/geodata/router/config.proto index 245faadf..98795740 100644 --- a/component/geodata/router/config.proto +++ b/component/geodata/router/config.proto @@ -1,9 +1,9 @@ syntax = "proto3"; -package clash.component.geodata.router; -option csharp_namespace = "Clash.Component.Geodata.Router"; -option go_package = "github.com/Dreamacro/clash/component/geodata/router"; -option java_package = "com.clash.component.geodata.router"; +package mihomo.component.geodata.router; +option csharp_namespace = "Mihomo.Component.Geodata.Router"; +option go_package = "github.com/metacubex/mihomo/component/geodata/router"; +option java_package = "com.mihomo.component.geodata.router"; option java_multiple_files = true; // Domain for routing decision. diff --git a/component/geodata/standard/standard.go b/component/geodata/standard/standard.go index 355cbf34..bfaae5ec 100644 --- a/component/geodata/standard/standard.go +++ b/component/geodata/standard/standard.go @@ -6,9 +6,9 @@ import ( "os" "strings" - "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/geodata/router" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata/router" + C "github.com/metacubex/mihomo/constant" "google.golang.org/protobuf/proto" ) diff --git a/component/geodata/strmatcher/ac_automaton_matcher.go b/component/geodata/strmatcher/ac_automaton_matcher.go index d134c68a..fd02d511 100644 --- a/component/geodata/strmatcher/ac_automaton_matcher.go +++ b/component/geodata/strmatcher/ac_automaton_matcher.go @@ -1,7 +1,7 @@ package strmatcher import ( - "github.com/Dreamacro/clash/common/generics/list" + list "github.com/bahlo/generic-list-go" ) const validCharCount = 53 @@ -39,7 +39,7 @@ func newNode() [validCharCount]Edge { return s } -var char2Index = []int{ +var char2Index = [...]int{ 'A': 0, 'a': 0, 'B': 1, diff --git a/component/geodata/strmatcher/domain_matcher.go b/component/geodata/strmatcher/domain_matcher.go deleted file mode 100644 index ae8e65bc..00000000 --- a/component/geodata/strmatcher/domain_matcher.go +++ /dev/null @@ -1,98 +0,0 @@ -package strmatcher - -import "strings" - -func breakDomain(domain string) []string { - return strings.Split(domain, ".") -} - -type node struct { - values []uint32 - sub map[string]*node -} - -// DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers. -// Visible for testing only. -type DomainMatcherGroup struct { - root *node -} - -func (g *DomainMatcherGroup) Add(domain string, value uint32) { - if g.root == nil { - g.root = new(node) - } - - current := g.root - parts := breakDomain(domain) - for i := len(parts) - 1; i >= 0; i-- { - part := parts[i] - if current.sub == nil { - current.sub = make(map[string]*node) - } - next := current.sub[part] - if next == nil { - next = new(node) - current.sub[part] = next - } - current = next - } - - current.values = append(current.values, value) -} - -func (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) { - g.Add(string(m), value) -} - -func (g *DomainMatcherGroup) Match(domain string) []uint32 { - if domain == "" { - return nil - } - - current := g.root - if current == nil { - return nil - } - - nextPart := func(idx int) int { - for i := idx - 1; i >= 0; i-- { - if domain[i] == '.' { - return i - } - } - return -1 - } - - matches := [][]uint32{} - idx := len(domain) - for { - if idx == -1 || current.sub == nil { - break - } - - nidx := nextPart(idx) - part := domain[nidx+1 : idx] - next := current.sub[part] - if next == nil { - break - } - current = next - idx = nidx - if len(current.values) > 0 { - matches = append(matches, current.values) - } - } - switch len(matches) { - case 0: - return nil - case 1: - return matches[0] - default: - result := []uint32{} - for idx := range matches { - // Insert reversely, the subdomain that matches further ranks higher - result = append(result, matches[len(matches)-1-idx]...) - } - return result - } -} diff --git a/component/geodata/strmatcher/full_matcher.go b/component/geodata/strmatcher/full_matcher.go deleted file mode 100644 index e00d02aa..00000000 --- a/component/geodata/strmatcher/full_matcher.go +++ /dev/null @@ -1,25 +0,0 @@ -package strmatcher - -type FullMatcherGroup struct { - matchers map[string][]uint32 -} - -func (g *FullMatcherGroup) Add(domain string, value uint32) { - if g.matchers == nil { - g.matchers = make(map[string][]uint32) - } - - g.matchers[domain] = append(g.matchers[domain], value) -} - -func (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) { - g.Add(string(m), value) -} - -func (g *FullMatcherGroup) Match(str string) []uint32 { - if g.matchers == nil { - return nil - } - - return g.matchers[str] -} diff --git a/component/geodata/strmatcher/mph_matcher.go b/component/geodata/strmatcher/mph_matcher.go index 8d8b0508..7c1b4062 100644 --- a/component/geodata/strmatcher/mph_matcher.go +++ b/component/geodata/strmatcher/mph_matcher.go @@ -236,25 +236,25 @@ tail: h ^= uint64(*(*byte)(p)) h ^= uint64(*(*byte)(unsafe.Add(p, s>>1))) << 8 h ^= uint64(*(*byte)(unsafe.Add(p, s-1))) << 16 - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 8: h ^= uint64(readUnaligned32(p)) h ^= uint64(readUnaligned32(unsafe.Add(p, s-4))) << 32 - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 16: h ^= readUnaligned64(p) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 32: h ^= readUnaligned64(p) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, 8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-16)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 default: v1 := h v2 := uint64(seed * hashkey[1]) @@ -262,16 +262,16 @@ tail: v4 := uint64(seed * hashkey[3]) for s >= 32 { v1 ^= readUnaligned64(p) - v1 = rotl31(v1*m1) * m2 + v1 = bits.RotateLeft64(v1*m1, 31) * m2 p = unsafe.Add(p, 8) v2 ^= readUnaligned64(p) - v2 = rotl31(v2*m2) * m3 + v2 = bits.RotateLeft64(v2*m2, 31) * m3 p = unsafe.Add(p, 8) v3 ^= readUnaligned64(p) - v3 = rotl31(v3*m3) * m4 + v3 = bits.RotateLeft64(v3*m3, 31) * m4 p = unsafe.Add(p, 8) v4 ^= readUnaligned64(p) - v4 = rotl31(v4*m4) * m1 + v4 = bits.RotateLeft64(v4*m4, 31) * m1 p = unsafe.Add(p, 8) s -= 32 } @@ -290,10 +290,6 @@ func readUnaligned32(p unsafe.Pointer) uint32 { return uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24 } -func rotl31(x uint64) uint64 { - return (x << 31) | (x >> (64 - 31)) -} - func readUnaligned64(p unsafe.Pointer) uint64 { q := (*[8]byte)(p) return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56 diff --git a/component/geodata/strmatcher/strmatcher.go b/component/geodata/strmatcher/strmatcher.go index 294e6e73..6bdb8b97 100644 --- a/component/geodata/strmatcher/strmatcher.go +++ b/component/geodata/strmatcher/strmatcher.go @@ -58,50 +58,3 @@ type matcherEntry struct { m Matcher id uint32 } - -// MatcherGroup is an implementation of IndexMatcher. -// Empty initialization works. -type MatcherGroup struct { - count uint32 - fullMatcher FullMatcherGroup - domainMatcher DomainMatcherGroup - otherMatchers []matcherEntry -} - -// Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0. -func (g *MatcherGroup) Add(m Matcher) uint32 { - g.count++ - c := g.count - - switch tm := m.(type) { - case fullMatcher: - g.fullMatcher.addMatcher(tm, c) - case domainMatcher: - g.domainMatcher.addMatcher(tm, c) - default: - g.otherMatchers = append(g.otherMatchers, matcherEntry{ - m: m, - id: c, - }) - } - - return c -} - -// Match implements IndexMatcher.Match. -func (g *MatcherGroup) Match(pattern string) []uint32 { - result := []uint32{} - result = append(result, g.fullMatcher.Match(pattern)...) - result = append(result, g.domainMatcher.Match(pattern)...) - for _, e := range g.otherMatchers { - if e.m.Match(pattern) { - result = append(result, e.id) - } - } - return result -} - -// Size returns the number of matchers in the MatcherGroup. -func (g *MatcherGroup) Size() uint32 { - return g.count -} diff --git a/component/geodata/utils.go b/component/geodata/utils.go index 04ccfa51..a4002aeb 100644 --- a/component/geodata/utils.go +++ b/component/geodata/utils.go @@ -6,12 +6,13 @@ import ( "golang.org/x/sync/singleflight" "strings" - "github.com/Dreamacro/clash/component/geodata/router" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/geodata/router" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) var geoLoaderName = "memconservative" +var geoSiteMatcher = "succinct" // geoLoaderName = "standard" @@ -19,6 +20,10 @@ func LoaderName() string { return geoLoaderName } +func SiteMatcherName() string { + return geoSiteMatcher +} + func SetLoader(newLoader string) { if newLoader == "memc" { newLoader = "memconservative" @@ -26,6 +31,15 @@ func SetLoader(newLoader string) { geoLoaderName = newLoader } +func SetSiteMatcher(newMatcher string) { + switch newMatcher { + case "mph", "hybrid": + geoSiteMatcher = "mph" + default: + geoSiteMatcher = "succinct" + } +} + func Verify(name string) error { switch name { case C.GeositeName: @@ -41,8 +55,8 @@ func Verify(name string) error { var loadGeoSiteMatcherSF = singleflight.Group{} -func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { - if len(countryCode) == 0 { +func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) { + if countryCode == "" { return nil, 0, fmt.Errorf("country code could not be empty") } @@ -60,7 +74,7 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) listName := strings.TrimSpace(parts[0]) attrVal := parts[1:] - if len(listName) == 0 { + if listName == "" { return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode) } @@ -104,7 +118,12 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) matcher, err := router.NewDomainMatcher(domains) mph:minimal perfect hash algorithm */ - matcher, err := router.NewMphMatcherGroup(domains, not) + var matcher router.DomainMatcher + if geoSiteMatcher == "mph" { + matcher, err = router.NewMphMatcherGroup(domains, not) + } else { + matcher, err = router.NewSuccinctMatcherGroup(domains, not) + } if err != nil { return nil, 0, err } diff --git a/component/http/http.go b/component/http/http.go index 8e682e94..455db681 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -7,12 +7,13 @@ import ( "net" "net/http" URL "net/url" + "runtime" "strings" "time" - "github.com/Dreamacro/clash/component/ca" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/inner" + "github.com/metacubex/mihomo/component/ca" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/inner" ) func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { @@ -47,6 +48,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st transport := &http.Transport{ // from http.DefaultTransport + DisableKeepAlives: runtime.GOOS == "android", MaxIdleConns: 100, IdleConnTimeout: 30 * time.Second, TLSHandshakeTimeout: 10 * time.Second, diff --git a/component/iface/iface.go b/component/iface/iface.go index c32b65ab..bf186165 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -7,13 +7,13 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/singledo" + "github.com/metacubex/mihomo/common/singledo" ) type Interface struct { Index int Name string - Addrs []*netip.Prefix + Addrs []netip.Prefix HardwareAddr net.HardwareAddr } @@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) { continue } - ipNets := make([]*netip.Prefix, 0, len(addrs)) + ipNets := make([]netip.Prefix, 0, len(addrs)) for _, addr := range addrs { ipNet := addr.(*net.IPNet) ip, _ := netip.AddrFromSlice(ipNet.IP) @@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) { } pf := netip.PrefixFrom(ip, ones) - ipNets = append(ipNets, &pf) + ipNets = append(ipNets, pf) } r[iface.Name] = &Interface{ @@ -89,27 +89,27 @@ func FlushCache() { interfaces.Reset() } -func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) { - return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { +func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) { + return iface.pickIPAddr(destination, func(addr netip.Prefix) bool { return addr.Addr().Is4() }) } -func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) { - return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool { +func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) { + return iface.pickIPAddr(destination, func(addr netip.Prefix) bool { return addr.Addr().Is6() }) } -func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) { - var fallback *netip.Prefix +func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) { + var fallback netip.Prefix for _, addr := range iface.Addrs { if !accept(addr) { continue } - if fallback == nil && !addr.Addr().IsLinkLocalUnicast() { + if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() { fallback = addr if !destination.IsValid() { @@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net } } - if fallback == nil { - return nil, ErrAddrNotFound + if !fallback.IsValid() { + return netip.Prefix{}, ErrAddrNotFound } return fallback, nil diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 5db8bee9..d411b2b4 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -8,9 +8,9 @@ import ( "sync" "time" - clashHttp "github.com/Dreamacro/clash/component/http" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + mihomoHttp "github.com/metacubex/mihomo/component/http" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/oschwald/maxminddb-golang" ) @@ -79,7 +79,7 @@ func Instance() Reader { func DownloadMMDB(path string) (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return } diff --git a/component/mmdb/patch_android.go b/component/mmdb/patch_android.go new file mode 100644 index 00000000..a994b75e --- /dev/null +++ b/component/mmdb/patch_android.go @@ -0,0 +1,18 @@ +//go:build android && cmfa + +package mmdb + +import "github.com/oschwald/maxminddb-golang" + +func InstallOverride(override *maxminddb.Reader) { + newReader := Reader{Reader: override} + switch override.Metadata.DatabaseType { + case "sing-geoip": + reader.databaseType = typeSing + case "Meta-geoip0": + reader.databaseType = typeMetaV0 + default: + reader.databaseType = typeMaxmind + } + reader = newReader +} diff --git a/component/nat/proxy.go b/component/nat/proxy.go index 29ff3c81..66af3be2 100644 --- a/component/nat/proxy.go +++ b/component/nat/proxy.go @@ -3,8 +3,8 @@ package nat import ( "net" - "github.com/Dreamacro/clash/common/atomic" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/atomic" + C "github.com/metacubex/mihomo/constant" ) type writeBackProxy struct { diff --git a/component/nat/table.go b/component/nat/table.go index df258dc2..bb5ab755 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -4,9 +4,9 @@ import ( "net" "sync" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" ) type Table struct { @@ -25,8 +25,8 @@ func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) { t.mapping.Store(key, &Entry{ PacketConn: e, WriteBackProxy: w, - LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](), - LocalLockMap: xsync.NewMapOf[*sync.Cond](), + LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](), + LocalLockMap: xsync.NewMapOf[string, *sync.Cond](), }) } @@ -116,7 +116,7 @@ func makeLock() *sync.Cond { // New return *Cache func New() *Table { return &Table{ - mapping: xsync.NewMapOf[*Entry](), - lockMap: xsync.NewMapOf[*sync.Cond](), + mapping: xsync.NewMapOf[string, *Entry](), + lockMap: xsync.NewMapOf[string, *sync.Cond](), } } diff --git a/component/process/process_android.go b/component/process/process_android.go new file mode 100644 index 00000000..fd5d3b6c --- /dev/null +++ b/component/process/process_android.go @@ -0,0 +1,16 @@ +//go:build android && cmfa + +package process + +import "github.com/metacubex/mihomo/constant" + +type PackageNameResolver func(metadata *constant.Metadata) (string, error) + +var DefaultPackageNameResolver PackageNameResolver + +func FindPackageName(metadata *constant.Metadata) (string, error) { + if resolver := DefaultPackageNameResolver; resolver != nil { + return resolver(metadata) + } + return "", ErrPlatformNotSupport +} diff --git a/component/process/process_common.go b/component/process/process_common.go new file mode 100644 index 00000000..fa7eeb9f --- /dev/null +++ b/component/process/process_common.go @@ -0,0 +1,9 @@ +//go:build !(android && cmfa) + +package process + +import "github.com/metacubex/mihomo/constant" + +func FindPackageName(metadata *constant.Metadata) (string, error) { + return "", nil +} diff --git a/component/process/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go index 709ade3b..1884afcc 100644 --- a/component/process/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -10,8 +10,8 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/nnip" + "github.com/metacubex/mihomo/log" ) // store process name for when dealing with multiple PROCESS-NAME rules diff --git a/component/process/process_windows.go b/component/process/process_windows.go index 21878bf6..f8cd00d8 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -7,8 +7,8 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/nnip" + "github.com/metacubex/mihomo/log" "golang.org/x/sys/windows" ) @@ -218,7 +218,7 @@ func getExecPathFromPID(pid uint32) (string, error) { r1, _, err := syscall.SyscallN( queryProcName, uintptr(h), - uintptr(1), + uintptr(0), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), ) diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index 3d2dd1de..11068647 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -5,11 +5,11 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/component/profile" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/profile" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" - "go.etcd.io/bbolt" + "github.com/sagernet/bbolt" ) var ( diff --git a/component/profile/profile.go b/component/profile/profile.go index aa6df2f7..36db8cc3 100644 --- a/component/profile/profile.go +++ b/component/profile/profile.go @@ -1,7 +1,7 @@ package profile import ( - "github.com/Dreamacro/clash/common/atomic" + "github.com/metacubex/mihomo/common/atomic" ) // StoreSelected is a global switch for storing selected proxy to cache diff --git a/component/proxydialer/proxydialer.go b/component/proxydialer/proxydialer.go index 83010f96..71a658b8 100644 --- a/component/proxydialer/proxydialer.go +++ b/component/proxydialer/proxydialer.go @@ -8,12 +8,12 @@ import ( "net/netip" "strings" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/tunnel" - "github.com/Dreamacro/clash/tunnel/statistic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/tunnel" + "github.com/metacubex/mihomo/tunnel/statistic" ) type proxyDialer struct { @@ -70,10 +70,7 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) ( } func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { - currentMeta := &C.Metadata{Type: C.INNER} - if err := currentMeta.SetRemoteAddress(rAddrPort.String()); err != nil { - return nil, err - } + currentMeta := &C.Metadata{Type: C.INNER, DstIP: rAddrPort.Addr(), DstPort: rAddrPort.Port()} return p.listenPacket(ctx, currentMeta) } diff --git a/component/proxydialer/sing.go b/component/proxydialer/sing.go index 9b116527..71180c01 100644 --- a/component/proxydialer/sing.go +++ b/component/proxydialer/sing.go @@ -4,7 +4,7 @@ import ( "context" "net" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" diff --git a/component/resolver/host.go b/component/resolver/host.go index d6eb5873..69c29a3c 100644 --- a/component/resolver/host.go +++ b/component/resolver/host.go @@ -6,8 +6,8 @@ import ( "strings" _ "unsafe" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/trie" "github.com/zhangyunhao116/fastrand" ) diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 6be6a95f..8cbc62fa 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/trie" "github.com/miekg/dns" "github.com/zhangyunhao116/fastrand" diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index c92687b1..44ca45c2 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -7,12 +7,16 @@ import ( "path/filepath" "time" - types "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/log" + types "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" "github.com/samber/lo" ) +const ( + minInterval = time.Minute * 5 +) + var ( fileMode os.FileMode = 0o666 dirMode os.FileMode = 0o755 @@ -24,8 +28,7 @@ type Fetcher[V any] struct { resourceType string name string vehicle types.Vehicle - UpdatedAt *time.Time - ticker *time.Ticker + UpdatedAt time.Time done chan struct{} hash [16]byte parser Parser[V] @@ -56,7 +59,7 @@ func (f *Fetcher[V]) Initial() (V, error) { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { buf, err = os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() - f.UpdatedAt = &modTime + f.UpdatedAt = modTime isLocal = true if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) { log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) @@ -64,6 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) { } } else { buf, err = f.vehicle.Read() + f.UpdatedAt = time.Now() } if err != nil { @@ -113,7 +117,7 @@ func (f *Fetcher[V]) Initial() (V, error) { f.hash = md5.Sum(buf) // pull contents automatically - if f.ticker != nil { + if f.interval > 0 { go f.pullLoop() } @@ -129,7 +133,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) { now := time.Now() hash := md5.Sum(buf) if bytes.Equal(f.hash[:], hash[:]) { - f.UpdatedAt = &now + f.UpdatedAt = now _ = os.Chtimes(f.vehicle.Path(), now, now) return lo.Empty[V](), true, nil } @@ -145,23 +149,31 @@ func (f *Fetcher[V]) Update() (V, bool, error) { } } - f.UpdatedAt = &now + f.UpdatedAt = now f.hash = hash return contents, false, nil } func (f *Fetcher[V]) Destroy() error { - if f.ticker != nil { + if f.interval > 0 { f.done <- struct{}{} } return nil } func (f *Fetcher[V]) pullLoop() { + initialInterval := f.interval - time.Since(f.UpdatedAt) + if initialInterval < minInterval { + initialInterval = minInterval + } + + timer := time.NewTimer(initialInterval) + defer timer.Stop() for { select { - case <-f.ticker.C: + case <-timer.C: + timer.Reset(f.interval) elm, same, err := f.Update() if err != nil { log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) @@ -178,7 +190,6 @@ func (f *Fetcher[V]) pullLoop() { f.OnUpdate(elm) } case <-f.done: - f.ticker.Stop() return } } @@ -197,17 +208,12 @@ func safeWrite(path string, buf []byte) error { } func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] { - var ticker *time.Ticker - if interval != 0 { - ticker = time.NewTicker(interval) - } return &Fetcher[V]{ name: name, - ticker: ticker, vehicle: vehicle, parser: parser, - done: make(chan struct{}, 1), + done: make(chan struct{}, 8), OnUpdate: onUpdate, interval: interval, } diff --git a/component/resource/vehicle.go b/component/resource/vehicle.go index 2f4bfbc8..b2e29418 100644 --- a/component/resource/vehicle.go +++ b/component/resource/vehicle.go @@ -8,8 +8,8 @@ import ( "os" "time" - clashHttp "github.com/Dreamacro/clash/component/http" - types "github.com/Dreamacro/clash/constant/provider" + mihomoHttp "github.com/metacubex/mihomo/component/http" + types "github.com/metacubex/mihomo/constant/provider" ) type FileVehicle struct { @@ -52,7 +52,7 @@ func (h *HTTPVehicle) Path() string { func (h *HTTPVehicle) Read() ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil) + resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil) if err != nil { return nil, err } diff --git a/component/sniffer/base_sniffer.go b/component/sniffer/base_sniffer.go index cf7cb940..55f51c50 100644 --- a/component/sniffer/base_sniffer.go +++ b/component/sniffer/base_sniffer.go @@ -3,9 +3,9 @@ package sniffer import ( "errors" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/sniffer" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/sniffer" ) type SnifferConfig struct { @@ -23,8 +23,8 @@ func (*BaseSniffer) Protocol() string { return "unknown" } -// SniffTCP implements sniffer.Sniffer -func (*BaseSniffer) SniffTCP(bytes []byte) (string, error) { +// SniffData implements sniffer.Sniffer +func (*BaseSniffer) SniffData(bytes []byte) (string, error) { return "", errors.New("TODO") } diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index a1c8a93f..96e9272c 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -8,12 +8,12 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/cache" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/trie" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/sniffer" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/lru" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/sniffer" + "github.com/metacubex/mihomo/log" ) var ( @@ -29,15 +29,46 @@ type SnifferDispatcher struct { sniffers map[sniffer.Sniffer]SnifferConfig forceDomain *trie.DomainSet skipSNI *trie.DomainSet - skipList *cache.LruCache[string, uint8] + skipList *lru.LruCache[string, uint8] rwMux sync.RWMutex forceDnsMapping bool parsePureIp bool } +func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool { + return (metadata.Host == "" && sd.parsePureIp) || + sd.forceDomain.Has(metadata.Host) || + (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) +} + +func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool { + metadata := packet.Metadata() + + if sd.shouldOverride(packet.Metadata()) { + for sniffer, config := range sd.sniffers { + if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet { + inWhitelist := sniffer.SupportPort(metadata.DstPort) + overrideDest := config.OverrideDest + + if inWhitelist { + host, err := sniffer.SniffData(packet.Data()) + if err != nil { + continue + } + + sd.replaceDomain(metadata, host, overrideDest) + return true + } + } + } + } + + return false +} + // TCPSniff returns true if the connection is sniffed to have a domain func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool { - if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { + if sd.shouldOverride(metadata) { inWhitelist := false overrideDest := false for sniffer, config := range sd.sniffers { @@ -86,7 +117,8 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { // show log early, since the following code may mutate `metadata.Host` - log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]", + log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]", + metadata.NetWork, metadata.SourceDetail(), metadata.RemoteAddress(), metadata.Host, host) @@ -125,7 +157,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad continue } - host, err := s.SniffTCP(bytes) + host, err := s.SniffData(bytes) if err != nil { //log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP) continue @@ -170,7 +202,7 @@ func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, enable: true, forceDomain: forceDomain, skipSNI: skipSNI, - skipList: cache.New(cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)), + skipList: lru.New(lru.WithSize[string, uint8](128), lru.WithAge[string, uint8](600)), forceDnsMapping: forceDnsMapping, parsePureIp: parsePureIp, sniffers: make(map[sniffer.Sniffer]SnifferConfig, 0), @@ -194,6 +226,8 @@ func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer return NewTLSSniffer(snifferConfig) case sniffer.HTTP: return NewHTTPSniffer(snifferConfig) + case sniffer.QUIC: + return NewQuicSniffer(snifferConfig) default: return nil, ErrorUnsupportedSniffer } diff --git a/component/sniffer/http_sniffer.go b/component/sniffer/http_sniffer.go index beb4bd20..76bf1559 100644 --- a/component/sniffer/http_sniffer.go +++ b/component/sniffer/http_sniffer.go @@ -7,9 +7,9 @@ import ( "net" "strings" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/sniffer" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/sniffer" ) var ( @@ -58,7 +58,7 @@ func (http *HTTPSniffer) SupportNetwork() C.NetWork { return C.TCP } -func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) { +func (http *HTTPSniffer) SniffData(bytes []byte) (string, error) { domain, err := SniffHTTP(bytes) if err == nil { return *domain, nil diff --git a/component/sniffer/quic_sniffer.go b/component/sniffer/quic_sniffer.go index de78cf82..0e3994f0 100644 --- a/component/sniffer/quic_sniffer.go +++ b/component/sniffer/quic_sniffer.go @@ -1,3 +1,287 @@ package sniffer -//TODO +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "encoding/binary" + "errors" + "io" + + "github.com/metacubex/mihomo/common/buf" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + + "github.com/metacubex/quic-go/quicvarint" + "golang.org/x/crypto/hkdf" +) + +// Modified from https://github.com/v2fly/v2ray-core/blob/master/common/protocol/quic/sniff.go + +const ( + versionDraft29 uint32 = 0xff00001d + version1 uint32 = 0x1 +) + +var ( + quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99} + quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a} + errNotQuic = errors.New("not QUIC") + errNotQuicInitial = errors.New("not QUIC initial packet") +) + +type QuicSniffer struct { + *BaseSniffer +} + +func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) { + ports := snifferConfig.Ports + if len(ports) == 0 { + ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)} + } + return &QuicSniffer{ + BaseSniffer: NewBaseSniffer(ports, C.UDP), + }, nil +} + +func (quic QuicSniffer) Protocol() string { + return "quic" +} + +func (quic QuicSniffer) SupportNetwork() C.NetWork { + return C.UDP +} + +func (quic QuicSniffer) SniffData(b []byte) (string, error) { + buffer := buf.As(b) + typeByte, err := buffer.ReadByte() + if err != nil { + return "", errNotQuic + } + isLongHeader := typeByte&0x80 > 0 + if !isLongHeader || typeByte&0x40 == 0 { + return "", errNotQuicInitial + } + + vb, err := buffer.ReadBytes(4) + if err != nil { + return "", errNotQuic + } + + versionNumber := binary.BigEndian.Uint32(vb) + + if versionNumber != 0 && typeByte&0x40 == 0 { + return "", errNotQuic + } else if versionNumber != versionDraft29 && versionNumber != version1 { + return "", errNotQuic + } + + if (typeByte&0x30)>>4 != 0x0 { + return "", errNotQuicInitial + } + + var destConnID []byte + if l, err := buffer.ReadByte(); err != nil { + return "", errNotQuic + } else if destConnID, err = buffer.ReadBytes(int(l)); err != nil { + return "", errNotQuic + } + + if l, err := buffer.ReadByte(); err != nil { + return "", errNotQuic + } else if _, err := buffer.ReadBytes(int(l)); err != nil { + return "", errNotQuic + } + + tokenLen, err := quicvarint.Read(buffer) + if err != nil || tokenLen > uint64(len(b)) { + return "", errNotQuic + } + + if _, err = buffer.ReadBytes(int(tokenLen)); err != nil { + return "", errNotQuic + } + + packetLen, err := quicvarint.Read(buffer) + if err != nil { + return "", errNotQuic + } + + hdrLen := len(b) - buffer.Len() + + var salt []byte + if versionNumber == version1 { + salt = quicSalt + } else { + salt = quicSaltOld + } + initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt) + secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size()) + hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16) + block, err := aes.NewCipher(hpKey) + if err != nil { + return "", err + } + + cache := buf.NewPacket() + defer cache.Release() + + mask := cache.Extend(block.BlockSize()) + block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16]) + firstByte := b[0] + // Encrypt/decrypt first byte. + if isLongHeader { + // Long header: 4 bits masked + // High 4 bits are not protected. + firstByte ^= mask[0] & 0x0f + } else { + // Short header: 5 bits masked + // High 3 bits are not protected. + firstByte ^= mask[0] & 0x1f + } + packetNumberLength := int(firstByte&0x3 + 1) // max = 4 (64-bit sequence number) + extHdrLen := hdrLen + packetNumberLength + + // copy to avoid modify origin data + extHdr := cache.Extend(extHdrLen) + copy(extHdr, b) + extHdr[0] = firstByte + + packetNumber := extHdr[hdrLen:extHdrLen] + // Encrypt/decrypt packet number. + for i := range packetNumber { + packetNumber[i] ^= mask[1+i] + } + + if packetNumber[0] != 0 && packetNumber[0] != 1 { + return "", errNotQuicInitial + } + + data := b[extHdrLen : int(packetLen)+hdrLen] + + key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16) + iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) + aesCipher, err := aes.NewCipher(key) + if err != nil { + return "", err + } + aead, err := cipher.NewGCM(aesCipher) + if err != nil { + return "", err + } + // We only decrypt once, so we do not need to XOR it back. + // https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496 + for i, b := range packetNumber { + iv[len(iv)-len(packetNumber)+i] ^= b + } + dst := cache.Extend(len(data)) + decrypted, err := aead.Open(dst[:0], iv, data, extHdr) + if err != nil { + return "", err + } + buffer = buf.As(decrypted) + + cryptoLen := uint(0) + cryptoData := cache.Extend(buffer.Len()) + for i := 0; !buffer.IsEmpty(); i++ { + frameType := byte(0x0) // Default to PADDING frame + for frameType == 0x0 && !buffer.IsEmpty() { + frameType, _ = buffer.ReadByte() + } + switch frameType { + case 0x00: // PADDING frame + case 0x01: // PING frame + case 0x02, 0x03: // ACK frame + if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay + return "", io.ErrUnexpectedEOF + } + ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count + if err != nil { + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range + return "", io.ErrUnexpectedEOF + } + for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length + return "", io.ErrUnexpectedEOF + } + } + if frameType == 0x03 { + if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count + return "", io.ErrUnexpectedEOF + } + } + case 0x06: // CRYPTO frame, we will use this frame + offset, err := quicvarint.Read(buffer) // Field: Offset + if err != nil { + return "", io.ErrUnexpectedEOF + } + length, err := quicvarint.Read(buffer) // Field: Length + if err != nil || length > uint64(buffer.Len()) { + return "", io.ErrUnexpectedEOF + } + if cryptoLen < uint(offset+length) { + cryptoLen = uint(offset + length) + } + if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data + return "", io.ErrUnexpectedEOF + } + case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet + if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code + return "", io.ErrUnexpectedEOF + } + if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type + return "", io.ErrUnexpectedEOF + } + length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length + if err != nil { + return "", io.ErrUnexpectedEOF + } + if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase + return "", io.ErrUnexpectedEOF + } + default: + // Only above frame types are permitted in initial packet. + // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8 + return "", errNotQuicInitial + } + } + + domain, err := ReadClientHello(cryptoData[:cryptoLen]) + if err != nil { + return "", err + } + + return *domain, nil +} + +func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { + b := make([]byte, 3, 3+6+len(label)+1+len(context)) + binary.BigEndian.PutUint16(b, uint16(length)) + b[2] = uint8(6 + len(label)) + b = append(b, []byte("tls13 ")...) + b = append(b, []byte(label)...) + b = b[:3+6+len(label)+1] + b[3+6+len(label)] = uint8(len(context)) + b = append(b, context...) + + out := make([]byte, length) + n, err := hkdf.Expand(hash.New, secret, b).Read(out) + if err != nil || n != length { + panic("quic: HKDF-Expand-Label invocation failed unexpectedly") + } + return out +} diff --git a/component/sniffer/sniff_test.go b/component/sniffer/sniff_test.go index e7ced43c..18cc9152 100644 --- a/component/sniffer/sniff_test.go +++ b/component/sniffer/sniff_test.go @@ -1,9 +1,40 @@ package sniffer import ( + "bytes" + "encoding/hex" + "github.com/stretchr/testify/assert" "testing" ) +func TestQuicHeaders(t *testing.T) { + cases := []struct { + input string + domain string + }{ + { + input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b", + domain: "www.google.com", + }, + { + input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738", + domain: "cloudflare-dns.com", + }, + } + q, err := NewQuicSniffer(SnifferConfig{}) + assert.NoError(t, err) + + for _, test := range cases { + pkt, err := hex.DecodeString(test.input) + assert.NoError(t, err) + oriPkt := bytes.Clone(pkt) + domain, err := q.SniffData(pkt) + assert.NoError(t, err) + assert.Equal(t, test.domain, domain) + assert.Equal(t, oriPkt, pkt) // ensure input data not changed + } +} + func TestTLSHeaders(t *testing.T) { cases := []struct { input []byte @@ -142,6 +173,7 @@ func TestTLSHeaders(t *testing.T) { } for _, test := range cases { + input := bytes.Clone(test.input) domain, err := SniffTLS(test.input) if test.err { if err == nil { @@ -155,5 +187,6 @@ func TestTLSHeaders(t *testing.T) { t.Error("expect domain ", test.domain, " but got ", domain) } } + assert.Equal(t, input, test.input) } } diff --git a/component/sniffer/tls_sniffer.go b/component/sniffer/tls_sniffer.go index 58e1e29e..974df79a 100644 --- a/component/sniffer/tls_sniffer.go +++ b/component/sniffer/tls_sniffer.go @@ -5,9 +5,9 @@ import ( "errors" "strings" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/sniffer" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/sniffer" ) var ( @@ -39,7 +39,7 @@ func (tls *TLSSniffer) SupportNetwork() C.NetWork { return C.TCP } -func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) { +func (tls *TLSSniffer) SniffData(bytes []byte) (string, error) { domain, err := SniffTLS(bytes) if err == nil { return *domain, nil diff --git a/component/tls/reality.go b/component/tls/reality.go index c995af0a..687ef1ef 100644 --- a/component/tls/reality.go +++ b/component/tls/reality.go @@ -5,6 +5,7 @@ import ( "context" "crypto/aes" "crypto/cipher" + "crypto/ecdh" "crypto/ed25519" "crypto/hmac" "crypto/sha256" @@ -20,14 +21,13 @@ import ( "time" "unsafe" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/ntp" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/ntp" utls "github.com/sagernet/utls" "github.com/zhangyunhao116/fastrand" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" "golang.org/x/net/http2" ) @@ -35,7 +35,7 @@ import ( const RealityMaxShortIDLen = 8 type RealityConfig struct { - PublicKey [curve25519.ScalarSize]byte + PublicKey *ecdh.PublicKey ShortID [RealityMaxShortIDLen]byte } @@ -43,7 +43,8 @@ type RealityConfig struct { func aesgcmPreferred(ciphers []uint16) bool func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { - if fingerprint, exists := GetFingerprint(ClientFingerprint); exists { + retry := 0 + for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ { verifier := &realityVerifier{ serverName: tlsConfig.ServerName, } @@ -80,7 +81,18 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string //log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16]) - authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:]) + ecdheKey := uConn.HandshakeState.State13.EcdheKey + if ecdheKey == nil { + // WTF??? + if retry > 2 { + return nil, errors.New("nil ecdheKey") + } + continue // retry + } + authKey, err := ecdheKey.ECDH(realityConfig.PublicKey) + if err != nil { + return nil, err + } if authKey == nil { return nil, errors.New("nil auth_key") } diff --git a/component/tls/utls.go b/component/tls/utls.go index 7ea2ad06..3063fc55 100644 --- a/component/tls/utls.go +++ b/component/tls/utls.go @@ -4,7 +4,7 @@ import ( "crypto/tls" "net" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" "github.com/mroth/weightedrand/v2" utls "github.com/sagernet/utls" @@ -21,7 +21,7 @@ type UClientHelloID struct { var initRandomFingerprint UClientHelloID var initUtlsClient string -func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn { +func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn { utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ Client: fingerprint.Client, Version: fingerprint.Version, @@ -68,16 +68,21 @@ func RollFingerprint() (UClientHelloID, bool) { } var Fingerprints = map[string]UClientHelloID{ - "chrome": {&utls.HelloChrome_Auto}, - "firefox": {&utls.HelloFirefox_Auto}, - "safari": {&utls.HelloSafari_Auto}, - "ios": {&utls.HelloIOS_Auto}, - "android": {&utls.HelloAndroid_11_OkHttp}, - "edge": {&utls.HelloEdge_Auto}, - "360": {&utls.Hello360_Auto}, - "qq": {&utls.HelloQQ_Auto}, - "random": {nil}, - "randomized": {nil}, + "chrome": {&utls.HelloChrome_Auto}, + "chrome_psk": {&utls.HelloChrome_100_PSK}, + "chrome_psk_shuffle": {&utls.HelloChrome_106_Shuffle}, + "chrome_padding_psk_shuffle": {&utls.HelloChrome_114_Padding_PSK_Shuf}, + "chrome_pq": {&utls.HelloChrome_115_PQ}, + "chrome_pq_psk": {&utls.HelloChrome_115_PQ_PSK}, + "firefox": {&utls.HelloFirefox_Auto}, + "safari": {&utls.HelloSafari_Auto}, + "ios": {&utls.HelloIOS_Auto}, + "android": {&utls.HelloAndroid_11_OkHttp}, + "edge": {&utls.HelloEdge_Auto}, + "360": {&utls.Hello360_Auto}, + "qq": {&utls.HelloQQ_Auto}, + "random": {nil}, + "randomized": {nil}, } func init() { @@ -99,10 +104,9 @@ func copyConfig(c *tls.Config) *utls.Config { } } -// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send -// http/1.1 in its ALPN. +// BuildWebsocketHandshakeState 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 { +func (c *UConn) BuildWebsocketHandshakeState() 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 { @@ -120,11 +124,11 @@ func (c *UConn) WebsocketHandshake() error { 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 + // Rebuild the client hello if err := c.BuildHandshakeState(); err != nil { return err } - return c.Handshake() + return nil } func SetGlobalUtlsClient(Client string) { diff --git a/component/trie/domain_set.go b/component/trie/domain_set.go index e1ad6559..860d1235 100644 --- a/component/trie/domain_set.go +++ b/component/trie/domain_set.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "github.com/openacid/low/bitmap" ) diff --git a/component/trie/domain_set_test.go b/component/trie/domain_set_test.go index 9e0e0b70..77106d5f 100644 --- a/component/trie/domain_set_test.go +++ b/component/trie/domain_set_test.go @@ -3,7 +3,7 @@ package trie_test import ( "testing" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/component/trie" "github.com/stretchr/testify/assert" ) diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go index 976055a9..4c5d8002 100644 --- a/component/trie/domain_test.go +++ b/component/trie/domain_test.go @@ -4,7 +4,7 @@ import ( "net/netip" "testing" - "github.com/Dreamacro/clash/component/trie" + "github.com/metacubex/mihomo/component/trie" "github.com/stretchr/testify/assert" ) diff --git a/component/trie/ipcidr_trie.go b/component/trie/ipcidr_trie.go index 08edbbeb..a2ccfa16 100644 --- a/component/trie/ipcidr_trie.go +++ b/component/trie/ipcidr_trie.go @@ -3,7 +3,7 @@ package trie import ( "net" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" ) type IPV6 bool diff --git a/config/config.go b/config/config.go index e240f9b9..bb503b49 100644 --- a/config/config.go +++ b/config/config.go @@ -13,33 +13,34 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/adapter/outbound" - "github.com/Dreamacro/clash/adapter/outboundgroup" - "github.com/Dreamacro/clash/adapter/provider" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/auth" - "github.com/Dreamacro/clash/component/dialer" - "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" - "github.com/Dreamacro/clash/component/resolver" - 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" - snifferTypes "github.com/Dreamacro/clash/constant/sniffer" - "github.com/Dreamacro/clash/dns" - L "github.com/Dreamacro/clash/listener" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/log" - R "github.com/Dreamacro/clash/rules" - RP "github.com/Dreamacro/clash/rules/provider" - T "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/adapter" + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/adapter/provider" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/auth" + "github.com/metacubex/mihomo/component/fakeip" + "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata/router" + P "github.com/metacubex/mihomo/component/process" + "github.com/metacubex/mihomo/component/resolver" + SNIFF "github.com/metacubex/mihomo/component/sniffer" + tlsC "github.com/metacubex/mihomo/component/tls" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + providerTypes "github.com/metacubex/mihomo/constant/provider" + snifferTypes "github.com/metacubex/mihomo/constant/sniffer" + "github.com/metacubex/mihomo/dns" + L "github.com/metacubex/mihomo/listener" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/log" + R "github.com/metacubex/mihomo/rules" + RP "github.com/metacubex/mihomo/rules/provider" + T "github.com/metacubex/mihomo/tunnel" + orderedmap "github.com/wk8/go-ordered-map/v2" "gopkg.in/yaml.v3" ) @@ -54,8 +55,11 @@ type General struct { Interface string `json:"interface-name"` RoutingMark int `json:"-"` GeoXUrl GeoXUrl `json:"geox-url"` + GeoAutoUpdate bool `json:"geo-auto-update"` + GeoUpdateInterval int `json:"geo-update-interval"` GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` + GeositeMatcher string `json:"geosite-matcher"` TCPConcurrent bool `json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `json:"find-process-mode"` Sniffing bool `json:"sniffing"` @@ -66,20 +70,23 @@ type General struct { // Inbound config type Inbound struct { - Port int `json:"port"` - SocksPort int `json:"socks-port"` - RedirPort int `json:"redir-port"` - TProxyPort int `json:"tproxy-port"` - MixedPort int `json:"mixed-port"` - Tun LC.Tun `json:"tun"` - TuicServer LC.TuicServer `json:"tuic-server"` - ShadowSocksConfig string `json:"ss-config"` - VmessConfig string `json:"vmess-config"` - Authentication []string `json:"authentication"` - AllowLan bool `json:"allow-lan"` - BindAddress string `json:"bind-address"` - InboundTfo bool `json:"inbound-tfo"` - InboundMPTCP bool `json:"inbound-mptcp"` + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + TProxyPort int `json:"tproxy-port"` + MixedPort int `json:"mixed-port"` + Tun LC.Tun `json:"tun"` + TuicServer LC.TuicServer `json:"tuic-server"` + ShadowSocksConfig string `json:"ss-config"` + VmessConfig string `json:"vmess-config"` + Authentication []string `json:"authentication"` + SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"` + LanAllowedIPs []netip.Prefix `json:"lan-allowed-ips"` + LanDisAllowedIPs []netip.Prefix `json:"lan-disallowed-ips"` + AllowLan bool `json:"allow-lan"` + BindAddress string `json:"bind-address"` + InboundTfo bool `json:"inbound-tfo"` + InboundMPTCP bool `json:"inbound-mptcp"` } // Controller config @@ -112,19 +119,20 @@ type DNS struct { Listen string `yaml:"listen"` EnhancedMode C.DNSMode `yaml:"enhanced-mode"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` + CacheAlgorithm string `yaml:"cache-algorithm"` FakeIPRange *fakeip.Pool Hosts *trie.DomainTrie[resolver.HostValue] - NameServerPolicy map[string][]dns.NameServer + NameServerPolicy *orderedmap.OrderedMap[string, []dns.NameServer] ProxyServerNameserver []dns.NameServer } // FallbackFilter config type FallbackFilter struct { - GeoIP bool `yaml:"geoip"` - GeoIPCode string `yaml:"geoip-code"` - IPCIDR []*netip.Prefix `yaml:"ipcidr"` - Domain []string `yaml:"domain"` - GeoSite []*router.DomainMatcher `yaml:"geosite"` + GeoIP bool `yaml:"geoip"` + GeoIPCode string `yaml:"geoip-code"` + IPCIDR []netip.Prefix `yaml:"ipcidr"` + Domain []string `yaml:"domain"` + GeoSite []router.DomainMatcher `yaml:"geosite"` } // Profile config @@ -159,9 +167,10 @@ type Sniffer struct { type Experimental struct { Fingerprints []string `yaml:"fingerprints"` QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"` + QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"` } -// Config is clash config manager +// Config is mihomo config manager type Config struct { General *General IPTables *IPTables @@ -192,29 +201,35 @@ type RawNTP struct { } type RawDNS struct { - Enable bool `yaml:"enable"` - PreferH3 bool `yaml:"prefer-h3"` - IPv6 bool `yaml:"ipv6"` - IPv6Timeout uint `yaml:"ipv6-timeout"` - UseHosts bool `yaml:"use-hosts"` - NameServer []string `yaml:"nameserver"` - Fallback []string `yaml:"fallback"` - FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` - Listen string `yaml:"listen"` - EnhancedMode C.DNSMode `yaml:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range"` - FakeIPFilter []string `yaml:"fake-ip-filter"` - DefaultNameserver []string `yaml:"default-nameserver"` - NameServerPolicy map[string]any `yaml:"nameserver-policy"` - ProxyServerNameserver []string `yaml:"proxy-server-nameserver"` + Enable bool `yaml:"enable" json:"enable"` + PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` + IPv6 bool `yaml:"ipv6" json:"ipv6"` + IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"` + UseHosts bool `yaml:"use-hosts" json:"use-hosts"` + NameServer []string `yaml:"nameserver" json:"nameserver"` + Fallback []string `yaml:"fallback" json:"fallback"` + FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"` + Listen string `yaml:"listen" json:"listen"` + EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"` + FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"` + DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"` + CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"` + NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"` + ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"` } type RawFallbackFilter struct { - GeoIP bool `yaml:"geoip"` - GeoIPCode string `yaml:"geoip-code"` - IPCIDR []string `yaml:"ipcidr"` - Domain []string `yaml:"domain"` - GeoSite []string `yaml:"geosite"` + GeoIP bool `yaml:"geoip" json:"geoip"` + GeoIPCode string `yaml:"geoip-code" json:"geoip-code"` + IPCIDR []string `yaml:"ipcidr" json:"ipcidr"` + Domain []string `yaml:"domain" json:"domain"` + GeoSite []string `yaml:"geosite" json:"geosite"` +} + +type RawClashForAndroid struct { + AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"` + UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"` } type RawTun struct { @@ -226,22 +241,28 @@ type RawTun struct { AutoDetectInterface bool `yaml:"auto-detect-interface"` RedirectToTun []string `yaml:"-" json:"-"` - MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` - //Inet4Address []LC.ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"` - Inet6Address []LC.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` - StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` - Inet4RouteAddress []LC.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` - Inet6RouteAddress []LC.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` - IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` - IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` - ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` - ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` - IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` - IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` - ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` - EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` - UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` - FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` + //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"` + Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` + Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"` + Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"` + Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"` + Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` + FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` } type RawTuicServer struct { @@ -260,22 +281,25 @@ 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"` + Port int `yaml:"port" json:"port"` + SocksPort int `yaml:"socks-port" json:"socks-port"` + RedirPort int `yaml:"redir-port" json:"redir-port"` + TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"` + MixedPort int `yaml:"mixed-port" json:"mixed-port"` ShadowSocksConfig string `yaml:"ss-config"` VmessConfig string `yaml:"vmess-config"` InboundTfo bool `yaml:"inbound-tfo"` InboundMPTCP bool `yaml:"inbound-mptcp"` - 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"` + Authentication []string `yaml:"authentication" json:"authentication"` + SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"` + LanAllowedIPs []netip.Prefix `yaml:"lan-allowed-ips"` + LanDisAllowedIPs []netip.Prefix `yaml:"lan-disallowed-ips"` + AllowLan bool `yaml:"allow-lan" json:"allow-lan"` + BindAddress string `yaml:"bind-address" json:"bind-address"` + Mode T.TunnelMode `yaml:"mode" json:"mode"` + UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"` + LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` + IPv6 bool `yaml:"ipv6" json:"ipv6"` ExternalController string `yaml:"external-controller"` ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalUI string `yaml:"external-ui"` @@ -285,20 +309,23 @@ type RawConfig struct { 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"` + GeoAutoUpdate bool `yaml:"geo-auto-update" json:"geo-auto-update"` + GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"` + GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"` + GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"` + GeositeMatcher string `yaml:"geosite-matcher" json:"geosite-matcher"` 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"` GlobalUA string `yaml:"global-ua"` KeepAliveInterval int `yaml:"keep-alive-interval"` - Sniffer RawSniffer `yaml:"sniffer"` + Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"` - Hosts map[string]any `yaml:"hosts"` - NTP RawNTP `yaml:"ntp"` - DNS RawDNS `yaml:"dns"` + Hosts map[string]any `yaml:"hosts" json:"hosts"` + NTP RawNTP `yaml:"ntp" json:"ntp"` + DNS RawDNS `yaml:"dns" json:"dns"` Tun RawTun `yaml:"tun"` TuicServer RawTuicServer `yaml:"tuic-server"` EBpf EBpf `yaml:"ebpf"` @@ -312,6 +339,8 @@ type RawConfig struct { SubRules map[string][]string `yaml:"sub-rules"` RawTLS TLS `yaml:"tls"` Listeners []map[string]any `yaml:"listeners"` + + ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"` } type GeoXUrl struct { @@ -362,22 +391,25 @@ func Parse(buf []byte) (*Config, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ - AllowLan: false, - BindAddress: "*", - IPv6: true, - Mode: T.Rule, - GeodataMode: C.GeodataMode, - GeodataLoader: "memconservative", - UnifiedDelay: false, - Authentication: []string{}, - LogLevel: log.INFO, - Hosts: map[string]any{}, - Rule: []string{}, - Proxy: []map[string]any{}, - ProxyGroup: []map[string]any{}, - TCPConcurrent: false, - FindProcessMode: P.FindProcessStrict, - GlobalUA: "clash.meta", + AllowLan: false, + BindAddress: "*", + LanAllowedIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}, + IPv6: true, + Mode: T.Rule, + GeoAutoUpdate: false, + GeoUpdateInterval: 24, + GeodataMode: C.GeodataMode, + GeodataLoader: "memconservative", + UnifiedDelay: false, + Authentication: []string{}, + LogLevel: log.INFO, + Hosts: map[string]any{}, + Rule: []string{}, + Proxy: []map[string]any{}, + ProxyGroup: []map[string]any{}, + TCPConcurrent: false, + FindProcessMode: P.FindProcessStrict, + GlobalUA: "clash.meta", Tun: RawTun{ Enable: false, Device: "", @@ -385,7 +417,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query AutoRoute: true, AutoDetectInterface: true, - Inet6Address: []LC.ListenPrefix{LC.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))}, + Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")}, }, TuicServer: RawTuicServer{ Enable: false, @@ -459,9 +491,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { StoreSelected: true, }, GeoXUrl: GeoXUrl{ - Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb", - GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", - GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", + Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb", + GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat", + GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat", }, ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", } @@ -507,6 +539,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Listeners = listener log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) + log.Infoln("Geosite Matcher implementation: %s", geodata.SiteMatcherName()) ruleProviders, err := parseRuleProviders(rawCfg) if err != nil { return nil, err @@ -541,7 +574,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.DNS = dnsCfg err = parseTun(rawCfg.Tun, config.General) - if err != nil { + if !features.CMFA && err != nil { return nil, err } @@ -575,6 +608,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { func parseGeneral(cfg *RawConfig) (*General, error) { geodata.SetLoader(cfg.GeodataLoader) + geodata.SetSiteMatcher(cfg.GeositeMatcher) + C.GeoAutoUpdate = cfg.GeoAutoUpdate + C.GeoUpdateInterval = cfg.GeoUpdateInterval C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.MmdbUrl = cfg.GeoXUrl.Mmdb @@ -619,6 +655,9 @@ func parseGeneral(cfg *RawConfig) (*General, error) { ShadowSocksConfig: cfg.ShadowSocksConfig, VmessConfig: cfg.VmessConfig, AllowLan: cfg.AllowLan, + SkipAuthPrefixes: cfg.SkipAuthPrefixes, + LanAllowedIPs: cfg.LanAllowedIPs, + LanDisAllowedIPs: cfg.LanDisAllowedIPs, BindAddress: cfg.BindAddress, InboundTfo: cfg.InboundTfo, InboundMPTCP: cfg.InboundMPTCP, @@ -636,6 +675,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Interface: cfg.Interface, RoutingMark: cfg.RoutingMark, GeoXUrl: cfg.GeoXUrl, + GeoAutoUpdate: cfg.GeoAutoUpdate, + GeoUpdateInterval: cfg.GeoUpdateInterval, GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, TCPConcurrent: cfg.TCPConcurrent, @@ -654,11 +695,13 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ providersConfig := cfg.ProxyProvider var proxyList []string + var AllProxies []string proxiesList := list.New() groupsList := list.New() proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect()) proxies["REJECT"] = adapter.NewProxy(outbound.NewReject()) + proxies["REJECT-DROP"] = adapter.NewProxy(outbound.NewRejectDrop()) proxies["COMPATIBLE"] = adapter.NewProxy(outbound.NewCompatible()) proxies["PASS"] = adapter.NewProxy(outbound.NewPass()) proxyList = append(proxyList, "DIRECT", "REJECT") @@ -675,6 +718,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ } proxies[proxy.Name()] = proxy proxyList = append(proxyList, proxy.Name()) + AllProxies = append(AllProxies, proxy.Name()) proxiesList.PushBack(mapping) } @@ -693,6 +737,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return nil, nil, err } + var AllProviders []string // parse and initial providers for name, mapping := range providersConfig { if name == provider.ReservedName { @@ -705,11 +750,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ } providersMap[name] = pd + AllProviders = append(AllProviders, name) } // parse proxy group for idx, mapping := range groupsConfig { - group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) + group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders) if err != nil { return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err) } @@ -912,9 +958,9 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) { if len(cfg.Hosts) != 0 { for domain, anyValue := range cfg.Hosts { - if str, ok := anyValue.(string); ok && str == "clash" { + if str, ok := anyValue.(string); ok && str == "lan" { if addrs, err := net.InterfaceAddrs(); err != nil { - log.Errorln("insert clash to host error: %s", err) + log.Errorln("insert lan to host error: %s", err) } else { ips := make([]netip.Addr, 0) for _, addr := range addrs { @@ -1044,7 +1090,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) Net: dnsNetType, Addr: addr, ProxyName: proxyName, - Interface: dialer.DefaultInterface, Params: params, PreferH3: preferH3, }, @@ -1081,41 +1126,48 @@ func parsePureDNSServer(server string) string { } } } -func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (map[string][]dns.NameServer, error) { - policy := map[string][]dns.NameServer{} - updatedPolicy := make(map[string]interface{}) +func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) { + policy := orderedmap.New[string, []dns.NameServer]() + updatedPolicy := orderedmap.New[string, any]() re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`) - for k, v := range nsPolicy { - if strings.Contains(k, ",") { + for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() { + k, v := pair.Key, pair.Value + if strings.Contains(strings.ToLower(k), ",") { if strings.Contains(k, "geosite:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, subkey := range subkeys { newKey := "geosite:" + subkey - updatedPolicy[newKey] = v + updatedPolicy.Store(newKey, v) } - } else if strings.Contains(k, "rule-set:") { + } else if strings.Contains(strings.ToLower(k), "rule-set:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") for _, subkey := range subkeys { newKey := "rule-set:" + subkey - updatedPolicy[newKey] = v + updatedPolicy.Store(newKey, v) } } else if re.MatchString(k) { subkeys := strings.Split(k, ",") for _, subkey := range subkeys { - updatedPolicy[subkey] = v + updatedPolicy.Store(subkey, v) } } } else { - updatedPolicy[k] = v + if strings.Contains(strings.ToLower(k), "geosite:") { + updatedPolicy.Store("geosite:"+k[8:], v) + } else if strings.Contains(strings.ToLower(k), "rule-set:") { + updatedPolicy.Store("rule-set:"+k[9:], v) + } + updatedPolicy.Store(k, v) } } - for domain, server := range updatedPolicy { + for pair := updatedPolicy.Oldest(); pair != nil; pair = pair.Next() { + domain, server := pair.Key, pair.Value servers, err := utils.ToStringSlice(server) if err != nil { return nil, err @@ -1140,28 +1192,28 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro } } } - policy[domain] = nameservers + policy.Store(domain, nameservers) } return policy, nil } -func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) { - var ipNets []*netip.Prefix +func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) { + var ipNets []netip.Prefix for idx, ip := range ips { ipnet, err := netip.ParsePrefix(ip) if err != nil { return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error()) } - ipNets = append(ipNets, &ipnet) + ipNets = append(ipNets, ipnet) } return ipNets, nil } -func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { - var sites []*router.DomainMatcher +func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]router.DomainMatcher, error) { + var sites []router.DomainMatcher if len(countries) > 0 { if err := geodata.InitGeoSite(); err != nil { return nil, fmt.Errorf("can't initial GeoSite: %s", err) @@ -1222,8 +1274,8 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul IPv6: cfg.IPv6, EnhancedMode: cfg.EnhancedMode, FallbackFilter: FallbackFilter{ - IPCIDR: []*netip.Prefix{}, - GeoSite: []*router.DomainMatcher{}, + IPCIDR: []netip.Prefix{}, + GeoSite: []router.DomainMatcher{}, }, } var err error @@ -1296,7 +1348,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul } pool, err := fakeip.New(fakeip.Options{ - IPNet: &fakeIPRange, + IPNet: fakeIPRange, Size: 1000, Host: host, Persistence: rawCfg.Profile.StoreFakeIP, @@ -1326,6 +1378,12 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul dnsCfg.Hosts = hosts } + if cfg.CacheAlgorithm == "" || cfg.CacheAlgorithm == "lru" { + dnsCfg.CacheAlgorithm = "lru" + } else { + dnsCfg.CacheAlgorithm = "arc" + } + return dnsCfg, nil } @@ -1359,22 +1417,28 @@ func parseTun(rawTun RawTun, general *General) error { AutoDetectInterface: rawTun.AutoDetectInterface, RedirectToTun: rawTun.RedirectToTun, - MTU: rawTun.MTU, - Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)}, - Inet6Address: rawTun.Inet6Address, - StrictRoute: rawTun.StrictRoute, - Inet4RouteAddress: rawTun.Inet4RouteAddress, - Inet6RouteAddress: rawTun.Inet6RouteAddress, - IncludeUID: rawTun.IncludeUID, - IncludeUIDRange: rawTun.IncludeUIDRange, - ExcludeUID: rawTun.ExcludeUID, - ExcludeUIDRange: rawTun.ExcludeUIDRange, - IncludeAndroidUser: rawTun.IncludeAndroidUser, - IncludePackage: rawTun.IncludePackage, - ExcludePackage: rawTun.ExcludePackage, - EndpointIndependentNat: rawTun.EndpointIndependentNat, - UDPTimeout: rawTun.UDPTimeout, - FileDescriptor: rawTun.FileDescriptor, + MTU: rawTun.MTU, + GSO: rawTun.GSO, + GSOMaxSize: rawTun.GSOMaxSize, + Inet4Address: []netip.Prefix{tunAddressPrefix}, + Inet6Address: rawTun.Inet6Address, + StrictRoute: rawTun.StrictRoute, + Inet4RouteAddress: rawTun.Inet4RouteAddress, + Inet6RouteAddress: rawTun.Inet6RouteAddress, + Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress, + IncludeInterface: rawTun.IncludeInterface, + ExcludeInterface: rawTun.ExcludeInterface, + IncludeUID: rawTun.IncludeUID, + IncludeUIDRange: rawTun.IncludeUIDRange, + ExcludeUID: rawTun.ExcludeUID, + ExcludeUIDRange: rawTun.ExcludeUIDRange, + IncludeAndroidUser: rawTun.IncludeAndroidUser, + IncludePackage: rawTun.IncludePackage, + ExcludePackage: rawTun.ExcludePackage, + EndpointIndependentNat: rawTun.EndpointIndependentNat, + UDPTimeout: rawTun.UDPTimeout, + FileDescriptor: rawTun.FileDescriptor, } return nil diff --git a/config/initial.go b/config/initial.go index 6d6429ab..61d12895 100644 --- a/config/initial.go +++ b/config/initial.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) // Init prepare necessary files diff --git a/config/update_geo.go b/config/update_geo.go index 07f211e4..2cde47a1 100644 --- a/config/update_geo.go +++ b/config/update_geo.go @@ -4,9 +4,9 @@ import ( "fmt" "runtime" - "github.com/Dreamacro/clash/component/geodata" - _ "github.com/Dreamacro/clash/component/geodata/standard" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/component/geodata" + _ "github.com/metacubex/mihomo/component/geodata/standard" + C "github.com/metacubex/mihomo/constant" "github.com/oschwald/maxminddb-golang" ) @@ -28,7 +28,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid GeoIP database file: %s", err) } - if saveFile(data, C.Path.GeoIP()) != nil { + if err = saveFile(data, C.Path.GeoIP()); err != nil { return fmt.Errorf("can't save GeoIP database file: %w", err) } @@ -43,8 +43,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid MMDB database file: %s", err) } _ = instance.Close() - - if saveFile(data, C.Path.MMDB()) != nil { + if err = saveFile(data, C.Path.MMDB()); err != nil { return fmt.Errorf("can't save MMDB database file: %w", err) } } @@ -58,7 +57,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid GeoSite database file: %s", err) } - if saveFile(data, C.Path.GeoSite()) != nil { + if err = saveFile(data, C.Path.GeoSite()); err != nil { return fmt.Errorf("can't save GeoSite database file: %w", err) } diff --git a/config/update_ui.go b/config/update_ui.go index 27e0f382..cff1d6d7 100644 --- a/config/update_ui.go +++ b/config/update_ui.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) var ( @@ -40,7 +40,7 @@ func UpdateUI() error { } saved := path.Join(C.Path.HomeDir(), "download.zip") - if saveFile(data, saved) != nil { + if err = saveFile(data, saved); err != nil { return fmt.Errorf("can't save zip file: %w", err) } defer os.Remove(saved) diff --git a/config/utils.go b/config/utils.go index 1fa54634..66bf3441 100644 --- a/config/utils.go +++ b/config/utils.go @@ -11,15 +11,16 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/adapter/outboundgroup" - "github.com/Dreamacro/clash/common/structure" - clashHttp "github.com/Dreamacro/clash/component/http" + "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/common/structure" + mihomoHttp "github.com/metacubex/mihomo/component/http" + C "github.com/metacubex/mihomo/constant" ) func downloadForBytes(url string) ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return nil, err } diff --git a/constant/adapters.go b/constant/adapters.go index 33b9a44f..9752de55 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -9,15 +9,16 @@ import ( "sync" "time" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/dialer" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/dialer" ) // Adapter Type const ( Direct AdapterType = iota Reject + RejectDrop Compatible Pass @@ -42,10 +43,11 @@ const ( ) const ( - DefaultTCPTimeout = 5 * time.Second - DefaultUDPTimeout = DefaultTCPTimeout - DefaultTLSTimeout = DefaultTCPTimeout - DefaultMaxHealthCheckUrlNum = 16 + DefaultTCPTimeout = 5 * time.Second + DefaultDropTime = 12 * DefaultTCPTimeout + DefaultUDPTimeout = DefaultTCPTimeout + DefaultTLSTimeout = DefaultTCPTimeout + DefaultTestURL = "https://www.gstatic.com/generate_204" ) var ErrNotSupport = errors.New("no support") @@ -145,23 +147,20 @@ type DelayHistory struct { Delay uint16 `json:"delay"` } -type DelayHistoryStoreType int +type ProxyState struct { + Alive bool `json:"alive"` + History []DelayHistory `json:"history"` +} -const ( - OriginalHistory DelayHistoryStoreType = iota - ExtraHistory - DropHistory -) +type DelayHistoryStoreType int type Proxy interface { ProxyAdapter - Alive() bool AliveForTestUrl(url string) bool DelayHistory() []DelayHistory - ExtraDelayHistory() map[string][]DelayHistory - LastDelay() uint16 + ExtraDelayHistories() map[string]ProxyState LastDelayForTestUrl(url string) uint16 - URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error) + URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (uint16, error) // Deprecated: use DialContext instead. Dial(metadata *Metadata) (Conn, error) @@ -179,6 +178,8 @@ func (at AdapterType) String() string { return "Direct" case Reject: return "Reject" + case RejectDrop: + return "RejectDrop" case Compatible: return "Compatible" case Pass: @@ -252,6 +253,23 @@ type PacketAdapter interface { Metadata() *Metadata } +type packetAdapter struct { + UDPPacket + metadata *Metadata +} + +// Metadata returns destination metadata +func (s *packetAdapter) Metadata() *Metadata { + return s.metadata +} + +func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter { + return &packetAdapter{ + packet, + metadata, + } +} + type WriteBack interface { WriteBack(b []byte, addr net.Addr) (n int, err error) } diff --git a/constant/context.go b/constant/context.go index 1c70124b..11ad7011 100644 --- a/constant/context.go +++ b/constant/context.go @@ -3,7 +3,7 @@ package constant import ( "net" - N "github.com/Dreamacro/clash/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/gofrs/uuid/v5" ) diff --git a/constant/ebpf.go b/constant/ebpf.go index b722dce1..e3bb62fe 100644 --- a/constant/ebpf.go +++ b/constant/ebpf.go @@ -3,14 +3,14 @@ package constant import ( "net/netip" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) const ( - BpfFSPath = "/sys/fs/bpf/clash" + BpfFSPath = "/sys/fs/bpf/mihomo" - TcpAutoRedirPort = 't'<<8 | 'r'<<0 - ClashTrafficMark = 'c'<<24 | 'l'<<16 | 't'<<8 | 'm'<<0 + TcpAutoRedirPort = 't'<<8 | 'r'<<0 + MihomoTrafficMark = 'c'<<24 | 'l'<<16 | 't'<<8 | 'm'<<0 ) type EBpf interface { diff --git a/constant/features/cmfa.go b/constant/features/cmfa.go new file mode 100644 index 00000000..9a81d82f --- /dev/null +++ b/constant/features/cmfa.go @@ -0,0 +1,5 @@ +//go:build cmfa + +package features + +const CMFA = true diff --git a/constant/features/cmfa_stub.go b/constant/features/cmfa_stub.go new file mode 100644 index 00000000..f7ef0827 --- /dev/null +++ b/constant/features/cmfa_stub.go @@ -0,0 +1,5 @@ +//go:build !cmfa + +package features + +const CMFA = false diff --git a/constant/features/low_memory.go b/constant/features/low_memory.go index 0d252113..2f0f4d50 100644 --- a/constant/features/low_memory.go +++ b/constant/features/low_memory.go @@ -1,6 +1,5 @@ //go:build with_low_memory + package features -func init() { - TAGS = append(TAGS, "with_low_memory") -} +const WithLowMemory = true diff --git a/constant/features/low_memory_stub.go b/constant/features/low_memory_stub.go new file mode 100644 index 00000000..00b0b947 --- /dev/null +++ b/constant/features/low_memory_stub.go @@ -0,0 +1,5 @@ +//go:build !with_low_memory + +package features + +const WithLowMemory = false diff --git a/constant/features/no_fake_tcp.go b/constant/features/no_fake_tcp.go index f536a066..a9e09728 100644 --- a/constant/features/no_fake_tcp.go +++ b/constant/features/no_fake_tcp.go @@ -2,6 +2,4 @@ package features -func init() { - TAGS = append(TAGS, "no_fake_tcp") -} +const NoFakeTCP = true diff --git a/constant/features/no_fake_tcp_stub.go b/constant/features/no_fake_tcp_stub.go new file mode 100644 index 00000000..f1620a2b --- /dev/null +++ b/constant/features/no_fake_tcp_stub.go @@ -0,0 +1,5 @@ +//go:build !no_fake_tcp + +package features + +const NoFakeTCP = false diff --git a/constant/features/tags.go b/constant/features/tags.go index c81f6d4e..12a5fbdd 100644 --- a/constant/features/tags.go +++ b/constant/features/tags.go @@ -1,3 +1,17 @@ package features -var TAGS = make([]string, 0, 0) +func Tags() (tags []string) { + if CMFA { + tags = append(tags, "cmfa") + } + if WithLowMemory { + tags = append(tags, "with_low_memory") + } + if NoFakeTCP { + tags = append(tags, "no_fake_tcp") + } + if WithGVisor { + tags = append(tags, "with_gvisor") + } + return +} diff --git a/constant/features/with_gvisor.go b/constant/features/with_gvisor.go index 1b3417b3..c00cc34e 100644 --- a/constant/features/with_gvisor.go +++ b/constant/features/with_gvisor.go @@ -2,6 +2,4 @@ package features -func init() { - TAGS = append(TAGS, "with_gvisor") -} +const WithGVisor = true diff --git a/constant/features/with_gvisor_stub.go b/constant/features/with_gvisor_stub.go new file mode 100644 index 00000000..5684c1b1 --- /dev/null +++ b/constant/features/with_gvisor_stub.go @@ -0,0 +1,5 @@ +//go:build !with_gvisor + +package features + +const WithGVisor = false diff --git a/constant/geodata.go b/constant/geodata.go index 72452270..e93d56b3 100644 --- a/constant/geodata.go +++ b/constant/geodata.go @@ -1,8 +1,10 @@ package constant var ( - GeodataMode bool - GeoIpUrl string - MmdbUrl string - GeoSiteUrl string + GeodataMode bool + GeoAutoUpdate bool + GeoUpdateInterval int + GeoIpUrl string + MmdbUrl string + GeoSiteUrl string ) diff --git a/constant/listener.go b/constant/listener.go index 6f9f169b..f69b4a9b 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, natTable NatTable) error + Listen(tunnel Tunnel) error Close() error Address() string RawAddress() string diff --git a/constant/metadata.go b/constant/metadata.go index 70478911..3c712909 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -7,7 +7,7 @@ import ( "net/netip" "strconv" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) // Socks addr type @@ -147,6 +147,9 @@ type Metadata struct { SpecialProxy string `json:"specialProxy"` SpecialRules string `json:"specialRules"` RemoteDst string `json:"remoteDestination"` + + RawSrcAddr net.Addr `json:"-"` + RawDstAddr net.Addr `json:"-"` // Only domain rule SniffHost string `json:"sniffHost"` } @@ -159,9 +162,13 @@ func (m *Metadata) SourceAddress() string { return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10)) } +func (m *Metadata) SourceAddrPort() netip.AddrPort { + return netip.AddrPortFrom(m.SrcIP.Unmap(), m.SrcPort) +} + func (m *Metadata) SourceDetail() string { if m.Type == INNER { - return fmt.Sprintf("%s", ClashName) + return fmt.Sprintf("%s", MihomoName) } switch { @@ -240,6 +247,34 @@ func (m *Metadata) Valid() bool { return m.Host != "" || m.DstIP.IsValid() } +func (m *Metadata) SetRemoteAddr(addr net.Addr) error { + if addr == nil { + return nil + } + if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok { + if rawAddr := rawAddr.RawAddr(); rawAddr != nil { + if err := m.SetRemoteAddr(rawAddr); err == nil { + return nil + } + } + } + if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok { // *net.TCPAddr, *net.UDPAddr, M.Socksaddr + if addrPort := addr.AddrPort(); addrPort.Port() != 0 { + m.DstPort = addrPort.Port() + if addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName + m.DstIP = addrPort.Addr().Unmap() + return nil + } else { + if addr, ok := addr.(interface{ AddrString() string }); ok { // must be sing's M.Socksaddr + m.Host = addr.AddrString() // actually is M.Socksaddr.Fqdn + return nil + } + } + } + } + return m.SetRemoteAddress(addr.String()) +} + func (m *Metadata) SetRemoteAddress(rawAddress string) error { host, port, err := net.SplitHostPort(rawAddress) if err != nil { diff --git a/constant/path.go b/constant/path.go index d7477e0e..a920fbbc 100644 --- a/constant/path.go +++ b/constant/path.go @@ -10,7 +10,7 @@ import ( "strings" ) -const Name = "clash" +const Name = "mihomo" var ( GeositeName = "GeoSite.dat" @@ -18,6 +18,9 @@ var ( ) // Path is used to get the configuration path +// +// on Unix systems, `$HOME/.config/mihomo`. +// on Windows, `%USERPROFILE%/.config/mihomo`. var Path = func() *path { homeDir, err := os.UserHomeDir() if err != nil { @@ -25,6 +28,13 @@ var Path = func() *path { } allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK")) homeDir = P.Join(homeDir, ".config", Name) + + if _, err = os.Stat(homeDir); err != nil { + if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok { + homeDir = P.Join(configHome, Name) + } + } + return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath} }() @@ -155,7 +165,7 @@ func (p *path) GetAssetLocation(file string) string { func (p *path) GetExecutableFullPath() string { exePath, err := os.Executable() if err != nil { - return "clash" + return "mihomo" } res, _ := filepath.EvalSymlinks(exePath) return res diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 34590a48..809db9c5 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -1,8 +1,8 @@ package provider import ( - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/constant" ) // Vehicle Type diff --git a/constant/rule_extra.go b/constant/rule_extra.go index 3c5de5d5..b4ba65d9 100644 --- a/constant/rule_extra.go +++ b/constant/rule_extra.go @@ -1,11 +1,11 @@ package constant import ( - "github.com/Dreamacro/clash/component/geodata/router" + "github.com/metacubex/mihomo/component/geodata/router" ) type RuleGeoSite interface { - GetDomainMatcher() *router.DomainMatcher + GetDomainMatcher() router.DomainMatcher } type RuleGeoIP interface { diff --git a/constant/sniffer/sniffer.go b/constant/sniffer/sniffer.go index 6b20b3f6..36da69a3 100644 --- a/constant/sniffer/sniffer.go +++ b/constant/sniffer/sniffer.go @@ -1,10 +1,11 @@ package sniffer -import "github.com/Dreamacro/clash/constant" +import "github.com/metacubex/mihomo/constant" type Sniffer interface { SupportNetwork() constant.NetWork - SniffTCP(bytes []byte) (string, error) + // SniffData must not change input bytes + SniffData(bytes []byte) (string, error) Protocol() string SupportPort(port uint16) bool } @@ -12,10 +13,11 @@ type Sniffer interface { const ( TLS Type = iota HTTP + QUIC ) var ( - List = []Type{TLS, HTTP} + List = []Type{TLS, HTTP, QUIC} ) type Type int @@ -26,6 +28,8 @@ func (rt Type) String() string { return "TLS" case HTTP: return "HTTP" + case QUIC: + return "QUIC" default: return "Unknown" } diff --git a/constant/tun.go b/constant/tun.go index 5e2841bc..f6c0e011 100644 --- a/constant/tun.go +++ b/constant/tun.go @@ -9,14 +9,12 @@ import ( var StackTypeMapping = map[string]TUNStack{ strings.ToLower(TunGvisor.String()): TunGvisor, strings.ToLower(TunSystem.String()): TunSystem, - strings.ToLower(TunLWIP.String()): TunLWIP, strings.ToLower(TunMixed.String()): TunMixed, } const ( TunGvisor TUNStack = iota TunSystem - TunLWIP TunMixed ) @@ -64,8 +62,6 @@ func (e TUNStack) String() string { return "gVisor" case TunSystem: return "System" - case TunLWIP: - return "LWIP" case TunMixed: return "Mixed" default: diff --git a/constant/tunnel.go b/constant/tunnel.go new file mode 100644 index 00000000..7c9d08e2 --- /dev/null +++ b/constant/tunnel.go @@ -0,0 +1,12 @@ +package constant + +import "net" + +type Tunnel interface { + // HandleTCPConn will handle a tcp connection blocking + HandleTCPConn(conn net.Conn, metadata *Metadata) + // HandleUDPPacket will handle a udp packet nonblocking + HandleUDPPacket(packet UDPPacket, metadata *Metadata) + // NatTable return nat table + NatTable() NatTable +} diff --git a/constant/version.go b/constant/version.go index cbb7ab61..c71024c2 100644 --- a/constant/version.go +++ b/constant/version.go @@ -1,8 +1,8 @@ package constant var ( - Meta = true - Version = "1.10.0" - BuildTime = "unknown time" - ClashName = "clash.meta" + Meta = true + Version = "1.10.0" + BuildTime = "unknown time" + MihomoName = "mihomo" ) diff --git a/context/conn.go b/context/conn.go index afeed852..bae07c23 100644 --- a/context/conn.go +++ b/context/conn.go @@ -1,11 +1,11 @@ package context import ( - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "net" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" "github.com/gofrs/uuid/v5" ) diff --git a/context/dns.go b/context/dns.go index ae29154f..1cc2067d 100644 --- a/context/dns.go +++ b/context/dns.go @@ -2,7 +2,7 @@ package context import ( "context" - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "github.com/gofrs/uuid/v5" "github.com/miekg/dns" diff --git a/context/packetconn.go b/context/packetconn.go index d695bae5..feab7666 100644 --- a/context/packetconn.go +++ b/context/packetconn.go @@ -3,8 +3,8 @@ package context import ( "net" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" "github.com/gofrs/uuid/v5" ) diff --git a/dns/client.go b/dns/client.go index 56f55668..95f0f29b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -8,11 +8,10 @@ import ( "net/netip" "strings" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" D "github.com/miekg/dns" "github.com/zhangyunhao116/fastrand" @@ -23,7 +22,7 @@ type client struct { r *Resolver port string host string - iface *atomic.TypedValue[string] + iface string proxyAdapter C.ProxyAdapter proxyName string addr string @@ -48,10 +47,6 @@ func (c *client) Address() string { return c.addr } -func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { - return c.ExchangeContext(context.Background(), m) -} - func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { var ( ip netip.Addr @@ -77,9 +72,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) network = "tcp" } - options := []dialer.Option{} - if c.iface != nil && c.iface.Load() != "" { - options = append(options, dialer.WithInterface(c.iface.Load())) + var options []dialer.Option + if c.iface != "" { + options = append(options, dialer.WithInterface(c.iface)) } conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port)) diff --git a/dns/dhcp.go b/dns/dhcp.go index 7d420d89..d4944a96 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -1,3 +1,5 @@ +//go:build !(android && cmfa) + package dns import ( @@ -8,11 +10,8 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/component/dhcp" - "github.com/Dreamacro/clash/component/iface" - "github.com/Dreamacro/clash/component/resolver" - + "github.com/metacubex/mihomo/component/dhcp" + "github.com/metacubex/mihomo/component/iface" D "github.com/miekg/dns" ) @@ -29,7 +28,7 @@ type dhcpClient struct { ifaceInvalidate time.Time dnsInvalidate time.Time - ifaceAddr *netip.Prefix + ifaceAddr netip.Prefix done chan struct{} clients []dnsClient err error @@ -46,13 +45,6 @@ func (d *dhcpClient) Address() string { 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() - - return d.ExchangeContext(ctx, m) -} - func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { clients, err := d.resolve(ctx) if err != nil { @@ -86,7 +78,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) { for _, item := range dns { nameserver = append(nameserver, NameServer{ Addr: net.JoinHostPort(item.String(), "53"), - Interface: atomic.NewTypedValue(d.ifaceName), + Interface: d.ifaceName, }) } diff --git a/dns/doh.go b/dns/doh.go index 0d84fc4f..9e173c84 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -15,9 +15,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/component/ca" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/ca" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/metacubex/quic-go" "github.com/metacubex/quic-go/http3" D "github.com/miekg/dns" @@ -157,11 +157,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D. return msg, err } -// Exchange implements the Upstream interface for *dnsOverHTTPS. -func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) { - return doh.ExchangeContext(context.Background(), m) -} - // Close implements the Upstream interface for *dnsOverHTTPS. func (doh *dnsOverHTTPS) Close() (err error) { doh.clientMu.Lock() diff --git a/dns/doq.go b/dns/doq.go index afa8259a..70b67c2a 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -12,9 +12,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/component/ca" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/ca" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/metacubex/quic-go" D "github.com/miekg/dns" @@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M return msg, err } -// Exchange implements the Upstream interface for *dnsOverQUIC. -func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) { - return doq.ExchangeContext(context.Background(), m) -} - // Close implements the Upstream interface for *dnsOverQUIC. func (doq *dnsOverQUIC) Close() (err error) { doq.connMu.Lock() diff --git a/dns/enhancer.go b/dns/enhancer.go index ab144fd3..a8c0a5ac 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -3,15 +3,15 @@ package dns import ( "net/netip" - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/component/fakeip" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/lru" + "github.com/metacubex/mihomo/component/fakeip" + C "github.com/metacubex/mihomo/constant" ) type ResolverEnhancer struct { mode C.DNSMode fakePool *fakeip.Pool - mapping *cache.LruCache[netip.Addr, string] + mapping *lru.LruCache[netip.Addr, string] } func (h *ResolverEnhancer) FakeIPEnabled() bool { @@ -105,11 +105,11 @@ func (h *ResolverEnhancer) StoreFakePoolState() { func NewEnhancer(cfg Config) *ResolverEnhancer { var fakePool *fakeip.Pool - var mapping *cache.LruCache[netip.Addr, string] + var mapping *lru.LruCache[netip.Addr, string] if cfg.EnhancedMode != C.DNSNormal { fakePool = cfg.Pool - mapping = cache.New(cache.WithSize[netip.Addr, string](4096)) + mapping = lru.New(lru.WithSize[netip.Addr, string](4096)) } return &ResolverEnhancer{ diff --git a/dns/filters.go b/dns/filters.go index 47e7adcd..46244c35 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -4,12 +4,12 @@ import ( "net/netip" "strings" - "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/geodata/router" - "github.com/Dreamacro/clash/component/mmdb" - "github.com/Dreamacro/clash/component/trie" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata/router" + "github.com/metacubex/mihomo/component/mmdb" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type fallbackIPFilter interface { @@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool { } type ipnetFilter struct { - ipnet *netip.Prefix + ipnet netip.Prefix } func (inf *ipnetFilter) Match(ip netip.Addr) bool { @@ -74,7 +74,7 @@ func (df *domainFilter) Match(domain string) bool { } type geoSiteFilter struct { - matchers []*router.DomainMatcher + matchers []router.DomainMatcher } func NewGeoSite(group string) (fallbackDomainFilter, error) { @@ -87,7 +87,7 @@ func NewGeoSite(group string) (fallbackDomainFilter, error) { return nil, err } filter := &geoSiteFilter{ - matchers: []*router.DomainMatcher{matcher}, + matchers: []router.DomainMatcher{matcher}, } return filter, nil } diff --git a/dns/patch.go b/dns/local.go similarity index 100% rename from dns/patch.go rename to dns/local.go diff --git a/dns/middleware.go b/dns/middleware.go index 695432da..b55adc6b 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -5,13 +5,13 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/component/fakeip" - R "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/lru" + "github.com/metacubex/mihomo/common/nnip" + "github.com/metacubex/mihomo/component/fakeip" + R "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/context" + "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" ) @@ -21,7 +21,7 @@ type ( middleware func(next handler) handler ) -func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middleware { +func withHosts(hosts R.Hosts, mapping *lru.LruCache[netip.Addr, string]) middleware { return func(next handler) handler { return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] @@ -98,7 +98,7 @@ func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middl } } -func withMapping(mapping *cache.LruCache[netip.Addr, string]) middleware { +func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware { return func(next handler) handler { return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] diff --git a/dns/patch_android.go b/dns/patch_android.go new file mode 100644 index 00000000..2e9a6b9f --- /dev/null +++ b/dns/patch_android.go @@ -0,0 +1,81 @@ +//go:build android && cmfa + +package dns + +import ( + "context" + + D "github.com/miekg/dns" + + "github.com/metacubex/mihomo/common/lru" + "github.com/metacubex/mihomo/component/dhcp" + "github.com/metacubex/mihomo/component/resolver" +) + +const SystemDNSPlaceholder = "system" + +var systemResolver *Resolver +var isolateHandler handler + +var _ dnsClient = (*dhcpClient)(nil) + +type dhcpClient struct { + enable bool +} + +func (d *dhcpClient) Address() string { + return SystemDNSPlaceholder +} + +func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return d.ExchangeContext(context.Background(), m) +} + +func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + if s := systemResolver; s != nil { + return s.ExchangeContext(ctx, m) + } + + return nil, dhcp.ErrNotFound +} + +func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) { + if h := isolateHandler; h != nil { + return handlerWithContext(context.Background(), h, msg) + } + + return nil, D.ErrTime +} + +func FlushCacheWithDefaultResolver() { + if r := resolver.DefaultResolver; r != nil { + r.(*Resolver).lruCache = lru.New[string, *D.Msg](lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) + } +} + +func UpdateSystemDNS(addr []string) { + if len(addr) == 0 { + systemResolver = nil + } + + ns := make([]NameServer, 0, len(addr)) + for _, d := range addr { + ns = append(ns, NameServer{Addr: d}) + } + + systemResolver = NewResolver(Config{Main: ns}) +} + +func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) { + if resolver == nil { + isolateHandler = nil + + return + } + + isolateHandler = NewHandler(resolver, mapper) +} + +func newDHCPClient(ifaceName string) *dhcpClient { + return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder} +} diff --git a/dns/patch_common.go b/dns/patch_common.go new file mode 100644 index 00000000..fae1e126 --- /dev/null +++ b/dns/patch_common.go @@ -0,0 +1,6 @@ +//go:build !(android && cmfa) + +package dns + +func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) { +} diff --git a/dns/policy.go b/dns/policy.go index a8b423e1..a58123e3 100644 --- a/dns/policy.go +++ b/dns/policy.go @@ -1,30 +1,50 @@ package dns -type Policy struct { - data []dnsClient +import ( + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" +) + +type dnsPolicy interface { + Match(domain string) []dnsClient } -func (p *Policy) GetData() []dnsClient { - return p.data +type domainTriePolicy struct { + *trie.DomainTrie[[]dnsClient] } -func (p *Policy) Compare(p2 *Policy) int { - if p2 == nil { - return 1 +func (p domainTriePolicy) Match(domain string) []dnsClient { + record := p.DomainTrie.Search(domain) + if record != nil { + return record.Data() } - l1 := len(p.data) - l2 := len(p2.data) - if l1 == l2 { - return 0 - } - if l1 > l2 { - return 1 - } - return -1 + return nil } -func NewPolicy(data []dnsClient) *Policy { - return &Policy{ - data: data, - } +type geositePolicy struct { + matcher fallbackDomainFilter + inverse bool + dnsClients []dnsClient +} + +func (p geositePolicy) Match(domain string) []dnsClient { + matched := p.matcher.Match(domain) + if matched != p.inverse { + return p.dnsClients + } + return nil +} + +type domainSetPolicy struct { + domainSetProvider provider.RuleProvider + dnsClients []dnsClient +} + +func (p domainSetPolicy) Match(domain string) []dnsClient { + metadata := &C.Metadata{Host: domain} + if ok := p.domainSetProvider.Match(metadata); ok { + return p.dnsClients + } + return nil } diff --git a/dns/rcode.go b/dns/rcode.go index 61fc8d72..9777d2e7 100644 --- a/dns/rcode.go +++ b/dns/rcode.go @@ -39,16 +39,12 @@ type rcodeClient struct { var _ dnsClient = rcodeClient{} -func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) { +func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { m.Response = true m.Rcode = r.rcode return m, nil } -func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) { - return r.Exchange(m) -} - func (r rcodeClient) Address() string { return r.addr } diff --git a/dns/resolver.go b/dns/resolver.go index 89e36214..8ea68ed7 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -7,42 +7,38 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/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/constant/provider" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/arc" + "github.com/metacubex/mihomo/common/lru" + "github.com/metacubex/mihomo/component/fakeip" + "github.com/metacubex/mihomo/component/geodata/router" + "github.com/metacubex/mihomo/component/resolver" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" + "github.com/samber/lo" + orderedmap "github.com/wk8/go-ordered-map/v2" + "golang.org/x/exp/maps" "golang.org/x/sync/singleflight" ) 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 dnsCache interface { + GetWithExpire(key string) (*D.Msg, time.Time, bool) + SetWithExpire(key string, value *D.Msg, expire time.Time) +} + type result struct { Msg *D.Msg Error error } -type geositePolicyRecord struct { - matcher fallbackDomainFilter - policy *Policy - inversedMatching bool -} - -type domainSetPolicyRecord struct { - domainSetProvider provider.RuleProvider - policy *Policy -} - type Resolver struct { ipv6 bool ipv6Timeout time.Duration @@ -52,10 +48,8 @@ type Resolver struct { fallbackDomainFilters []fallbackDomainFilter fallbackIPFilters []fallbackIPFilter group singleflight.Group - lruCache *cache.LruCache[string, *D.Msg] - policy *trie.DomainTrie[*Policy] - domainSetPolicy []domainSetPolicyRecord - geositePolicy []geositePolicyRecord + cache dnsCache + policy []dnsPolicy proxyServer []dnsClient } @@ -135,11 +129,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool { return false } -// Exchange a batch of dns request, and it use cache -func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { - return r.ExchangeContext(context.Background(), m) -} - // ExchangeContext a batch of dns request with context.Context, and it use cache func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { if len(m.Question) == 0 { @@ -157,8 +146,9 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e }() q := m.Question[0] - cacheM, expireTime, hit := r.lruCache.GetWithExpire(q.String()) + cacheM, expireTime, hit := r.cache.GetWithExpire(q.String()) if hit { + log.Debugln("[DNS] cache hit for %s, expire at %s", q.Name, expireTime.Format("2006-01-02 15:04:05")) now := time.Now() msg = cacheM.Copy() if expireTime.Before(now) { @@ -194,21 +184,25 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M msg := result.(*D.Msg) if cache { - putMsgToCache(r.lruCache, q.String(), msg) + // OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files. + msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool { + return rr.Header().Rrtype != D.TypeOPT + }) + putMsgToCache(r.cache, q.String(), q, msg) } }() isIPReq := isIPRequest(q) if isIPReq { - cache=true + cache = true return r.ipExchange(ctx, m) } if matched := r.matchPolicy(m); len(matched) != 0 { - result, cache, err = r.batchExchange(ctx, matched, m) + result, cache, err = batchExchange(ctx, matched, m) return } - result, cache, err = r.batchExchange(ctx, r.main, m) + result, cache, err = batchExchange(ctx, r.main, m) return } @@ -250,13 +244,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M return } -func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { - ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout) - defer cancel() - - return batchExchange(ctx, clients, m) -} - func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient { if r.policy == nil { return nil @@ -267,22 +254,9 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient { return nil } - record := r.policy.Search(domain) - if record != nil { - p := record.Data() - return p.GetData() - } - - for _, geositeRecord := range r.geositePolicy { - matched := geositeRecord.matcher.Match(domain) - if matched != geositeRecord.inversedMatching { - return geositeRecord.policy.GetData() - } - } - metadata := &C.Metadata{Host: domain} - for _, domainSetRecord := range r.domainSetPolicy { - if ok := domainSetRecord.domainSetProvider.Match(metadata); ok { - return domainSetRecord.policy.GetData() + for _, policy := range r.policy { + if dnsClients := policy.Match(domain); len(dnsClients) > 0 { + return dnsClients } } return nil @@ -332,7 +306,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er res := <-msgCh if res.Error == nil { if ips := msgToIP(res.Msg); len(ips) != 0 { - if !r.shouldIPFallback(ips[0]) { + shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool { + return !r.shouldIPFallback(ip) + }) + if shouldNotFallback { msg, err = res.Msg, res.Error // no need to wait for fallback result return } @@ -377,7 +354,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result { ch := make(chan *result, 1) go func() { - res, _, err := r.batchExchange(ctx, client, msg) + res, _, err := batchExchange(ctx, client, msg) ch <- &result{Msg: res, Error: err} }() return ch @@ -394,92 +371,173 @@ func (r *Resolver) Invalid() bool { type NameServer struct { Net string Addr string - Interface *atomic.TypedValue[string] + Interface string ProxyAdapter C.ProxyAdapter ProxyName string Params map[string]string PreferH3 bool } +func (ns NameServer) Equal(ns2 NameServer) bool { + defer func() { + // C.ProxyAdapter compare maybe panic, just ignore + recover() + }() + if ns.Net == ns2.Net && + ns.Addr == ns2.Addr && + ns.Interface == ns2.Interface && + ns.ProxyAdapter == ns2.ProxyAdapter && + ns.ProxyName == ns2.ProxyName && + maps.Equal(ns.Params, ns2.Params) && + ns.PreferH3 == ns2.PreferH3 { + return true + } + return false +} + type FallbackFilter struct { GeoIP bool GeoIPCode string - IPCIDR []*netip.Prefix + IPCIDR []netip.Prefix Domain []string - GeoSite []*router.DomainMatcher + GeoSite []router.DomainMatcher } type Config struct { - Main, Fallback []NameServer - Default []NameServer - ProxyServer []NameServer - IPv6 bool - IPv6Timeout uint - EnhancedMode C.DNSMode - FallbackFilter FallbackFilter - Pool *fakeip.Pool - Hosts *trie.DomainTrie[resolver.HostValue] - Policy map[string][]NameServer - DomainSetPolicy map[provider.RuleProvider][]NameServer - GeositePolicy map[router.DomainMatcher][]NameServer + Main, Fallback []NameServer + Default []NameServer + ProxyServer []NameServer + IPv6 bool + IPv6Timeout uint + EnhancedMode C.DNSMode + FallbackFilter FallbackFilter + Pool *fakeip.Pool + Hosts *trie.DomainTrie[resolver.HostValue] + Policy *orderedmap.OrderedMap[string, []NameServer] + RuleProviders map[string]provider.RuleProvider + CacheAlgorithm string } func NewResolver(config Config) *Resolver { + var cache dnsCache + if config.CacheAlgorithm == "lru" { + cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) + } else { + cache = arc.New(arc.WithSize[string, *D.Msg](4096)) + } defaultResolver := &Resolver{ main: transform(config.Default, nil), - lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), + cache: cache, ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, } + var nameServerCache []struct { + NameServer + dnsClient + } + cacheTransform := func(nameserver []NameServer) (result []dnsClient) { + LOOP: + for _, ns := range nameserver { + for _, nsc := range nameServerCache { + if nsc.NameServer.Equal(ns) { + result = append(result, nsc.dnsClient) + continue LOOP + } + } + // not in cache + dc := transform([]NameServer{ns}, defaultResolver) + if len(dc) > 0 { + dc := dc[0] + nameServerCache = append(nameServerCache, struct { + NameServer + dnsClient + }{NameServer: ns, dnsClient: dc}) + result = append(result, dc) + } + } + return + } + + if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" { + cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) + } else { + cache = arc.New(arc.WithSize[string, *D.Msg](4096)) + } r := &Resolver{ ipv6: config.IPv6, - main: transform(config.Main, defaultResolver), - lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), + main: cacheTransform(config.Main), + cache: cache, hosts: config.Hosts, ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, } if len(config.Fallback) != 0 { - r.fallback = transform(config.Fallback, defaultResolver) + r.fallback = cacheTransform(config.Fallback) } if len(config.ProxyServer) != 0 { - r.proxyServer = transform(config.ProxyServer, defaultResolver) + r.proxyServer = cacheTransform(config.ProxyServer) } - if len(config.Policy) != 0 { - r.policy = trie.New[*Policy]() - for domain, nameserver := range config.Policy { - 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))) + if config.Policy.Len() != 0 { + r.policy = make([]dnsPolicy, 0) + + var triePolicy *trie.DomainTrie[[]dnsClient] + insertPolicy := func(policy dnsPolicy) { + if triePolicy != nil { + triePolicy.Optimize() + r.policy = append(r.policy, domainTriePolicy{triePolicy}) + triePolicy = nil + } + if policy != nil { + r.policy = append(r.policy, policy) } } - r.policy.Optimize() - } - if len(config.DomainSetPolicy) > 0 { - for p, n := range config.DomainSetPolicy { - r.domainSetPolicy = append(r.domainSetPolicy, domainSetPolicyRecord{ - domainSetProvider: p, - policy: NewPolicy(transform(n, defaultResolver)), - }) + + for pair := config.Policy.Oldest(); pair != nil; pair = pair.Next() { + domain, nameserver := pair.Key, pair.Value + + if temp := strings.Split(domain, ":"); len(temp) == 2 { + prefix := temp[0] + key := temp[1] + switch prefix { + case "rule-set": + if p, ok := config.RuleProviders[key]; ok { + log.Debugln("Adding rule-set policy: %s ", key) + insertPolicy(domainSetPolicy{ + domainSetProvider: p, + dnsClients: cacheTransform(nameserver), + }) + continue + } else { + log.Warnln("Can't found ruleset policy: %s", key) + } + case "geosite": + inverse := false + if strings.HasPrefix(key, "!") { + inverse = true + key = key[1:] + } + log.Debugln("Adding geosite policy: %s inversed %t", key, inverse) + matcher, err := NewGeoSite(key) + if err != nil { + log.Warnln("adding geosite policy %s error: %s", key, err) + continue + } + insertPolicy(geositePolicy{ + matcher: matcher, + inverse: inverse, + dnsClients: cacheTransform(nameserver), + }) + continue // skip triePolicy new + } + } + if triePolicy == nil { + triePolicy = trie.New[[]dnsClient]() + } + _ = triePolicy.Insert(domain, cacheTransform(nameserver)) } + insertPolicy(nil) } fallbackIPFilters := []fallbackIPFilter{} @@ -512,9 +570,8 @@ func NewProxyServerHostResolver(old *Resolver) *Resolver { r := &Resolver{ ipv6: old.ipv6, main: old.proxyServer, - lruCache: old.lruCache, + cache: old.cache, hosts: old.hosts, - policy: trie.New[*Policy](), ipv6Timeout: old.ipv6Timeout, } return r diff --git a/dns/server.go b/dns/server.go index 5c5970db..d45fb5eb 100644 --- a/dns/server.go +++ b/dns/server.go @@ -5,9 +5,10 @@ import ( "errors" "net" - "github.com/Dreamacro/clash/common/sockopt" - "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/sockopt" + "github.com/metacubex/mihomo/constant/features" + "github.com/metacubex/mihomo/context" + "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" ) @@ -49,6 +50,10 @@ func (s *Server) SetHandler(handler handler) { } func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { + if features.CMFA { + UpdateIsolateHandler(resolver, mapper) + } + if addr == address && resolver != nil { handler := NewHandler(resolver, mapper) server.SetHandler(handler) diff --git a/dns/system.go b/dns/system.go index f5ab0efb..37607a60 100644 --- a/dns/system.go +++ b/dns/system.go @@ -1,23 +1,113 @@ package dns import ( + "context" + "fmt" "net" + "strings" + "sync" + "time" + + "github.com/metacubex/mihomo/log" + + D "github.com/miekg/dns" + "golang.org/x/exp/slices" ) -func loadSystemResolver() (clients []dnsClient, err error) { - nameservers, err := dnsReadConfig() +const ( + SystemDnsFlushTime = 5 * time.Minute + SystemDnsDeleteTimes = 12 // 12*5 = 60min +) + +type systemDnsClient struct { + disableTimes uint32 + dnsClient +} + +type systemClient struct { + mu sync.Mutex + dnsClients map[string]*systemDnsClient + lastFlush time.Time +} + +func (c *systemClient) getDnsClients() ([]dnsClient, error) { + c.mu.Lock() + defer c.mu.Unlock() + var err error + if time.Since(c.lastFlush) > SystemDnsFlushTime { + var nameservers []string + if nameservers, err = dnsReadConfig(); err == nil { + log.Debugln("[DNS] system dns update to %s", nameservers) + for _, addr := range nameservers { + if _, ok := c.dnsClients[addr]; !ok { + clients := transform( + []NameServer{{ + Addr: net.JoinHostPort(addr, "53"), + Net: "udp", + }}, + nil, + ) + if len(clients) > 0 { + c.dnsClients[addr] = &systemDnsClient{ + disableTimes: 0, + dnsClient: clients[0], + } + } + } + } + available := 0 + for nameserver, sdc := range c.dnsClients { + if slices.Contains(nameservers, nameserver) { + sdc.disableTimes = 0 // enable + available++ + } else { + if sdc.disableTimes > SystemDnsDeleteTimes { + delete(c.dnsClients, nameserver) // drop too old dnsClient + } else { + sdc.disableTimes++ + } + } + } + if available > 0 { + c.lastFlush = time.Now() + } + } + } + dnsClients := make([]dnsClient, 0, len(c.dnsClients)) + for _, sdc := range c.dnsClients { + if sdc.disableTimes == 0 { + dnsClients = append(dnsClients, sdc.dnsClient) + } + } + if len(dnsClients) > 0 { + return dnsClients, nil + } + return nil, err +} + +func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + dnsClients, err := c.getDnsClients() if err != nil { return } - if len(nameservers) == 0 { - return - } - servers := make([]NameServer, 0, len(nameservers)) - for _, addr := range nameservers { - servers = append(servers, NameServer{ - Addr: net.JoinHostPort(addr, "53"), - Net: "udp", - }) - } - return transform(servers, nil), nil + msg, _, err = batchExchange(ctx, dnsClients, m) + return +} + +// Address implements dnsClient +func (c *systemClient) Address() string { + dnsClients, _ := c.getDnsClients() + addrs := make([]string, 0, len(dnsClients)) + for _, c := range dnsClients { + addrs = append(addrs, c.Address()) + } + return fmt.Sprintf("system(%s)", strings.Join(addrs, ",")) +} + +var _ dnsClient = (*systemClient)(nil) + +func newSystemClient() *systemClient { + return &systemClient{ + dnsClients: map[string]*systemDnsClient{}, + } } diff --git a/dns/util.go b/dns/util.go index 29de4e2a..f2243917 100644 --- a/dns/util.go +++ b/dns/util.go @@ -11,15 +11,14 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/cache" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/nnip" - "github.com/Dreamacro/clash/common/picker" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/nnip" + "github.com/metacubex/mihomo/common/picker" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel" D "github.com/miekg/dns" "github.com/samber/lo" @@ -29,14 +28,16 @@ const ( MaxMsgSize = 65535 ) +const serverFailureCacheTTL uint32 = 5 + func minimalTTL(records []D.RR) uint32 { - minObj := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { + rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { return r1.Header().Ttl < r2.Header().Ttl }) - if minObj != nil { - return minObj.Header().Ttl + if rr == nil { + return 0 } - return 0 + return rr.Header().Ttl } func updateTTL(records []D.RR, ttl uint32) { @@ -49,28 +50,25 @@ func updateTTL(records []D.RR, ttl uint32) { } } -func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) { - putMsgToCacheWithExpire(c, key, msg, 0) -} - -func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) { - if sec == 0 { - if sec = minimalTTL(msg.Answer); sec == 0 { - if sec = minimalTTL(msg.Ns); sec == 0 { - sec = minimalTTL(msg.Extra) - } - } - if sec == 0 { - return - } - - if sec > 120 { - sec = 120 // at least 2 minutes to cache - } - +func putMsgToCache(c dnsCache, key string, q D.Question, msg *D.Msg) { + // skip dns cache for acme challenge + if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") { + log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) + return } - c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(sec)*time.Second)) + var ttl uint32 + if msg.Rcode == D.RcodeServerFailure { + // [...] a resolver MAY cache a server failure response. + // If it does so it MUST NOT cache it for longer than five (5) minutes [...] + ttl = serverFailureCacheTTL + } else { + ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...)) + } + if ttl == 0 { + return + } + c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second)) } func setMsgTTL(msg *D.Msg, ttl uint32) { @@ -108,16 +106,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret = append(ret, newDHCPClient(s.Addr)) continue case "system": - clients, err := loadSystemResolver() - if err != nil { - log.Errorln("[DNS:system] load system resolver failed: %s", err.Error()) - continue - } - if len(clients) == 0 { - log.Errorln("[DNS:system] no nameserver found in system") - continue - } - ret = append(ret, clients...) + ret = append(ret, newSystemClient()) continue case "rcode": ret = append(ret, newRCodeClient(s.Addr)) @@ -290,7 +279,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st DstPort: uint16(uintPort), } if proxyAdapter == nil { - return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) + return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) } if !proxyAdapter.SupportUDP() { @@ -300,14 +289,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st return proxyAdapter.ListenPacketContext(ctx, metadata, opts...) } +var errIPNotFound = errors.New("couldn't find ip") + func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) { cache = true fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) defer fast.Close() domain := msgToDomain(m) + var noIpMsg *D.Msg for _, client := range clients { if _, isRCodeClient := client.(rcodeClient); isRCodeClient { - msg, err = client.Exchange(m) + msg, err = client.ExchangeContext(ctx, m) return msg, false, err } client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop @@ -321,13 +313,31 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M // so we would ignore RCode errors from RCode clients. return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) } - log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address()) + if ips := msgToIP(m); len(m.Question) > 0 { + qType := m.Question[0].Qtype + log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address()) + switch qType { + case D.TypeAAAA: + if len(ips) == 0 { + noIpMsg = m + return nil, errIPNotFound + } + case D.TypeA: + if len(ips) == 0 { + noIpMsg = m + return nil, errIPNotFound + } + } + } return m, nil }) } msg = fast.Wait() if msg == nil { + if noIpMsg != nil { + return noIpMsg, false, nil + } err = errors.New("all DNS requests failed") if fErr := fast.Error(); fErr != nil { err = fmt.Errorf("%w, first error: %w", err, fErr) diff --git a/docker/file-name.sh b/docker/file-name.sh index 1ac2cee0..3b2d61f9 100644 --- a/docker/file-name.sh +++ b/docker/file-name.sh @@ -1,5 +1,5 @@ #!/bin/sh -os="clash.meta-linux-" +os="mihomo-linux-" case $TARGETPLATFORM in "linux/amd64") arch="amd64-compatible" diff --git a/docs/config.yaml b/docs/config.yaml index c2bdc263..2a44a993 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -8,10 +8,20 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 allow-lan: true # 允许局域网连接 bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址 +authentication: # http,socks入口的验证用户名,密码 + - "username:password" +skip-auth-prefixes: # 设置跳过验证的IP段 + - 127.0.0.1/8 + - ::1/128 +lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0 + - 0.0.0.0/0 + - ::/0 +lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空 + - 192.168.0.3/32 # find-process-mode has 3 values:always, strict, off # - always, 开启,强制匹配所有进程 -# - strict, 默认,由 clash 判断是否开启 +# - strict, 默认,由 mihomo 判断是否开启 # - off, 不匹配进程,推荐在路由器上使用此模式 find-process-mode: strict @@ -23,6 +33,14 @@ geox-url: geosite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat" mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb" +geo-auto-update: false # 是否自动更新 geodata +geo-update-interval: 24 # 更新间隔,单位:小时 + +# Matcher implementation used by GeoSite, available implementations: +# - succinct (default, same as rule-set) +# - mph (from V2Ray, also `hybrid` in Xray) +# geosite-matcher: succinct + log-level: debug # 日志等级 silent/error/warning/info/debug ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录 @@ -69,11 +87,11 @@ experimental: # 类似于 /etc/hosts, 仅支持配置单个 IP hosts: -# '*.clash.dev': 127.0.0.1 +# '*.mihomo.dev': 127.0.0.1 # '.dev': 127.0.0.1 -# 'alpha.clash.dev': '::1' +# 'alpha.mihomo.dev': '::1' # test.com: [1.1.1.1, 2.2.2.2] -# clash.lan: clash # clash 为特别字段,将加入本地所有网卡的地址 +# home.lan: lan # lan 为特别字段,将加入本地所有网卡的地址 # baidu.com: google.com # 只允许配置一个别名 profile: # 存储 select 选择记录 @@ -85,28 +103,34 @@ profile: # 存储 select 选择记录 # Tun 配置 tun: enable: false - stack: system # gvisor / lwip + stack: system # gvisor/mixed dns-hijack: - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: true # 自动识别出口网卡 # auto-route: true # 配置路由表 # mtu: 9000 # 最大传输单元 + # gso: false # 启用通用分段卸载, 仅支持 Linux + # gso-max-size: 65536 # 通用分段卸载包的最大大小 # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 - inet4-route-address: # 启用 auto_route 时使用自定义路由而不是默认路由 + inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - 0.0.0.0/1 - 128.0.0.0/1 - inet6-route-address: # 启用 auto_route 时使用自定义路由而不是默认路由 + inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - "::/1" - "8000::/1" # endpoint-independent-nat: false # 启用独立于端点的 NAT - # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route + # include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突 + # - "lan0" + # exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突 + # - "lan1" + # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 # include-uid-range: # 限制被路由的的用户范围 - # - 1000-99999 + # - 1000:9999 # exclude-uid: # 排除路由的的用户 #- 1000 # exclude-uid-range: # 排除路由的的用户范围 - # - 1000-99999 + # - 1000:9999 # Android 用户和应用规则仅在 Android 下被支持 # 并且需要 auto-route @@ -137,7 +161,9 @@ sniffer: # 是否使用嗅探结果作为实际访问,默认 true # 全局配置,优先级低于 sniffer.sniff 实际配置 override-destination: false - sniff: # TLS 默认如果不配置 ports 默认嗅探 443 + sniff: # TLS 和 QUIC 默认如果不配置 ports 默认嗅探 443 + QUIC: + # ports: [ 443 ] TLS: # ports: [443, 8443] @@ -176,6 +202,7 @@ tunnels: # one line config # DNS配置 dns: + cache-algorithm: arc enable: false # 关闭将使用系统 DNS prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试 listen: 0.0.0.0:53 # 开启 DNS 服务器监听 @@ -345,16 +372,17 @@ proxies: # socks5 plugin: v2ray-plugin 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 - # path: "/" - # mux: true - # headers: - # custom: value + # tls: true # wss + # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 + # 配置指纹将实现 SSL Pining 效果 + # fingerprint: xxxx + # skip-cert-verify: true + # host: bing.com + # path: "/" + # mux: true + # headers: + # custom: value + # v2ray-http-upgrade: false - name: "ss4-shadow-tls" type: ss @@ -427,11 +455,12 @@ proxies: # socks5 # servername: example.com # priority over wss host # network: ws # ws-opts: - # path: /path - # headers: - # Host: v2ray.com - # max-early-data: 2048 - # early-data-header-name: Sec-WebSocket-Protocol + # path: /path + # headers: + # Host: v2ray.com + # max-early-data: 2048 + # early-data-header-name: Sec-WebSocket-Protocol + # v2ray-http-upgrade: false - name: "vmess-h2" type: vmess @@ -559,6 +588,7 @@ proxies: # socks5 path: "/" headers: Host: example.com + # v2ray-http-upgrade: false # Trojan - name: "trojan" @@ -599,9 +629,10 @@ proxies: # socks5 # fingerprint: xxxx udp: true # ws-opts: - # path: /path - # headers: - # Host: example.com + # path: /path + # headers: + # Host: example.com + # v2ray-http-upgrade: false - name: "trojan-xtls" type: trojan @@ -620,9 +651,8 @@ proxies: # socks5 type: hysteria server: server.com port: 443 - # ports: 1000,2000-3000,5000 # port 不可省略, - auth_str: yourpassword # 将会在未来某个时候删除 - # auth-str: yourpassword + # ports: 1000,2000-3000,5000 # port 不可省略 + auth-str: yourpassword # obfs: obfs_str # alpn: # - h3 @@ -631,14 +661,11 @@ proxies: # socks5 down: "200 Mbps" # 若不写单位,默认为 Mbps # sni: server.com # skip-cert-verify: false - # recv_window_conn: 12582912 # 将会在未来某个时候删除 # recv-window-conn: 12582912 - # recv_window: 52428800 # 将会在未来某个时候删除 # recv-window: 52428800 # ca: "./my.ca" - # ca_str: "xyz" # 将会在未来某个时候删除 # ca-str: "xyz" - # disable_mtu_discovery: false + # disable-mtu-discovery: false # fingerprint: xxxx # fast-open: true # 支持 TCP 快速打开,默认为 false @@ -679,7 +706,7 @@ proxies: # socks5 # dialer-proxy: "ss1" # remote-dns-resolve: true # 强制dns远程解析,默认值为false # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 - # 如果peers不为空,该段落中的allowed_ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1 # port: 2480 @@ -687,7 +714,7 @@ proxies: # socks5 # ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5 # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= - # allowed_ips: ['0.0.0.0/0'] + # allowed-ips: ['0.0.0.0/0'] # reserved: [209,98,59] # tuic @@ -725,7 +752,7 @@ proxies: # socks5 # The supported obfses: # plain http_simple http_post # random_head tls1.2_ticket_auth tls1.2_ticket_fastauth - # The supported supported protocols: + # The supported protocols: # origin auth_sha1_v4 auth_aes128_md5 # auth_aes128_sha1 auth_chain_a auth_chain_b - name: "ssr" @@ -743,7 +770,7 @@ proxies: # socks5 proxy-groups: # 代理链,目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic # wireguard目前不支持在relay中使用,请使用proxy中的dialer-proxy配置项 - # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet + # Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - name: "relay" type: relay proxies: @@ -812,18 +839,27 @@ proxy-groups: - Proxy - DIRECT -# Clash 格式的节点或支持 *ray 的分享格式 +# Mihomo 格式的节点或支持 *ray 的分享格式 proxy-providers: provider1: type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5 url: "url" interval: 3600 - path: ./provider1.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 health-check: enable: true interval: 600 # lazy: true url: https://cp.cloudflare.com/generate_204 + override: # 覆写节点加载时的一些配置项 + skip-cert-verify: true + udp: true + # down: "50 Mbps" + # up: "10 Mbps" + # dialer-proxy: proxy + # interface-name: tailscale0 + # routing-mark: 233 + # ip-version: ipv4-prefer test: type: file path: /test.yaml @@ -835,7 +871,7 @@ rule-providers: rule1: behavior: classical # domain ipcidr interval: 259200 - path: /path/to/save/file.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 + path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5 url: "url" rule2: @@ -936,6 +972,10 @@ listeners: - username: 1 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 alterId: 1 + # ws-path: "/" # 如果不为空则开启websocket传输层 + # 下面两项如果填写则开启tls(需要同时填写) + # certificate: ./server.crt + # private-key: ./server.key - name: tuic-in-1 type: tuic @@ -970,42 +1010,42 @@ listeners: type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - stack: system # gvisor / lwip + stack: system # gvisor / mixed dns-hijack: - - 0.0.0.0:53 # 需要劫持的 DNS + - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: false # 自动识别出口网卡 # auto-route: false # 配置路由表 # mtu: 9000 # 最大传输单元 inet4-address: # 必须手动设置ipv4地址段 - - 198.19.0.1/30 + - 198.19.0.1/30 inet6-address: # 必须手动设置ipv6地址段 - - "fdfe:dcba:9877::1/126" - # strict_route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 - # inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由 - # - 0.0.0.0/1 - # - 128.0.0.0/1 - # inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由 - # - "::/1" - # - "8000::/1" - # endpoint_independent_nat: false # 启用独立于端点的 NAT - # include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route + - "fdfe:dcba:9877::1/126" + # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 + # - 0.0.0.0/1 + # - 128.0.0.0/1 + # inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 + # - "::/1" + # - "8000::/1" + # endpoint-independent-nat: false # 启用独立于端点的 NAT + # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 - # include_uid_range: # 限制被路由的的用户范围 - # - 1000-99999 - # exclude_uid: # 排除路由的的用户 + # include-uid-range: # 限制被路由的的用户范围 + # - 1000:99999 + # exclude-uid: # 排除路由的的用户 # - 1000 - # exclude_uid_range: # 排除路由的的用户范围 - # - 1000-99999 + # exclude-uid-range: # 排除路由的的用户范围 + # - 1000:99999 # Android 用户和应用规则仅在 Android 下被支持 - # 并且需要 auto_route + # 并且需要 auto-route - # include_android_user: # 限制被路由的 Android 用户 + # include-android-user: # 限制被路由的 Android 用户 # - 0 # - 10 - # include_package: # 限制被路由的 Android 应用包名 + # include-package: # 限制被路由的 Android 应用包名 # - com.android.chrome - # exclude_package: # 排除被路由的 Android 应用包名 + # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 # shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) diff --git a/flake.nix b/flake.nix index ffd18629..afe6e1c1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "Another Clash Kernel"; + description = "Another Mihomo Kernel"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/master"; @@ -15,7 +15,7 @@ }; in rec { - packages.default = pkgs.clash-meta; + packages.default = pkgs.mihomo-meta; } ) // ( @@ -23,8 +23,8 @@ { overlay = final: prev: { - clash-meta = final.buildGo119Module { - pname = "clash-meta"; + mihomo-meta = final.buildGo119Module { + pname = "mihomo-meta"; inherit version; src = ./.; @@ -38,8 +38,8 @@ ldflags = [ "-s" "-w" - "-X github.com/Dreamacro/clash/constant.Version=dev-${version}" - "-X github.com/Dreamacro/clash/constant.BuildTime=${version}" + "-X github.com/metacubex/mihomo/constant.Version=dev-${version}" + "-X github.com/metacubex/mihomo/constant.BuildTime=${version}" ]; tags = [ @@ -50,7 +50,7 @@ doCheck = false; postInstall = '' - mv $out/bin/clash $out/bin/clash-meta + mv $out/bin/mihomo $out/bin/mihomo-meta ''; }; diff --git a/go.mod b/go.mod index 6c67a38a..866ac035 100644 --- a/go.mod +++ b/go.mod @@ -1,93 +1,98 @@ -module github.com/Dreamacro/clash +module github.com/metacubex/mihomo go 1.20 require ( github.com/3andne/restls-client-go v0.1.6 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da - github.com/cilium/ebpf v0.11.0 + github.com/bahlo/generic-list-go v0.2.0 + github.com/cilium/ebpf v0.12.3 github.com/coreos/go-iptables v0.7.0 github.com/dlclark/regexp2 v1.10.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 + github.com/gobwas/ws v1.3.1 github.com/gofrs/uuid/v5 v5.0.0 - github.com/gorilla/websocket v1.5.0 - github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a + github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/jpillora/backoff v1.0.0 - github.com/klauspost/cpuid/v2 v2.2.5 + github.com/klauspost/cpuid/v2 v2.2.6 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf - github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 - github.com/metacubex/sing-shadowsocks v0.2.5 - github.com/metacubex/sing-shadowsocks2 v0.1.4 - github.com/metacubex/sing-tun v0.1.12 - github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 - github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 - github.com/miekg/dns v1.1.56 + github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 + github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 + github.com/metacubex/sing-shadowsocks v0.2.6 + github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 + github.com/metacubex/sing-tun v0.2.0-beta.4 + github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f + github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 + github.com/miekg/dns v1.1.57 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 - github.com/puzpuzpuz/xsync/v2 v2.5.0 + github.com/puzpuzpuz/xsync/v3 v3.0.2 + github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.2.11 - github.com/sagernet/sing-mux v0.1.3 + github.com/sagernet/sing v0.3.0-rc.3 + github.com/sagernet/sing-mux v0.1.6-beta.1 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 - github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 - github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f - github.com/samber/lo v1.38.1 - github.com/shirou/gopsutil/v3 v3.23.8 + github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 + github.com/sagernet/utls v1.5.4 + github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e + github.com/samber/lo v1.39.0 + github.com/shirou/gopsutil/v3 v3.23.11 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 + github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/zhangyunhao116/fastrand v0.3.0 - go.etcd.io/bbolt v1.3.7 go.uber.org/automaxprocs v1.5.3 - golang.org/x/crypto v0.13.0 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/net v0.15.0 - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.12.0 + golang.org/x/crypto v0.16.0 + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb + golang.org/x/net v0.19.0 + golang.org/x/sync v0.5.0 + golang.org/x/sys v0.15.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.2.1 ) require ( - github.com/RyuaNerin/go-krypto v1.0.2 // indirect + github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/cloudflare/circl v1.3.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/golang/mock v1.6.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/socket v0.4.1 // indirect - github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect + github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.3 // indirect - github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect - github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect + github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect @@ -99,10 +104,12 @@ require ( github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + go.uber.org/mock v0.3.0 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.16.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f diff --git a/go.sum b/go.sum index 0105d57d..3f80c92d 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,28 @@ github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08= github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY= -github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM= -github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA= +github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A= +github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go= +github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 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.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 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= -github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= -github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= +github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -33,8 +40,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4Rfsap github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= @@ -49,10 +56,14 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU= +github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -60,59 +71,61 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ= -github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs= +github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= +github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww= -github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg= -github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8= -github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8= -github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4= -github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg= -github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 h1:6g+ohVa8FQLXz/ATmped/4kWuK0HKvhy1hwzQXyF0EI= -github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4= -github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc= -github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo= -github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE= -github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k= -github.com/metacubex/sing-tun v0.1.12 h1:Jgmz0k3ddRiJ8zfS4X7j6B/iSy6GnOdDEU0nhqiZcK4= -github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M= -github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY= -github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM= -github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA= -github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs= -github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= -github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= +github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc h1:+yTZ6q2EeQCAJNpKNEu5j32Pm23ShD38ElIa635wTrk= +github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc/go.mod h1:rhBU9tD5ktoGPBtXUquhWuGJ4u+8ZZzBMi2cAdv9q8Y= +github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y= +github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= +github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f h1:T2PuaAiXMSC3mjRRUmIomuiu3jhi7EWSbzXtVIrVUC4= +github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= +github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 h1:xiCPttMGAaIh4Ad6t85VxUoUv+Sg88eXzzUvYN8gT5w= +github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796/go.mod h1:E1e1Uu6YaJddD+c0DtJlSOkfMI0NLdOVhM60KAlcssY= +github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= +github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= +github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 h1:ftbpVCK1+n3jxIP7+NMkRYOFEQtGPodV42MizsPey0w= +github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1/go.mod h1:kUVj2X+2wUh6Z5pAk9WrjDRehPyXolC6nJyFl7ln4V4= +github.com/metacubex/sing-tun v0.2.0-beta.4 h1:42F+uF9zKsaWsiUXNKzZD8aRkyPN9m5SdpF2yZEZar8= +github.com/metacubex/sing-tun v0.2.0-beta.4/go.mod h1:O8wFThUDfiwb6y56I714dQuyaqT8DW9VCD/wvGesyLM= +github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= +github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 h1:loWjR+k9dxqBSgruGyT5hE8UCRMmCEjxqZbryfY9no4= +github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232/go.mod h1:NGCrBZ+fUmp81yaA1kVskcNWBnwl5z4UHxz47A01zm8= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= @@ -135,35 +148,35 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c= -github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= +github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= 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-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= -github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= -github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= 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-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA= -github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0= +github.com/sagernet/sing-mux v0.1.6-beta.1 h1:ADs1TgiMfA628Y2qfv21tEvePDZjBRRYddwtNFZiwe8= +github.com/sagernet/sing-mux v0.1.6-beta.1/go.mod h1:WWtRmrwCDgb+g+7Da6o62I9WiMNB0a3w6BJhEpNQlNA= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= -github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= -github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q= -github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M= -github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4= -github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= -github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho= -github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= +github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= +github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= +github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= +github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= +github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= +github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo= +github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= -github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= -github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= +github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= +github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -197,37 +210,37 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -235,36 +248,26 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 1831584f..783da4d3 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -7,36 +7,35 @@ import ( "os" "runtime" "strconv" - "strings" "sync" "time" - "github.com/Dreamacro/clash/ntp" - - "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/adapter/outboundgroup" - "github.com/Dreamacro/clash/component/auth" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/component/dialer" - G "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/iface" - "github.com/Dreamacro/clash/component/profile" - "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/trie" - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/dns" - "github.com/Dreamacro/clash/listener" - authStore "github.com/Dreamacro/clash/listener/auth" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/inner" - "github.com/Dreamacro/clash/listener/tproxy" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/adapter" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/component/auth" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" + G "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/iface" + "github.com/metacubex/mihomo/component/profile" + "github.com/metacubex/mihomo/component/profile/cachefile" + "github.com/metacubex/mihomo/component/resolver" + SNI "github.com/metacubex/mihomo/component/sniffer" + "github.com/metacubex/mihomo/component/trie" + "github.com/metacubex/mihomo/config" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/dns" + "github.com/metacubex/mihomo/listener" + authStore "github.com/metacubex/mihomo/listener/auth" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/inner" + "github.com/metacubex/mihomo/listener/tproxy" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/ntp" + "github.com/metacubex/mihomo/tunnel" ) var mux sync.Mutex @@ -113,12 +112,13 @@ func ApplyConfig(cfg *config.Config, force bool) { loadRuleProvider(cfg.RuleProviders) runtime.GC() tunnel.OnRunning() + hcCompatibleProvider(cfg.Providers) log.SetLevel(cfg.General.LogLevel) } func initInnerTcp() { - inner.New(tunnel.TCPIn()) + inner.New(tunnel.Tunnel) } func GetGeneral() *config.General { @@ -140,51 +140,59 @@ func GetGeneral() *config.General { ShadowSocksConfig: ports.ShadowSocksConfig, VmessConfig: ports.VmessConfig, Authentication: authenticator, + SkipAuthPrefixes: inbound.SkipAuthPrefixes(), + LanAllowedIPs: inbound.AllowedIPs(), + LanDisAllowedIPs: inbound.DisAllowedIPs(), AllowLan: listener.AllowLan(), BindAddress: listener.BindAddress(), }, - Controller: config.Controller{}, - Mode: tunnel.Mode(), - LogLevel: log.Level(), - IPv6: !resolver.DisableIPv6, - GeodataLoader: G.LoaderName(), - Interface: dialer.DefaultInterface.Load(), - Sniffing: tunnel.IsSniffing(), - TCPConcurrent: dialer.GetTcpConcurrent(), + Controller: config.Controller{}, + Mode: tunnel.Mode(), + LogLevel: log.Level(), + IPv6: !resolver.DisableIPv6, + GeodataLoader: G.LoaderName(), + GeositeMatcher: G.SiteMatcherName(), + Interface: dialer.DefaultInterface.Load(), + Sniffing: tunnel.IsSniffing(), + TCPConcurrent: dialer.GetTcpConcurrent(), } return general } func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) { - tcpIn := tunnel.TCPIn() - udpIn := tunnel.UDPIn() - natTable := tunnel.NatTable() - - listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true) + listener.PatchInboundListeners(listeners, tunnel.Tunnel, true) if !force { return } allowLan := general.AllowLan listener.SetAllowLan(allowLan) + inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes) + inbound.SetAllowedIPs(general.LanAllowedIPs) + inbound.SetDisAllowedIPs(general.LanDisAllowedIPs) bindAddress := general.BindAddress listener.SetBindAddress(bindAddress) - listener.ReCreateHTTP(general.Port, tcpIn) - listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn) - listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable) - listener.ReCreateAutoRedir(general.EBpf.AutoRedir, 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) - listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn) + listener.ReCreateHTTP(general.Port, tunnel.Tunnel) + listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel) + listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel) + if !features.CMFA { + listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel) + } + listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel) + listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel) + listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel) + listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel) + listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel) } func updateExperimental(c *config.Config) { if c.Experimental.QUICGoDisableGSO { - _ = os.Setenv("QUIC_GO_DISABLE_GSO", "1") + _ = os.Setenv("QUIC_GO_DISABLE_GSO", strconv.FormatBool(true)) + } + if c.Experimental.QUICGoDisableECN { + _ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true)) } } @@ -207,25 +215,6 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen dns.ReCreateServer("", nil, nil) return } - policy := make(map[string][]dns.NameServer) - domainSetPolicies := make(map[provider.RuleProvider][]dns.NameServer) - for key, nameservers := range c.NameServerPolicy { - temp := strings.Split(key, ":") - if len(temp) == 2 { - prefix := temp[0] - key := temp[1] - switch strings.ToLower(prefix) { - case "rule-set": - if p, ok := ruleProvider[key]; ok { - domainSetPolicies[p] = nameservers - } - case "geosite": - // TODO: - } - } else { - policy[key] = nameservers - } - } cfg := dns.Config{ Main: c.NameServer, Fallback: c.Fallback, @@ -241,10 +230,11 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen Domain: c.FallbackFilter.Domain, GeoSite: c.FallbackFilter.GeoSite, }, - Default: c.DefaultNameserver, - Policy: c.NameServerPolicy, - ProxyServer: c.ProxyServerNameserver, - DomainSetPolicy: domainSetPolicies, + Default: c.DefaultNameserver, + Policy: c.NameServerPolicy, + ProxyServer: c.ProxyServerNameserver, + RuleProviders: ruleProvider, + CacheAlgorithm: c.CacheAlgorithm, } r := dns.NewResolver(cfg) @@ -281,7 +271,7 @@ func updateRules(rules []C.Rule, subRules map[string][]C.Rule, ruleProviders map func loadProvider(pv provider.Provider) { if pv.VehicleType() == provider.Compatible { - log.Infoln("Start initial compatible provider %s", pv.Name()) + return } else { log.Infoln("Start initial provider %s", (pv).Name()) } @@ -334,12 +324,32 @@ func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) { wg.Wait() } +func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) { + // limit concurrent size + wg := sync.WaitGroup{} + ch := make(chan struct{}, concurrentCount) + for _, proxyProvider := range proxyProviders { + proxyProvider := proxyProvider + if proxyProvider.VehicleType() == provider.Compatible { + log.Infoln("Start initial Compatible provider %s", proxyProvider.Name()) + wg.Add(1) + ch <- struct{}{} + go func() { + defer func() { <-ch; wg.Done() }() + if err := proxyProvider.Initial(); err != nil { + log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err) + } + }() + } + } + +} func updateTun(general *config.General) { if general == nil { return } - listener.ReCreateTun(LC.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn()) + listener.ReCreateTun(general.Tun, tunnel.Tunnel) listener.ReCreateRedirToTun(general.Tun.RedirectToTun) } @@ -367,7 +377,7 @@ func updateSniffer(sniffer *config.Sniffer) { } func updateTunnels(tunnels []LC.Tunnel) { - listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn()) + listener.PatchTunnel(tunnels, tunnel.Tunnel) } func updateGeneral(general *config.General) { @@ -392,8 +402,8 @@ func updateGeneral(general *config.General) { } iface.FlushCache() - geodataLoader := general.GeodataLoader - G.SetLoader(geodataLoader) + G.SetLoader(general.GeodataLoader) + G.SetSiteMatcher(general.GeositeMatcher) } func updateUsers(users []auth.AuthUser) { @@ -504,5 +514,5 @@ func Shutdown() { tproxy.CleanupTProxyIPTables() resolver.StoreFakePoolState() - log.Warnln("Clash shutting down") + log.Warnln("Mihomo shutting down") } diff --git a/hub/hub.go b/hub/hub.go index bd228fad..323f8749 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -1,10 +1,10 @@ package hub import ( - "github.com/Dreamacro/clash/config" - "github.com/Dreamacro/clash/hub/executor" - "github.com/Dreamacro/clash/hub/route" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/config" + "github.com/metacubex/mihomo/hub/executor" + "github.com/metacubex/mihomo/hub/route" + "github.com/metacubex/mihomo/log" ) type Option func(*config.Config) @@ -27,7 +27,7 @@ func WithSecret(secret string) Option { } } -// Parse call at the beginning of clash +// Parse call at the beginning of mihomo func Parse(options ...Option) error { cfg, err := executor.Parse() if err != nil { diff --git a/hub/route/cache.go b/hub/route/cache.go index bdfd2e35..f07eb33a 100644 --- a/hub/route/cache.go +++ b/hub/route/cache.go @@ -3,7 +3,7 @@ package route import ( "net/http" - "github.com/Dreamacro/clash/component/resolver" + "github.com/metacubex/mihomo/component/resolver" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/route/configs.go b/hub/route/configs.go index cb7c93f6..ec0b464c 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -2,19 +2,20 @@ package route import ( "net/http" + "net/netip" "path/filepath" "sync" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/config" - "github.com/Dreamacro/clash/constant" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/hub/executor" - P "github.com/Dreamacro/clash/listener" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + "github.com/metacubex/mihomo/config" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/hub/executor" + P "github.com/metacubex/mihomo/listener" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -47,6 +48,9 @@ type configSchema struct { TcptunConfig *string `json:"tcptun-config"` UdptunConfig *string `json:"udptun-config"` AllowLan *bool `json:"allow-lan"` + SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"` + LanAllowedIPs *[]netip.Prefix `json:"lan-allowed-ips"` + LanDisAllowedIPs *[]netip.Prefix `json:"lan-disallowed-ips"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` LogLevel *log.LogLevel `json:"log-level"` @@ -65,22 +69,28 @@ type tunSchema struct { AutoDetectInterface *bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` //RedirectToTun []string `yaml:"-" json:"-"` - MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` - //Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address *[]LC.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` - StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` - Inet4RouteAddress *[]LC.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` - Inet6RouteAddress *[]LC.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` - IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` - IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` - ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` - ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` - IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` - IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` - ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` - EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` - UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` - FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` + MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO *bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` + //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` + Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` + Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` + Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` + Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` + Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` + IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` + IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` + ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` + ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` + IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` + IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` + ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` + EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` + UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` + FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` } type tuicServerSchema struct { @@ -139,12 +149,36 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { if p.MTU != nil { def.MTU = *p.MTU } + if p.GSO != nil { + def.GSO = *p.GSO + } + if p.GSOMaxSize != nil { + def.GSOMaxSize = *p.GSOMaxSize + } //if p.Inet4Address != nil { // def.Inet4Address = *p.Inet4Address //} if p.Inet6Address != nil { def.Inet6Address = *p.Inet6Address } + if p.Inet4RouteAddress != nil { + def.Inet4RouteAddress = *p.Inet4RouteAddress + } + if p.Inet6RouteAddress != nil { + def.Inet6RouteAddress = *p.Inet6RouteAddress + } + if p.Inet4RouteExcludeAddress != nil { + def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress + } + if p.Inet6RouteExcludeAddress != nil { + def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress + } + if p.IncludeInterface != nil { + def.IncludeInterface = *p.IncludeInterface + } + if p.ExcludeInterface != nil { + def.ExcludeInterface = *p.ExcludeInterface + } if p.IncludeUID != nil { def.IncludeUID = *p.IncludeUID } @@ -231,6 +265,18 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.SetAllowLan(*general.AllowLan) } + if general.SkipAuthPrefixes != nil { + inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes) + } + + if general.LanAllowedIPs != nil { + inbound.SetAllowedIPs(*general.LanAllowedIPs) + } + + if general.LanDisAllowedIPs != nil { + inbound.SetDisAllowedIPs(*general.LanDisAllowedIPs) + } + if general.BindAddress != nil { P.SetBindAddress(*general.BindAddress) } @@ -249,19 +295,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { ports := P.GetPorts() - 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, 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) - P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tcpIn, udpIn) - P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tcpIn, udpIn) + P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tunnel.Tunnel) + P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tunnel.Tunnel) + P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tunnel.Tunnel) + P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tunnel.Tunnel) + P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tunnel.Tunnel) + P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tunnel.Tunnel) + P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tunnel.Tunnel) + P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tunnel.Tunnel) + P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tunnel.Tunnel) if general.Mode != nil { tunnel.SetMode(*general.Mode) @@ -302,7 +344,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } } else { if req.Path == "" { - req.Path = constant.Path.Config() + req.Path = C.Path.Config() } if !filepath.IsAbs(req.Path) { render.Status(r, http.StatusBadRequest) @@ -347,7 +389,7 @@ func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { return } - cfg, err := executor.ParseWithPath(constant.Path.Config()) + cfg, err := executor.ParseWithPath(C.Path.Config()) if err != nil { log.Errorln("[REST-API] update GEO databases failed: %v", err) return diff --git a/hub/route/connections.go b/hub/route/connections.go index b123ecae..e0ff2426 100644 --- a/hub/route/connections.go +++ b/hub/route/connections.go @@ -7,11 +7,12 @@ import ( "strconv" "time" - "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/metacubex/mihomo/tunnel/statistic" "github.com/go-chi/chi/v5" "github.com/go-chi/render" - "github.com/gorilla/websocket" + "github.com/gobwas/ws" + "github.com/gobwas/ws/wsutil" ) func connectionRouter() http.Handler { @@ -23,13 +24,13 @@ func connectionRouter() http.Handler { } func getConnections(w http.ResponseWriter, r *http.Request) { - if !websocket.IsWebSocketUpgrade(r) { + if !(r.Header.Get("Upgrade") == "websocket") { snapshot := statistic.DefaultManager.Snapshot() render.JSON(w, r, snapshot) return } - conn, err := upgrader.Upgrade(w, r, nil) + conn, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { return } @@ -55,7 +56,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) { return err } - return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) + return wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpText, buf.Bytes()) } if err := sendSnapshot(); err != nil { diff --git a/hub/route/ctxkeys.go b/hub/route/ctxkeys.go index 56370192..6883b208 100644 --- a/hub/route/ctxkeys.go +++ b/hub/route/ctxkeys.go @@ -10,5 +10,5 @@ var ( type contextKey string func (c contextKey) String() string { - return "clash context key " + string(c) + return "mihomo context key " + string(c) } diff --git a/hub/route/dns.go b/hub/route/dns.go index 2918b059..1762c947 100644 --- a/hub/route/dns.go +++ b/hub/route/dns.go @@ -5,7 +5,7 @@ import ( "math" "net/http" - "github.com/Dreamacro/clash/component/resolver" + "github.com/metacubex/mihomo/component/resolver" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/route/external.go b/hub/route/external.go new file mode 100644 index 00000000..d2f06358 --- /dev/null +++ b/hub/route/external.go @@ -0,0 +1,21 @@ +package route + +import "github.com/go-chi/chi/v5" + +type externalRouter func(r chi.Router) + +var externalRouters = make([]externalRouter, 0) + +func Register(route ...externalRouter) { + externalRouters = append(externalRouters, route...) +} + +func addExternalRouters(r chi.Router) { + if len(externalRouters) == 0 { + return + } + + for _, caller := range externalRouters { + caller(r) + } +} diff --git a/hub/route/groups.go b/hub/route/groups.go index c82207f0..18aabf74 100644 --- a/hub/route/groups.go +++ b/hub/route/groups.go @@ -2,17 +2,19 @@ package route import ( "context" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" "net/http" "strconv" "time" - "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/adapter/outboundgroup" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/tunnel" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + + "github.com/metacubex/mihomo/adapter" + "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/profile/cachefile" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/tunnel" ) func GroupRouter() http.Handler { @@ -63,6 +65,10 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) { URLTestGroup.ForceSet("") } + if proxy.(*adapter.Proxy).Type() != C.Selector { + cachefile.Cache().SetSelected(proxy.Name(), "") + } + query := r.URL.Query() url := query.Get("url") timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32) diff --git a/hub/route/provider.go b/hub/route/provider.go index c050a9f1..a8611a79 100644 --- a/hub/route/provider.go +++ b/hub/route/provider.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/tunnel" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/tunnel" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/route/proxies.go b/hub/route/proxies.go index c1e30b21..48359749 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -7,12 +7,12 @@ import ( "strconv" "time" - "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/adapter/outboundgroup" - "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/profile/cachefile" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/adapter" + "github.com/metacubex/mihomo/adapter/outboundgroup" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/profile/cachefile" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/tunnel" "github.com/go-chi/chi/v5" "github.com/go-chi/render" @@ -125,7 +125,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) defer cancel() - delay, err := proxy.URLTest(ctx, url, expectedStatus, C.ExtraHistory) + delay, err := proxy.URLTest(ctx, url, expectedStatus) if ctx.Err() != nil { render.Status(r, http.StatusGatewayTimeout) render.JSON(w, r, ErrRequestTimeout) diff --git a/hub/route/restart.go b/hub/route/restart.go index a907021f..49d7e517 100644 --- a/hub/route/restart.go +++ b/hub/route/restart.go @@ -8,8 +8,8 @@ import ( "runtime" "syscall" - "github.com/Dreamacro/clash/hub/executor" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/hub/executor" + "github.com/metacubex/mihomo/log" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/route/rules.go b/hub/route/rules.go index 51f8f01c..43d33299 100644 --- a/hub/route/rules.go +++ b/hub/route/rules.go @@ -1,10 +1,10 @@ package route import ( - "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/constant" "net/http" - "github.com/Dreamacro/clash/tunnel" + "github.com/metacubex/mihomo/tunnel" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/route/server.go b/hub/route/server.go index d2fecd05..8e7f225f 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -5,23 +5,25 @@ import ( "crypto/subtle" "crypto/tls" "encoding/json" + "net" "net/http" "runtime/debug" "strings" "time" - "github.com/Dreamacro/clash/adapter/inbound" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/metacubex/mihomo/adapter/inbound" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel/statistic" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/go-chi/render" - "github.com/gorilla/websocket" + "github.com/gobwas/ws" + "github.com/gobwas/ws/wsutil" ) var ( @@ -29,12 +31,6 @@ var ( serverAddr = "" uiPath = "" - - upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - } ) type Traffic struct { @@ -67,6 +63,7 @@ func Start(addr string, tlsAddr string, secret string, AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) + r.Use(setPrivateNetworkAccess) r.Use(corsM.Handler) if isDebug { r.Mount("/debug", func() http.Handler { @@ -97,6 +94,7 @@ func Start(addr string, tlsAddr string, secret string, r.Mount("/dns", dnsRouter()) r.Mount("/restart", restartRouter()) r.Mount("/upgrade", upgradeRouter()) + addExternalRouters(r) }) @@ -112,7 +110,7 @@ func Start(addr string, tlsAddr string, secret string, if len(tlsAddr) > 0 { go func() { - c, err := CN.ParseCert(certificat, privateKey) + c, err := CN.ParseCert(certificat, privateKey, C.Path) if err != nil { log.Errorln("External controller tls listen error: %s", err) return @@ -152,6 +150,15 @@ func Start(addr string, tlsAddr string, secret string, } +func setPrivateNetworkAccess(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { + w.Header().Add("Access-Control-Allow-Private-Network", "true") + } + next.ServeHTTP(w, r) + }) +} + func safeEuqal(a, b string) bool { aBuf := utils.ImmutableBytesFromString(a) bBuf := utils.ImmutableBytesFromString(b) @@ -166,7 +173,7 @@ func authentication(next http.Handler) http.Handler { } // Browser websocket not support custom header - if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" { + if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" { token := r.URL.Query().Get("token") if !safeEuqal(token, serverSecret) { render.Status(r, http.StatusUnauthorized) @@ -193,14 +200,14 @@ func authentication(next http.Handler) http.Handler { } func hello(w http.ResponseWriter, r *http.Request) { - render.JSON(w, r, render.M{"hello": "clash.meta"}) + render.JSON(w, r, render.M{"hello": "mihomo"}) } func traffic(w http.ResponseWriter, r *http.Request) { - var wsConn *websocket.Conn - if websocket.IsWebSocketUpgrade(r) { + var wsConn net.Conn + if r.Header.Get("Upgrade") == "websocket" { var err error - wsConn, err = upgrader.Upgrade(w, r, nil) + wsConn, _, _, err = ws.UpgradeHTTP(r, w) if err != nil { return } @@ -230,7 +237,7 @@ func traffic(w http.ResponseWriter, r *http.Request) { _, err = w.Write(buf.Bytes()) w.(http.Flusher).Flush() } else { - err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) } if err != nil { @@ -240,10 +247,10 @@ func traffic(w http.ResponseWriter, r *http.Request) { } func memory(w http.ResponseWriter, r *http.Request) { - var wsConn *websocket.Conn - if websocket.IsWebSocketUpgrade(r) { + var wsConn net.Conn + if r.Header.Get("Upgrade") == "websocket" { var err error - wsConn, err = upgrader.Upgrade(w, r, nil) + wsConn, _, _, err = ws.UpgradeHTTP(r, w) if err != nil { return } @@ -280,7 +287,7 @@ func memory(w http.ResponseWriter, r *http.Request) { _, err = w.Write(buf.Bytes()) w.(http.Flusher).Flush() } else { - err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) } if err != nil { @@ -293,6 +300,16 @@ type Log struct { Type string `json:"type"` Payload string `json:"payload"` } +type LogStructuredField struct { + Key string `json:"key"` + Value string `json:"value"` +} +type LogStructured struct { + Time string `json:"time"` + Level string `json:"level"` + Message string `json:"message"` + Fields []LogStructuredField `json:"fields"` +} func getLogs(w http.ResponseWriter, r *http.Request) { levelText := r.URL.Query().Get("level") @@ -300,6 +317,12 @@ func getLogs(w http.ResponseWriter, r *http.Request) { levelText = "info" } + formatText := r.URL.Query().Get("format") + isStructured := false + if formatText == "structured" { + isStructured = true + } + level, ok := log.LogLevelMapping[levelText] if !ok { render.Status(r, http.StatusBadRequest) @@ -307,10 +330,10 @@ func getLogs(w http.ResponseWriter, r *http.Request) { return } - var wsConn *websocket.Conn - if websocket.IsWebSocketUpgrade(r) { + var wsConn net.Conn + if r.Header.Get("Upgrade") == "websocket" { var err error - wsConn, err = upgrader.Upgrade(w, r, nil) + wsConn, _, _, err = ws.UpgradeHTTP(r, w) if err != nil { return } @@ -342,11 +365,26 @@ func getLogs(w http.ResponseWriter, r *http.Request) { } buf.Reset() - if err := json.NewEncoder(buf).Encode(Log{ - Type: logM.Type(), - Payload: logM.Payload, - }); err != nil { - break + if !isStructured { + if err := json.NewEncoder(buf).Encode(Log{ + Type: logM.Type(), + Payload: logM.Payload, + }); err != nil { + break + } + } else { + newLevel := logM.Type() + if newLevel == "warning" { + newLevel = "warn" + } + if err := json.NewEncoder(buf).Encode(LogStructured{ + Time: time.Now().Format(time.TimeOnly), + Level: newLevel, + Message: logM.Payload, + Fields: []LogStructuredField{}, + }); err != nil { + break + } } var err error @@ -354,7 +392,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) { _, err = w.Write(buf.Bytes()) w.(http.Flusher).Flush() } else { - err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes()) } if err != nil { diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index 7b486ee3..ea371798 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -6,9 +6,9 @@ import ( "net/http" "os" - "github.com/Dreamacro/clash/config" - "github.com/Dreamacro/clash/hub/updater" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/config" + "github.com/metacubex/mihomo/hub/updater" + "github.com/metacubex/mihomo/log" "github.com/go-chi/chi/v5" "github.com/go-chi/render" diff --git a/hub/updater/updater.go b/hub/updater/updater.go index 1a930c03..1967af42 100644 --- a/hub/updater/updater.go +++ b/hub/updater/updater.go @@ -15,15 +15,16 @@ import ( "sync" "time" - clashHttp "github.com/Dreamacro/clash/component/http" - "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + mihomoHttp "github.com/metacubex/mihomo/component/http" + "github.com/metacubex/mihomo/constant" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/klauspost/cpuid/v2" ) // modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/updater/updater.go -// Updater is the Clash.Meta updater. +// Updater is the mihomo updater. var ( goarm string gomips string @@ -41,8 +42,8 @@ var ( backupExeName string // 备份文件名 updateExeName string // 更新后的可执行文件 - baseURL string = "https://github.com/MetaCubeX/Clash.Meta/releases/download/Prerelease-Alpha/clash.meta" - versionURL string = "https://github.com/MetaCubeX/Clash.Meta/releases/download/Prerelease-Alpha/version.txt" + baseURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/mihomo" + versionURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt" packageURL string latestVersion string ) @@ -135,9 +136,9 @@ func prepare(exePath string) (err error) { backupDir = filepath.Join(workDir, "meta-backup") if runtime.GOOS == "windows" { - updateExeName = "clash.meta" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe" + updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe" } else { - updateExeName = "clash.meta" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible } log.Infoln("updateExeName: %s ", updateExeName) @@ -231,7 +232,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024 func downloadPackageFile() (err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return fmt.Errorf("http request failed: %w", err) } @@ -412,7 +413,7 @@ func copyFile(src, dst string) error { func getLatestVersion() (version string, err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - resp, err := clashHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) if err != nil { return "", fmt.Errorf("get Latest Version fail: %w", err) } diff --git a/listener/auth/auth.go b/listener/auth/auth.go index 70473114..46f552b8 100644 --- a/listener/auth/auth.go +++ b/listener/auth/auth.go @@ -1,7 +1,7 @@ package auth import ( - "github.com/Dreamacro/clash/component/auth" + "github.com/metacubex/mihomo/component/auth" ) var authenticator auth.Authenticator diff --git a/listener/autoredir/tcp.go b/listener/autoredir/tcp.go index c390d89a..2b21b087 100644 --- a/listener/autoredir/tcp.go +++ b/listener/autoredir/tcp.go @@ -4,11 +4,11 @@ import ( "net" "net/netip" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { @@ -43,7 +43,7 @@ func (l *Listener) SetLookupFunc(lookupFunc func(netip.AddrPort) (socks5.Addr, e l.lookupFunc = lookupFunc } -func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) { +func (l *Listener) handleRedir(conn net.Conn, tunnel C.Tunnel) { if l.lookupFunc == nil { log.Errorln("[Auto Redirect] lookup function is nil") return @@ -58,10 +58,10 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) { N.TCPKeepAlive(conn) - in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...) + tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, l.additions...)) } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-REDIR"), @@ -87,7 +87,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } continue } - go rl.handleRedir(c, in) + go rl.handleRedir(c, tunnel) } }() diff --git a/listener/config/hysteria2.go b/listener/config/hysteria2.go index 5520babc..898204c6 100644 --- a/listener/config/hysteria2.go +++ b/listener/config/hysteria2.go @@ -1,6 +1,10 @@ package config -import "encoding/json" +import ( + "github.com/metacubex/mihomo/listener/sing" + + "encoding/json" +) type Hysteria2Server struct { Enable bool `yaml:"enable" json:"enable"` @@ -17,6 +21,7 @@ type Hysteria2Server struct { IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"` Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"` CWND int `yaml:"cwnd" json:"cwnd,omitempty"` + MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` } func (h Hysteria2Server) String() string { diff --git a/listener/config/shadowsocks.go b/listener/config/shadowsocks.go index 60540bbd..c5c60f10 100644 --- a/listener/config/shadowsocks.go +++ b/listener/config/shadowsocks.go @@ -1,15 +1,18 @@ package config import ( + "github.com/metacubex/mihomo/listener/sing" + "encoding/json" ) type ShadowsocksServer struct { - Enable bool - Listen string - Password string - Cipher string - Udp bool + Enable bool + Listen string + Password string + Cipher string + Udp bool + MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` } func (t ShadowsocksServer) String() string { diff --git a/listener/config/tuic.go b/listener/config/tuic.go index 191cb59c..14a46809 100644 --- a/listener/config/tuic.go +++ b/listener/config/tuic.go @@ -1,6 +1,8 @@ package config import ( + "github.com/metacubex/mihomo/listener/sing" + "encoding/json" ) @@ -18,6 +20,7 @@ type TuicServer struct { MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"` MaxDatagramFrameSize int `yaml:"max-datagram-frame-size" json:"max-datagram-frame-size,omitempty"` CWND int `yaml:"cwnd" json:"cwnd,omitempty"` + MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` } func (t TuicServer) String() string { diff --git a/listener/config/tun.go b/listener/config/tun.go index 50f5cf7d..1772c6f5 100644 --- a/listener/config/tun.go +++ b/listener/config/tun.go @@ -1,72 +1,19 @@ package config import ( - "encoding/json" "net/netip" - C "github.com/Dreamacro/clash/constant" - - "gopkg.in/yaml.v3" + C "github.com/metacubex/mihomo/constant" ) -type ListenPrefix netip.Prefix - -func (p ListenPrefix) MarshalJSON() ([]byte, error) { - prefix := netip.Prefix(p) - if !prefix.IsValid() { - return json.Marshal(nil) - } - return json.Marshal(prefix.String()) -} - -func (p ListenPrefix) MarshalYAML() (interface{}, error) { - prefix := netip.Prefix(p) - if !prefix.IsValid() { - return nil, nil - } - return prefix.String(), nil -} - -func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - prefix, err := netip.ParsePrefix(value) - if err != nil { - return err - } - *p = ListenPrefix(prefix) - return nil -} - -func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error { - var value string - err := node.Decode(&value) - if err != nil { - return err - } - prefix, err := netip.ParsePrefix(value) - if err != nil { - return err - } - *p = ListenPrefix(prefix) - return nil -} - -func (p ListenPrefix) Build() netip.Prefix { - return netip.Prefix(p) -} - -func StringSliceToListenPrefixSlice(ss []string) ([]ListenPrefix, error) { - lps := make([]ListenPrefix, 0, len(ss)) +func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) { + lps := make([]netip.Prefix, 0, len(ss)) for _, s := range ss { prefix, err := netip.ParsePrefix(s) if err != nil { return nil, err } - lps = append(lps, ListenPrefix(prefix)) + lps = append(lps, prefix) } return lps, nil } @@ -80,20 +27,26 @@ type Tun struct { AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` RedirectToTun []string `yaml:"-" json:"-"` - MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` - Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` - StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` - Inet4RouteAddress []ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` - Inet6RouteAddress []ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` - IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` - IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` - ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` - ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` - IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` - IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` - ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` - EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` - UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` - FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` + Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` + Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` + Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` + Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` + Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` + Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` + FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` } diff --git a/listener/config/vmess.go b/listener/config/vmess.go index cc49433e..810d6bc1 100644 --- a/listener/config/vmess.go +++ b/listener/config/vmess.go @@ -1,6 +1,8 @@ package config import ( + "github.com/metacubex/mihomo/listener/sing" + "encoding/json" ) @@ -11,9 +13,13 @@ type VmessUser struct { } type VmessServer struct { - Enable bool - Listen string - Users []VmessUser + Enable bool + Listen string + Users []VmessUser + WsPath string + Certificate string + PrivateKey string + MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` } func (t VmessServer) String() string { diff --git a/listener/http/client.go b/listener/http/client.go index 15c21f91..c35cadad 100644 --- a/listener/http/client.go +++ b/listener/http/client.go @@ -7,12 +7,12 @@ import ( "net/http" "time" - "github.com/Dreamacro/clash/adapter/inbound" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) -func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Addition) *http.Client { +func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client { return &http.Client{ Transport: &http.Transport{ // from http.DefaultTransport @@ -32,7 +32,7 @@ func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Ad left, right := net.Pipe() - in <- inbound.NewHTTP(dstAddr, source, right, additions...) + go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, srcConn, right, additions...)) return left, nil }, diff --git a/listener/http/patch_android.go b/listener/http/patch_android.go new file mode 100644 index 00000000..b1b8bfc7 --- /dev/null +++ b/listener/http/patch_android.go @@ -0,0 +1,9 @@ +//go:build android && cmfa + +package http + +import "net" + +func (l *Listener) Listener() net.Listener { + return l.listener +} diff --git a/listener/http/proxy.go b/listener/http/proxy.go index a95f7195..4822eabc 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -1,30 +1,45 @@ package http import ( + "context" "fmt" + "io" "net" "net/http" "strings" + "sync" + _ "unsafe" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/cache" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - authStore "github.com/Dreamacro/clash/listener/auth" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/lru" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + authStore "github.com/metacubex/mihomo/listener/auth" + "github.com/metacubex/mihomo/log" ) -func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) { - client := newClient(c.RemoteAddr(), in, additions...) +//go:linkname registerOnHitEOF net/http.registerOnHitEOF +func registerOnHitEOF(rc io.ReadCloser, fn func()) + +//go:linkname requestBodyRemains net/http.requestBodyRemains +func requestBodyRemains(rc io.ReadCloser) bool + +func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { + client := newClient(c, tunnel, additions...) defer client.CloseIdleConnections() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + peekMutex := sync.Mutex{} conn := N.NewBufferedConn(c) keepAlive := true - trusted := cache == nil // disable authenticate if cache is nil + trusted := cache == nil // disable authenticate if lru is nil for keepAlive { + peekMutex.Lock() request, err := ReadRequest(conn.Reader()) + peekMutex.Unlock() if err != nil { break } @@ -48,7 +63,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin break // close connection } - in <- inbound.NewHTTPS(request, conn, additions...) + tunnel.HandleTCPConn(inbound.NewHTTPS(request, conn, additions...)) return // hijack connection } @@ -61,7 +76,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin request.RequestURI = "" if isUpgradeRequest(request) { - handleUpgrade(conn, request, in, additions...) + handleUpgrade(conn, request, tunnel, additions...) return // hijack connection } @@ -72,6 +87,23 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin if request.URL.Scheme == "" || request.URL.Host == "" { resp = responseWith(request, http.StatusBadRequest) } else { + request = request.WithContext(ctx) + + startBackgroundRead := func() { + go func() { + peekMutex.Lock() + defer peekMutex.Unlock() + _, err := conn.Peek(1) + if err != nil { + cancel() + } + }() + } + if requestBodyRemains(request.Body) { + registerOnHitEOF(request.Body, startBackgroundRead) + } else { + startBackgroundRead() + } resp, err = client.Do(request) if err != nil { resp = responseWith(request, http.StatusBadGateway) @@ -98,8 +130,11 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin _ = conn.Close() } -func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response { +func authenticate(request *http.Request, cache *lru.LruCache[string, bool]) *http.Response { authenticator := authStore.Authenticator() + if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { + authenticator = nil + } if authenticator != nil { credential := parseBasicProxyAuthorization(request) if credential == "" { diff --git a/listener/http/server.go b/listener/http/server.go index 8819af11..3ff1679d 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -3,9 +3,10 @@ package http import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/lru" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" ) type Listener struct { @@ -30,11 +31,11 @@ func (l *Listener) Close() error { return l.listener.Close() } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { - return NewWithAuthenticate(addr, in, true, additions...) +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { + return NewWithAuthenticate(addr, tunnel, true, additions...) } -func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool, additions ...inbound.Addition) (*Listener, error) { +func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-HTTP"), @@ -47,9 +48,9 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool return nil, err } - var c *cache.LruCache[string, bool] + var c *lru.LruCache[string, bool] if authenticate { - c = cache.New[string, bool](cache.WithAge[string, bool](30)) + c = lru.New[string, bool](lru.WithAge[string, bool](30)) } hl := &Listener{ @@ -65,7 +66,18 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool } continue } - go HandleConn(conn, in, c, additions...) + if features.CMFA { + if t, ok := conn.(*net.TCPConn); ok { + t.SetKeepAlive(false) + } + } + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { + _ = conn.Close() + continue + } + } + go HandleConn(conn, tunnel, c, additions...) } }() diff --git a/listener/http/upgrade.go b/listener/http/upgrade.go index 90e28f0a..8a6291d1 100644 --- a/listener/http/upgrade.go +++ b/listener/http/upgrade.go @@ -7,10 +7,10 @@ import ( "net/http" "strings" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) func isUpgradeRequest(req *http.Request) bool { @@ -25,7 +25,7 @@ func isUpgradeRequest(req *http.Request) bool { return false } -func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext, additions ...inbound.Addition) { +func handleUpgrade(conn net.Conn, request *http.Request, tunnel C.Tunnel, additions ...inbound.Addition) { defer conn.Close() removeProxyHeaders(request.Header) @@ -43,7 +43,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext left, right := net.Pipe() - in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right, additions...) + go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, conn, right, additions...)) var bufferedLeft *N.BufferedConn if request.TLS != nil { diff --git a/listener/inbound/base.go b/listener/inbound/base.go index b132ac6c..e8f860a0 100644 --- a/listener/inbound/base.go +++ b/listener/inbound/base.go @@ -6,8 +6,8 @@ import ( "net/netip" "strconv" - "github.com/Dreamacro/clash/adapter/inbound" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/adapter/inbound" + C "github.com/metacubex/mihomo/constant" ) type Base struct { @@ -61,7 +61,7 @@ func (b *Base) RawAddress() string { } // Listen implements constant.InboundListener -func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (*Base) Listen(tunnel C.Tunnel) error { return nil } diff --git a/listener/inbound/http.go b/listener/inbound/http.go index a93f9684..f5301f46 100644 --- a/listener/inbound/http.go +++ b/listener/inbound/http.go @@ -1,9 +1,9 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/http" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/http" + "github.com/metacubex/mihomo/log" ) type HTTPOption struct { @@ -42,9 +42,9 @@ func (h *HTTP) Address() string { } // Listen implements constant.InboundListener -func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (h *HTTP) Listen(tunnel C.Tunnel) error { var err error - h.l, err = http.New(h.RawAddress(), tcpIn, h.Additions()...) + h.l, err = http.New(h.RawAddress(), tunnel, h.Additions()...) if err != nil { return err } diff --git a/listener/inbound/hysteria2.go b/listener/inbound/hysteria2.go index 430d0e68..acd5f9a8 100644 --- a/listener/inbound/hysteria2.go +++ b/listener/inbound/hysteria2.go @@ -1,10 +1,10 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing_hysteria2" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing_hysteria2" + "github.com/metacubex/mihomo/log" ) type Hysteria2Option struct { @@ -21,6 +21,7 @@ type Hysteria2Option struct { IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"` Masquerade string `inbound:"masquerade,omitempty"` CWND int `inbound:"cwnd,omitempty"` + MuxOption MuxOption `inbound:"mux-option,omitempty"` } func (o Hysteria2Option) Equal(config C.InboundConfig) bool { @@ -57,6 +58,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) { IgnoreClientBandwidth: options.IgnoreClientBandwidth, Masquerade: options.Masquerade, CWND: options.CWND, + MuxOption: options.MuxOption.Build(), }, }, nil } @@ -77,9 +79,9 @@ func (t *Hysteria2) Address() string { } // Listen implements constant.InboundListener -func (t *Hysteria2) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (t *Hysteria2) Listen(tunnel C.Tunnel) error { var err error - t.l, err = sing_hysteria2.New(t.ts, tcpIn, udpIn, t.Additions()...) + t.l, err = sing_hysteria2.New(t.ts, tunnel, t.Additions()...) if err != nil { return err } diff --git a/listener/inbound/mixed.go b/listener/inbound/mixed.go index dbba264c..fc643821 100644 --- a/listener/inbound/mixed.go +++ b/listener/inbound/mixed.go @@ -3,11 +3,11 @@ package inbound import ( "fmt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" - "github.com/Dreamacro/clash/listener/mixed" - "github.com/Dreamacro/clash/listener/socks" + "github.com/metacubex/mihomo/listener/mixed" + "github.com/metacubex/mihomo/listener/socks" ) type MixedOption struct { @@ -50,14 +50,14 @@ func (m *Mixed) Address() string { } // Listen implements constant.InboundListener -func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (m *Mixed) Listen(tunnel C.Tunnel) error { var err error - m.l, err = mixed.New(m.RawAddress(), tcpIn, m.Additions()...) + m.l, err = mixed.New(m.RawAddress(), tunnel, m.Additions()...) if err != nil { return err } if m.udp { - m.lUDP, err = socks.NewUDP(m.RawAddress(), udpIn, m.Additions()...) + m.lUDP, err = socks.NewUDP(m.RawAddress(), tunnel, m.Additions()...) if err != nil { return err } diff --git a/listener/inbound/mux.go b/listener/inbound/mux.go new file mode 100644 index 00000000..c4068e10 --- /dev/null +++ b/listener/inbound/mux.go @@ -0,0 +1,25 @@ +package inbound + +import "github.com/metacubex/mihomo/listener/sing" + +type MuxOption struct { + Padding bool `inbound:"padding,omitempty"` + Brutal BrutalOptions `inbound:"brutal,omitempty"` +} + +type BrutalOptions struct { + Enabled bool `inbound:"enabled,omitempty"` + Up string `inbound:"up,omitempty"` + Down string `inbound:"down,omitempty"` +} + +func (m MuxOption) Build() sing.MuxOption { + return sing.MuxOption{ + Padding: m.Padding, + Brutal: sing.BrutalOptions{ + Enabled: m.Brutal.Enabled, + Up: m.Brutal.Up, + Down: m.Brutal.Down, + }, + } +} diff --git a/listener/inbound/redir.go b/listener/inbound/redir.go index 4b88d895..ee090ade 100644 --- a/listener/inbound/redir.go +++ b/listener/inbound/redir.go @@ -1,9 +1,9 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/redir" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/redir" + "github.com/metacubex/mihomo/log" ) type RedirOption struct { @@ -42,9 +42,9 @@ func (r *Redir) Address() string { } // Listen implements constant.InboundListener -func (r *Redir) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (r *Redir) Listen(tunnel C.Tunnel) error { var err error - r.l, err = redir.New(r.RawAddress(), tcpIn, r.Additions()...) + r.l, err = redir.New(r.RawAddress(), tunnel, r.Additions()...) if err != nil { return err } diff --git a/listener/inbound/shadowsocks.go b/listener/inbound/shadowsocks.go index 4659f4d7..240e6419 100644 --- a/listener/inbound/shadowsocks.go +++ b/listener/inbound/shadowsocks.go @@ -1,17 +1,18 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing_shadowsocks" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing_shadowsocks" + "github.com/metacubex/mihomo/log" ) type ShadowSocksOption struct { BaseOption - Password string `inbound:"password"` - Cipher string `inbound:"cipher"` - UDP bool `inbound:"udp,omitempty"` + Password string `inbound:"password"` + Cipher string `inbound:"cipher"` + UDP bool `inbound:"udp,omitempty"` + MuxOption MuxOption `inbound:"mux-option,omitempty"` } func (o ShadowSocksOption) Equal(config C.InboundConfig) bool { @@ -34,11 +35,12 @@ func NewShadowSocks(options *ShadowSocksOption) (*ShadowSocks, error) { Base: base, config: options, ss: LC.ShadowsocksServer{ - Enable: true, - Listen: base.RawAddress(), - Password: options.Password, - Cipher: options.Cipher, - Udp: options.UDP, + Enable: true, + Listen: base.RawAddress(), + Password: options.Password, + Cipher: options.Cipher, + Udp: options.UDP, + MuxOption: options.MuxOption.Build(), }, }, nil } @@ -59,9 +61,9 @@ func (s *ShadowSocks) Address() string { } // Listen implements constant.InboundListener -func (s *ShadowSocks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (s *ShadowSocks) Listen(tunnel C.Tunnel) error { var err error - s.l, err = sing_shadowsocks.New(s.ss, tcpIn, udpIn, s.Additions()...) + s.l, err = sing_shadowsocks.New(s.ss, tunnel, s.Additions()...) if err != nil { return err } diff --git a/listener/inbound/socks.go b/listener/inbound/socks.go index aac2ee23..7e10d93a 100644 --- a/listener/inbound/socks.go +++ b/listener/inbound/socks.go @@ -2,9 +2,9 @@ package inbound import ( "fmt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/socks" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/socks" + "github.com/metacubex/mihomo/log" ) type SocksOption struct { @@ -68,13 +68,13 @@ func (s *Socks) Address() string { } // Listen implements constant.InboundListener -func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (s *Socks) Listen(tunnel C.Tunnel) error { var err error - if s.stl, err = socks.New(s.RawAddress(), tcpIn, s.Additions()...); err != nil { + if s.stl, err = socks.New(s.RawAddress(), tunnel, s.Additions()...); err != nil { return err } if s.udp { - if s.sul, err = socks.NewUDP(s.RawAddress(), udpIn, s.Additions()...); err != nil { + if s.sul, err = socks.NewUDP(s.RawAddress(), tunnel, s.Additions()...); err != nil { return err } } diff --git a/listener/inbound/tproxy.go b/listener/inbound/tproxy.go index 00cd0849..acc8cb5e 100644 --- a/listener/inbound/tproxy.go +++ b/listener/inbound/tproxy.go @@ -3,9 +3,9 @@ package inbound import ( "fmt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/tproxy" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/tproxy" + "github.com/metacubex/mihomo/log" ) type TProxyOption struct { @@ -49,14 +49,14 @@ func (t *TProxy) Address() string { } // Listen implements constant.InboundListener -func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (t *TProxy) Listen(tunnel C.Tunnel) error { var err error - t.lTCP, err = tproxy.New(t.RawAddress(), tcpIn, t.Additions()...) + t.lTCP, err = tproxy.New(t.RawAddress(), tunnel, t.Additions()...) if err != nil { return err } if t.udp { - t.lUDP, err = tproxy.NewUDP(t.RawAddress(), udpIn, natTable, t.Additions()...) + t.lUDP, err = tproxy.NewUDP(t.RawAddress(), tunnel, t.Additions()...) if err != nil { return err } diff --git a/listener/inbound/tuic.go b/listener/inbound/tuic.go index bf448d31..562228ee 100644 --- a/listener/inbound/tuic.go +++ b/listener/inbound/tuic.go @@ -1,10 +1,10 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/tuic" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/tuic" + "github.com/metacubex/mihomo/log" ) type TuicOption struct { @@ -19,6 +19,7 @@ type TuicOption struct { ALPN []string `inbound:"alpn,omitempty"` MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"` CWND int `inbound:"cwnd,omitempty"` + MuxOption MuxOption `inbound:"mux-option,omitempty"` } func (o TuicOption) Equal(config C.InboundConfig) bool { @@ -53,6 +54,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) { ALPN: options.ALPN, MaxUdpRelayPacketSize: options.MaxUdpRelayPacketSize, CWND: options.CWND, + MuxOption: options.MuxOption.Build(), }, }, nil } @@ -73,9 +75,9 @@ func (t *Tuic) Address() string { } // Listen implements constant.InboundListener -func (t *Tuic) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (t *Tuic) Listen(tunnel C.Tunnel) error { var err error - t.l, err = tuic.New(t.ts, tcpIn, udpIn, t.Additions()...) + t.l, err = tuic.New(t.ts, tunnel, t.Additions()...) if err != nil { return err } diff --git a/listener/inbound/tun.go b/listener/inbound/tun.go index eb16d2dd..a1fdebfa 100644 --- a/listener/inbound/tun.go +++ b/listener/inbound/tun.go @@ -4,10 +4,10 @@ import ( "errors" "strings" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing_tun" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing_tun" + "github.com/metacubex/mihomo/log" ) type TunOption struct { @@ -18,22 +18,28 @@ type TunOption struct { AutoRoute bool `inbound:"auto-route,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` - MTU uint32 `inbound:"mtu,omitempty"` - Inet4Address []string `inbound:"inet4_address,omitempty"` - Inet6Address []string `inbound:"inet6_address,omitempty"` - StrictRoute bool `inbound:"strict_route,omitempty"` - Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` - Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` - IncludeUID []uint32 `inbound:"include_uid,omitempty"` - IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` - ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` - ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` - IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` - IncludePackage []string `inbound:"include_package,omitempty"` - ExcludePackage []string `inbound:"exclude_package,omitempty"` - EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` - UDPTimeout int64 `inbound:"udp_timeout,omitempty"` - FileDescriptor int `inbound:"file-descriptor,omitempty"` + MTU uint32 `inbound:"mtu,omitempty"` + GSO bool `inbound:"gso,omitempty"` + GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` + Inet4Address []string `inbound:"inet4_address,omitempty"` + Inet6Address []string `inbound:"inet6_address,omitempty"` + StrictRoute bool `inbound:"strict_route,omitempty"` + Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` + Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` + Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"` + Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"` + IncludeInterface []string `inbound:"include-interface,omitempty"` + ExcludeInterface []string `inbound:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID []uint32 `inbound:"include_uid,omitempty"` + IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` + ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` + ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` + IncludePackage []string `inbound:"include_package,omitempty"` + ExcludePackage []string `inbound:"exclude_package,omitempty"` + EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `inbound:"udp_timeout,omitempty"` + FileDescriptor int `inbound:"file-descriptor,omitempty"` } func (o TunOption) Equal(config C.InboundConfig) bool { @@ -56,19 +62,27 @@ func NewTun(options *TunOption) (*Tun, error) { if !exist { return nil, errors.New("invalid tun stack") } - inet4Address, err := LC.StringSliceToListenPrefixSlice(options.Inet4Address) + inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address) if err != nil { return nil, err } - inet6Address, err := LC.StringSliceToListenPrefixSlice(options.Inet6Address) + inet6Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet6Address) if err != nil { return nil, err } - inet4RouteAddress, err := LC.StringSliceToListenPrefixSlice(options.Inet4RouteAddress) + inet4RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteAddress) if err != nil { return nil, err } - inet6RouteAddress, err := LC.StringSliceToListenPrefixSlice(options.Inet6RouteAddress) + inet6RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteAddress) + if err != nil { + return nil, err + } + inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress) + if err != nil { + return nil, err + } + inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress) if err != nil { return nil, err } @@ -76,28 +90,34 @@ func NewTun(options *TunOption) (*Tun, error) { Base: base, config: options, tun: LC.Tun{ - Enable: true, - Device: options.Device, - Stack: stack, - DNSHijack: options.DNSHijack, - AutoRoute: options.AutoRoute, - AutoDetectInterface: options.AutoDetectInterface, - MTU: options.MTU, - Inet4Address: inet4Address, - Inet6Address: inet6Address, - StrictRoute: options.StrictRoute, - Inet4RouteAddress: inet4RouteAddress, - Inet6RouteAddress: inet6RouteAddress, - IncludeUID: options.IncludeUID, - IncludeUIDRange: options.IncludeUIDRange, - ExcludeUID: options.ExcludeUID, - ExcludeUIDRange: options.ExcludeUIDRange, - IncludeAndroidUser: options.IncludeAndroidUser, - IncludePackage: options.IncludePackage, - ExcludePackage: options.ExcludePackage, - EndpointIndependentNat: options.EndpointIndependentNat, - UDPTimeout: options.UDPTimeout, - FileDescriptor: options.FileDescriptor, + Enable: true, + Device: options.Device, + Stack: stack, + DNSHijack: options.DNSHijack, + AutoRoute: options.AutoRoute, + AutoDetectInterface: options.AutoDetectInterface, + MTU: options.MTU, + GSO: options.GSO, + GSOMaxSize: options.GSOMaxSize, + Inet4Address: inet4Address, + Inet6Address: inet6Address, + StrictRoute: options.StrictRoute, + Inet4RouteAddress: inet4RouteAddress, + Inet6RouteAddress: inet6RouteAddress, + Inet4RouteExcludeAddress: inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: inet6RouteExcludeAddress, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, + IncludeUID: options.IncludeUID, + IncludeUIDRange: options.IncludeUIDRange, + ExcludeUID: options.ExcludeUID, + ExcludeUIDRange: options.ExcludeUIDRange, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + EndpointIndependentNat: options.EndpointIndependentNat, + UDPTimeout: options.UDPTimeout, + FileDescriptor: options.FileDescriptor, }, }, nil } @@ -113,9 +133,9 @@ func (t *Tun) Address() string { } // Listen implements constant.InboundListener -func (t *Tun) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (t *Tun) Listen(tunnel C.Tunnel) error { var err error - t.l, err = sing_tun.New(t.tun, tcpIn, udpIn, t.Additions()...) + t.l, err = sing_tun.New(t.tun, tunnel, t.Additions()...) if err != nil { return err } diff --git a/listener/inbound/tunnel.go b/listener/inbound/tunnel.go index 41d024ef..2dfaac74 100644 --- a/listener/inbound/tunnel.go +++ b/listener/inbound/tunnel.go @@ -3,9 +3,9 @@ package inbound import ( "fmt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/tunnel" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LT "github.com/metacubex/mihomo/listener/tunnel" + "github.com/metacubex/mihomo/log" ) type TunnelOption struct { @@ -21,8 +21,8 @@ func (o TunnelOption) Equal(config C.InboundConfig) bool { type Tunnel struct { *Base config *TunnelOption - ttl *tunnel.Listener - tul *tunnel.PacketConn + ttl *LT.Listener + tul *LT.PacketConn } func NewTunnel(options *TunnelOption) (*Tunnel, error) { @@ -74,16 +74,16 @@ func (t *Tunnel) Address() string { } // Listen implements constant.InboundListener -func (t *Tunnel) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (t *Tunnel) Listen(tunnel C.Tunnel) error { var err error for _, network := range t.config.Network { switch network { case "tcp": - if t.ttl, err = tunnel.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tcpIn, t.Additions()...); err != nil { + if t.ttl, err = LT.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil { return err } case "udp": - if t.tul, err = tunnel.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, udpIn, t.Additions()...); err != nil { + if t.tul, err = LT.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil { return err } default: diff --git a/listener/inbound/vmess.go b/listener/inbound/vmess.go index 70e840a5..226a54d5 100644 --- a/listener/inbound/vmess.go +++ b/listener/inbound/vmess.go @@ -1,15 +1,19 @@ package inbound import ( - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing_vmess" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing_vmess" + "github.com/metacubex/mihomo/log" ) type VmessOption struct { BaseOption - Users []VmessUser `inbound:"users"` + Users []VmessUser `inbound:"users"` + WsPath string `inbound:"ws-path,omitempty"` + Certificate string `inbound:"certificate,omitempty"` + PrivateKey string `inbound:"private-key,omitempty"` + MuxOption MuxOption `inbound:"mux-option,omitempty"` } type VmessUser struct { @@ -46,9 +50,13 @@ func NewVmess(options *VmessOption) (*Vmess, error) { Base: base, config: options, vs: LC.VmessServer{ - Enable: true, - Listen: base.RawAddress(), - Users: users, + Enable: true, + Listen: base.RawAddress(), + Users: users, + WsPath: options.WsPath, + Certificate: options.Certificate, + PrivateKey: options.PrivateKey, + MuxOption: options.MuxOption.Build(), }, }, nil } @@ -69,7 +77,7 @@ func (v *Vmess) Address() string { } // Listen implements constant.InboundListener -func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { +func (v *Vmess) Listen(tunnel C.Tunnel) error { var err error users := make([]LC.VmessUser, len(v.config.Users)) for i, v := range v.config.Users { @@ -79,7 +87,7 @@ func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, AlterID: v.AlterID, } } - v.l, err = sing_vmess.New(v.vs, tcpIn, udpIn, v.Additions()...) + v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...) if err != nil { return err } diff --git a/listener/inner/tcp.go b/listener/inner/tcp.go index 9ba87e2f..373fd2b4 100644 --- a/listener/inner/tcp.go +++ b/listener/inner/tcp.go @@ -3,24 +3,41 @@ package inner import ( "errors" "net" + "net/netip" + "strconv" - "github.com/Dreamacro/clash/adapter/inbound" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) -var tcpIn chan<- C.ConnContext +var tunnel C.Tunnel -func New(in chan<- C.ConnContext) { - tcpIn = in +func New(t C.Tunnel) { + tunnel = t } func HandleTcp(address string) (conn net.Conn, err error) { - if tcpIn == nil { + if tunnel == nil { return nil, errors.New("tcp uninitialized") } // executor Parsed conn1, conn2 := net.Pipe() - context := inbound.NewInner(conn2, address) - tcpIn <- context + + metadata := &C.Metadata{} + metadata.NetWork = C.TCP + metadata.Type = C.INNER + metadata.DNSMode = C.DNSNormal + metadata.Process = C.MihomoName + if h, port, err := net.SplitHostPort(address); err == nil { + if port, err := strconv.ParseUint(port, 10, 16); err == nil { + metadata.DstPort = uint16(port) + } + if ip, err := netip.ParseAddr(h); err == nil { + metadata.DstIP = ip + } else { + metadata.Host = h + } + } + + go tunnel.HandleTCPConn(conn2, metadata) return conn1, nil } diff --git a/listener/listener.go b/listener/listener.go index b1d59d49..ac602971 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -9,22 +9,22 @@ import ( "strings" "sync" - "github.com/Dreamacro/clash/component/ebpf" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/autoredir" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/http" - "github.com/Dreamacro/clash/listener/mixed" - "github.com/Dreamacro/clash/listener/redir" - embedSS "github.com/Dreamacro/clash/listener/shadowsocks" - "github.com/Dreamacro/clash/listener/sing_shadowsocks" - "github.com/Dreamacro/clash/listener/sing_tun" - "github.com/Dreamacro/clash/listener/sing_vmess" - "github.com/Dreamacro/clash/listener/socks" - "github.com/Dreamacro/clash/listener/tproxy" - "github.com/Dreamacro/clash/listener/tuic" - "github.com/Dreamacro/clash/listener/tunnel" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/ebpf" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/autoredir" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/http" + "github.com/metacubex/mihomo/listener/mixed" + "github.com/metacubex/mihomo/listener/redir" + embedSS "github.com/metacubex/mihomo/listener/shadowsocks" + "github.com/metacubex/mihomo/listener/sing_shadowsocks" + "github.com/metacubex/mihomo/listener/sing_tun" + "github.com/metacubex/mihomo/listener/sing_vmess" + "github.com/metacubex/mihomo/listener/socks" + "github.com/metacubex/mihomo/listener/tproxy" + "github.com/metacubex/mihomo/listener/tuic" + LT "github.com/metacubex/mihomo/listener/tunnel" + "github.com/metacubex/mihomo/log" "github.com/samber/lo" ) @@ -42,8 +42,8 @@ var ( tproxyUDPListener *tproxy.UDPListener mixedListener *mixed.Listener mixedUDPLister *socks.UDPListener - tunnelTCPListeners = map[string]*tunnel.Listener{} - tunnelUDPListeners = map[string]*tunnel.PacketConn{} + tunnelTCPListeners = map[string]*LT.Listener{} + tunnelUDPListeners = map[string]*LT.PacketConn{} inboundListeners = map[string]C.InboundListener{} tunLister *sing_tun.Listener shadowSocksListener C.MultiAddrListener @@ -112,7 +112,7 @@ func SetBindAddress(host string) { bindAddress = host } -func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { +func ReCreateHTTP(port int, tunnel C.Tunnel) { httpMux.Lock() defer httpMux.Unlock() @@ -137,7 +137,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { return } - httpListener, err = http.New(addr, tcpIn) + httpListener, err = http.New(addr, tunnel) if err != nil { log.Errorln("Start HTTP server error: %s", err.Error()) return @@ -146,7 +146,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { log.Infoln("HTTP proxy listening at: %s", httpListener.Address()) } -func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateSocks(port int, tunnel C.Tunnel) { socksMux.Lock() defer socksMux.Unlock() @@ -188,12 +188,12 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd return } - tcpListener, err := socks.New(addr, tcpIn) + tcpListener, err := socks.New(addr, tunnel) if err != nil { return } - udpListener, err := socks.NewUDP(addr, udpIn) + udpListener, err := socks.NewUDP(addr, tunnel) if err != nil { tcpListener.Close() return @@ -205,7 +205,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, natTable C.NatTable) { +func ReCreateRedir(port int, tunnel C.Tunnel) { redirMux.Lock() defer redirMux.Unlock() @@ -238,12 +238,12 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd return } - redirListener, err = redir.New(addr, tcpIn) + redirListener, err = redir.New(addr, tunnel) if err != nil { return } - redirUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) + redirUDPListener, err = tproxy.NewUDP(addr, tunnel) if err != nil { log.Warnln("Failed to start Redir UDP Listener: %s", err) } @@ -251,7 +251,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd log.Infoln("Redirect proxy listening at: %s", redirListener.Address()) } -func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateShadowSocks(shadowSocksConfig string, tunnel C.Tunnel) { ssMux.Lock() defer ssMux.Unlock() @@ -292,7 +292,7 @@ func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, u return } - listener, err := sing_shadowsocks.New(ssConfig, tcpIn, udpIn) + listener, err := sing_shadowsocks.New(ssConfig, tunnel) if err != nil { return } @@ -305,7 +305,7 @@ func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, u return } -func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateVmess(vmessConfig string, tunnel C.Tunnel) { vmessMux.Lock() defer vmessMux.Unlock() @@ -344,7 +344,7 @@ func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- return } - listener, err := sing_vmess.New(vsConfig, tcpIn, udpIn) + listener, err := sing_vmess.New(vsConfig, tunnel) if err != nil { return } @@ -357,7 +357,7 @@ func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- return } -func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateTuic(config LC.TuicServer, tunnel C.Tunnel) { tuicMux.Lock() defer func() { LastTuicConf = config @@ -389,7 +389,7 @@ func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- return } - listener, err := tuic.New(config, tcpIn, udpIn) + listener, err := tuic.New(config, tunnel) if err != nil { return } @@ -402,7 +402,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, natTable C.NatTable) { +func ReCreateTProxy(port int, tunnel C.Tunnel) { tproxyMux.Lock() defer tproxyMux.Unlock() @@ -435,12 +435,12 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketA return } - tproxyListener, err = tproxy.New(addr, tcpIn) + tproxyListener, err = tproxy.New(addr, tunnel) if err != nil { return } - tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) + tproxyUDPListener, err = tproxy.NewUDP(addr, tunnel) if err != nil { log.Warnln("Failed to start TProxy UDP Listener: %s", err) } @@ -448,7 +448,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketA log.Infoln("TProxy server listening at: %s", tproxyListener.Address()) } -func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateMixed(port int, tunnel C.Tunnel) { mixedMux.Lock() defer mixedMux.Unlock() @@ -489,12 +489,12 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd return } - mixedListener, err = mixed.New(addr, tcpIn) + mixedListener, err = mixed.New(addr, tunnel) if err != nil { return } - mixedUDPLister, err = socks.NewUDP(addr, udpIn) + mixedUDPLister, err = socks.NewUDP(addr, tunnel) if err != nil { mixedListener.Close() return @@ -503,7 +503,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address()) } -func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateTun(tunConf LC.Tun, tunnel C.Tunnel) { tunMux.Lock() defer func() { LastTunConf = tunConf @@ -531,7 +531,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack return } - lister, err := sing_tun.New(tunConf, tcpIn, udpIn) + lister, err := sing_tun.New(tunConf, tunnel) if err != nil { return } @@ -573,7 +573,7 @@ func ReCreateRedirToTun(ifaceNames []string) { log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs()) } -func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- C.PacketAdapter) { +func ReCreateAutoRedir(ifaceNames []string, tunnel C.Tunnel) { autoRedirMux.Lock() defer autoRedirMux.Unlock() @@ -614,7 +614,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- addr := genAddr("*", C.TcpAutoRedirPort, true) - autoRedirListener, err = autoredir.New(addr, tcpIn) + autoRedirListener, err = autoredir.New(addr, tunnel) if err != nil { return } @@ -629,7 +629,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs()) } -func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func PatchTunnel(tunnels []LC.Tunnel, tunnel C.Tunnel) { tunnelMux.Lock() defer tunnelMux.Unlock() @@ -699,7 +699,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C for _, elm := range needCreate { key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy) if elm.network == "tcp" { - l, err := tunnel.New(elm.addr, elm.target, elm.proxy, tcpIn) + l, err := LT.New(elm.addr, elm.target, elm.proxy, tunnel) if err != nil { log.Errorln("Start tunnel %s error: %s", elm.target, err.Error()) continue @@ -707,7 +707,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C tunnelTCPListeners[key] = l log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address()) } else { - l, err := tunnel.NewUDP(elm.addr, elm.target, elm.proxy, udpIn) + l, err := LT.NewUDP(elm.addr, elm.target, elm.proxy, tunnel) if err != nil { log.Errorln("Start tunnel %s error: %s", elm.target, err.Error()) continue @@ -718,7 +718,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, natTable C.NatTable, dropOld bool) { +func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tunnel C.Tunnel, dropOld bool) { inboundMux.Lock() defer inboundMux.Unlock() @@ -730,7 +730,7 @@ func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn ch continue } } - if err := newListener.Listen(tcpIn, udpIn, natTable); err != nil { + if err := newListener.Listen(tunnel); err != nil { log.Errorln("Listener %s listen err: %s", name, err.Error()) continue } @@ -818,6 +818,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { LastTunConf.AutoRoute != tunConf.AutoRoute || LastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface || LastTunConf.MTU != tunConf.MTU || + LastTunConf.GSO != tunConf.GSO || + LastTunConf.GSOMaxSize != tunConf.GSOMaxSize || LastTunConf.StrictRoute != tunConf.StrictRoute || LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat || LastTunConf.UDPTimeout != tunConf.UDPTimeout || @@ -834,19 +836,35 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { }) sort.Slice(tunConf.Inet4Address, func(i, j int) bool { - return tunConf.Inet4Address[i].Build().String() < tunConf.Inet4Address[j].Build().String() + return tunConf.Inet4Address[i].String() < tunConf.Inet4Address[j].String() }) sort.Slice(tunConf.Inet6Address, func(i, j int) bool { - return tunConf.Inet6Address[i].Build().String() < tunConf.Inet6Address[j].Build().String() + return tunConf.Inet6Address[i].String() < tunConf.Inet6Address[j].String() }) sort.Slice(tunConf.Inet4RouteAddress, func(i, j int) bool { - return tunConf.Inet4RouteAddress[i].Build().String() < tunConf.Inet4RouteAddress[j].Build().String() + return tunConf.Inet4RouteAddress[i].String() < tunConf.Inet4RouteAddress[j].String() }) sort.Slice(tunConf.Inet6RouteAddress, func(i, j int) bool { - return tunConf.Inet6RouteAddress[i].Build().String() < tunConf.Inet6RouteAddress[j].Build().String() + return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String() + }) + + sort.Slice(tunConf.Inet4RouteExcludeAddress, func(i, j int) bool { + return tunConf.Inet4RouteExcludeAddress[i].String() < tunConf.Inet4RouteExcludeAddress[j].String() + }) + + sort.Slice(tunConf.Inet6RouteExcludeAddress, func(i, j int) bool { + return tunConf.Inet6RouteExcludeAddress[i].String() < tunConf.Inet6RouteExcludeAddress[j].String() + }) + + sort.Slice(tunConf.IncludeInterface, func(i, j int) bool { + return tunConf.IncludeInterface[i] < tunConf.IncludeInterface[j] + }) + + sort.Slice(tunConf.ExcludeInterface, func(i, j int) bool { + return tunConf.ExcludeInterface[i] < tunConf.ExcludeInterface[j] }) sort.Slice(tunConf.IncludeUID, func(i, j int) bool { @@ -882,6 +900,10 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { !slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) || !slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) || !slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) || + !slices.Equal(tunConf.Inet4RouteExcludeAddress, LastTunConf.Inet4RouteExcludeAddress) || + !slices.Equal(tunConf.Inet6RouteExcludeAddress, LastTunConf.Inet6RouteExcludeAddress) || + !slices.Equal(tunConf.IncludeInterface, LastTunConf.IncludeInterface) || + !slices.Equal(tunConf.ExcludeInterface, LastTunConf.ExcludeInterface) || !slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) || !slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) || !slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) || diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index 7241927d..94613039 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -3,20 +3,20 @@ package mixed import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/cache" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/listener/http" - "github.com/Dreamacro/clash/listener/socks" - "github.com/Dreamacro/clash/transport/socks4" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/lru" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/http" + "github.com/metacubex/mihomo/listener/socks" + "github.com/metacubex/mihomo/transport/socks4" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { listener net.Listener addr string - cache *cache.LruCache[string, bool] + cache *lru.LruCache[string, bool] closed bool } @@ -36,7 +36,7 @@ func (l *Listener) Close() error { return l.listener.Close() } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-MIXED"), @@ -51,7 +51,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* ml := &Listener{ listener: l, addr: addr, - cache: cache.New[string, bool](cache.WithAge[string, bool](30)), + cache: lru.New[string, bool](lru.WithAge[string, bool](30)), } go func() { for { @@ -62,14 +62,20 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } continue } - go handleConn(c, in, ml.cache, additions...) + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { + _ = c.Close() + continue + } + } + go handleConn(c, tunnel, ml.cache, additions...) } }() return ml, nil } -func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) { +func handleConn(conn net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) @@ -80,10 +86,10 @@ func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[st switch head[0] { case socks4.Version: - socks.HandleSocks4(bufConn, in, additions...) + socks.HandleSocks4(bufConn, tunnel, additions...) case socks5.Version: - socks.HandleSocks5(bufConn, in, additions...) + socks.HandleSocks5(bufConn, tunnel, additions...) default: - http.HandleConn(bufConn, in, cache, additions...) + http.HandleConn(bufConn, tunnel, cache, additions...) } } diff --git a/listener/parse.go b/listener/parse.go index b0fac86a..1c8b6463 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -3,9 +3,9 @@ package listener import ( "fmt" - "github.com/Dreamacro/clash/common/structure" - C "github.com/Dreamacro/clash/constant" - IN "github.com/Dreamacro/clash/listener/inbound" + "github.com/metacubex/mihomo/common/structure" + C "github.com/metacubex/mihomo/constant" + IN "github.com/metacubex/mihomo/listener/inbound" ) func ParseListener(mapping map[string]any) (C.InboundListener, error) { diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go index 9a843af8..8474a8e2 100644 --- a/listener/redir/tcp.go +++ b/listener/redir/tcp.go @@ -3,9 +3,9 @@ package redir import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" ) type Listener struct { @@ -30,7 +30,7 @@ func (l *Listener) Close() error { return l.listener.Close() } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-REDIR"), @@ -55,18 +55,19 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } continue } - go handleRedir(c, in, additions...) + go handleRedir(c, tunnel, additions...) } }() return rl, nil } -func handleRedir(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { + +func handleRedir(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { target, err := parserPacket(conn) if err != nil { conn.Close() return } N.TCPKeepAlive(conn) - in <- inbound.NewSocket(target, conn, C.REDIR, additions...) + tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, additions...)) } diff --git a/listener/redir/tcp_darwin.go b/listener/redir/tcp_darwin.go index 5a2f331c..6e1821bb 100644 --- a/listener/redir/tcp_darwin.go +++ b/listener/redir/tcp_darwin.go @@ -5,7 +5,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) func parserPacket(c net.Conn) (socks5.Addr, error) { diff --git a/listener/redir/tcp_freebsd.go b/listener/redir/tcp_freebsd.go index 6ecb2496..9eb199f0 100644 --- a/listener/redir/tcp_freebsd.go +++ b/listener/redir/tcp_freebsd.go @@ -8,7 +8,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" "golang.org/x/sys/unix" ) diff --git a/listener/redir/tcp_linux.go b/listener/redir/tcp_linux.go index b65c34ee..fce74678 100644 --- a/listener/redir/tcp_linux.go +++ b/listener/redir/tcp_linux.go @@ -8,7 +8,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" "golang.org/x/sys/unix" ) diff --git a/listener/redir/tcp_other.go b/listener/redir/tcp_other.go index a01550c7..ae3bebfd 100644 --- a/listener/redir/tcp_other.go +++ b/listener/redir/tcp_other.go @@ -6,7 +6,7 @@ import ( "errors" "net" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) func parserPacket(conn net.Conn) (socks5.Addr, error) { diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go index 2d0958a0..c08667de 100644 --- a/listener/shadowsocks/tcp.go +++ b/listener/shadowsocks/tcp.go @@ -4,12 +4,12 @@ import ( "net" "strings" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/transport/shadowsocks/core" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/transport/shadowsocks/core" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { @@ -22,7 +22,7 @@ type Listener struct { var _listener *Listener -func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) (*Listener, error) { +func New(config LC.ShadowsocksServer, tunnel C.Tunnel) (*Listener, error) { pickCipher, err := core.PickCipher(config.Cipher, nil, config.Password) if err != nil { return nil, err @@ -36,7 +36,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C if config.Udp { //UDP - ul, err := NewUDP(addr, pickCipher, udpIn) + ul, err := NewUDP(addr, pickCipher, tunnel) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C continue } N.TCPKeepAlive(c) - go sl.HandleConn(c, tcpIn) + go sl.HandleConn(c, tunnel) } }() } @@ -99,7 +99,7 @@ func (l *Listener) AddrList() (addrList []net.Addr) { return } -func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { conn = l.pickCipher.StreamConn(conn) conn = N.NewDeadlineConn(conn) // embed ss can't handle readDeadline correctly @@ -108,12 +108,12 @@ func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions _ = conn.Close() return } - in <- inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...) + tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...)) } -func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) bool { +func HandleShadowSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) bool { if _listener != nil && _listener.pickCipher != nil { - go _listener.HandleConn(conn, in, additions...) + go _listener.HandleConn(conn, tunnel, additions...) return true } return false diff --git a/listener/shadowsocks/udp.go b/listener/shadowsocks/udp.go index af610431..4336db22 100644 --- a/listener/shadowsocks/udp.go +++ b/listener/shadowsocks/udp.go @@ -3,13 +3,13 @@ package shadowsocks import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/sockopt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/shadowsocks/core" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/sockopt" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/shadowsocks/core" + "github.com/metacubex/mihomo/transport/socks5" ) type UDPListener struct { @@ -17,7 +17,7 @@ type UDPListener struct { closed bool } -func NewUDP(addr string, pickCipher core.Cipher, in chan<- C.PacketAdapter) (*UDPListener, error) { +func NewUDP(addr string, pickCipher core.Cipher, tunnel C.Tunnel) (*UDPListener, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -42,7 +42,7 @@ func NewUDP(addr string, pickCipher core.Cipher, in chan<- C.PacketAdapter) (*UD } continue } - handleSocksUDP(conn, in, data, put, remoteAddr) + handleSocksUDP(conn, tunnel, data, put, remoteAddr) } }() @@ -58,7 +58,7 @@ func (l *UDPListener) LocalAddr() net.Addr { return l.packetConn.LocalAddr() } -func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) { +func handleSocksUDP(pc net.PacketConn, tunnel C.Tunnel, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) { tgtAddr := socks5.SplitAddr(buf) if tgtAddr == nil { // Unresolved UDP packet, return buffer to the pool @@ -67,7 +67,7 @@ func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, pu } return } - target := socks5.ParseAddr(tgtAddr.String()) + target := tgtAddr payload := buf[len(tgtAddr):] packet := &packet{ @@ -76,8 +76,5 @@ func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, pu payload: payload, put: put, } - select { - case in <- inbound.NewPacket(target, packet, C.SHADOWSOCKS, additions...): - default: - } + tunnel.HandleUDPPacket(inbound.NewPacket(target, packet, C.SHADOWSOCKS, additions...)) } diff --git a/listener/shadowsocks/utils.go b/listener/shadowsocks/utils.go index a732cbbe..5d6a2977 100644 --- a/listener/shadowsocks/utils.go +++ b/listener/shadowsocks/utils.go @@ -6,7 +6,7 @@ import ( "net" "net/url" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) type packet struct { diff --git a/listener/sing/context.go b/listener/sing/context.go index a500e4a4..e1e8b452 100644 --- a/listener/sing/context.go +++ b/listener/sing/context.go @@ -4,7 +4,7 @@ import ( "context" "golang.org/x/exp/slices" - "github.com/Dreamacro/clash/adapter/inbound" + "github.com/metacubex/mihomo/adapter/inbound" "github.com/sagernet/sing/common/auth" ) @@ -17,28 +17,15 @@ func WithAdditions(ctx context.Context, additions ...inbound.Addition) context.C return context.WithValue(ctx, ctxKeyAdditions, additions) } -func getAdditions(ctx context.Context) []inbound.Addition { +func getAdditions(ctx context.Context) (additions []inbound.Addition) { if v := ctx.Value(ctxKeyAdditions); v != nil { if a, ok := v.([]inbound.Addition); ok { - return a + additions = a } } - return nil -} - -func combineAdditions(ctx context.Context, additions []inbound.Addition) []inbound.Addition { - additionsCloned := false - if ctxAdditions := getAdditions(ctx); len(ctxAdditions) > 0 { - additions = slices.Clone(additions) - additionsCloned = true - additions = append(additions, ctxAdditions...) - } if user, ok := auth.UserFromContext[string](ctx); ok { - if !additionsCloned { - additions = slices.Clone(additions) - additionsCloned = true - } + additions = slices.Clone(additions) additions = append(additions, inbound.WithInUser(user)) } - return additions + return } diff --git a/listener/sing/sing.go b/listener/sing/sing.go index d5731bbf..4e31faeb 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -8,11 +8,11 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/adapter/outbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" vmess "github.com/metacubex/sing-vmess" mux "github.com/sagernet/sing-mux" @@ -27,42 +27,28 @@ import ( const UDPTimeout = 5 * time.Minute -type ListenerHandler struct { - TcpIn chan<- C.ConnContext - UdpIn chan<- C.PacketAdapter +type ListenerConfig struct { + Tunnel C.Tunnel Type C.Type Additions []inbound.Addition UDPTimeout time.Duration + MuxOption MuxOption } -type waitCloseConn struct { - N.ExtendedConn - wg *sync.WaitGroup - close sync.Once - rAddr net.Addr +type MuxOption struct { + Padding bool `yaml:"padding" json:"padding,omitempty"` + Brutal BrutalOptions `yaml:"brutal" json:"brutal,omitempty"` } -func (c *waitCloseConn) Close() error { // call from handleTCPConn(connCtx C.ConnContext) - c.close.Do(func() { - c.wg.Done() - }) - return c.ExtendedConn.Close() +type BrutalOptions struct { + Enabled bool `yaml:"enabled" json:"enabled"` + Up string `yaml:"up" json:"up,omitempty"` + Down string `yaml:"down" json:"down,omitempty"` } -func (c *waitCloseConn) RemoteAddr() net.Addr { - return c.rAddr -} - -func (c *waitCloseConn) Upstream() any { - return c.ExtendedConn -} - -func (c *waitCloseConn) ReaderReplaceable() bool { - return true -} - -func (c *waitCloseConn) WriterReplaceable() bool { - return true +type ListenerHandler struct { + ListenerConfig + muxService *mux.Service } func UpstreamMetadata(metadata M.Metadata) M.Metadata { @@ -80,22 +66,40 @@ func ConvertMetadata(metadata *C.Metadata) M.Metadata { } } +func NewListenerHandler(lc ListenerConfig) (h *ListenerHandler, err error) { + h = &ListenerHandler{ListenerConfig: lc} + h.muxService, err = mux.NewService(mux.ServiceOptions{ + NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { + return ctx + }, + Logger: log.SingLogger, + Handler: h, + Padding: lc.MuxOption.Padding, + Brutal: mux.BrutalOptions{ + Enabled: lc.MuxOption.Brutal.Enabled, + SendBPS: outbound.StringToBps(lc.MuxOption.Brutal.Up), + ReceiveBPS: outbound.StringToBps(lc.MuxOption.Brutal.Down), + }, + }) + return +} + func (h *ListenerHandler) IsSpecialFqdn(fqdn string) bool { switch fqdn { - case mux.Destination.Fqdn: - case vmess.MuxDestination.Fqdn: - case uot.MagicAddress: - case uot.LegacyMagicAddress: + case mux.Destination.Fqdn, + vmess.MuxDestination.Fqdn, + uot.MagicAddress, + uot.LegacyMagicAddress: + return true default: return false } - return true } func (h *ListenerHandler) ParseSpecialFqdn(ctx context.Context, conn net.Conn, metadata M.Metadata) error { switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: - return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata)) + return h.muxService.NewConnection(ctx, conn, UpstreamMetadata(metadata)) case vmess.MuxDestination.Fqdn: return vmess.HandleMuxConnection(ctx, conn, h) case uot.MagicAddress: @@ -116,71 +120,76 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta if h.IsSpecialFqdn(metadata.Destination.Fqdn) { return h.ParseSpecialFqdn(ctx, conn, metadata) } - target := socks5.ParseAddr(metadata.Destination.String()) - wg := &sync.WaitGroup{} - defer wg.Wait() // this goroutine must exit after conn.Close() - wg.Add(1) if deadline.NeedAdditionalReadDeadline(conn) { conn = N.NewDeadlineConn(conn) // conn from sing should check NeedAdditionalReadDeadline } - h.TcpIn <- inbound.NewSocket(target, &waitCloseConn{ExtendedConn: N.NewExtendedConn(conn), wg: wg, rAddr: metadata.Source.TCPAddr()}, h.Type, combineAdditions(ctx, h.Additions)...) + + cMetadata := &C.Metadata{ + NetWork: C.TCP, + Type: h.Type, + } + inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(metadata.Destination), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr())) + inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...) + inbound.ApplyAdditions(cMetadata, h.Additions...) + + h.Tunnel.HandleTCPConn(conn, cMetadata) // this goroutine must exit after conn unused return nil } func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { - if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewFallbackPacketConn(bufio.NewNetPacketConn(conn)) // conn from sing should check NeedAdditionalReadDeadline - } defer func() { _ = conn.Close() }() mutex := sync.Mutex{} - conn2 := conn // a new interface to set nil in defer + conn2 := bufio.NewNetPacketConn(conn) // a new interface to set nil in defer defer func() { mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running defer mutex.Unlock() conn2 = nil }() - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff - } + rwOptions := network.ReadWaitOptions{} readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( + buff *buf.Buffer dest M.Socksaddr err error ) - buff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + buff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() if ShouldIgnorePacketError(err) { break } return err } - target := socks5.ParseAddr(dest.String()) - packet := &packet{ + cPacket := &packet{ conn: &conn2, mutex: &mutex, rAddr: metadata.Source.UDPAddr(), lAddr: conn.LocalAddr(), buff: buff, } - select { - case h.UdpIn <- inbound.NewPacket(target, packet, h.Type, combineAdditions(ctx, h.Additions)...): - default: + + cMetadata := &C.Metadata{ + NetWork: C.UDP, + Type: h.Type, } + inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(dest), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr())) + inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...) + inbound.ApplyAdditions(cMetadata, h.Additions...) + + h.Tunnel.HandleUDPPacket(cPacket, cMetadata) } return nil } @@ -198,7 +207,7 @@ func ShouldIgnorePacketError(err error) bool { } type packet struct { - conn *network.PacketConn + conn *network.NetPacketConn mutex *sync.Mutex rAddr net.Addr lAddr net.Addr @@ -215,12 +224,6 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { err = errors.New("address is invalid") return } - buff := buf.NewPacket() - defer buff.Release() - n, err = buff.Write(b) - if err != nil { - return - } c.mutex.Lock() defer c.mutex.Unlock() @@ -229,11 +232,8 @@ 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.SocksaddrFromNet(addr)) - if err != nil { - return - } - return + + return conn.WriteTo(b, addr) } // LocalAddr returns the source IP/Port of UDP Packet @@ -248,11 +248,3 @@ 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_hysteria2/server.go b/listener/sing_hysteria2/server.go index 4e0a7c07..de4c95f5 100644 --- a/listener/sing_hysteria2/server.go +++ b/listener/sing_hysteria2/server.go @@ -11,14 +11,14 @@ import ( "net/url" "strings" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/adapter/outbound" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/sockopt" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/adapter/outbound" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/sockopt" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" "github.com/metacubex/sing-quic/hysteria2" @@ -32,7 +32,7 @@ type Listener struct { services []*hysteria2.Service[string] } -func New(config LC.Hysteria2Server, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (*Listener, error) { +func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { var sl *Listener var err error if len(additions) == 0 { @@ -42,16 +42,19 @@ func New(config LC.Hysteria2Server, tcpIn chan<- C.ConnContext, udpIn chan<- C.P } } - h := &sing.ListenerHandler{ - TcpIn: tcpIn, - UdpIn: udpIn, + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, Type: C.HYSTERIA2, Additions: additions, + MuxOption: config.MuxOption, + }) + if err != nil { + return nil, err } sl = &Listener{false, config, nil, nil} - cert, err := CN.ParseCert(config.Certificate, config.PrivateKey) + cert, err := CN.ParseCert(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index d0e137a7..bd5002a4 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -6,15 +6,15 @@ import ( "net" "strings" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/sockopt" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - embedSS "github.com/Dreamacro/clash/listener/shadowsocks" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/ntp" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/sockopt" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + embedSS "github.com/metacubex/mihomo/listener/shadowsocks" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/ntp" shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowaead" @@ -23,6 +23,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" ) type Listener struct { @@ -35,7 +36,7 @@ type Listener struct { var _listener *Listener -func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (C.MultiAddrListener, error) { +func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addition) (C.MultiAddrListener, error) { var sl *Listener var err error if len(additions) == 0 { @@ -50,11 +51,14 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C udpTimeout := int64(sing.UDPTimeout.Seconds()) - h := &sing.ListenerHandler{ - TcpIn: tcpIn, - UdpIn: udpIn, + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, Type: C.SHADOWSOCKS, Additions: additions, + MuxOption: config.MuxOption, + }) + if err != nil { + return nil, err } sl = &Listener{false, config, nil, nil, nil} @@ -68,7 +72,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, ntp.Now) default: err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher) - return embedSS.New(config, tcpIn, udpIn) + return embedSS.New(config, tunnel) } if err != nil { return nil, err @@ -93,30 +97,33 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C go func() { conn := bufio.NewPacketConn(ul) - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff + rwOptions := network.ReadWaitOptions{ + FrontHeadroom: network.CalculateFrontHeadroom(sl.service), + RearHeadroom: network.CalculateRearHeadroom(sl.service), + MTU: network.CalculateMTU(conn, sl.service), } readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( + buff *buf.Buffer dest M.Socksaddr err error ) buff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + buff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() if sl.closed { break } @@ -148,7 +155,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C } N.TCPKeepAlive(c) - go sl.HandleConn(c, tcpIn) + go sl.HandleConn(c, tunnel) } }() } @@ -188,7 +195,7 @@ func (l *Listener) AddrList() (addrList []net.Addr) { return } -func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { ctx := sing.WithAdditions(context.TODO(), additions...) err := l.service.NewConnection(ctx, conn, M.Metadata{ Protocol: "shadowsocks", @@ -200,10 +207,10 @@ func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions } } -func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) bool { +func HandleShadowSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) bool { if _listener != nil && _listener.service != nil { - go _listener.HandleConn(conn, in, additions...) + go _listener.HandleConn(conn, tunnel, additions...) return true } - return embedSS.HandleShadowSocks(conn, in, additions...) + return embedSS.HandleShadowSocks(conn, tunnel, additions...) } diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go index 88e3f6d6..056c9169 100644 --- a/listener/sing_tun/dns.go +++ b/listener/sing_tun/dns.go @@ -9,10 +9,10 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/component/resolver" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" D "github.com/miekg/dns" @@ -26,7 +26,7 @@ const DefaultDnsReadTimeout = time.Second * 10 const DefaultDnsRelayTimeout = time.Second * 5 type ListenerHandler struct { - sing.ListenerHandler + *sing.ListenerHandler DnsAdds []netip.AddrPort } @@ -73,7 +73,7 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) defer cancel() inData := buff[:n] - msg, err := RelayDnsPacket(ctx, inData) + msg, err := RelayDnsPacket(ctx, inData, buff) if err != nil { return err } @@ -98,6 +98,8 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta return h.ListenerHandler.NewConnection(ctx, conn, metadata) } +const SafeDnsPacketSize = 2 * 1024 // safe size which is 1232 from https://dnsflagday.net/2020/, so 2048 is enough + func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { if h.ShouldHijackDns(metadata.Destination.AddrPort()) { log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String()) @@ -109,72 +111,79 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network. defer mutex.Unlock() conn2 = nil }() - - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - // safe size which is 1232 from https://dnsflagday.net/2020/. - // so 2048 is enough - buff = buf.NewSize(2 * 1024) - return buff + rwOptions := network.ReadWaitOptions{ + FrontHeadroom: network.CalculateFrontHeadroom(conn), + RearHeadroom: network.CalculateRearHeadroom(conn), + MTU: SafeDnsPacketSize, } readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( - dest M.Socksaddr - err error + readBuff *buf.Buffer + dest M.Socksaddr + err error ) _ = conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) - buff = nil // clear last loop status, avoid repeat release + readBuff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + readBuff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + readBuff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(readBuff) + if readBuff != nil { + rwOptions.PostReturn(readBuff) + } } if err != nil { - if buff != nil { - buff.Release() + if readBuff != nil { + readBuff.Release() } if sing.ShouldIgnorePacketError(err) { break } return err } - go func(buff *buf.Buffer) { + go func() { ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) defer cancel() - inData := buff.Bytes() - msg, err := RelayDnsPacket(ctx, inData) + inData := readBuff.Bytes() + writeBuff := readBuff + writeBuff.Resize(writeBuff.Start(), 0) + if len(writeBuff.FreeBytes()) < SafeDnsPacketSize { // only create a new buffer when space don't enough + writeBuff = rwOptions.NewPacketBuffer() + } + msg, err := RelayDnsPacket(ctx, inData, writeBuff.FreeBytes()) + if writeBuff != readBuff { + readBuff.Release() + } if err != nil { - buff.Release() - return - } - buff.Reset() - _, err = buff.Write(msg) - if err != nil { - buff.Release() + writeBuff.Release() return } + writeBuff.Truncate(len(msg)) mutex.Lock() defer mutex.Unlock() conn := conn2 if conn == nil { + writeBuff.Release() return } - err = conn.WritePacket(buff, dest) // WritePacket will release buff + err = conn.WritePacket(writeBuff, dest) // WritePacket will release writeBuff if err != nil { + writeBuff.Release() return } - }(buff) // catch buff at goroutine create, avoid next loop change buff + }() } return nil } return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) } -func RelayDnsPacket(ctx context.Context, payload []byte) ([]byte, error) { +func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { msg := &D.Msg{} if err := msg.Unpack(payload); err != nil { return nil, err @@ -184,10 +193,10 @@ func RelayDnsPacket(ctx context.Context, payload []byte) ([]byte, error) { if err != nil { m := new(D.Msg) m.SetRcode(msg, D.RcodeServerFailure) - return m.Pack() + return m.PackBuffer(target) } r.SetRcode(msg, r.Rcode) r.Compress = true - return r.Pack() + return r.PackBuffer(target) } diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 66fe8cd5..cc26d37d 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -8,15 +8,14 @@ import ( "runtime" "strconv" "strings" - "time" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/iface" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/iface" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" tun "github.com/metacubex/sing-tun" "github.com/sagernet/sing/common" @@ -88,13 +87,16 @@ func checkTunName(tunName string) (ok bool) { return true } -func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (l *Listener, err error) { +func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Listener, err error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-TUN"), inbound.WithSpecialRules(""), } } + if options.GSOMaxSize == 0 { + options.GSOMaxSize = 65536 + } tunName := options.Device if tunName == "" || !checkTunName(tunName) { tunName = CalculateInterfaceName(InterfaceName) @@ -142,23 +144,26 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte dnsAdds = append(dnsAdds, addrPort) } for _, a := range options.Inet4Address { - addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53) + addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) dnsAdds = append(dnsAdds, addrPort) } for _, a := range options.Inet6Address { - addrPort := netip.AddrPortFrom(a.Build().Addr().Next(), 53) + addrPort := netip.AddrPortFrom(a.Addr().Next(), 53) dnsAdds = append(dnsAdds, addrPort) } + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, + Type: C.TUN, + Additions: additions, + }) + if err != nil { + return nil, err + } + handler := &ListenerHandler{ - ListenerHandler: sing.ListenerHandler{ - TcpIn: tcpIn, - UdpIn: udpIn, - Type: C.TUN, - Additions: additions, - UDPTimeout: time.Second * time.Duration(udpTimeout), - }, - DnsAdds: dnsAdds, + ListenerHandler: h, + DnsAdds: dnsAdds, } l = &Listener{ closed: false, @@ -200,22 +205,27 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte } tunOptions := tun.Options{ - Name: tunName, - MTU: tunMTU, - Inet4Address: common.Map(options.Inet4Address, LC.ListenPrefix.Build), - Inet6Address: common.Map(options.Inet6Address, LC.ListenPrefix.Build), - AutoRoute: options.AutoRoute, - StrictRoute: options.StrictRoute, - Inet4RouteAddress: common.Map(options.Inet4RouteAddress, LC.ListenPrefix.Build), - Inet6RouteAddress: common.Map(options.Inet6RouteAddress, LC.ListenPrefix.Build), - IncludeUID: includeUID, - ExcludeUID: excludeUID, - IncludeAndroidUser: options.IncludeAndroidUser, - IncludePackage: options.IncludePackage, - ExcludePackage: options.ExcludePackage, - FileDescriptor: options.FileDescriptor, - InterfaceMonitor: defaultInterfaceMonitor, - TableIndex: 2022, + Name: tunName, + MTU: tunMTU, + GSO: options.GSO, + Inet4Address: options.Inet4Address, + Inet6Address: options.Inet6Address, + AutoRoute: options.AutoRoute, + StrictRoute: options.StrictRoute, + Inet4RouteAddress: options.Inet4RouteAddress, + Inet6RouteAddress: options.Inet6RouteAddress, + Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, + IncludeUID: includeUID, + ExcludeUID: excludeUID, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + FileDescriptor: options.FileDescriptor, + InterfaceMonitor: defaultInterfaceMonitor, + TableIndex: 2022, } err = l.buildAndroidRules(&tunOptions) @@ -232,10 +242,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte stackOptions := tun.StackOptions{ Context: context.TODO(), Tun: tunIf, - MTU: tunOptions.MTU, - Name: tunOptions.Name, - Inet4Address: tunOptions.Inet4Address, - Inet6Address: tunOptions.Inet6Address, + TunOptions: tunOptions, EndpointIndependentNat: options.EndpointIndependentNat, UDPTimeout: udpTimeout, Handler: handler, @@ -244,7 +251,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte if options.FileDescriptor > 0 { if tunName, err := getTunnelName(int32(options.FileDescriptor)); err != nil { - stackOptions.Name = tunName + stackOptions.TunOptions.Name = tunName stackOptions.ForwarderBindInterface = true } } diff --git a/listener/sing_tun/server_android.go b/listener/sing_tun/server_android.go index 4f85c418..ac41282d 100644 --- a/listener/sing_tun/server_android.go +++ b/listener/sing_tun/server_android.go @@ -1,7 +1,7 @@ package sing_tun import ( - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" tun "github.com/metacubex/sing-tun" "github.com/sagernet/netlink" "golang.org/x/sys/unix" diff --git a/listener/sing_tun/server_windows.go b/listener/sing_tun/server_windows.go index 9584f32f..8da21287 100644 --- a/listener/sing_tun/server_windows.go +++ b/listener/sing_tun/server_windows.go @@ -3,7 +3,7 @@ package sing_tun import ( "time" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" tun "github.com/metacubex/sing-tun" ) diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index 06f3e051..ce422b16 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -2,16 +2,19 @@ package sing_vmess import ( "context" + "crypto/tls" "net" + "net/http" "net/url" "strings" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/ntp" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/ntp" + mihomoVMess "github.com/metacubex/mihomo/transport/vmess" vmess "github.com/metacubex/sing-vmess" "github.com/sagernet/sing/common" @@ -27,7 +30,7 @@ type Listener struct { var _listener *Listener -func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (sl *Listener, err error) { +func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-VMESS"), @@ -37,11 +40,14 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe _listener = sl }() } - h := &sing.ListenerHandler{ - TcpIn: tcpIn, - UdpIn: udpIn, + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, Type: C.VMESS, Additions: additions, + MuxOption: config.MuxOption, + }) + if err != nil { + return nil, err } service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection(), vmess.ServiceWithTimeFunc(ntp.Now)) @@ -66,6 +72,29 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe sl = &Listener{false, config, nil, service} + tlsConfig := &tls.Config{} + var httpMux *http.ServeMux + + if config.Certificate != "" && config.PrivateKey != "" { + cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + if config.WsPath != "" { + httpMux = http.NewServeMux() + httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) { + conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + sl.HandleConn(conn, tunnel) + }) + tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1") + } + for _, addr := range strings.Split(config.Listen, ",") { addr := addr @@ -74,9 +103,16 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe if err != nil { return nil, err } + if len(tlsConfig.Certificates) > 0 { + l = tls.NewListener(l, tlsConfig) + } sl.listeners = append(sl.listeners, l) go func() { + if httpMux != nil { + _ = http.Serve(l, httpMux) + return + } for { c, err := l.Accept() if err != nil { @@ -87,7 +123,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe } N.TCPKeepAlive(c) - go sl.HandleConn(c, tcpIn) + go sl.HandleConn(c, tunnel) } }() } @@ -122,7 +158,7 @@ func (l *Listener) AddrList() (addrList []net.Addr) { return } -func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { ctx := sing.WithAdditions(context.TODO(), additions...) err := l.service.NewConnection(ctx, conn, metadata.Metadata{ Protocol: "vmess", @@ -134,9 +170,9 @@ func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext, additions } } -func HandleVmess(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) bool { +func HandleVmess(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) bool { if _listener != nil && _listener.service != nil { - go _listener.HandleConn(conn, in, additions...) + go _listener.HandleConn(conn, tunnel, additions...) return true } return false diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index 2fd252a3..8016e958 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -4,12 +4,12 @@ import ( "io" "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - authStore "github.com/Dreamacro/clash/listener/auth" - "github.com/Dreamacro/clash/transport/socks4" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + authStore "github.com/metacubex/mihomo/listener/auth" + "github.com/metacubex/mihomo/transport/socks4" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { @@ -34,7 +34,7 @@ func (l *Listener) Close() error { return l.listener.Close() } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-SOCKS"), @@ -59,14 +59,20 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } continue } - go handleSocks(c, in, additions...) + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { + _ = c.Close() + continue + } + } + go handleSocks(c, tunnel, additions...) } }() return sl, nil } -func handleSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func handleSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) @@ -77,25 +83,33 @@ func handleSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Ad switch head[0] { case socks4.Version: - HandleSocks4(bufConn, in, additions...) + HandleSocks4(bufConn, tunnel, additions...) case socks5.Version: - HandleSocks5(bufConn, in, additions...) + HandleSocks5(bufConn, tunnel, additions...) default: conn.Close() } } -func HandleSocks4(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - addr, _, err := socks4.ServerHandshake(conn, authStore.Authenticator()) +func HandleSocks4(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { + authenticator := authStore.Authenticator() + if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { + authenticator = nil + } + addr, _, err := socks4.ServerHandshake(conn, authenticator) if err != nil { conn.Close() return } - in <- inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...) + tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...)) } -func HandleSocks5(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) +func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { + authenticator := authStore.Authenticator() + if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { + authenticator = nil + } + target, command, err := socks5.ServerHandshake(conn, authenticator) if err != nil { conn.Close() return @@ -105,5 +119,5 @@ func HandleSocks5(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.A io.Copy(io.Discard, conn) return } - in <- inbound.NewSocket(target, conn, C.SOCKS5, additions...) + tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SOCKS5, additions...)) } diff --git a/listener/socks/udp.go b/listener/socks/udp.go index 31858f74..ef31b20e 100644 --- a/listener/socks/udp.go +++ b/listener/socks/udp.go @@ -3,12 +3,12 @@ package socks import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/sockopt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/sockopt" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/socks5" ) type UDPListener struct { @@ -33,7 +33,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, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPListener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-SOCKS"), @@ -66,14 +66,14 @@ func NewUDP(addr string, in chan<- C.PacketAdapter, additions ...inbound.Additio } continue } - handleSocksUDP(l, in, data, put, remoteAddr, additions...) + handleSocksUDP(l, tunnel, data, put, remoteAddr, additions...) } }() return sl, nil } -func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) { +func handleSocksUDP(pc net.PacketConn, tunnel C.Tunnel, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) { target, payload, err := socks5.DecodeUDPPacket(buf) if err != nil { // Unresolved UDP packet, return buffer to the pool @@ -88,8 +88,5 @@ func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, pu payload: payload, put: put, } - select { - case in <- inbound.NewPacket(target, packet, C.SOCKS5, additions...): - default: - } + tunnel.HandleUDPPacket(inbound.NewPacket(target, packet, C.SOCKS5, additions...)) } diff --git a/listener/socks/utils.go b/listener/socks/utils.go index 3456b595..d113d45c 100644 --- a/listener/socks/utils.go +++ b/listener/socks/utils.go @@ -3,7 +3,7 @@ package socks import ( "net" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/transport/socks5" ) type packet struct { diff --git a/listener/tproxy/packet.go b/listener/tproxy/packet.go index b73339a1..e4852665 100644 --- a/listener/tproxy/packet.go +++ b/listener/tproxy/packet.go @@ -6,18 +6,17 @@ import ( "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" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type packet struct { - pc net.PacketConn - lAddr netip.AddrPort - buf []byte - in chan<- C.PacketAdapter - natTable C.NatTable + pc net.PacketConn + lAddr netip.AddrPort + buf []byte + tunnel C.Tunnel } func (c *packet) Data() []byte { @@ -26,7 +25,7 @@ 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 := createOrGetLocalConn(addr, c.LocalAddr(), c.in, c.natTable) + tc, err := createOrGetLocalConn(addr, c.LocalAddr(), c.tunnel) if err != nil { n = 0 return @@ -52,9 +51,10 @@ func (c *packet) InAddr() net.Addr { // 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) { +func createOrGetLocalConn(rAddr, lAddr net.Addr, tunnel C.Tunnel) (*net.UDPConn, error) { remote := rAddr.String() local := lAddr.String() + natTable := tunnel.NatTable() localConn := natTable.GetForLocalConn(local, remote) // localConn not exist if localConn == nil { @@ -76,7 +76,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT natTable.DeleteLockForLocalConn(local, remote) cond.Broadcast() }() - conn, err := listenLocalConn(rAddr, lAddr, in, natTable) + conn, err := listenLocalConn(rAddr, lAddr, tunnel) if err != nil { log.Errorln("listenLocalConn failed with error: %s, packet loss (rAddr[%T]=%s lAddr[%T]=%s)", err.Error(), rAddr, remote, lAddr, local) return nil, err @@ -90,7 +90,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT // 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) { +func listenLocalConn(rAddr, lAddr net.Addr, tunnel C.Tunnel) (*net.UDPConn, error) { additions := []inbound.Addition{ inbound.WithInName("DEFAULT-TPROXY"), inbound.WithSpecialRules(""), @@ -113,7 +113,7 @@ func listenLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natTable } // 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...) + handlePacketConn(lc, tunnel, buf[:br], lAddr.(*net.UDPAddr).AddrPort(), rAddr.(*net.UDPAddr).AddrPort(), additions...) } }() return lc, nil diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index 8c868609..efb144a9 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -3,10 +3,10 @@ package tproxy import ( "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { @@ -31,13 +31,13 @@ func (l *Listener) Close() error { return l.listener.Close() } -func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) N.TCPKeepAlive(conn) - in <- inbound.NewSocket(target, conn, C.TPROXY, additions...) + tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...)) } -func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-TPROXY"), @@ -74,7 +74,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } continue } - go rl.handleTProxy(c, in, additions...) + go rl.handleTProxy(c, tunnel, additions...) } }() diff --git a/listener/tproxy/tproxy_iptables.go b/listener/tproxy/tproxy_iptables.go index 31ac24e5..5ddd7b4c 100644 --- a/listener/tproxy/tproxy_iptables.go +++ b/listener/tproxy/tproxy_iptables.go @@ -6,9 +6,9 @@ import ( "net" "runtime" - "github.com/Dreamacro/clash/common/cmd" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/cmd" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/log" ) var ( @@ -48,25 +48,25 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1 execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) } - // set clash divert - execCmd("iptables -t mangle -N clash_divert") - execCmd("iptables -t mangle -F clash_divert") - execCmd(fmt.Sprintf("iptables -t mangle -A clash_divert -j MARK --set-mark %s", PROXY_FWMARK)) - execCmd("iptables -t mangle -A clash_divert -j ACCEPT") + // set mihomo divert + execCmd("iptables -t mangle -N mihomo_divert") + execCmd("iptables -t mangle -F mihomo_divert") + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_divert -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd("iptables -t mangle -A mihomo_divert -j ACCEPT") // set pre routing - execCmd("iptables -t mangle -N clash_prerouting") - execCmd("iptables -t mangle -F clash_prerouting") - execCmd("iptables -t mangle -A clash_prerouting -s 172.17.0.0/16 -j RETURN") - execCmd("iptables -t mangle -A clash_prerouting -p udp --dport 53 -j ACCEPT") - execCmd("iptables -t mangle -A clash_prerouting -p tcp --dport 53 -j ACCEPT") - execCmd("iptables -t mangle -A clash_prerouting -m addrtype --dst-type LOCAL -j RETURN") - addLocalnetworkToChain("clash_prerouting", bypass) - execCmd("iptables -t mangle -A clash_prerouting -p tcp -m socket -j clash_divert") - execCmd("iptables -t mangle -A clash_prerouting -p udp -m socket -j clash_divert") - execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p tcp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK)) - execCmd(fmt.Sprintf("iptables -t mangle -A clash_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK)) - execCmd("iptables -t mangle -A PREROUTING -j clash_prerouting") + execCmd("iptables -t mangle -N mihomo_prerouting") + execCmd("iptables -t mangle -F mihomo_prerouting") + execCmd("iptables -t mangle -A mihomo_prerouting -s 172.17.0.0/16 -j RETURN") + execCmd("iptables -t mangle -A mihomo_prerouting -p udp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A mihomo_prerouting -p tcp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A mihomo_prerouting -m addrtype --dst-type LOCAL -j RETURN") + addLocalnetworkToChain("mihomo_prerouting", bypass) + execCmd("iptables -t mangle -A mihomo_prerouting -p tcp -m socket -j mihomo_divert") + execCmd("iptables -t mangle -A mihomo_prerouting -p udp -m socket -j mihomo_divert") + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_prerouting -p tcp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_prerouting -p udp -j TPROXY --on-port %d --tproxy-mark %s/%s", tProxyPort, PROXY_FWMARK, PROXY_FWMARK)) + execCmd("iptables -t mangle -A PREROUTING -j mihomo_prerouting") execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) @@ -77,27 +77,27 @@ func SetTProxyIPTables(ifname string, bypass []string, tport uint16, dport uint1 } // set output - execCmd("iptables -t mangle -N clash_output") - execCmd("iptables -t mangle -F clash_output") - execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) - execCmd("iptables -t mangle -A clash_output -p udp -m multiport --dports 53,123,137 -j ACCEPT") - execCmd("iptables -t mangle -A clash_output -p tcp --dport 53 -j ACCEPT") - execCmd("iptables -t mangle -A clash_output -m addrtype --dst-type LOCAL -j RETURN") - execCmd("iptables -t mangle -A clash_output -m addrtype --dst-type BROADCAST -j RETURN") - addLocalnetworkToChain("clash_output", bypass) - execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -p tcp -j MARK --set-mark %s", PROXY_FWMARK)) - execCmd(fmt.Sprintf("iptables -t mangle -A clash_output -p udp -j MARK --set-mark %s", PROXY_FWMARK)) - execCmd(fmt.Sprintf("iptables -t mangle -I OUTPUT -o %s -j clash_output", interfaceName)) + execCmd("iptables -t mangle -N mihomo_output") + execCmd("iptables -t mangle -F mihomo_output") + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) + execCmd("iptables -t mangle -A mihomo_output -p udp -m multiport --dports 53,123,137 -j ACCEPT") + execCmd("iptables -t mangle -A mihomo_output -p tcp --dport 53 -j ACCEPT") + execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type LOCAL -j RETURN") + execCmd("iptables -t mangle -A mihomo_output -m addrtype --dst-type BROADCAST -j RETURN") + addLocalnetworkToChain("mihomo_output", bypass) + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_output -p tcp -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -A mihomo_output -p udp -j MARK --set-mark %s", PROXY_FWMARK)) + execCmd(fmt.Sprintf("iptables -t mangle -I OUTPUT -o %s -j mihomo_output", interfaceName)) // set dns output - execCmd("iptables -t nat -N clash_dns_output") - execCmd("iptables -t nat -F clash_dns_output") - execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) - execCmd("iptables -t nat -A clash_dns_output -s 172.17.0.0/16 -j RETURN") - execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -p udp -j REDIRECT --to-ports %d", dnsPort)) - execCmd(fmt.Sprintf("iptables -t nat -A clash_dns_output -p tcp -j REDIRECT --to-ports %d", dnsPort)) - execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j clash_dns_output") - execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j clash_dns_output") + execCmd("iptables -t nat -N mihomo_dns_output") + execCmd("iptables -t nat -F mihomo_dns_output") + execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -m mark --mark %#x -j RETURN", dialer.DefaultRoutingMark.Load())) + execCmd("iptables -t nat -A mihomo_dns_output -s 172.17.0.0/16 -j RETURN") + execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p udp -j REDIRECT --to-ports %d", dnsPort)) + execCmd(fmt.Sprintf("iptables -t nat -A mihomo_dns_output -p tcp -j REDIRECT --to-ports %d", dnsPort)) + execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j mihomo_dns_output") + execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j mihomo_dns_output") return nil } @@ -113,7 +113,7 @@ func CleanupTProxyIPTables() { dialer.DefaultRoutingMark.Store(0) } - if _, err := cmd.ExecCmd("iptables -t mangle -L clash_divert"); err != nil { + if _, err := cmd.ExecCmd("iptables -t mangle -L mihomo_divert"); err != nil { return } @@ -132,7 +132,7 @@ func CleanupTProxyIPTables() { // clean PREROUTING execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) - execCmd("iptables -t mangle -D PREROUTING -j clash_prerouting") + execCmd("iptables -t mangle -D PREROUTING -j mihomo_prerouting") // clean POSTROUTING if interfaceName != "lo" { @@ -140,19 +140,19 @@ func CleanupTProxyIPTables() { } // clean OUTPUT - execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j clash_output", interfaceName)) - execCmd("iptables -t nat -D OUTPUT -p tcp --dport 53 -j clash_dns_output") - execCmd("iptables -t nat -D OUTPUT -p udp --dport 53 -j clash_dns_output") + execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j mihomo_output", interfaceName)) + execCmd("iptables -t nat -D OUTPUT -p tcp --dport 53 -j mihomo_dns_output") + execCmd("iptables -t nat -D OUTPUT -p udp --dport 53 -j mihomo_dns_output") // clean chain - execCmd("iptables -t mangle -F clash_prerouting") - execCmd("iptables -t mangle -X clash_prerouting") - execCmd("iptables -t mangle -F clash_divert") - execCmd("iptables -t mangle -X clash_divert") - execCmd("iptables -t mangle -F clash_output") - execCmd("iptables -t mangle -X clash_output") - execCmd("iptables -t nat -F clash_dns_output") - execCmd("iptables -t nat -X clash_dns_output") + execCmd("iptables -t mangle -F mihomo_prerouting") + execCmd("iptables -t mangle -X mihomo_prerouting") + execCmd("iptables -t mangle -F mihomo_divert") + execCmd("iptables -t mangle -X mihomo_divert") + execCmd("iptables -t mangle -F mihomo_output") + execCmd("iptables -t mangle -X mihomo_output") + execCmd("iptables -t nat -F mihomo_dns_output") + execCmd("iptables -t nat -X mihomo_dns_output") interfaceName = "" tProxyPort = 0 diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go index d3727180..aa0fee19 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -4,10 +4,10 @@ import ( "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/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type UDPListener struct { @@ -32,7 +32,7 @@ func (l *UDPListener) Close() error { return l.packetConn.Close() } -func NewUDP(addr string, in chan<- C.PacketAdapter, natTable C.NatTable, additions ...inbound.Addition) (*UDPListener, error) { +func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPListener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-TPROXY"), @@ -83,24 +83,20 @@ func NewUDP(addr string, in chan<- C.PacketAdapter, natTable C.NatTable, additio // try to unmap 4in6 address lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) } - handlePacketConn(l, in, natTable, buf[:n], lAddr, rAddr, additions...) + handlePacketConn(l, tunnel, buf[:n], lAddr, rAddr, additions...) } }() return rl, nil } -func handlePacketConn(pc net.PacketConn, in chan<- C.PacketAdapter, natTable C.NatTable, buf []byte, lAddr, rAddr netip.AddrPort, additions ...inbound.Addition) { +func handlePacketConn(pc net.PacketConn, tunnel C.Tunnel, buf []byte, lAddr, rAddr netip.AddrPort, additions ...inbound.Addition) { target := socks5.AddrFromStdAddrPort(rAddr) pkt := &packet{ - pc: pc, - lAddr: lAddr, - buf: buf, - in: in, - natTable: natTable, - } - select { - case in <- inbound.NewPacket(target, pkt, C.TPROXY, additions...): - default: + pc: pc, + lAddr: lAddr, + buf: buf, + tunnel: tunnel, } + tunnel.HandleUDPPacket(inbound.NewPacket(target, pkt, C.TPROXY, additions...)) } diff --git a/listener/tuic/server.go b/listener/tuic/server.go index 125c53e1..5d807cbc 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -7,15 +7,15 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/adapter/inbound" - CN "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/sockopt" - C "github.com/Dreamacro/clash/constant" - LC "github.com/Dreamacro/clash/listener/config" - "github.com/Dreamacro/clash/listener/sing" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/tuic" + "github.com/metacubex/mihomo/adapter/inbound" + CN "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/sockopt" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/tuic" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" @@ -31,20 +31,24 @@ type Listener struct { servers []*tuic.Server } -func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (*Listener, error) { +func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-TUIC"), inbound.WithSpecialRules(""), } } - h := &sing.ListenerHandler{ - TcpIn: tcpIn, - UdpIn: udpIn, + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, Type: C.TUIC, Additions: additions, + MuxOption: config.MuxOption, + }) + if err != nil { + return nil, err } - cert, err := CN.ParseCert(config.Certificate, config.PrivateKey) + + cert, err := CN.ParseCert(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } @@ -94,19 +98,18 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet newAdditions = slices.Clone(additions) newAdditions = append(newAdditions, _additions...) } - connCtx := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...) - metadata := sing.ConvertMetadata(connCtx.Metadata()) - if h.IsSpecialFqdn(metadata.Destination.Fqdn) { + conn, metadata := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...) + if h.IsSpecialFqdn(metadata.Host) { go func() { // ParseSpecialFqdn will block, so open a new goroutine _ = h.ParseSpecialFqdn( sing.WithAdditions(context.Background(), newAdditions...), conn, - metadata, + sing.ConvertMetadata(metadata), ) }() return nil } - tcpIn <- connCtx + go tunnel.HandleTCPConn(conn, metadata) return nil } handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error { @@ -115,10 +118,7 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet newAdditions = slices.Clone(additions) newAdditions = append(newAdditions, _additions...) } - select { - case udpIn <- inbound.NewPacket(addr, packet, C.TUIC, newAdditions...): - default: - } + tunnel.HandleUDPPacket(inbound.NewPacket(addr, packet, C.TUIC, newAdditions...)) return nil } diff --git a/listener/tunnel/packet.go b/listener/tunnel/packet.go index 35601e38..165004d6 100644 --- a/listener/tunnel/packet.go +++ b/listener/tunnel/packet.go @@ -3,7 +3,7 @@ package tunnel import ( "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) type packet struct { diff --git a/listener/tunnel/tcp.go b/listener/tunnel/tcp.go index d660d2b8..794dc8ac 100644 --- a/listener/tunnel/tcp.go +++ b/listener/tunnel/tcp.go @@ -4,10 +4,10 @@ import ( "fmt" "net" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type Listener struct { @@ -34,14 +34,12 @@ func (l *Listener) Close() error { return l.listener.Close() } -func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { +func (l *Listener) handleTCP(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) { N.TCPKeepAlive(conn) - ctx := inbound.NewSocket(l.target, conn, C.TUNNEL, additions...) - ctx.Metadata().SpecialProxy = l.proxy - in <- ctx + tunnel.HandleTCPConn(inbound.NewSocket(l.target, conn, C.TUNNEL, additions...)) } -func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { +func New(addr, target, proxy string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err @@ -59,6 +57,10 @@ func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbou addr: addr, } + if proxy != "" { + additions = append([]inbound.Addition{inbound.WithSpecialProxy(proxy)}, additions...) + } + go func() { for { c, err := l.Accept() @@ -68,7 +70,7 @@ func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbou } continue } - go rl.handleTCP(c, in, additions...) + go rl.handleTCP(c, tunnel, additions...) } }() diff --git a/listener/tunnel/udp.go b/listener/tunnel/udp.go index 0795084c..f7d980ab 100644 --- a/listener/tunnel/udp.go +++ b/listener/tunnel/udp.go @@ -4,10 +4,10 @@ import ( "fmt" "net" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type PacketConn struct { @@ -34,7 +34,7 @@ func (l *PacketConn) Close() error { return l.conn.Close() } -func NewUDP(addr, target, proxy string, in chan<- C.PacketAdapter, additions ...inbound.Addition) (*PacketConn, error) { +func NewUDP(addr, target, proxy string, tunnel C.Tunnel, additions ...inbound.Addition) (*PacketConn, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -51,6 +51,11 @@ func NewUDP(addr, target, proxy string, in chan<- C.PacketAdapter, additions ... proxy: proxy, addr: addr, } + + if proxy != "" { + additions = append([]inbound.Addition{inbound.WithSpecialProxy(proxy)}, additions...) + } + go func() { for { buf := pool.Get(pool.UDPBufferSize) @@ -62,24 +67,19 @@ func NewUDP(addr, target, proxy string, in chan<- C.PacketAdapter, additions ... } continue } - sl.handleUDP(l, in, buf[:n], remoteAddr, additions...) + sl.handleUDP(l, tunnel, buf[:n], remoteAddr, additions...) } }() return sl, nil } -func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, addr net.Addr, additions ...inbound.Addition) { - packet := &packet{ +func (l *PacketConn) handleUDP(pc net.PacketConn, tunnel C.Tunnel, buf []byte, addr net.Addr, additions ...inbound.Addition) { + cPacket := &packet{ pc: pc, rAddr: addr, payload: buf, } - ctx := inbound.NewPacket(l.target, packet, C.TUNNEL, additions...) - ctx.Metadata().SpecialProxy = l.proxy - select { - case in <- ctx: - default: - } + tunnel.HandleUDPPacket(inbound.NewPacket(l.target, cPacket, C.TUNNEL, additions...)) } diff --git a/log/log.go b/log/log.go index acddeaff..6f565e7c 100644 --- a/log/log.go +++ b/log/log.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/Dreamacro/clash/common/observable" + "github.com/metacubex/mihomo/common/observable" log "github.com/sirupsen/logrus" ) @@ -19,8 +19,9 @@ func init() { log.SetOutput(os.Stdout) log.SetLevel(log.DebugLevel) log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00", + FullTimestamp: true, + TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00", + EnvironmentOverrideColors: true, }) } diff --git a/main.go b/main.go index e13b8dc8..425b4661 100644 --- a/main.go +++ b/main.go @@ -3,25 +3,26 @@ package main import ( "flag" "fmt" - "github.com/Dreamacro/clash/constant/features" "os" "os/signal" "path/filepath" "runtime" "strings" + "sync" "syscall" + "time" - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/hub" - "github.com/Dreamacro/clash/hub/executor" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/config" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + "github.com/metacubex/mihomo/hub" + "github.com/metacubex/mihomo/hub/executor" + "github.com/metacubex/mihomo/log" "go.uber.org/automaxprocs/maxprocs" ) var ( - flagset map[string]bool version bool testConfig bool geodataMode bool @@ -30,32 +31,29 @@ var ( externalUI string externalController string secret string + updateGeoMux sync.Mutex + updatingGeo = false ) func init() { - flag.StringVar(&homeDir, "d", "", "set configuration directory") - flag.StringVar(&configFile, "f", "", "specify configuration file") - flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory") - flag.StringVar(&externalController, "ext-ctl", "", "override external controller address") - flag.StringVar(&secret, "secret", "", "override secret for RESTful API") + flag.StringVar(&homeDir, "d", os.Getenv("CLASH_HOME_DIR"), "set configuration directory") + flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file") + flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory") + flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address") + flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API") flag.BoolVar(&geodataMode, "m", false, "set geodata mode") - flag.BoolVar(&version, "v", false, "show current version of clash") + flag.BoolVar(&version, "v", false, "show current version of mihomo") flag.BoolVar(&testConfig, "t", false, "test configuration and exit") flag.Parse() - - flagset = map[string]bool{} - flag.Visit(func(f *flag.Flag) { - flagset[f.Name] = true - }) } func main() { _, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) if version { - fmt.Printf("Clash Meta %s %s %s with %s %s\n", + fmt.Printf("Mihomo Meta %s %s %s with %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime) - if len(features.TAGS) != 0 { - fmt.Printf("Use tags: %s\n", strings.Join(features.TAGS, ", ")) + if tags := features.Tags(); len(tags) != 0 { + fmt.Printf("Use tags: %s\n", strings.Join(tags, ", ")) } return @@ -99,13 +97,13 @@ func main() { } var options []hub.Option - if flagset["ext-ui"] { + if externalUI != "" { options = append(options, hub.WithExternalUI(externalUI)) } - if flagset["ext-ctl"] { + if externalController != "" { options = append(options, hub.WithExternalController(externalController)) } - if flagset["secret"] { + if secret != "" { options = append(options, hub.WithSecret(secret)) } @@ -113,9 +111,69 @@ func main() { log.Fatalln("Parse config error: %s", err.Error()) } + if C.GeoAutoUpdate { + ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) + + log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval) + go func() { + for range ticker.C { + updateGeoDatabases() + } + }() + } + defer executor.Shutdown() - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - <-sigCh + termSign := make(chan os.Signal, 1) + hupSign := make(chan os.Signal, 1) + signal.Notify(termSign, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(hupSign, syscall.SIGHUP) + for { + select { + case <-termSign: + return + case <-hupSign: + if cfg, err := executor.ParseWithPath(C.Path.Config()); err == nil { + executor.ApplyConfig(cfg, true) + } else { + log.Errorln("Parse config error: %s", err.Error()) + } + } + } +} + +func updateGeoDatabases() { + log.Infoln("[GEO] Start updating GEO database") + updateGeoMux.Lock() + + if updatingGeo { + updateGeoMux.Unlock() + log.Infoln("[GEO] GEO database is updating, skip") + return + } + + updatingGeo = true + updateGeoMux.Unlock() + + go func() { + defer func() { + updatingGeo = false + }() + + log.Infoln("[GEO] Updating GEO database") + + if err := config.UpdateGeoDatabases(); err != nil { + log.Errorln("[GEO] update GEO database error: %s", err.Error()) + return + } + + cfg, err := executor.ParseWithPath(C.Path.Config()) + if err != nil { + log.Errorln("[GEO] update GEO database failed: %s", err.Error()) + return + } + + log.Infoln("[GEO] Update GEO database success, apply new config") + executor.ApplyConfig(cfg, false) + }() } diff --git a/ntp/service.go b/ntp/service.go index c5506197..4c95045a 100644 --- a/ntp/service.go +++ b/ntp/service.go @@ -5,9 +5,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/proxydialer" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/proxydialer" + "github.com/metacubex/mihomo/log" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/ntp" diff --git a/rules/common/domain.go b/rules/common/domain.go index 35a06a70..23f21185 100644 --- a/rules/common/domain.go +++ b/rules/common/domain.go @@ -3,7 +3,7 @@ package common import ( "strings" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type Domain struct { diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go index d945f200..ec01293a 100644 --- a/rules/common/domain_keyword.go +++ b/rules/common/domain_keyword.go @@ -3,7 +3,7 @@ package common import ( "strings" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type DomainKeyword struct { diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go index b13036a3..b7b1794d 100644 --- a/rules/common/domain_suffix.go +++ b/rules/common/domain_suffix.go @@ -3,7 +3,7 @@ package common import ( "strings" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type DomainSuffix struct { diff --git a/rules/common/final.go b/rules/common/final.go index 8aa5ed7b..d3a415a0 100644 --- a/rules/common/final.go +++ b/rules/common/final.go @@ -1,7 +1,7 @@ package common import ( - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type Match struct { diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 2f96c2ef..3a29fae4 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -4,12 +4,12 @@ import ( "fmt" "strings" - "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/geodata/router" - "github.com/Dreamacro/clash/component/mmdb" - "github.com/Dreamacro/clash/component/resolver" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/geodata" + "github.com/metacubex/mihomo/component/geodata/router" + "github.com/metacubex/mihomo/component/mmdb" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type GEOIP struct { diff --git a/rules/common/geosite.go b/rules/common/geosite.go index e89dc19b..1e3c1ab5 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -3,19 +3,19 @@ package common import ( "fmt" - "github.com/Dreamacro/clash/component/geodata" - _ "github.com/Dreamacro/clash/component/geodata/memconservative" - "github.com/Dreamacro/clash/component/geodata/router" - _ "github.com/Dreamacro/clash/component/geodata/standard" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/geodata" + _ "github.com/metacubex/mihomo/component/geodata/memconservative" + "github.com/metacubex/mihomo/component/geodata/router" + _ "github.com/metacubex/mihomo/component/geodata/standard" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type GEOSITE struct { *Base country string adapter string - matcher *router.DomainMatcher + matcher router.DomainMatcher recodeSize int } @@ -39,7 +39,7 @@ func (gs *GEOSITE) Payload() string { return gs.country } -func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher { +func (gs *GEOSITE) GetDomainMatcher() router.DomainMatcher { return gs.matcher } diff --git a/rules/common/in_name.go b/rules/common/in_name.go index 1e2abe15..9b14ef6a 100644 --- a/rules/common/in_name.go +++ b/rules/common/in_name.go @@ -2,7 +2,7 @@ package common import ( "fmt" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" "strings" ) diff --git a/rules/common/in_type.go b/rules/common/in_type.go index 453045d8..fc73b208 100644 --- a/rules/common/in_type.go +++ b/rules/common/in_type.go @@ -2,7 +2,7 @@ package common import ( "fmt" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" "strings" ) diff --git a/rules/common/in_user.go b/rules/common/in_user.go index 24f4b2e5..ebe881af 100644 --- a/rules/common/in_user.go +++ b/rules/common/in_user.go @@ -2,7 +2,7 @@ package common import ( "fmt" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" "strings" ) diff --git a/rules/common/ipcidr.go b/rules/common/ipcidr.go index 8ab6cf5a..663c9397 100644 --- a/rules/common/ipcidr.go +++ b/rules/common/ipcidr.go @@ -3,7 +3,7 @@ package common import ( "net/netip" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type IPCIDROption func(*IPCIDR) @@ -22,7 +22,7 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { type IPCIDR struct { *Base - ipnet *netip.Prefix + ipnet netip.Prefix adapter string isSourceIP bool noResolveIP bool @@ -63,7 +63,7 @@ func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) ipcidr := &IPCIDR{ Base: &Base{}, - ipnet: &ipnet, + ipnet: ipnet, adapter: adapter, } diff --git a/rules/common/ipsuffix.go b/rules/common/ipsuffix.go index b01557dc..3251faf8 100644 --- a/rules/common/ipsuffix.go +++ b/rules/common/ipsuffix.go @@ -1,7 +1,7 @@ package common import ( - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" "net/netip" ) diff --git a/rules/common/network_type.go b/rules/common/network_type.go index 1184ba89..83a332d8 100644 --- a/rules/common/network_type.go +++ b/rules/common/network_type.go @@ -2,7 +2,7 @@ package common import ( "fmt" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" "strings" ) diff --git a/rules/common/port.go b/rules/common/port.go index 334d083f..ec76cf30 100644 --- a/rules/common/port.go +++ b/rules/common/port.go @@ -3,8 +3,8 @@ package common import ( "fmt" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" ) type Port struct { diff --git a/rules/common/process.go b/rules/common/process.go index e972d2bc..ce643594 100644 --- a/rules/common/process.go +++ b/rules/common/process.go @@ -3,7 +3,7 @@ package common import ( "strings" - C "github.com/Dreamacro/clash/constant" + C "github.com/metacubex/mihomo/constant" ) type Process struct { diff --git a/rules/common/uid.go b/rules/common/uid.go index 3b20928f..de46c409 100644 --- a/rules/common/uid.go +++ b/rules/common/uid.go @@ -4,9 +4,9 @@ import ( "fmt" "runtime" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type Uid struct { diff --git a/rules/logic/logic.go b/rules/logic/logic.go index a53503df..fde96e19 100644 --- a/rules/logic/logic.go +++ b/rules/logic/logic.go @@ -2,12 +2,12 @@ package logic import ( "fmt" + list "github.com/bahlo/generic-list-go" "regexp" "strings" - "github.com/Dreamacro/clash/common/collections" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/rules/common" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/rules/common" ) type Logic struct { @@ -133,7 +133,7 @@ func (logic *Logic) payloadToRule(subPayload string, parseRule ParseRuleFunc) (C } func (logic *Logic) format(payload string) ([]Range, error) { - stack := collections.NewStack() + stack := list.New[Range]() num := 0 subRanges := make([]Range, 0) for i, c := range payload { @@ -144,15 +144,16 @@ func (logic *Logic) format(payload string) ([]Range, error) { } num++ - stack.Push(sr) + stack.PushBack(sr) } else if c == ')' { if stack.Len() == 0 { return nil, fmt.Errorf("missing '('") } - sr := stack.Pop().(Range) - sr.end = i - subRanges = append(subRanges, sr) + sr := stack.Back() + stack.Remove(sr) + sr.Value.end = i + subRanges = append(subRanges, sr.Value) } } diff --git a/rules/logic_test/logic_test.go b/rules/logic_test/logic_test.go index 52318b3f..e88c8578 100644 --- a/rules/logic_test/logic_test.go +++ b/rules/logic_test/logic_test.go @@ -2,10 +2,10 @@ package logic_test import ( // https://github.com/golang/go/wiki/CodeReviewComments#import-dot - . "github.com/Dreamacro/clash/rules/logic" + . "github.com/metacubex/mihomo/rules/logic" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/rules" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/rules" "github.com/stretchr/testify/assert" "testing" ) diff --git a/rules/parser.go b/rules/parser.go index df790bc3..b1baa758 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -2,10 +2,10 @@ package rules import ( "fmt" - C "github.com/Dreamacro/clash/constant" - RC "github.com/Dreamacro/clash/rules/common" - "github.com/Dreamacro/clash/rules/logic" - RP "github.com/Dreamacro/clash/rules/provider" + C "github.com/metacubex/mihomo/constant" + RC "github.com/metacubex/mihomo/rules/common" + "github.com/metacubex/mihomo/rules/logic" + RP "github.com/metacubex/mihomo/rules/provider" ) func ParseRule(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error) { diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index e187e213..f8042164 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -2,8 +2,8 @@ package provider import ( "fmt" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "strings" ) @@ -76,7 +76,11 @@ func ruleParse(ruleRaw string) (string, string, []string) { } else if len(item) == 2 { return item[0], item[1], nil } else if len(item) > 2 { - return item[0], item[1], item[2:] + if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" { + return item[0], strings.Join(item[1:len(item)], ","), nil + } else { + return item[0], item[1], item[2:] + } } return "", "", nil @@ -85,7 +89,7 @@ func ruleParse(ruleRaw string) (string, string, []string) { func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { - case "MATCH", "SUB-RULE": + case "MATCH": return nil, fmt.Errorf("unsupported rule type on rule-set") default: return parse(tp, payload, target, params, nil) diff --git a/rules/provider/domain_strategy.go b/rules/provider/domain_strategy.go index d686d598..c0787d58 100644 --- a/rules/provider/domain_strategy.go +++ b/rules/provider/domain_strategy.go @@ -1,9 +1,9 @@ package provider import ( - "github.com/Dreamacro/clash/component/trie" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type domainStrategy struct { diff --git a/rules/provider/ipcidr_strategy.go b/rules/provider/ipcidr_strategy.go index f54302f1..321e901a 100644 --- a/rules/provider/ipcidr_strategy.go +++ b/rules/provider/ipcidr_strategy.go @@ -1,9 +1,9 @@ package provider import ( - "github.com/Dreamacro/clash/component/trie" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/trie" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) type ipcidrStrategy struct { diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 0fbfb2cc..a867d570 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -5,10 +5,11 @@ import ( "fmt" "time" - "github.com/Dreamacro/clash/common/structure" - "github.com/Dreamacro/clash/component/resource" - C "github.com/Dreamacro/clash/constant" - P "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/common/structure" + "github.com/metacubex/mihomo/component/resource" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + P "github.com/metacubex/mihomo/constant/provider" ) var ( @@ -62,7 +63,7 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t case "http": if schema.Path != "" { path := C.Path.Resolve(schema.Path) - if !C.Path.IsSafePath(path) { + if !features.CMFA && !C.Path.IsSafePath(path) { return nil, fmt.Errorf("%w: %s", errSubPath, path) } vehicle = resource.NewHTTPVehicle(schema.URL, path) diff --git a/rules/provider/patch_android.go b/rules/provider/patch_android.go new file mode 100644 index 00000000..2bd5ffc8 --- /dev/null +++ b/rules/provider/patch_android.go @@ -0,0 +1,27 @@ +//go:build android && cmfa + +package provider + +import "time" + +var ( + suspended bool +) + +type UpdatableProvider interface { + UpdatedAt() time.Time +} + +func (f *ruleSetProvider) UpdatedAt() time.Time { + return f.Fetcher.UpdatedAt +} + +func (rp *ruleSetProvider) Close() error { + rp.Fetcher.Destroy() + + return nil +} + +func Suspend(s bool) { + suspended = s +} diff --git a/rules/provider/provider.go b/rules/provider/provider.go index 65d21d2f..adc2e44a 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -9,10 +9,10 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/resource" - C "github.com/Dreamacro/clash/constant" - P "github.com/Dreamacro/clash/constant/provider" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/component/resource" + C "github.com/metacubex/mihomo/constant" + P "github.com/metacubex/mihomo/constant/provider" ) var ( diff --git a/rules/provider/rule_set.go b/rules/provider/rule_set.go index 45cddf6c..1d940188 100644 --- a/rules/provider/rule_set.go +++ b/rules/provider/rule_set.go @@ -2,9 +2,9 @@ package provider import ( "fmt" - C "github.com/Dreamacro/clash/constant" - P "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/rules/common" + C "github.com/metacubex/mihomo/constant" + P "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/rules/common" ) type RuleSet struct { diff --git a/test/.golangci.yaml b/test/.golangci.yaml index b65afc5e..e1fbbf76 100644 --- a/test/.golangci.yaml +++ b/test/.golangci.yaml @@ -10,7 +10,7 @@ linters-settings: gci: sections: - standard - - prefix(github.com/Dreamacro/clash) + - prefix(github.com/metacubex/mihomo) - default staticcheck: go: '1.19' diff --git a/test/README.md b/test/README.md index a95f3aea..e9fa5630 100644 --- a/test/README.md +++ b/test/README.md @@ -1,4 +1,4 @@ -## Clash testing suit +## Mihomo testing suit ### Protocol testing suit @@ -51,8 +51,8 @@ $ make test benchmark (Linux) > Cannot represent the throughput of the protocol on your machine -> but you can compare the corresponding throughput of the protocol on clash -> (change chunkSize to measure the maximum throughput of clash on your machine) +> but you can compare the corresponding throughput of the protocol on mihomo +> (change chunkSize to measure the maximum throughput of mihomo on your machine) ``` $ make benchmark diff --git a/test/clash_test.go b/test/clash_test.go index 60b99791..90ac9d22 100644 --- a/test/clash_test.go +++ b/test/clash_test.go @@ -16,13 +16,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/hub/executor" - "github.com/Dreamacro/clash/transport/socks5" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/hub/executor" + "github.com/metacubex/mihomo/transport/socks5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -658,7 +658,7 @@ func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) { }) } -func TestClash_Basic(t *testing.T) { +func TestMihomo_Basic(t *testing.T) { basic := ` mixed-port: 10000 log-level: silent diff --git a/test/dns_test.go b/test/dns_test.go index 8e30ba98..f45ffbe0 100644 --- a/test/dns_test.go +++ b/test/dns_test.go @@ -21,7 +21,7 @@ func exchange(address, domain string, tp uint16) ([]dns.RR, error) { return r.Answer, nil } -func TestClash_DNS(t *testing.T) { +func TestMihomo_DNS(t *testing.T) { basic := ` log-level: silent dns: @@ -49,11 +49,11 @@ dns: assert.Empty(t, rr) } -func TestClash_DNSHostAndFakeIP(t *testing.T) { +func TestMihomo_DNSHostAndFakeIP(t *testing.T) { basic := ` log-level: silent hosts: - foo.clash.dev: 1.1.1.1 + foo.mihomo.dev: 1.1.1.1 dns: enable: true listen: 0.0.0.0:8553 @@ -81,7 +81,7 @@ dns: {"foo.org", "198.18.0.4"}, {"bar.org", "198.18.0.5"}, {"foo.org", "198.18.0.4"}, - {"foo.clash.dev", "1.1.1.1"}, + {"foo.mihomo.dev", "1.1.1.1"}, } for _, pair := range list { diff --git a/test/go.mod b/test/go.mod index 5582dd04..92374886 100644 --- a/test/go.mod +++ b/test/go.mod @@ -1,17 +1,17 @@ -module clash-test +module mihomo-test -go 1.19 +go 1.20 require ( - github.com/Dreamacro/clash v0.0.0 github.com/docker/docker v20.10.21+incompatible github.com/docker/go-connections v0.4.0 - github.com/miekg/dns v1.1.55 + github.com/metacubex/mihomo v0.0.0 + github.com/miekg/dns v1.1.57 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.15.0 + golang.org/x/net v0.18.0 ) -replace github.com/Dreamacro/clash => ../ +replace github.com/metacubex/mihomo => ../ require ( github.com/3andne/restls-client-go v0.1.6 // indirect @@ -20,45 +20,51 @@ require ( github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/cilium/ebpf v0.11.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/cilium/ebpf v0.12.3 // indirect github.com/coreos/go-iptables v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.5.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/go-units v0.4.0 // indirect github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.3.1 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect - github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect + github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c // indirect github.com/josharian/native v1.1.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.16.7 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect - github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect - github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf // indirect - github.com/metacubex/sing-shadowsocks v0.2.4 // indirect - github.com/metacubex/sing-shadowsocks2 v0.1.3 // indirect - github.com/metacubex/sing-tun v0.1.11 // indirect - github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 // indirect - github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 // indirect + github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect + github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 // indirect + github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b // indirect + github.com/metacubex/sing-shadowsocks v0.2.5 // indirect + github.com/metacubex/sing-shadowsocks2 v0.1.4 // indirect + github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd // indirect + github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 // indirect + github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect @@ -72,13 +78,14 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/puzpuzpuz/xsync/v2 v2.5.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.3 // indirect + github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect - github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a // indirect - github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect + github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c // indirect + github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect @@ -86,7 +93,7 @@ require ( github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/samber/lo v1.38.1 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect - github.com/shirou/gopsutil/v3 v3.23.8 // indirect + github.com/shirou/gopsutil/v3 v3.23.10 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect @@ -96,18 +103,20 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zhangyunhao116/fastrand v0.3.0 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect + go.uber.org/mock v0.3.0 // indirect + go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.15.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/test/go.sum b/test/go.sum index 609d2fcb..af34b356 100644 --- a/test/go.sum +++ b/test/go.sum @@ -11,21 +11,27 @@ 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/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 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= -github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= -github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 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= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog= github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -42,21 +48,26 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4Rfsap github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU= +github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -64,18 +75,18 @@ github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ= -github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs= +github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM= +github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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= @@ -85,36 +96,40 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww= -github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg= -github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8= -github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8= -github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0= -github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI= -github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y= -github.com/metacubex/sing-shadowsocks2 v0.1.3/go.mod h1:5Mt93RlmRlIcDmvtapkhQJ8YTRGLFhHciLYopJjs7j8= -github.com/metacubex/sing-tun v0.1.11 h1:B8meDewklvKkeUfjqR2ViuYLam0/m4IgkTi3qcJIOuc= -github.com/metacubex/sing-tun v0.1.11/go.mod h1:vbki176Y5sxXC1DWXucrPh3q5j8cKai1D87y8m8rjQc= -github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 h1:AqqZCr9gOeKdO6oIzFh4b2puOUFcw8MdpmGHWRehyX8= -github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8/go.mod h1:tyJg7b4s8NrSztl/Y1ajA7X0sJLlIsEJWkgRVocjmgY= -github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA= -github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs= +github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c= +github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y= +github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= +github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b h1:7XXoEePvxfkQN9b2wB8UXU3uzb9uL8syEFF7A9VAKKQ= +github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b/go.mod h1:Gu5/zqZDd5G1AUtoV2yjAPWOEy7zwbU2DBUjdxJh0Kw= +github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc= +github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo= +github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE= +github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k= +github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY= +github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA= +github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY= +github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM= +github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk= +github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -145,23 +160,25 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c= -github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= +github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= 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-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= -github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= 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.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a h1:b89t6Mjgk4rJ5lrNMnCzy1/J116XkhgdB3YNd9FHyF4= -github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I= -github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= +github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c h1:uask61Pxc3nGqsOSjqnBKrwfODWRoEa80lXm04LNk0E= +github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= +github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 h1:ncKb5tVOsCQgCsv6UpsA0jinbNb5OQ5GMPJlyQP3EHM= +github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07/go.mod h1:u/MZf32xPG8jEKe3t+xUV67EBnKtDtCaPhsJQOQGUYU= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= @@ -176,8 +193,8 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= -github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= -github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= +github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= +github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -211,44 +228,44 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 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/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= +go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -257,24 +274,22 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -282,9 +297,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 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= diff --git a/test/hysteria_test.go b/test/hysteria_test.go index ae638e62..e783d9c2 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/assert" ) -func TestClash_Hysteria(t *testing.T) { +func TestMihomo_Hysteria(t *testing.T) { cfg := &container.Config{ Image: ImageHysteria, ExposedPorts: defaultExposedPorts, diff --git a/test/snell_test.go b/test/snell_test.go index ae9ce0c1..311ca7b7 100644 --- a/test/snell_test.go +++ b/test/snell_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/require" ) -func TestClash_SnellObfsHTTP(t *testing.T) { +func TestMihomo_SnellObfsHTTP(t *testing.T) { cfg := &container.Config{ Image: ImageSnell, ExposedPorts: defaultExposedPorts, @@ -44,7 +44,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) { testSuit(t, proxy) } -func TestClash_SnellObfsTLS(t *testing.T) { +func TestMihomo_SnellObfsTLS(t *testing.T) { cfg := &container.Config{ Image: ImageSnell, ExposedPorts: defaultExposedPorts, @@ -77,7 +77,7 @@ func TestClash_SnellObfsTLS(t *testing.T) { testSuit(t, proxy) } -func TestClash_Snell(t *testing.T) { +func TestMihomo_Snell(t *testing.T) { cfg := &container.Config{ Image: ImageSnell, ExposedPorts: defaultExposedPorts, @@ -107,7 +107,7 @@ func TestClash_Snell(t *testing.T) { testSuit(t, proxy) } -func TestClash_Snellv3(t *testing.T) { +func TestMihomo_Snellv3(t *testing.T) { cfg := &container.Config{ Image: ImageSnell, ExposedPorts: defaultExposedPorts, diff --git a/test/ss_test.go b/test/ss_test.go index bec1734b..866fe3a8 100644 --- a/test/ss_test.go +++ b/test/ss_test.go @@ -8,13 +8,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/require" ) -func TestClash_Shadowsocks(t *testing.T) { +func TestMihomo_Shadowsocks(t *testing.T) { for _, method := range []string{ "aes-128-ctr", "aes-192-ctr", @@ -30,7 +30,7 @@ func TestClash_Shadowsocks(t *testing.T) { "xchacha20-ietf-poly1305", } { t.Run(method, func(t *testing.T) { - testClash_Shadowsocks(t, method, "FzcLbKs2dY9mhL") + testMihomo_Shadowsocks(t, method, "FzcLbKs2dY9mhL") }) } for _, method := range []string{ @@ -39,17 +39,17 @@ func TestClash_Shadowsocks(t *testing.T) { "chacha20-ietf-poly1305", } { t.Run(method, func(t *testing.T) { - testClash_ShadowsocksRust(t, method, "FzcLbKs2dY9mhL") + testMihomo_ShadowsocksRust(t, method, "FzcLbKs2dY9mhL") }) } } -func TestClash_Shadowsocks2022(t *testing.T) { +func TestMihomo_Shadowsocks2022(t *testing.T) { for _, method := range []string{ "2022-blake3-aes-128-gcm", } { t.Run(method, func(t *testing.T) { - testClash_ShadowsocksRust(t, method, mkKey(16)) + testMihomo_ShadowsocksRust(t, method, mkKey(16)) }) } for _, method := range []string{ @@ -57,7 +57,7 @@ func TestClash_Shadowsocks2022(t *testing.T) { "2022-blake3-chacha20-poly1305", } { t.Run(method, func(t *testing.T) { - testClash_ShadowsocksRust(t, method, mkKey(32)) + testMihomo_ShadowsocksRust(t, method, mkKey(32)) }) } } @@ -68,7 +68,7 @@ func mkKey(bits int) string { return base64.StdEncoding.EncodeToString(k) } -func testClash_Shadowsocks(t *testing.T, method string, password string) { +func testMihomo_Shadowsocks(t *testing.T, method string, password string) { cfg := &container.Config{ Image: ImageShadowsocks, Env: []string{ @@ -102,7 +102,7 @@ func testClash_Shadowsocks(t *testing.T, method string, password string) { testSuit(t, proxy) } -func testClash_ShadowsocksRust(t *testing.T, method string, password string) { +func testMihomo_ShadowsocksRust(t *testing.T, method string, password string) { cfg := &container.Config{ Image: ImageShadowsocksRust, Entrypoint: []string{"ssserver"}, @@ -134,7 +134,7 @@ func testClash_ShadowsocksRust(t *testing.T, method string, password string) { testSuit(t, proxy) } -func TestClash_ShadowsocksObfsHTTP(t *testing.T) { +func TestMihomo_ShadowsocksObfsHTTP(t *testing.T) { cfg := &container.Config{ Image: ImageShadowsocks, Env: []string{ @@ -172,7 +172,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) { testSuit(t, proxy) } -func TestClash_ShadowsocksObfsTLS(t *testing.T) { +func TestMihomo_ShadowsocksObfsTLS(t *testing.T) { cfg := &container.Config{ Image: ImageShadowsocks, Env: []string{ @@ -210,7 +210,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) { testSuit(t, proxy) } -func TestClash_ShadowsocksV2RayPlugin(t *testing.T) { +func TestMihomo_ShadowsocksV2RayPlugin(t *testing.T) { cfg := &container.Config{ Image: ImageShadowsocks, Env: []string{ @@ -280,7 +280,7 @@ func Benchmark_Shadowsocks(b *testing.B) { benchmarkProxy(b, proxy) } -func TestClash_ShadowsocksUoT(t *testing.T) { +func TestMihomo_ShadowsocksUoT(t *testing.T) { configPath := C.Path.Resolve("xray-shadowsocks.json") cfg := &container.Config{ diff --git a/test/trojan_test.go b/test/trojan_test.go index 4885fd3b..c6b1fea0 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -6,13 +6,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/require" ) -func TestClash_Trojan(t *testing.T) { +func TestMihomo_Trojan(t *testing.T) { cfg := &container.Config{ Image: ImageTrojan, ExposedPorts: defaultExposedPorts, @@ -48,7 +48,7 @@ func TestClash_Trojan(t *testing.T) { testSuit(t, proxy) } -func TestClash_TrojanGrpc(t *testing.T) { +func TestMihomo_TrojanGrpc(t *testing.T) { cfg := &container.Config{ Image: ImageXray, ExposedPorts: defaultExposedPorts, @@ -87,7 +87,7 @@ func TestClash_TrojanGrpc(t *testing.T) { testSuit(t, proxy) } -func TestClash_TrojanWebsocket(t *testing.T) { +func TestMihomo_TrojanWebsocket(t *testing.T) { cfg := &container.Config{ Image: ImageTrojanGo, ExposedPorts: defaultExposedPorts, @@ -123,7 +123,7 @@ func TestClash_TrojanWebsocket(t *testing.T) { testSuit(t, proxy) } -func TestClash_TrojanXTLS(t *testing.T) { +func TestMihomo_TrojanXTLS(t *testing.T) { cfg := &container.Config{ Image: ImageXray, ExposedPorts: defaultExposedPorts, diff --git a/test/vless_test.go b/test/vless_test.go index b75fb3ad..d0e6f071 100644 --- a/test/vless_test.go +++ b/test/vless_test.go @@ -5,14 +5,14 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/assert" ) // TODO: fix udp test -func TestClash_VlessTLS(t *testing.T) { +func TestMihomo_VlessTLS(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -51,7 +51,7 @@ func TestClash_VlessTLS(t *testing.T) { } // TODO: fix udp test -func TestClash_VlessXTLS(t *testing.T) { +func TestMihomo_VlessXTLS(t *testing.T) { cfg := &container.Config{ Image: ImageXray, ExposedPorts: defaultExposedPorts, @@ -81,7 +81,6 @@ func TestClash_VlessXTLS(t *testing.T) { ServerName: "example.org", UDP: true, Flow: "xtls-rprx-direct", - FlowShow: true, }) if err != nil { assert.FailNow(t, err.Error()) @@ -92,7 +91,7 @@ func TestClash_VlessXTLS(t *testing.T) { } // TODO: fix udp test -func TestClash_VlessWS(t *testing.T) { +func TestMihomo_VlessWS(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, diff --git a/test/vmess_test.go b/test/vmess_test.go index fd83fff8..80c3d4d8 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/Dreamacro/clash/adapter/outbound" - C "github.com/Dreamacro/clash/constant" "github.com/docker/docker/api/types/container" + "github.com/metacubex/mihomo/adapter/outbound" + C "github.com/metacubex/mihomo/constant" "github.com/stretchr/testify/require" ) -func TestClash_Vmess(t *testing.T) { +func TestMihomo_Vmess(t *testing.T) { configPath := C.Path.Resolve("vmess.json") cfg := &container.Config{ @@ -44,7 +44,7 @@ func TestClash_Vmess(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessAuthenticatedLength(t *testing.T) { +func TestMihomo_VmessAuthenticatedLength(t *testing.T) { configPath := C.Path.Resolve("vmess.json") cfg := &container.Config{ @@ -78,7 +78,7 @@ func TestClash_VmessAuthenticatedLength(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessPacketAddr(t *testing.T) { +func TestMihomo_VmessPacketAddr(t *testing.T) { configPath := C.Path.Resolve("vmess.json") cfg := &container.Config{ @@ -112,7 +112,7 @@ func TestClash_VmessPacketAddr(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessTLS(t *testing.T) { +func TestMihomo_VmessTLS(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -149,7 +149,7 @@ func TestClash_VmessTLS(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessHTTP2(t *testing.T) { +func TestMihomo_VmessHTTP2(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -191,7 +191,7 @@ func TestClash_VmessHTTP2(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessHTTP(t *testing.T) { +func TestMihomo_VmessHTTP(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -241,7 +241,7 @@ func TestClash_VmessHTTP(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessWebsocket(t *testing.T) { +func TestMihomo_VmessWebsocket(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -274,7 +274,7 @@ func TestClash_VmessWebsocket(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessWebsocketTLS(t *testing.T) { +func TestMihomo_VmessWebsocketTLS(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -311,7 +311,7 @@ func TestClash_VmessWebsocketTLS(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessGrpc(t *testing.T) { +func TestMihomo_VmessGrpc(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -352,7 +352,7 @@ func TestClash_VmessGrpc(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessWebsocket0RTT(t *testing.T) { +func TestMihomo_VmessWebsocket0RTT(t *testing.T) { cfg := &container.Config{ Image: ImageVmess, ExposedPorts: defaultExposedPorts, @@ -390,7 +390,7 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) { testSuit(t, proxy) } -func TestClash_VmessWebsocketXray0RTT(t *testing.T) { +func TestMihomo_VmessWebsocketXray0RTT(t *testing.T) { cfg := &container.Config{ Image: ImageXray, ExposedPorts: defaultExposedPorts, diff --git a/transport/gun/gun.go b/transport/gun/gun.go index e98f7fb5..cf986c8e 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -17,10 +17,10 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/buf" - "github.com/Dreamacro/clash/common/pool" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/buf" + "github.com/metacubex/mihomo/common/pool" + tlsC "github.com/metacubex/mihomo/component/tls" "golang.org/x/net/http2" ) @@ -43,7 +43,7 @@ type Conn struct { transport *TransportWrap writer *io.PipeWriter once sync.Once - close *atomic.Bool + close atomic.Bool err error remain int br *bufio.Reader @@ -209,11 +209,11 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re if realityConfig == nil { if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists { utlsConn := tlsC.UClient(pconn, cfg, fingerprint) - if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil { + if err := utlsConn.HandshakeContext(ctx); err != nil { pconn.Close() return nil, err } - state := utlsConn.(*tlsC.UConn).ConnectionState() + state := utlsConn.ConnectionState() if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { utlsConn.Close() return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) diff --git a/transport/hysteria/congestion/brutal.go b/transport/hysteria/congestion/brutal.go index 9992f6a0..601949de 100644 --- a/transport/hysteria/congestion/brutal.go +++ b/transport/hysteria/congestion/brutal.go @@ -8,11 +8,13 @@ import ( const ( initMaxDatagramSize = 1252 - pktInfoSlotCount = 4 + pktInfoSlotCount = 5 // slot index is based on seconds, so this is basically how many seconds we sample minSampleCount = 50 minAckRate = 0.8 ) +var _ congestion.CongestionControlEx = &BrutalSender{} + type BrutalSender struct { rttStats congestion.RTTStatsProvider bps congestion.ByteCount @@ -72,30 +74,25 @@ func (b *BrutalSender) OnPacketSent(sentTime time.Time, bytesInFlight congestion func (b *BrutalSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount, priorInFlight congestion.ByteCount, eventTime time.Time) { + // Stub +} + +func (b *BrutalSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes congestion.ByteCount, + priorInFlight congestion.ByteCount) { + // Stub +} + +func (b *BrutalSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) { currentTimestamp := eventTime.Unix() slot := currentTimestamp % pktInfoSlotCount if b.pktInfoSlots[slot].Timestamp == currentTimestamp { - b.pktInfoSlots[slot].AckCount++ + b.pktInfoSlots[slot].LossCount += uint64(len(lostPackets)) + b.pktInfoSlots[slot].AckCount += uint64(len(ackedPackets)) } else { // uninitialized slot or too old, reset b.pktInfoSlots[slot].Timestamp = currentTimestamp - b.pktInfoSlots[slot].AckCount = 1 - b.pktInfoSlots[slot].LossCount = 0 - } - b.updateAckRate(currentTimestamp) -} - -func (b *BrutalSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount, - priorInFlight congestion.ByteCount) { - currentTimestamp := time.Now().Unix() - slot := currentTimestamp % pktInfoSlotCount - if b.pktInfoSlots[slot].Timestamp == currentTimestamp { - b.pktInfoSlots[slot].LossCount++ - } else { - // uninitialized slot or too old, reset - b.pktInfoSlots[slot].Timestamp = currentTimestamp - b.pktInfoSlots[slot].AckCount = 0 - b.pktInfoSlots[slot].LossCount = 1 + b.pktInfoSlots[slot].AckCount = uint64(len(ackedPackets)) + b.pktInfoSlots[slot].LossCount = uint64(len(lostPackets)) } b.updateAckRate(currentTimestamp) } @@ -117,10 +114,12 @@ func (b *BrutalSender) updateAckRate(currentTimestamp int64) { } if ackCount+lossCount < minSampleCount { b.ackRate = 1 + return } rate := float64(ackCount) / float64(ackCount+lossCount) if rate < minAckRate { b.ackRate = minAckRate + return } b.ackRate = rate } diff --git a/transport/hysteria/conns/faketcp/obfs.go b/transport/hysteria/conns/faketcp/obfs.go index 35f7d013..cf58e569 100644 --- a/transport/hysteria/conns/faketcp/obfs.go +++ b/transport/hysteria/conns/faketcp/obfs.go @@ -1,7 +1,7 @@ package faketcp import ( - "github.com/Dreamacro/clash/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/obfs" "net" "sync" "syscall" diff --git a/transport/hysteria/conns/faketcp/tcp_linux.go b/transport/hysteria/conns/faketcp/tcp_linux.go index 76ed0d5e..2aaaf139 100644 --- a/transport/hysteria/conns/faketcp/tcp_linux.go +++ b/transport/hysteria/conns/faketcp/tcp_linux.go @@ -20,7 +20,7 @@ import ( "github.com/metacubex/gopacket" "github.com/metacubex/gopacket/layers" - "github.com/Dreamacro/clash/component/dialer" + "github.com/metacubex/mihomo/component/dialer" ) var ( diff --git a/transport/hysteria/conns/udp/hop.go b/transport/hysteria/conns/udp/hop.go index 447a7592..eb0732f0 100644 --- a/transport/hysteria/conns/udp/hop.go +++ b/transport/hysteria/conns/udp/hop.go @@ -9,8 +9,8 @@ import ( "syscall" "time" - "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/utils" + "github.com/metacubex/mihomo/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/utils" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/hysteria/conns/udp/obfs.go b/transport/hysteria/conns/udp/obfs.go index d63034b5..a5c6c06c 100644 --- a/transport/hysteria/conns/udp/obfs.go +++ b/transport/hysteria/conns/udp/obfs.go @@ -1,7 +1,7 @@ package udp import ( - "github.com/Dreamacro/clash/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/obfs" "net" "sync" "time" diff --git a/transport/hysteria/conns/wechat/obfs.go b/transport/hysteria/conns/wechat/obfs.go index d13cca55..4266d268 100644 --- a/transport/hysteria/conns/wechat/obfs.go +++ b/transport/hysteria/conns/wechat/obfs.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/hysteria/obfs" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/hysteria/obfs" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/hysteria/core/client.go b/transport/hysteria/core/client.go index a219e76c..258a0005 100644 --- a/transport/hysteria/core/client.go +++ b/transport/hysteria/core/client.go @@ -11,10 +11,10 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" - "github.com/Dreamacro/clash/transport/hysteria/transport" - "github.com/Dreamacro/clash/transport/hysteria/utils" + "github.com/metacubex/mihomo/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix" + "github.com/metacubex/mihomo/transport/hysteria/transport" + "github.com/metacubex/mihomo/transport/hysteria/utils" "github.com/lunixbochs/struc" "github.com/metacubex/quic-go" @@ -135,7 +135,7 @@ func (c *Client) handleControlStream(qs quic.Connection, stream quic.Stream) (bo func (c *Client) handleMessage(qs quic.Connection) { for { - msg, err := qs.ReceiveMessage(context.Background()) + msg, err := qs.ReceiveDatagram(context.Background()) if err != nil { break } @@ -194,11 +194,7 @@ func (c *Client) openStreamWithReconnect(dialer utils.PacketDialer) (quic.Connec return c.quicSession, &wrappedQUICStream{stream}, err } -func (c *Client) DialTCP(addr string, dialer utils.PacketDialer) (net.Conn, error) { - host, port, err := utils.SplitHostPort(addr) - if err != nil { - return nil, err - } +func (c *Client) DialTCP(host string, port uint16, dialer utils.PacketDialer) (net.Conn, error) { session, stream, err := c.openStreamWithReconnect(dialer) if err != nil { return nil, err @@ -404,7 +400,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error { // try no frag first var msgBuf bytes.Buffer _ = struc.Pack(&msgBuf, &msg) - err = c.Session.SendMessage(msgBuf.Bytes()) + err = c.Session.SendDatagram(msgBuf.Bytes()) if err != nil { if errSize, ok := err.(quic.ErrMessageTooLarge); ok { // need to frag @@ -413,7 +409,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error { for _, fragMsg := range fragMsgs { msgBuf.Reset() _ = struc.Pack(&msgBuf, &fragMsg) - err = c.Session.SendMessage(msgBuf.Bytes()) + err = c.Session.SendDatagram(msgBuf.Bytes()) if err != nil { return err } diff --git a/transport/hysteria/transport/client.go b/transport/hysteria/transport/client.go index 67568bc8..f5cc9f07 100644 --- a/transport/hysteria/transport/client.go +++ b/transport/hysteria/transport/client.go @@ -9,11 +9,11 @@ import ( "github.com/metacubex/quic-go" - "github.com/Dreamacro/clash/transport/hysteria/conns/faketcp" - "github.com/Dreamacro/clash/transport/hysteria/conns/udp" - "github.com/Dreamacro/clash/transport/hysteria/conns/wechat" - obfsPkg "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/utils" + "github.com/metacubex/mihomo/transport/hysteria/conns/faketcp" + "github.com/metacubex/mihomo/transport/hysteria/conns/udp" + "github.com/metacubex/mihomo/transport/hysteria/conns/wechat" + obfsPkg "github.com/metacubex/mihomo/transport/hysteria/obfs" + "github.com/metacubex/mihomo/transport/hysteria/utils" ) type ClientTransport struct { diff --git a/transport/shadowsocks/core/cipher.go b/transport/shadowsocks/core/cipher.go index cd30360c..44b2e8d4 100644 --- a/transport/shadowsocks/core/cipher.go +++ b/transport/shadowsocks/core/cipher.go @@ -7,9 +7,9 @@ import ( "sort" "strings" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowstream" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowstream" ) type Cipher interface { diff --git a/transport/shadowsocks/shadowaead/packet.go b/transport/shadowsocks/shadowaead/packet.go index e84ac570..f9d21ec7 100644 --- a/transport/shadowsocks/shadowaead/packet.go +++ b/transport/shadowsocks/shadowaead/packet.go @@ -6,8 +6,8 @@ import ( "io" "net" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" ) // ErrShortPacket means that the packet is too short for a valid encrypted packet. diff --git a/transport/shadowsocks/shadowaead/stream.go b/transport/shadowsocks/shadowaead/stream.go index e92bddab..de0993b2 100644 --- a/transport/shadowsocks/shadowaead/stream.go +++ b/transport/shadowsocks/shadowaead/stream.go @@ -7,7 +7,7 @@ import ( "io" "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) const ( diff --git a/transport/shadowsocks/shadowstream/packet.go b/transport/shadowsocks/shadowstream/packet.go index f0bf43ef..39d09a70 100644 --- a/transport/shadowsocks/shadowstream/packet.go +++ b/transport/shadowsocks/shadowstream/packet.go @@ -6,8 +6,8 @@ import ( "io" "net" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" ) // ErrShortPacket means the packet is too short to be a valid encrypted packet. diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 2c0c5946..a0a3d7fb 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -11,8 +11,8 @@ import ( "io" "net" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" ) const ( diff --git a/transport/simple-obfs/http.go b/transport/simple-obfs/http.go index 80db34ba..681c1864 100644 --- a/transport/simple-obfs/http.go +++ b/transport/simple-obfs/http.go @@ -8,7 +8,7 @@ import ( "net" "net/http" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 20166bbe..78317f0a 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -7,7 +7,7 @@ import ( "net" "time" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/sing-shadowtls/shadowtls.go b/transport/sing-shadowtls/shadowtls.go index 6d731ae6..982d847a 100644 --- a/transport/sing-shadowtls/shadowtls.go +++ b/transport/sing-shadowtls/shadowtls.go @@ -5,9 +5,9 @@ import ( "crypto/tls" "net" - "github.com/Dreamacro/clash/component/ca" - tlsC "github.com/Dreamacro/clash/component/tls" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" + "github.com/metacubex/mihomo/log" "github.com/sagernet/sing-shadowtls" sing_common "github.com/sagernet/sing/common" diff --git a/transport/snell/cipher.go b/transport/snell/cipher.go index 24999e28..e18ce510 100644 --- a/transport/snell/cipher.go +++ b/transport/snell/cipher.go @@ -4,7 +4,7 @@ import ( "crypto/aes" "crypto/cipher" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" "golang.org/x/crypto/argon2" "golang.org/x/crypto/chacha20poly1305" diff --git a/transport/snell/pool.go b/transport/snell/pool.go index 2a233a75..cc097df7 100644 --- a/transport/snell/pool.go +++ b/transport/snell/pool.go @@ -5,8 +5,8 @@ import ( "net" "time" - "github.com/Dreamacro/clash/component/pool" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" + "github.com/metacubex/mihomo/component/pool" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" ) type Pool struct { @@ -61,7 +61,7 @@ func (pc *PoolConn) Write(b []byte) (int, error) { } func (pc *PoolConn) Close() error { - // clash use SetReadDeadline to break bidirectional copy between client and server. + // mihomo use SetReadDeadline to break bidirectional copy between client and server. // reset it before reuse connection to avoid io timeout error. _ = pc.Snell.Conn.SetReadDeadline(time.Time{}) pc.pool.Put(pc.Snell) diff --git a/transport/snell/snell.go b/transport/snell/snell.go index e2bd2820..fe3e4ee0 100644 --- a/transport/snell/snell.go +++ b/transport/snell/snell.go @@ -8,9 +8,9 @@ import ( "net" "sync" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/transport/shadowsocks/shadowaead" + "github.com/metacubex/mihomo/transport/socks5" ) const ( diff --git a/transport/socks4/socks4.go b/transport/socks4/socks4.go index 0d5c5a77..9533a1c0 100644 --- a/transport/socks4/socks4.go +++ b/transport/socks4/socks4.go @@ -9,7 +9,7 @@ import ( "net/netip" "strconv" - "github.com/Dreamacro/clash/component/auth" + "github.com/metacubex/mihomo/component/auth" ) const Version = 0x04 diff --git a/transport/socks5/socks5.go b/transport/socks5/socks5.go index 6f95cce9..c97c370c 100644 --- a/transport/socks5/socks5.go +++ b/transport/socks5/socks5.go @@ -9,7 +9,7 @@ import ( "net/netip" "strconv" - "github.com/Dreamacro/clash/component/auth" + "github.com/metacubex/mihomo/component/auth" ) // Error represents a SOCKS error diff --git a/transport/ssr/obfs/http_simple.go b/transport/ssr/obfs/http_simple.go index c91cca49..359ca342 100644 --- a/transport/ssr/obfs/http_simple.go +++ b/transport/ssr/obfs/http_simple.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/obfs/random_head.go b/transport/ssr/obfs/random_head.go index 4c55d951..9a2072fe 100644 --- a/transport/ssr/obfs/random_head.go +++ b/transport/ssr/obfs/random_head.go @@ -5,7 +5,7 @@ import ( "hash/crc32" "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/obfs/tls1.2_ticket_auth.go b/transport/ssr/obfs/tls1.2_ticket_auth.go index af945133..d5e3ca88 100644 --- a/transport/ssr/obfs/tls1.2_ticket_auth.go +++ b/transport/ssr/obfs/tls1.2_ticket_auth.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/ssr/tools" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/transport/ssr/tools" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/protocol/auth_aes128_md5.go b/transport/ssr/protocol/auth_aes128_md5.go index d3bc9417..c6ae415e 100644 --- a/transport/ssr/protocol/auth_aes128_md5.go +++ b/transport/ssr/protocol/auth_aes128_md5.go @@ -1,6 +1,6 @@ package protocol -import "github.com/Dreamacro/clash/transport/ssr/tools" +import "github.com/metacubex/mihomo/transport/ssr/tools" func init() { register("auth_aes128_md5", newAuthAES128MD5, 9) diff --git a/transport/ssr/protocol/auth_aes128_sha1.go b/transport/ssr/protocol/auth_aes128_sha1.go index e2f0e143..6ee4160e 100644 --- a/transport/ssr/protocol/auth_aes128_sha1.go +++ b/transport/ssr/protocol/auth_aes128_sha1.go @@ -8,10 +8,10 @@ import ( "strconv" "strings" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/ssr/tools" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/ssr/tools" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/protocol/auth_chain_a.go b/transport/ssr/protocol/auth_chain_a.go index 23efb390..396172ef 100644 --- a/transport/ssr/protocol/auth_chain_a.go +++ b/transport/ssr/protocol/auth_chain_a.go @@ -7,15 +7,16 @@ import ( "crypto/rc4" "encoding/base64" "encoding/binary" + "errors" "net" "strconv" "strings" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/shadowsocks/core" - "github.com/Dreamacro/clash/transport/ssr/tools" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/shadowsocks/core" + "github.com/metacubex/mihomo/transport/ssr/tools" ) func init() { @@ -107,6 +108,10 @@ func (a *authChainA) Decode(dst, src *bytes.Buffer) error { dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16])) randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer) length := dataLength + randDataLength + // Temporary workaround for https://github.com/metacubex/mihomo/issues/1352 + if dataLength < 0 || randDataLength < 0 || length < 0 { + return errors.New("ssr crashing blocked") + } if length >= 4096 { a.rawTrans = true @@ -130,6 +135,11 @@ func (a *authChainA) Decode(dst, src *bytes.Buffer) error { if dataLength > 0 && randDataLength > 0 { pos += getRandStartPos(randDataLength, &a.randomServer) } + // Temporary workaround for https://github.com/metacubex/mihomo/issues/1352 + if pos < 0 || pos+dataLength < 0 || dataLength < 0 { + return errors.New("ssr crashing blocked") + } + wantedData := src.Bytes()[pos : pos+dataLength] a.decrypter.XORKeyStream(wantedData, wantedData) if a.recvID == 1 { diff --git a/transport/ssr/protocol/auth_chain_b.go b/transport/ssr/protocol/auth_chain_b.go index 857b2a3a..223613a9 100644 --- a/transport/ssr/protocol/auth_chain_b.go +++ b/transport/ssr/protocol/auth_chain_b.go @@ -4,7 +4,7 @@ import ( "net" "sort" - "github.com/Dreamacro/clash/transport/ssr/tools" + "github.com/metacubex/mihomo/transport/ssr/tools" ) func init() { diff --git a/transport/ssr/protocol/auth_sha1_v4.go b/transport/ssr/protocol/auth_sha1_v4.go index 26039181..ed1a39f1 100644 --- a/transport/ssr/protocol/auth_sha1_v4.go +++ b/transport/ssr/protocol/auth_sha1_v4.go @@ -7,9 +7,9 @@ import ( "hash/crc32" "net" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/ssr/tools" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/transport/ssr/tools" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/protocol/base.go b/transport/ssr/protocol/base.go index a826bec8..e26a6587 100644 --- a/transport/ssr/protocol/base.go +++ b/transport/ssr/protocol/base.go @@ -9,9 +9,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/shadowsocks/core" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/shadowsocks/core" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/protocol/origin.go b/transport/ssr/protocol/origin.go index 52525a2f..4d8b630a 100644 --- a/transport/ssr/protocol/origin.go +++ b/transport/ssr/protocol/origin.go @@ -4,7 +4,7 @@ import ( "bytes" "net" - N "github.com/Dreamacro/clash/common/net" + N "github.com/metacubex/mihomo/common/net" ) type origin struct{} diff --git a/transport/ssr/protocol/packet.go b/transport/ssr/protocol/packet.go index 988ff75d..86d573f3 100644 --- a/transport/ssr/protocol/packet.go +++ b/transport/ssr/protocol/packet.go @@ -3,8 +3,8 @@ package protocol import ( "net" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" ) type PacketConn struct { diff --git a/transport/ssr/protocol/protocol.go b/transport/ssr/protocol/protocol.go index 1c27da48..a04e6bd4 100644 --- a/transport/ssr/protocol/protocol.go +++ b/transport/ssr/protocol/protocol.go @@ -6,7 +6,7 @@ import ( "fmt" "net" - N "github.com/Dreamacro/clash/common/net" + N "github.com/metacubex/mihomo/common/net" "github.com/zhangyunhao116/fastrand" ) diff --git a/transport/ssr/protocol/stream.go b/transport/ssr/protocol/stream.go index 3c846157..436859c3 100644 --- a/transport/ssr/protocol/stream.go +++ b/transport/ssr/protocol/stream.go @@ -4,7 +4,7 @@ import ( "bytes" "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) type Conn struct { diff --git a/transport/ssr/tools/random.go b/transport/ssr/tools/random.go index 338543ea..c76011e4 100644 --- a/transport/ssr/tools/random.go +++ b/transport/ssr/tools/random.go @@ -3,7 +3,7 @@ package tools import ( "encoding/binary" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) // XorShift128Plus - a pseudorandom number generator diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 6dfcfe11..09be1124 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -12,13 +12,13 @@ import ( "net/http" "sync" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ca" - tlsC "github.com/Dreamacro/clash/component/tls" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/vmess" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/vmess" ) const ( @@ -55,10 +55,12 @@ type Option struct { } type WebsocketOption struct { - Host string - Port string - Path string - Headers http.Header + Host string + Port string + Path string + Headers http.Header + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } type Trojan struct { @@ -128,13 +130,15 @@ func (t *Trojan) StreamWebsocketConn(ctx context.Context, conn net.Conn, wsOptio } return vmess.StreamWebsocketConn(ctx, conn, &vmess.WebsocketConfig{ - Host: wsOptions.Host, - Port: wsOptions.Port, - Path: wsOptions.Path, - Headers: wsOptions.Headers, - TLS: true, - TLSConfig: tlsConfig, - ClientFingerprint: t.option.ClientFingerprint, + Host: wsOptions.Host, + Port: wsOptions.Port, + Path: wsOptions.Path, + Headers: wsOptions.Headers, + V2rayHttpUpgrade: wsOptions.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: wsOptions.V2rayHttpUpgradeFastOpen, + TLS: true, + TLSConfig: tlsConfig, + ClientFingerprint: t.option.ClientFingerprint, }) } diff --git a/transport/tuic/common/congestion.go b/transport/tuic/common/congestion.go index 36ee01a1..485e2e6a 100644 --- a/transport/tuic/common/congestion.go +++ b/transport/tuic/common/congestion.go @@ -1,7 +1,8 @@ package common import ( - "github.com/Dreamacro/clash/transport/tuic/congestion" + "github.com/metacubex/mihomo/transport/tuic/congestion" + congestionv2 "github.com/metacubex/mihomo/transport/tuic/congestion_v2" "github.com/metacubex/quic-go" c "github.com/metacubex/quic-go/congestion" @@ -23,7 +24,6 @@ func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { congestion.DefaultClock{}, congestion.GetInitialPacketSize(quicConn.RemoteAddr()), false, - nil, ), ) case "new_reno": @@ -32,10 +32,9 @@ func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { congestion.DefaultClock{}, congestion.GetInitialPacketSize(quicConn.RemoteAddr()), true, - nil, ), ) - case "bbr": + case "bbr_meta_v1": quicConn.SetCongestionControl( congestion.NewBBRSender( congestion.DefaultClock{}, @@ -44,5 +43,15 @@ func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize, ), ) + case "bbr_meta_v2": + fallthrough + case "bbr": + quicConn.SetCongestionControl( + congestionv2.NewBbrSender( + congestionv2.DefaultClock{}, + congestionv2.GetInitialPacketSize(quicConn.RemoteAddr()), + c.ByteCount(cwnd), + ), + ) } } diff --git a/transport/tuic/common/type.go b/transport/tuic/common/type.go index 9a568dd7..c663fa0b 100644 --- a/transport/tuic/common/type.go +++ b/transport/tuic/common/type.go @@ -7,8 +7,8 @@ import ( "net" "time" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" "github.com/metacubex/quic-go" ) diff --git a/transport/tuic/congestion/bandwidth_sampler.go b/transport/tuic/congestion/bandwidth_sampler.go index b82d391f..e415fe7a 100644 --- a/transport/tuic/congestion/bandwidth_sampler.go +++ b/transport/tuic/congestion/bandwidth_sampler.go @@ -296,9 +296,9 @@ func (s *BandwidthSampler) onPacketAckedInner(ackTime time.Time, lastAckedPacket return sample } -// OnPacketLost Informs the sampler that a packet is considered lost and it should no +// OnCongestionEvent Informs the sampler that a packet is considered lost and it should no // longer keep track of it. -func (s *BandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber) SendTimeState { +func (s *BandwidthSampler) OnCongestionEvent(packetNumber congestion.PacketNumber) SendTimeState { ok, sentPacket := s.connectionStats.Remove(packetNumber) sendTimeState := SendTimeState{ isValid: ok, diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go index e78819c7..8c18c616 100644 --- a/transport/tuic/congestion/bbr_sender.go +++ b/transport/tuic/congestion/bbr_sender.go @@ -347,15 +347,36 @@ func (b *bbrSender) MaybeExitSlowStart() { } func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes congestion.ByteCount, priorInFlight congestion.ByteCount, eventTime time.Time) { + // Stub +} + +func (b *bbrSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes congestion.ByteCount, priorInFlight congestion.ByteCount) { + // Stub +} + +func (b *bbrSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) { totalBytesAckedBefore := b.sampler.totalBytesAcked isRoundStart, minRttExpired := false, false - lastAckedPacket := number - isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) - minRttExpired = b.UpdateBandwidthAndMinRtt(eventTime, number, ackedBytes) - b.UpdateRecoveryState(false, isRoundStart) - bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore - excessAcked := b.UpdateAckAggregationBytes(eventTime, bytesAcked) + if lostPackets != nil { + b.DiscardLostPackets(lostPackets) + } + + // Input the new data into the BBR model of the connection. + var excessAcked congestion.ByteCount + if len(ackedPackets) > 0 { + lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber + isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) + minRttExpired = b.UpdateBandwidthAndMinRtt(eventTime, ackedPackets) + b.UpdateRecoveryState(len(lostPackets) > 0, isRoundStart) + bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore + excessAcked = b.UpdateAckAggregationBytes(eventTime, bytesAcked) + } + + // Handle logic specific to PROBE_BW mode. + if b.mode == PROBE_BW { + b.UpdateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) > 0) + } // Handle logic specific to STARTUP and DRAIN modes. if isRoundStart && !b.isAtFullBandwidth { @@ -366,38 +387,12 @@ func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes con // Handle logic specific to PROBE_RTT. b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) - // After the model is updated, recalculate the pacing rate and congestion - // window. - 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) { - eventTime := time.Now() - totalBytesAckedBefore := b.sampler.totalBytesAcked - isRoundStart, minRttExpired := false, false - - b.DiscardLostPackets(number, lostBytes) - - // Input the new data into the BBR model of the connection. - var excessAcked congestion.ByteCount - - // Handle logic specific to PROBE_BW mode. - if b.mode == PROBE_BW { - b.UpdateGainCyclePhase(time.Now(), priorInFlight, true) - } - - // Handle logic specific to STARTUP and DRAIN modes. - b.MaybeExitStartupOrDrain(eventTime) - - // Handle logic specific to PROBE_RTT. - b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) - // Calculate number of packets acked and lost. bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore - bytesLost := lostBytes + bytesLost := congestion.ByteCount(0) + for _, packet := range lostPackets { + bytesLost += packet.BytesLost + } // After the model is updated, recalculate the pacing rate and congestion // window. @@ -406,53 +401,6 @@ func (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes conge b.CalculateRecoveryWindow(bytesAcked, bytesLost) } -//func (b *bbrSender) OnCongestionEvent(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets, lostPackets []*congestion.Packet) { -// totalBytesAckedBefore := b.sampler.totalBytesAcked -// isRoundStart, minRttExpired := false, false -// -// if lostPackets != nil { -// b.DiscardLostPackets(lostPackets) -// } -// -// // Input the new data into the BBR model of the connection. -// var excessAcked congestion.ByteCount -// if len(ackedPackets) > 0 { -// lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber -// isRoundStart = b.UpdateRoundTripCounter(lastAckedPacket) -// minRttExpired = b.UpdateBandwidthAndMinRtt(eventTime, ackedPackets) -// b.UpdateRecoveryState(lastAckedPacket, len(lostPackets) > 0, isRoundStart) -// bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore -// excessAcked = b.UpdateAckAggregationBytes(eventTime, bytesAcked) -// } -// -// // Handle logic specific to PROBE_BW mode. -// if b.mode == PROBE_BW { -// b.UpdateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) > 0) -// } -// -// // Handle logic specific to STARTUP and DRAIN modes. -// if isRoundStart && !b.isAtFullBandwidth { -// b.CheckIfFullBandwidthReached() -// } -// b.MaybeExitStartupOrDrain(eventTime) -// -// // Handle logic specific to PROBE_RTT. -// b.MaybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) -// -// // Calculate number of packets acked and lost. -// bytesAcked := b.sampler.totalBytesAcked - totalBytesAckedBefore -// bytesLost := congestion.ByteCount(0) -// for _, packet := range lostPackets { -// bytesLost += packet.Length -// } -// -// // After the model is updated, recalculate the pacing rate and congestion -// // window. -// b.CalculatePacingRate() -// b.CalculateCongestionWindow(bytesAcked, excessAcked) -// b.CalculateRecoveryWindow(bytesAcked, bytesLost) -//} - //func (b *bbrSender) SetNumEmulatedConnections(n int) { // //} @@ -553,30 +501,32 @@ func (b *bbrSender) UpdateRoundTripCounter(lastAckedPacket congestion.PacketNumb return false } -func (b *bbrSender) UpdateBandwidthAndMinRtt(now time.Time, number congestion.PacketNumber, ackedBytes congestion.ByteCount) bool { +func (b *bbrSender) UpdateBandwidthAndMinRtt(now time.Time, ackedPackets []congestion.AckedPacketInfo) bool { sampleMinRtt := InfiniteRTT - if !b.alwaysGetBwSampleWhenAcked && ackedBytes == 0 { - // Skip acked packets with 0 in flight bytes when updating bandwidth. - return false - } - bandwidthSample := b.sampler.OnPacketAcked(now, number) - if b.alwaysGetBwSampleWhenAcked && !bandwidthSample.stateAtSend.isValid { - // From the sampler's perspective, the packet has never been sent, or the - // packet has been acked or marked as lost previously. - return false - } - b.lastSampleIsAppLimited = bandwidthSample.stateAtSend.isAppLimited - // has_non_app_limited_sample_ |= - // !bandwidth_sample.state_at_send.is_app_limited; - if !bandwidthSample.stateAtSend.isAppLimited { - b.hasNoAppLimitedSample = true - } - if bandwidthSample.rtt > 0 { - sampleMinRtt = minRtt(sampleMinRtt, bandwidthSample.rtt) - } - if !bandwidthSample.stateAtSend.isAppLimited || bandwidthSample.bandwidth > b.BandwidthEstimate() { - b.maxBandwidth.Update(int64(bandwidthSample.bandwidth), b.roundTripCount) + for _, packet := range ackedPackets { + if !b.alwaysGetBwSampleWhenAcked && packet.BytesAcked == 0 { + // Skip acked packets with 0 in flight bytes when updating bandwidth. + return false + } + bandwidthSample := b.sampler.OnPacketAcked(now, packet.PacketNumber) + if b.alwaysGetBwSampleWhenAcked && !bandwidthSample.stateAtSend.isValid { + // From the sampler's perspective, the packet has never been sent, or the + // packet has been acked or marked as lost previously. + return false + } + b.lastSampleIsAppLimited = bandwidthSample.stateAtSend.isAppLimited + // has_non_app_limited_sample_ |= + // !bandwidth_sample.state_at_send.is_app_limited; + if !bandwidthSample.stateAtSend.isAppLimited { + b.hasNoAppLimitedSample = true + } + if bandwidthSample.rtt > 0 { + sampleMinRtt = minRtt(sampleMinRtt, bandwidthSample.rtt) + } + if !bandwidthSample.stateAtSend.isAppLimited || bandwidthSample.bandwidth > b.BandwidthEstimate() { + b.maxBandwidth.Update(int64(bandwidthSample.bandwidth), b.roundTripCount) + } } // If none of the RTT samples are valid, return immediately. @@ -619,14 +569,16 @@ func (b *bbrSender) ShouldExtendMinRttExpiry() bool { return false } -func (b *bbrSender) DiscardLostPackets(number congestion.PacketNumber, lostBytes congestion.ByteCount) { - b.sampler.OnPacketLost(number) - if b.mode == STARTUP { - // if b.rttStats != nil { - // TODO: slow start. - // } - if b.startupRateReductionMultiplier != 0 { - b.startupBytesLost += lostBytes +func (b *bbrSender) DiscardLostPackets(lostPackets []congestion.LostPacketInfo) { + for _, packet := range lostPackets { + b.sampler.OnCongestionEvent(packet.PacketNumber) + if b.mode == STARTUP { + // if b.rttStats != nil { + // TODO: slow start. + // } + if b.startupRateReductionMultiplier != 0 { + b.startupBytesLost += packet.BytesLost + } } } } diff --git a/transport/tuic/congestion/cubic_sender.go b/transport/tuic/congestion/cubic_sender.go index ca20b420..f544cd74 100644 --- a/transport/tuic/congestion/cubic_sender.go +++ b/transport/tuic/congestion/cubic_sender.go @@ -5,7 +5,6 @@ import ( "time" "github.com/metacubex/quic-go/congestion" - "github.com/metacubex/quic-go/logging" ) const ( @@ -54,9 +53,6 @@ type cubicSender struct { initialMaxCongestionWindow congestion.ByteCount maxDatagramSize congestion.ByteCount - - lastState logging.CongestionState - tracer logging.ConnectionTracer } var ( @@ -68,7 +64,6 @@ func NewCubicSender( clock Clock, initialMaxDatagramSize congestion.ByteCount, reno bool, - tracer logging.ConnectionTracer, ) *cubicSender { return newCubicSender( clock, @@ -76,7 +71,6 @@ func NewCubicSender( initialMaxDatagramSize, initialCongestionWindow*initialMaxDatagramSize, MaxCongestionWindowPackets*initialMaxDatagramSize, - tracer, ) } @@ -86,7 +80,6 @@ func newCubicSender( initialMaxDatagramSize, initialCongestionWindow, initialMaxCongestionWindow congestion.ByteCount, - tracer logging.ConnectionTracer, ) *cubicSender { c := &cubicSender{ largestSentPacketNumber: InvalidPacketNumber, @@ -99,14 +92,9 @@ func newCubicSender( cubic: NewCubic(clock), clock: clock, reno: reno, - tracer: tracer, maxDatagramSize: initialMaxDatagramSize, } c.pacer = newPacer(c.BandwidthEstimate) - if c.tracer != nil { - c.lastState = logging.CongestionStateSlowStart - c.tracer.UpdatedCongestionState(logging.CongestionStateSlowStart) - } return c } @@ -167,7 +155,6 @@ func (c *cubicSender) MaybeExitSlowStart() { c.hybridSlowStart.ShouldExitSlowStart(c.rttStats.LatestRTT(), c.rttStats.MinRTT(), c.GetCongestionWindow()/c.maxDatagramSize) { // exit slow start c.slowStartThreshold = c.congestionWindow - c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) } } @@ -187,14 +174,13 @@ func (c *cubicSender) OnPacketAcked( } } -func (c *cubicSender) OnPacketLost(packetNumber congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) { +func (c *cubicSender) OnCongestionEvent(packetNumber congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) { // TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets // already sent should be treated as a single loss event, since it's expected. if packetNumber <= c.largestSentAtLastCutback { return } c.lastCutbackExitedSlowstart = c.InSlowStart() - c.maybeTraceStateChange(logging.CongestionStateRecovery) if c.reno { c.congestionWindow = congestion.ByteCount(float64(c.congestionWindow) * renoBeta) @@ -211,6 +197,10 @@ func (c *cubicSender) OnPacketLost(packetNumber congestion.PacketNumber, lostByt c.numAckedPackets = 0 } +func (b *cubicSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) { + // Stub +} + // Called when we receive an ack. Normal TCP tracks how many packets one ack // represents, but quic has a separate ack for each packet. func (c *cubicSender) maybeIncreaseCwnd( @@ -223,7 +213,6 @@ func (c *cubicSender) maybeIncreaseCwnd( // the current window. if !c.isCwndLimited(priorInFlight) { c.cubic.OnApplicationLimited() - c.maybeTraceStateChange(logging.CongestionStateApplicationLimited) return } if c.congestionWindow >= c.maxCongestionWindow() { @@ -232,11 +221,9 @@ func (c *cubicSender) maybeIncreaseCwnd( if c.InSlowStart() { // TCP slow start, exponential growth, increase by one for each ACK. c.congestionWindow += c.maxDatagramSize - c.maybeTraceStateChange(logging.CongestionStateSlowStart) return } // Congestion avoidance - c.maybeTraceStateChange(logging.CongestionStateCongestionAvoidance) if c.reno { // Classic Reno congestion avoidance. c.numAckedPackets++ @@ -297,14 +284,6 @@ func (c *cubicSender) OnConnectionMigration() { c.slowStartThreshold = c.initialMaxCongestionWindow } -func (c *cubicSender) maybeTraceStateChange(new logging.CongestionState) { - if c.tracer == nil || new == c.lastState { - return - } - c.tracer.UpdatedCongestionState(new) - c.lastState = new -} - func (c *cubicSender) SetMaxDatagramSize(s congestion.ByteCount) { if s < c.maxDatagramSize { panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", c.maxDatagramSize, s)) diff --git a/transport/tuic/congestion_v2/bandwidth.go b/transport/tuic/congestion_v2/bandwidth.go new file mode 100644 index 00000000..df39a077 --- /dev/null +++ b/transport/tuic/congestion_v2/bandwidth.go @@ -0,0 +1,27 @@ +package congestion + +import ( + "math" + "time" + + "github.com/metacubex/quic-go/congestion" +) + +const ( + infBandwidth = Bandwidth(math.MaxUint64) +) + +// Bandwidth of a connection +type Bandwidth uint64 + +const ( + // BitsPerSecond is 1 bit per second + BitsPerSecond Bandwidth = 1 + // BytesPerSecond is 1 byte per second + BytesPerSecond = 8 * BitsPerSecond +) + +// BandwidthFromDelta calculates the bandwidth from a number of bytes and a time delta +func BandwidthFromDelta(bytes congestion.ByteCount, delta time.Duration) Bandwidth { + return Bandwidth(bytes) * Bandwidth(time.Second) / Bandwidth(delta) * BytesPerSecond +} diff --git a/transport/tuic/congestion_v2/bandwidth_sampler.go b/transport/tuic/congestion_v2/bandwidth_sampler.go new file mode 100644 index 00000000..9028df64 --- /dev/null +++ b/transport/tuic/congestion_v2/bandwidth_sampler.go @@ -0,0 +1,874 @@ +package congestion + +import ( + "math" + "time" + + "github.com/metacubex/quic-go/congestion" +) + +const ( + infRTT = time.Duration(math.MaxInt64) + defaultConnectionStateMapQueueSize = 256 + defaultCandidatesBufferSize = 256 +) + +type roundTripCount uint64 + +// SendTimeState is a subset of ConnectionStateOnSentPacket which is returned +// to the caller when the packet is acked or lost. +type sendTimeState struct { + // Whether other states in this object is valid. + isValid bool + // Whether the sender is app limited at the time the packet was sent. + // App limited bandwidth sample might be artificially low because the sender + // did not have enough data to send in order to saturate the link. + isAppLimited bool + // Total number of sent bytes at the time the packet was sent. + // Includes the packet itself. + totalBytesSent congestion.ByteCount + // Total number of acked bytes at the time the packet was sent. + totalBytesAcked congestion.ByteCount + // Total number of lost bytes at the time the packet was sent. + totalBytesLost congestion.ByteCount + // Total number of inflight bytes at the time the packet was sent. + // Includes the packet itself. + // It should be equal to |total_bytes_sent| minus the sum of + // |total_bytes_acked|, |total_bytes_lost| and total neutered bytes. + bytesInFlight congestion.ByteCount +} + +func newSendTimeState( + isAppLimited bool, + totalBytesSent congestion.ByteCount, + totalBytesAcked congestion.ByteCount, + totalBytesLost congestion.ByteCount, + bytesInFlight congestion.ByteCount, +) *sendTimeState { + return &sendTimeState{ + isValid: true, + isAppLimited: isAppLimited, + totalBytesSent: totalBytesSent, + totalBytesAcked: totalBytesAcked, + totalBytesLost: totalBytesLost, + bytesInFlight: bytesInFlight, + } +} + +type extraAckedEvent struct { + // The excess bytes acknowlwedged in the time delta for this event. + extraAcked congestion.ByteCount + + // The bytes acknowledged and time delta from the event. + bytesAcked congestion.ByteCount + timeDelta time.Duration + // The round trip of the event. + round roundTripCount +} + +func maxExtraAckedEventFunc(a, b extraAckedEvent) int { + if a.extraAcked > b.extraAcked { + return 1 + } else if a.extraAcked < b.extraAcked { + return -1 + } + return 0 +} + +// BandwidthSample +type bandwidthSample struct { + // The bandwidth at that particular sample. Zero if no valid bandwidth sample + // is available. + bandwidth Bandwidth + // The RTT measurement at this particular sample. Zero if no RTT sample is + // available. Does not correct for delayed ack time. + rtt time.Duration + // |send_rate| is computed from the current packet being acked('P') and an + // earlier packet that is acked before P was sent. + sendRate Bandwidth + // States captured when the packet was sent. + stateAtSend sendTimeState +} + +func newBandwidthSample() *bandwidthSample { + return &bandwidthSample{ + sendRate: infBandwidth, + } +} + +// MaxAckHeightTracker is part of the BandwidthSampler. It is called after every +// ack event to keep track the degree of ack aggregation(a.k.a "ack height"). +type maxAckHeightTracker struct { + // Tracks the maximum number of bytes acked faster than the estimated + // bandwidth. + maxAckHeightFilter *WindowedFilter[extraAckedEvent, roundTripCount] + // The time this aggregation started and the number of bytes acked during it. + aggregationEpochStartTime time.Time + aggregationEpochBytes congestion.ByteCount + // The last sent packet number before the current aggregation epoch started. + lastSentPacketNumberBeforeEpoch congestion.PacketNumber + // The number of ack aggregation epochs ever started, including the ongoing + // one. Stats only. + numAckAggregationEpochs uint64 + ackAggregationBandwidthThreshold float64 + startNewAggregationEpochAfterFullRound bool + reduceExtraAckedOnBandwidthIncrease bool +} + +func newMaxAckHeightTracker(windowLength roundTripCount) *maxAckHeightTracker { + return &maxAckHeightTracker{ + maxAckHeightFilter: NewWindowedFilter(windowLength, maxExtraAckedEventFunc), + lastSentPacketNumberBeforeEpoch: invalidPacketNumber, + ackAggregationBandwidthThreshold: 1.0, + } +} + +func (m *maxAckHeightTracker) Get() congestion.ByteCount { + return m.maxAckHeightFilter.GetBest().extraAcked +} + +func (m *maxAckHeightTracker) Update( + bandwidthEstimate Bandwidth, + isNewMaxBandwidth bool, + roundTripCount roundTripCount, + lastSentPacketNumber congestion.PacketNumber, + lastAckedPacketNumber congestion.PacketNumber, + ackTime time.Time, + bytesAcked congestion.ByteCount, +) congestion.ByteCount { + forceNewEpoch := false + + if m.reduceExtraAckedOnBandwidthIncrease && isNewMaxBandwidth { + // Save and clear existing entries. + best := m.maxAckHeightFilter.GetBest() + secondBest := m.maxAckHeightFilter.GetSecondBest() + thirdBest := m.maxAckHeightFilter.GetThirdBest() + m.maxAckHeightFilter.Clear() + + // Reinsert the heights into the filter after recalculating. + expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, best.timeDelta) + if expectedBytesAcked < best.bytesAcked { + best.extraAcked = best.bytesAcked - expectedBytesAcked + m.maxAckHeightFilter.Update(best, best.round) + } + expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, secondBest.timeDelta) + if expectedBytesAcked < secondBest.bytesAcked { + secondBest.extraAcked = secondBest.bytesAcked - expectedBytesAcked + m.maxAckHeightFilter.Update(secondBest, secondBest.round) + } + expectedBytesAcked = bytesFromBandwidthAndTimeDelta(bandwidthEstimate, thirdBest.timeDelta) + if expectedBytesAcked < thirdBest.bytesAcked { + thirdBest.extraAcked = thirdBest.bytesAcked - expectedBytesAcked + m.maxAckHeightFilter.Update(thirdBest, thirdBest.round) + } + } + + // If any packet sent after the start of the epoch has been acked, start a new + // epoch. + if m.startNewAggregationEpochAfterFullRound && + m.lastSentPacketNumberBeforeEpoch != invalidPacketNumber && + lastAckedPacketNumber != invalidPacketNumber && + lastAckedPacketNumber > m.lastSentPacketNumberBeforeEpoch { + forceNewEpoch = true + } + if m.aggregationEpochStartTime.IsZero() || forceNewEpoch { + m.aggregationEpochBytes = bytesAcked + m.aggregationEpochStartTime = ackTime + m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber + m.numAckAggregationEpochs++ + return 0 + } + + // Compute how many bytes are expected to be delivered, assuming max bandwidth + // is correct. + aggregationDelta := ackTime.Sub(m.aggregationEpochStartTime) + expectedBytesAcked := bytesFromBandwidthAndTimeDelta(bandwidthEstimate, aggregationDelta) + // Reset the current aggregation epoch as soon as the ack arrival rate is less + // than or equal to the max bandwidth. + if m.aggregationEpochBytes <= congestion.ByteCount(m.ackAggregationBandwidthThreshold*float64(expectedBytesAcked)) { + // Reset to start measuring a new aggregation epoch. + m.aggregationEpochBytes = bytesAcked + m.aggregationEpochStartTime = ackTime + m.lastSentPacketNumberBeforeEpoch = lastSentPacketNumber + m.numAckAggregationEpochs++ + return 0 + } + + m.aggregationEpochBytes += bytesAcked + + // Compute how many extra bytes were delivered vs max bandwidth. + extraBytesAcked := m.aggregationEpochBytes - expectedBytesAcked + newEvent := extraAckedEvent{ + extraAcked: expectedBytesAcked, + bytesAcked: m.aggregationEpochBytes, + timeDelta: aggregationDelta, + } + m.maxAckHeightFilter.Update(newEvent, roundTripCount) + return extraBytesAcked +} + +func (m *maxAckHeightTracker) SetFilterWindowLength(length roundTripCount) { + m.maxAckHeightFilter.SetWindowLength(length) +} + +func (m *maxAckHeightTracker) Reset(newHeight congestion.ByteCount, newTime roundTripCount) { + newEvent := extraAckedEvent{ + extraAcked: newHeight, + round: newTime, + } + m.maxAckHeightFilter.Reset(newEvent, newTime) +} + +func (m *maxAckHeightTracker) SetAckAggregationBandwidthThreshold(threshold float64) { + m.ackAggregationBandwidthThreshold = threshold +} + +func (m *maxAckHeightTracker) SetStartNewAggregationEpochAfterFullRound(value bool) { + m.startNewAggregationEpochAfterFullRound = value +} + +func (m *maxAckHeightTracker) SetReduceExtraAckedOnBandwidthIncrease(value bool) { + m.reduceExtraAckedOnBandwidthIncrease = value +} + +func (m *maxAckHeightTracker) AckAggregationBandwidthThreshold() float64 { + return m.ackAggregationBandwidthThreshold +} + +func (m *maxAckHeightTracker) NumAckAggregationEpochs() uint64 { + return m.numAckAggregationEpochs +} + +// AckPoint represents a point on the ack line. +type ackPoint struct { + ackTime time.Time + totalBytesAcked congestion.ByteCount +} + +// RecentAckPoints maintains the most recent 2 ack points at distinct times. +type recentAckPoints struct { + ackPoints [2]ackPoint +} + +func (r *recentAckPoints) Update(ackTime time.Time, totalBytesAcked congestion.ByteCount) { + if ackTime.Before(r.ackPoints[1].ackTime) { + r.ackPoints[1].ackTime = ackTime + } else if ackTime.After(r.ackPoints[1].ackTime) { + r.ackPoints[0] = r.ackPoints[1] + r.ackPoints[1].ackTime = ackTime + } + + r.ackPoints[1].totalBytesAcked = totalBytesAcked +} + +func (r *recentAckPoints) Clear() { + r.ackPoints[0] = ackPoint{} + r.ackPoints[1] = ackPoint{} +} + +func (r *recentAckPoints) MostRecentPoint() *ackPoint { + return &r.ackPoints[1] +} + +func (r *recentAckPoints) LessRecentPoint() *ackPoint { + if r.ackPoints[0].totalBytesAcked != 0 { + return &r.ackPoints[0] + } + + return &r.ackPoints[1] +} + +// ConnectionStateOnSentPacket represents the information about a sent packet +// and the state of the connection at the moment the packet was sent, +// specifically the information about the most recently acknowledged packet at +// that moment. +type connectionStateOnSentPacket struct { + // Time at which the packet is sent. + sentTime time.Time + // Size of the packet. + size congestion.ByteCount + // The value of |totalBytesSentAtLastAckedPacket| at the time the + // packet was sent. + totalBytesSentAtLastAckedPacket congestion.ByteCount + // The value of |lastAckedPacketSentTime| at the time the packet was + // sent. + lastAckedPacketSentTime time.Time + // The value of |lastAckedPacketAckTime| at the time the packet was + // sent. + lastAckedPacketAckTime time.Time + // Send time states that are returned to the congestion controller when the + // packet is acked or lost. + sendTimeState sendTimeState +} + +// Snapshot constructor. Records the current state of the bandwidth +// sampler. +// |bytes_in_flight| is the bytes in flight right after the packet is sent. +func newConnectionStateOnSentPacket( + sentTime time.Time, + size congestion.ByteCount, + bytesInFlight congestion.ByteCount, + sampler *bandwidthSampler, +) *connectionStateOnSentPacket { + return &connectionStateOnSentPacket{ + sentTime: sentTime, + size: size, + totalBytesSentAtLastAckedPacket: sampler.totalBytesSentAtLastAckedPacket, + lastAckedPacketSentTime: sampler.lastAckedPacketSentTime, + lastAckedPacketAckTime: sampler.lastAckedPacketAckTime, + sendTimeState: *newSendTimeState( + sampler.isAppLimited, + sampler.totalBytesSent, + sampler.totalBytesAcked, + sampler.totalBytesLost, + bytesInFlight, + ), + } +} + +// BandwidthSampler keeps track of sent and acknowledged packets and outputs a +// bandwidth sample for every packet acknowledged. The samples are taken for +// individual packets, and are not filtered; the consumer has to filter the +// bandwidth samples itself. In certain cases, the sampler will locally severely +// underestimate the bandwidth, hence a maximum filter with a size of at least +// one RTT is recommended. +// +// This class bases its samples on the slope of two curves: the number of bytes +// sent over time, and the number of bytes acknowledged as received over time. +// It produces a sample of both slopes for every packet that gets acknowledged, +// based on a slope between two points on each of the corresponding curves. Note +// that due to the packet loss, the number of bytes on each curve might get +// further and further away from each other, meaning that it is not feasible to +// compare byte values coming from different curves with each other. +// +// The obvious points for measuring slope sample are the ones corresponding to +// the packet that was just acknowledged. Let us denote them as S_1 (point at +// which the current packet was sent) and A_1 (point at which the current packet +// was acknowledged). However, taking a slope requires two points on each line, +// so estimating bandwidth requires picking a packet in the past with respect to +// which the slope is measured. +// +// For that purpose, BandwidthSampler always keeps track of the most recently +// acknowledged packet, and records it together with every outgoing packet. +// When a packet gets acknowledged (A_1), it has not only information about when +// it itself was sent (S_1), but also the information about the latest +// acknowledged packet right before it was sent (S_0 and A_0). +// +// Based on that data, send and ack rate are estimated as: +// +// send_rate = (bytes(S_1) - bytes(S_0)) / (time(S_1) - time(S_0)) +// ack_rate = (bytes(A_1) - bytes(A_0)) / (time(A_1) - time(A_0)) +// +// Here, the ack rate is intuitively the rate we want to treat as bandwidth. +// However, in certain cases (e.g. ack compression) the ack rate at a point may +// end up higher than the rate at which the data was originally sent, which is +// not indicative of the real bandwidth. Hence, we use the send rate as an upper +// bound, and the sample value is +// +// rate_sample = Min(send_rate, ack_rate) +// +// An important edge case handled by the sampler is tracking the app-limited +// samples. There are multiple meaning of "app-limited" used interchangeably, +// hence it is important to understand and to be able to distinguish between +// them. +// +// Meaning 1: connection state. The connection is said to be app-limited when +// there is no outstanding data to send. This means that certain bandwidth +// samples in the future would not be an accurate indication of the link +// capacity, and it is important to inform consumer about that. Whenever +// connection becomes app-limited, the sampler is notified via OnAppLimited() +// method. +// +// Meaning 2: a phase in the bandwidth sampler. As soon as the bandwidth +// sampler becomes notified about the connection being app-limited, it enters +// app-limited phase. In that phase, all *sent* packets are marked as +// app-limited. Note that the connection itself does not have to be +// app-limited during the app-limited phase, and in fact it will not be +// (otherwise how would it send packets?). The boolean flag below indicates +// whether the sampler is in that phase. +// +// Meaning 3: a flag on the sent packet and on the sample. If a sent packet is +// sent during the app-limited phase, the resulting sample related to the +// packet will be marked as app-limited. +// +// With the terminology issue out of the way, let us consider the question of +// what kind of situation it addresses. +// +// Consider a scenario where we first send packets 1 to 20 at a regular +// bandwidth, and then immediately run out of data. After a few seconds, we send +// packets 21 to 60, and only receive ack for 21 between sending packets 40 and +// 41. In this case, when we sample bandwidth for packets 21 to 40, the S_0/A_0 +// we use to compute the slope is going to be packet 20, a few seconds apart +// from the current packet, hence the resulting estimate would be extremely low +// and not indicative of anything. Only at packet 41 the S_0/A_0 will become 21, +// meaning that the bandwidth sample would exclude the quiescence. +// +// Based on the analysis of that scenario, we implement the following rule: once +// OnAppLimited() is called, all sent packets will produce app-limited samples +// up until an ack for a packet that was sent after OnAppLimited() was called. +// Note that while the scenario above is not the only scenario when the +// connection is app-limited, the approach works in other cases too. + +type congestionEventSample struct { + // The maximum bandwidth sample from all acked packets. + // QuicBandwidth::Zero() if no samples are available. + sampleMaxBandwidth Bandwidth + // Whether |sample_max_bandwidth| is from a app-limited sample. + sampleIsAppLimited bool + // The minimum rtt sample from all acked packets. + // QuicTime::Delta::Infinite() if no samples are available. + sampleRtt time.Duration + // For each packet p in acked packets, this is the max value of INFLIGHT(p), + // where INFLIGHT(p) is the number of bytes acked while p is inflight. + sampleMaxInflight congestion.ByteCount + // The send state of the largest packet in acked_packets, unless it is + // empty. If acked_packets is empty, it's the send state of the largest + // packet in lost_packets. + lastPacketSendState sendTimeState + // The number of extra bytes acked from this ack event, compared to what is + // expected from the flow's bandwidth. Larger value means more ack + // aggregation. + extraAcked congestion.ByteCount +} + +func newCongestionEventSample() *congestionEventSample { + return &congestionEventSample{ + sampleRtt: infRTT, + } +} + +type bandwidthSampler struct { + // The total number of congestion controlled bytes sent during the connection. + totalBytesSent congestion.ByteCount + + // The total number of congestion controlled bytes which were acknowledged. + totalBytesAcked congestion.ByteCount + + // The total number of congestion controlled bytes which were lost. + totalBytesLost congestion.ByteCount + + // The total number of congestion controlled bytes which have been neutered. + totalBytesNeutered congestion.ByteCount + + // The value of |total_bytes_sent_| at the time the last acknowledged packet + // was sent. Valid only when |last_acked_packet_sent_time_| is valid. + totalBytesSentAtLastAckedPacket congestion.ByteCount + + // The time at which the last acknowledged packet was sent. Set to + // QuicTime::Zero() if no valid timestamp is available. + lastAckedPacketSentTime time.Time + + // The time at which the most recent packet was acknowledged. + lastAckedPacketAckTime time.Time + + // The most recently sent packet. + lastSentPacket congestion.PacketNumber + + // The most recently acked packet. + lastAckedPacket congestion.PacketNumber + + // Indicates whether the bandwidth sampler is currently in an app-limited + // phase. + isAppLimited bool + + // The packet that will be acknowledged after this one will cause the sampler + // to exit the app-limited phase. + endOfAppLimitedPhase congestion.PacketNumber + + // Record of the connection state at the point where each packet in flight was + // sent, indexed by the packet number. + connectionStateMap *packetNumberIndexedQueue[connectionStateOnSentPacket] + + recentAckPoints recentAckPoints + a0Candidates RingBuffer[ackPoint] + + // Maximum number of tracked packets. + maxTrackedPackets congestion.ByteCount + + maxAckHeightTracker *maxAckHeightTracker + totalBytesAckedAfterLastAckEvent congestion.ByteCount + + // True if connection option 'BSAO' is set. + overestimateAvoidance bool + + // True if connection option 'BBRB' is set. + limitMaxAckHeightTrackerBySendRate bool +} + +func newBandwidthSampler(maxAckHeightTrackerWindowLength roundTripCount) *bandwidthSampler { + b := &bandwidthSampler{ + maxAckHeightTracker: newMaxAckHeightTracker(maxAckHeightTrackerWindowLength), + connectionStateMap: newPacketNumberIndexedQueue[connectionStateOnSentPacket](defaultConnectionStateMapQueueSize), + lastSentPacket: invalidPacketNumber, + lastAckedPacket: invalidPacketNumber, + endOfAppLimitedPhase: invalidPacketNumber, + } + + b.a0Candidates.Init(defaultCandidatesBufferSize) + + return b +} + +func (b *bandwidthSampler) MaxAckHeight() congestion.ByteCount { + return b.maxAckHeightTracker.Get() +} + +func (b *bandwidthSampler) NumAckAggregationEpochs() uint64 { + return b.maxAckHeightTracker.NumAckAggregationEpochs() +} + +func (b *bandwidthSampler) SetMaxAckHeightTrackerWindowLength(length roundTripCount) { + b.maxAckHeightTracker.SetFilterWindowLength(length) +} + +func (b *bandwidthSampler) ResetMaxAckHeightTracker(newHeight congestion.ByteCount, newTime roundTripCount) { + b.maxAckHeightTracker.Reset(newHeight, newTime) +} + +func (b *bandwidthSampler) SetStartNewAggregationEpochAfterFullRound(value bool) { + b.maxAckHeightTracker.SetStartNewAggregationEpochAfterFullRound(value) +} + +func (b *bandwidthSampler) SetLimitMaxAckHeightTrackerBySendRate(value bool) { + b.limitMaxAckHeightTrackerBySendRate = value +} + +func (b *bandwidthSampler) SetReduceExtraAckedOnBandwidthIncrease(value bool) { + b.maxAckHeightTracker.SetReduceExtraAckedOnBandwidthIncrease(value) +} + +func (b *bandwidthSampler) EnableOverestimateAvoidance() { + if b.overestimateAvoidance { + return + } + + b.overestimateAvoidance = true + b.maxAckHeightTracker.SetAckAggregationBandwidthThreshold(2.0) +} + +func (b *bandwidthSampler) IsOverestimateAvoidanceEnabled() bool { + return b.overestimateAvoidance +} + +func (b *bandwidthSampler) OnPacketSent( + sentTime time.Time, + packetNumber congestion.PacketNumber, + bytes congestion.ByteCount, + bytesInFlight congestion.ByteCount, + isRetransmittable bool, +) { + b.lastSentPacket = packetNumber + + if !isRetransmittable { + return + } + + b.totalBytesSent += bytes + + // If there are no packets in flight, the time at which the new transmission + // opens can be treated as the A_0 point for the purpose of bandwidth + // sampling. This underestimates bandwidth to some extent, and produces some + // artificially low samples for most packets in flight, but it provides with + // samples at important points where we would not have them otherwise, most + // importantly at the beginning of the connection. + if bytesInFlight == 0 { + b.lastAckedPacketAckTime = sentTime + if b.overestimateAvoidance { + b.recentAckPoints.Clear() + b.recentAckPoints.Update(sentTime, b.totalBytesAcked) + b.a0Candidates.Clear() + b.a0Candidates.PushBack(*b.recentAckPoints.MostRecentPoint()) + } + b.totalBytesSentAtLastAckedPacket = b.totalBytesSent + + // In this situation ack compression is not a concern, set send rate to + // effectively infinite. + b.lastAckedPacketSentTime = sentTime + } + + b.connectionStateMap.Emplace(packetNumber, newConnectionStateOnSentPacket( + sentTime, + bytes, + bytesInFlight+bytes, + b, + )) +} + +func (b *bandwidthSampler) OnCongestionEvent( + ackTime time.Time, + ackedPackets []congestion.AckedPacketInfo, + lostPackets []congestion.LostPacketInfo, + maxBandwidth Bandwidth, + estBandwidthUpperBound Bandwidth, + roundTripCount roundTripCount, +) congestionEventSample { + eventSample := newCongestionEventSample() + + var lastLostPacketSendState sendTimeState + + for _, p := range lostPackets { + sendState := b.OnPacketLost(p.PacketNumber, p.BytesLost) + if sendState.isValid { + lastLostPacketSendState = sendState + } + } + + if len(ackedPackets) == 0 { + // Only populate send state for a loss-only event. + eventSample.lastPacketSendState = lastLostPacketSendState + return *eventSample + } + + var lastAckedPacketSendState sendTimeState + var maxSendRate Bandwidth + + for _, p := range ackedPackets { + sample := b.onPacketAcknowledged(ackTime, p.PacketNumber) + if !sample.stateAtSend.isValid { + continue + } + + lastAckedPacketSendState = sample.stateAtSend + + if sample.rtt != 0 { + eventSample.sampleRtt = Min(eventSample.sampleRtt, sample.rtt) + } + if sample.bandwidth > eventSample.sampleMaxBandwidth { + eventSample.sampleMaxBandwidth = sample.bandwidth + eventSample.sampleIsAppLimited = sample.stateAtSend.isAppLimited + } + if sample.sendRate != infBandwidth { + maxSendRate = Max(maxSendRate, sample.sendRate) + } + inflightSample := b.totalBytesAcked - lastAckedPacketSendState.totalBytesAcked + if inflightSample > eventSample.sampleMaxInflight { + eventSample.sampleMaxInflight = inflightSample + } + } + + if !lastLostPacketSendState.isValid { + eventSample.lastPacketSendState = lastAckedPacketSendState + } else if !lastAckedPacketSendState.isValid { + eventSample.lastPacketSendState = lastLostPacketSendState + } else { + // If two packets are inflight and an alarm is armed to lose a packet and it + // wakes up late, then the first of two in flight packets could have been + // acknowledged before the wakeup, which re-evaluates loss detection, and + // could declare the later of the two lost. + if lostPackets[len(lostPackets)-1].PacketNumber > ackedPackets[len(ackedPackets)-1].PacketNumber { + eventSample.lastPacketSendState = lastLostPacketSendState + } else { + eventSample.lastPacketSendState = lastAckedPacketSendState + } + } + + isNewMaxBandwidth := eventSample.sampleMaxBandwidth > maxBandwidth + maxBandwidth = Max(maxBandwidth, eventSample.sampleMaxBandwidth) + if b.limitMaxAckHeightTrackerBySendRate { + maxBandwidth = Max(maxBandwidth, maxSendRate) + } + + eventSample.extraAcked = b.onAckEventEnd(Min(estBandwidthUpperBound, maxBandwidth), isNewMaxBandwidth, roundTripCount) + + return *eventSample +} + +func (b *bandwidthSampler) OnPacketLost(packetNumber congestion.PacketNumber, bytesLost congestion.ByteCount) (s sendTimeState) { + b.totalBytesLost += bytesLost + if sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber); sentPacketPointer != nil { + sentPacketToSendTimeState(sentPacketPointer, &s) + } + return s +} + +func (b *bandwidthSampler) OnPacketNeutered(packetNumber congestion.PacketNumber) { + b.connectionStateMap.Remove(packetNumber, func(sentPacket connectionStateOnSentPacket) { + b.totalBytesNeutered += sentPacket.size + }) +} + +func (b *bandwidthSampler) OnAppLimited() { + b.isAppLimited = true + b.endOfAppLimitedPhase = b.lastSentPacket +} + +func (b *bandwidthSampler) RemoveObsoletePackets(leastUnacked congestion.PacketNumber) { + // A packet can become obsolete when it is removed from QuicUnackedPacketMap's + // view of inflight before it is acked or marked as lost. For example, when + // QuicSentPacketManager::RetransmitCryptoPackets retransmits a crypto packet, + // the packet is removed from QuicUnackedPacketMap's inflight, but is not + // marked as acked or lost in the BandwidthSampler. + b.connectionStateMap.RemoveUpTo(leastUnacked) +} + +func (b *bandwidthSampler) TotalBytesSent() congestion.ByteCount { + return b.totalBytesSent +} + +func (b *bandwidthSampler) TotalBytesLost() congestion.ByteCount { + return b.totalBytesLost +} + +func (b *bandwidthSampler) TotalBytesAcked() congestion.ByteCount { + return b.totalBytesAcked +} + +func (b *bandwidthSampler) TotalBytesNeutered() congestion.ByteCount { + return b.totalBytesNeutered +} + +func (b *bandwidthSampler) IsAppLimited() bool { + return b.isAppLimited +} + +func (b *bandwidthSampler) EndOfAppLimitedPhase() congestion.PacketNumber { + return b.endOfAppLimitedPhase +} + +func (b *bandwidthSampler) max_ack_height() congestion.ByteCount { + return b.maxAckHeightTracker.Get() +} + +func (b *bandwidthSampler) chooseA0Point(totalBytesAcked congestion.ByteCount, a0 *ackPoint) bool { + if b.a0Candidates.Empty() { + return false + } + + if b.a0Candidates.Len() == 1 { + *a0 = *b.a0Candidates.Front() + return true + } + + for i := 1; i < b.a0Candidates.Len(); i++ { + if b.a0Candidates.Offset(i).totalBytesAcked > totalBytesAcked { + *a0 = *b.a0Candidates.Offset(i - 1) + if i > 1 { + for j := 0; j < i-1; j++ { + b.a0Candidates.PopFront() + } + } + return true + } + } + + *a0 = *b.a0Candidates.Back() + for k := 0; k < b.a0Candidates.Len()-1; k++ { + b.a0Candidates.PopFront() + } + return true +} + +func (b *bandwidthSampler) onPacketAcknowledged(ackTime time.Time, packetNumber congestion.PacketNumber) bandwidthSample { + sample := newBandwidthSample() + b.lastAckedPacket = packetNumber + sentPacketPointer := b.connectionStateMap.GetEntry(packetNumber) + if sentPacketPointer == nil { + return *sample + } + + // OnPacketAcknowledgedInner + b.totalBytesAcked += sentPacketPointer.size + b.totalBytesSentAtLastAckedPacket = sentPacketPointer.sendTimeState.totalBytesSent + b.lastAckedPacketSentTime = sentPacketPointer.sentTime + b.lastAckedPacketAckTime = ackTime + if b.overestimateAvoidance { + b.recentAckPoints.Update(ackTime, b.totalBytesAcked) + } + + if b.isAppLimited { + // Exit app-limited phase in two cases: + // (1) end_of_app_limited_phase_ is not initialized, i.e., so far all + // packets are sent while there are buffered packets or pending data. + // (2) The current acked packet is after the sent packet marked as the end + // of the app limit phase. + if b.endOfAppLimitedPhase == invalidPacketNumber || + packetNumber > b.endOfAppLimitedPhase { + b.isAppLimited = false + } + } + + // There might have been no packets acknowledged at the moment when the + // current packet was sent. In that case, there is no bandwidth sample to + // make. + if sentPacketPointer.lastAckedPacketSentTime.IsZero() { + return *sample + } + + // Infinite rate indicates that the sampler is supposed to discard the + // current send rate sample and use only the ack rate. + sendRate := infBandwidth + if sentPacketPointer.sentTime.After(sentPacketPointer.lastAckedPacketSentTime) { + sendRate = BandwidthFromDelta( + sentPacketPointer.sendTimeState.totalBytesSent-sentPacketPointer.totalBytesSentAtLastAckedPacket, + sentPacketPointer.sentTime.Sub(sentPacketPointer.lastAckedPacketSentTime)) + } + + var a0 ackPoint + if b.overestimateAvoidance && b.chooseA0Point(sentPacketPointer.sendTimeState.totalBytesAcked, &a0) { + } else { + a0.ackTime = sentPacketPointer.lastAckedPacketAckTime + a0.totalBytesAcked = sentPacketPointer.sendTimeState.totalBytesAcked + } + + // During the slope calculation, ensure that ack time of the current packet is + // always larger than the time of the previous packet, otherwise division by + // zero or integer underflow can occur. + if ackTime.Sub(a0.ackTime) <= 0 { + return *sample + } + + ackRate := BandwidthFromDelta(b.totalBytesAcked-a0.totalBytesAcked, ackTime.Sub(a0.ackTime)) + + sample.bandwidth = Min(sendRate, ackRate) + // Note: this sample does not account for delayed acknowledgement time. This + // means that the RTT measurements here can be artificially high, especially + // on low bandwidth connections. + sample.rtt = ackTime.Sub(sentPacketPointer.sentTime) + sample.sendRate = sendRate + sentPacketToSendTimeState(sentPacketPointer, &sample.stateAtSend) + + return *sample +} + +func (b *bandwidthSampler) onAckEventEnd( + bandwidthEstimate Bandwidth, + isNewMaxBandwidth bool, + roundTripCount roundTripCount, +) congestion.ByteCount { + newlyAckedBytes := b.totalBytesAcked - b.totalBytesAckedAfterLastAckEvent + if newlyAckedBytes == 0 { + return 0 + } + b.totalBytesAckedAfterLastAckEvent = b.totalBytesAcked + extraAcked := b.maxAckHeightTracker.Update( + bandwidthEstimate, + isNewMaxBandwidth, + roundTripCount, + b.lastSentPacket, + b.lastAckedPacket, + b.lastAckedPacketAckTime, + newlyAckedBytes) + // If |extra_acked| is zero, i.e. this ack event marks the start of a new ack + // aggregation epoch, save LessRecentPoint, which is the last ack point of the + // previous epoch, as a A0 candidate. + if b.overestimateAvoidance && extraAcked == 0 { + b.a0Candidates.PushBack(*b.recentAckPoints.LessRecentPoint()) + } + return extraAcked +} + +func sentPacketToSendTimeState(sentPacket *connectionStateOnSentPacket, sendTimeState *sendTimeState) { + *sendTimeState = sentPacket.sendTimeState + sendTimeState.isValid = true +} + +// BytesFromBandwidthAndTimeDelta calculates the bytes +// from a bandwidth(bits per second) and a time delta +func bytesFromBandwidthAndTimeDelta(bandwidth Bandwidth, delta time.Duration) congestion.ByteCount { + return (congestion.ByteCount(bandwidth) * congestion.ByteCount(delta)) / + (congestion.ByteCount(time.Second) * 8) +} + +func timeDeltaFromBytesAndBandwidth(bytes congestion.ByteCount, bandwidth Bandwidth) time.Duration { + return time.Duration(bytes*8) * time.Second / time.Duration(bandwidth) +} diff --git a/transport/tuic/congestion_v2/bbr_sender.go b/transport/tuic/congestion_v2/bbr_sender.go new file mode 100644 index 00000000..084f85b1 --- /dev/null +++ b/transport/tuic/congestion_v2/bbr_sender.go @@ -0,0 +1,946 @@ +package congestion + +// src from https://github.com/google/quiche/blob/e7872fc9e12bb1d46a118949c3d4da36de58aa44/quiche/quic/core/congestion_control/bbr_sender.cc + +import ( + "fmt" + "net" + "time" + + "github.com/metacubex/quic-go/congestion" + + "github.com/zhangyunhao116/fastrand" +) + +// BbrSender implements BBR congestion control algorithm. BBR aims to estimate +// the current available Bottleneck Bandwidth and RTT (hence the name), and +// regulates the pacing rate and the size of the congestion window based on +// those signals. +// +// BBR relies on pacing in order to function properly. Do not use BBR when +// pacing is disabled. +// + +const ( + minBps = 65536 // 64 kbps + + invalidPacketNumber = -1 + initialCongestionWindowPackets = 32 + + // 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 * congestion.ByteCount(congestion.InitialPacketSizeIPv4) + + // The gain used for the STARTUP, equal to 2/ln(2). + defaultHighGain = 2.885 + // The newly derived gain for STARTUP, equal to 4 * ln(2) + derivedHighGain = 2.773 + // The newly derived CWND gain for STARTUP, 2. + derivedHighCWNDGain = 2.0 +) + +// The cycle of gains used during the PROBE_BW stage. +var pacingGain = [...]float64{1.25, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0} + +const ( + // The length of the gain cycle. + gainCycleLength = len(pacingGain) + // The size of the bandwidth filter window, in round-trips. + bandwidthWindowSize = gainCycleLength + 2 + + // The time after which the current min_rtt value expires. + minRttExpiry = 10 * time.Second + // The minimum time the connection can spend in PROBE_RTT mode. + probeRttTime = 200 * time.Millisecond + // If the bandwidth does not increase by the factor of |kStartupGrowthTarget| + // within |kRoundTripsWithoutGrowthBeforeExitingStartup| rounds, the connection + // will exit the STARTUP mode. + startupGrowthTarget = 1.25 + roundTripsWithoutGrowthBeforeExitingStartup = int64(3) + + // Flag. + defaultStartupFullLossCount = 8 + quicBbr2DefaultLossThreshold = 0.02 + maxBbrBurstPackets = 3 +) + +type bbrMode int + +const ( + // Startup phase of the connection. + bbrModeStartup = iota + // After achieving the highest possible bandwidth during the startup, lower + // the pacing rate in order to drain the queue. + bbrModeDrain + // Cruising mode. + bbrModeProbeBw + // Temporarily slow down sending in order to empty the buffer and measure + // the real minimum RTT. + bbrModeProbeRtt +) + +// Indicates how the congestion control limits the amount of bytes in flight. +type bbrRecoveryState int + +const ( + // Do not limit. + bbrRecoveryStateNotInRecovery = iota + // Allow an extra outstanding byte for each byte acknowledged. + bbrRecoveryStateConservation + // Allow two extra outstanding bytes for each byte acknowledged (slow + // start). + bbrRecoveryStateGrowth +) + +type bbrSender struct { + rttStats congestion.RTTStatsProvider + clock Clock + pacer *Pacer + + mode bbrMode + + // Bandwidth sampler provides BBR with the bandwidth measurements at + // individual points. + sampler *bandwidthSampler + + // The number of the round trips that have occurred during the connection. + roundTripCount roundTripCount + + // The packet number of the most recently sent packet. + lastSentPacket congestion.PacketNumber + // Acknowledgement of any packet after |current_round_trip_end_| will cause + // the round trip counter to advance. + currentRoundTripEnd congestion.PacketNumber + + // Number of congestion events with some losses, in the current round. + numLossEventsInRound uint64 + + // Number of total bytes lost in the current round. + bytesLostInRound congestion.ByteCount + + // The filter that tracks the maximum bandwidth over the multiple recent + // round-trips. + maxBandwidth *WindowedFilter[Bandwidth, roundTripCount] + + // Minimum RTT estimate. Automatically expires within 10 seconds (and + // triggers PROBE_RTT mode) if no new value is sampled during that period. + minRtt time.Duration + // The time at which the current value of |min_rtt_| was assigned. + minRttTimestamp time.Time + + // The maximum allowed number of bytes in flight. + congestionWindow congestion.ByteCount + + // The initial value of the |congestion_window_|. + initialCongestionWindow congestion.ByteCount + + // The largest value the |congestion_window_| can achieve. + maxCongestionWindow congestion.ByteCount + + // The smallest value the |congestion_window_| can achieve. + minCongestionWindow congestion.ByteCount + + // The pacing gain applied during the STARTUP phase. + highGain float64 + + // The CWND gain applied during the STARTUP phase. + highCwndGain float64 + + // The pacing gain applied during the DRAIN phase. + drainGain float64 + + // The current pacing rate of the connection. + pacingRate Bandwidth + + // The gain currently applied to the pacing rate. + pacingGain float64 + // The gain currently applied to the congestion window. + congestionWindowGain float64 + + // The gain used for the congestion window during PROBE_BW. Latched from + // quic_bbr_cwnd_gain flag. + congestionWindowGainConstant float64 + // The number of RTTs to stay in STARTUP mode. Defaults to 3. + numStartupRtts int64 + + // Number of round-trips in PROBE_BW mode, used for determining the current + // pacing gain cycle. + cycleCurrentOffset int + // The time at which the last pacing gain cycle was started. + lastCycleStart time.Time + + // Indicates whether the connection has reached the full bandwidth mode. + isAtFullBandwidth bool + // Number of rounds during which there was no significant bandwidth increase. + roundsWithoutBandwidthGain int64 + // The bandwidth compared to which the increase is measured. + bandwidthAtLastRound Bandwidth + + // Set to true upon exiting quiescence. + exitingQuiescence bool + + // Time at which PROBE_RTT has to be exited. Setting it to zero indicates + // that the time is yet unknown as the number of packets in flight has not + // reached the required value. + exitProbeRttAt time.Time + // Indicates whether a round-trip has passed since PROBE_RTT became active. + probeRttRoundPassed bool + + // Indicates whether the most recent bandwidth sample was marked as + // app-limited. + lastSampleIsAppLimited bool + // Indicates whether any non app-limited samples have been recorded. + hasNoAppLimitedSample bool + + // Current state of recovery. + recoveryState bbrRecoveryState + // Receiving acknowledgement of a packet after |end_recovery_at_| will cause + // BBR to exit the recovery mode. A value above zero indicates at least one + // loss has been detected, so it must not be set back to zero. + endRecoveryAt congestion.PacketNumber + // A window used to limit the number of bytes in flight during loss recovery. + recoveryWindow congestion.ByteCount + // If true, consider all samples in recovery app-limited. + isAppLimitedRecovery bool // not used + + // When true, pace at 1.5x and disable packet conservation in STARTUP. + slowerStartup bool // not used + // When true, disables packet conservation in STARTUP. + rateBasedStartup bool // not used + + // When true, add the most recent ack aggregation measurement during STARTUP. + enableAckAggregationDuringStartup bool + // When true, expire the windowed ack aggregation values in STARTUP when + // bandwidth increases more than 25%. + expireAckAggregationInStartup bool + + // If true, will not exit low gain mode until bytes_in_flight drops below BDP + // or it's time for high gain mode. + drainToTarget bool + + // If true, slow down pacing rate in STARTUP when overshooting is detected. + detectOvershooting bool + // Bytes lost while detect_overshooting_ is true. + bytesLostWhileDetectingOvershooting congestion.ByteCount + // Slow down pacing rate if + // bytes_lost_while_detecting_overshooting_ * + // bytes_lost_multiplier_while_detecting_overshooting_ > IW. + bytesLostMultiplierWhileDetectingOvershooting uint8 + // When overshooting is detected, do not drop pacing_rate_ below this value / + // min_rtt. + cwndToCalculateMinPacingRate congestion.ByteCount + + // Max congestion window when adjusting network parameters. + maxCongestionWindowWithNetworkParametersAdjusted congestion.ByteCount // not used + + // Params. + maxDatagramSize congestion.ByteCount + // Recorded on packet sent. equivalent |unacked_packets_->bytes_in_flight()| + bytesInFlight congestion.ByteCount +} + +var _ congestion.CongestionControl = &bbrSender{} + +func NewBbrSender( + clock Clock, + initialMaxDatagramSize congestion.ByteCount, + initialCongestionWindowPackets congestion.ByteCount, +) *bbrSender { + return newBbrSender( + clock, + initialMaxDatagramSize, + initialCongestionWindowPackets*initialMaxDatagramSize, + congestion.MaxCongestionWindowPackets*initialMaxDatagramSize, + ) +} + +func newBbrSender( + clock Clock, + initialMaxDatagramSize, + initialCongestionWindow, + initialMaxCongestionWindow congestion.ByteCount, +) *bbrSender { + b := &bbrSender{ + clock: clock, + mode: bbrModeStartup, + sampler: newBandwidthSampler(roundTripCount(bandwidthWindowSize)), + lastSentPacket: invalidPacketNumber, + currentRoundTripEnd: invalidPacketNumber, + maxBandwidth: NewWindowedFilter(roundTripCount(bandwidthWindowSize), MaxFilter[Bandwidth]), + congestionWindow: initialCongestionWindow, + initialCongestionWindow: initialCongestionWindow, + maxCongestionWindow: initialMaxCongestionWindow, + minCongestionWindow: defaultMinimumCongestionWindow, + highGain: defaultHighGain, + highCwndGain: defaultHighGain, + drainGain: 1.0 / defaultHighGain, + pacingGain: 1.0, + congestionWindowGain: 1.0, + congestionWindowGainConstant: 2.0, + numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup, + recoveryState: bbrRecoveryStateNotInRecovery, + endRecoveryAt: invalidPacketNumber, + recoveryWindow: initialMaxCongestionWindow, + bytesLostMultiplierWhileDetectingOvershooting: 2, + cwndToCalculateMinPacingRate: initialCongestionWindow, + maxCongestionWindowWithNetworkParametersAdjusted: initialMaxCongestionWindow, + maxDatagramSize: initialMaxDatagramSize, + } + b.pacer = NewPacer(b.bandwidthForPacer) + + /* + if b.tracer != nil { + b.lastState = logging.CongestionStateStartup + b.tracer.UpdatedCongestionState(logging.CongestionStateStartup) + } + */ + + b.enterStartupMode(b.clock.Now()) + b.setHighCwndGain(derivedHighCWNDGain) + + return b +} + +func (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) { + b.rttStats = provider +} + +// TimeUntilSend implements the SendAlgorithm interface. +func (b *bbrSender) TimeUntilSend(bytesInFlight congestion.ByteCount) time.Time { + return b.pacer.TimeUntilSend() +} + +// HasPacingBudget implements the SendAlgorithm interface. +func (b *bbrSender) HasPacingBudget(now time.Time) bool { + return b.pacer.Budget(now) >= b.maxDatagramSize +} + +// OnPacketSent implements the SendAlgorithm interface. +func (b *bbrSender) OnPacketSent( + sentTime time.Time, + bytesInFlight congestion.ByteCount, + packetNumber congestion.PacketNumber, + bytes congestion.ByteCount, + isRetransmittable bool, +) { + b.pacer.SentPacket(sentTime, bytes) + + b.lastSentPacket = packetNumber + b.bytesInFlight = bytesInFlight + + if bytesInFlight == 0 { + b.exitingQuiescence = true + } + + b.sampler.OnPacketSent(sentTime, packetNumber, bytes, bytesInFlight, isRetransmittable) +} + +// CanSend implements the SendAlgorithm interface. +func (b *bbrSender) CanSend(bytesInFlight congestion.ByteCount) bool { + return bytesInFlight < b.GetCongestionWindow() +} + +// MaybeExitSlowStart implements the SendAlgorithm interface. +func (b *bbrSender) MaybeExitSlowStart() { + // Do nothing +} + +// OnPacketAcked implements the SendAlgorithm interface. +func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes, priorInFlight congestion.ByteCount, eventTime time.Time) { + // Do nothing. +} + +// OnPacketLost implements the SendAlgorithm interface. +func (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) { + // Do nothing. +} + +// OnRetransmissionTimeout implements the SendAlgorithm interface. +func (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) { + // Do nothing. +} + +// SetMaxDatagramSize implements the SendAlgorithm interface. +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 + b.maxDatagramSize = s + if cwndIsMinCwnd { + b.congestionWindow = b.minCongestionWindow + } + b.pacer.SetMaxDatagramSize(s) +} + +// InSlowStart implements the SendAlgorithmWithDebugInfos interface. +func (b *bbrSender) InSlowStart() bool { + return b.mode == bbrModeStartup +} + +// InRecovery implements the SendAlgorithmWithDebugInfos interface. +func (b *bbrSender) InRecovery() bool { + return b.recoveryState != bbrRecoveryStateNotInRecovery +} + +// GetCongestionWindow implements the SendAlgorithmWithDebugInfos interface. +func (b *bbrSender) GetCongestionWindow() congestion.ByteCount { + if b.mode == bbrModeProbeRtt { + return b.probeRttCongestionWindow() + } + + if b.InRecovery() { + return Min(b.congestionWindow, b.recoveryWindow) + } + + return b.congestionWindow +} + +func (b *bbrSender) OnCongestionEvent(number congestion.PacketNumber, lostBytes, priorInFlight congestion.ByteCount) { + // Do nothing. +} + +func (b *bbrSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, eventTime time.Time, ackedPackets []congestion.AckedPacketInfo, lostPackets []congestion.LostPacketInfo) { + totalBytesAckedBefore := b.sampler.TotalBytesAcked() + totalBytesLostBefore := b.sampler.TotalBytesLost() + + var isRoundStart, minRttExpired bool + var excessAcked, bytesLost congestion.ByteCount + + // The send state of the largest packet in acked_packets, unless it is + // empty. If acked_packets is empty, it's the send state of the largest + // packet in lost_packets. + var lastPacketSendState sendTimeState + + b.maybeApplimited(priorInFlight) + + // Update bytesInFlight + b.bytesInFlight = priorInFlight + for _, p := range ackedPackets { + b.bytesInFlight -= p.BytesAcked + } + for _, p := range lostPackets { + b.bytesInFlight -= p.BytesLost + } + + if len(ackedPackets) != 0 { + lastAckedPacket := ackedPackets[len(ackedPackets)-1].PacketNumber + isRoundStart = b.updateRoundTripCounter(lastAckedPacket) + b.updateRecoveryState(lastAckedPacket, len(lostPackets) != 0, isRoundStart) + } + + sample := b.sampler.OnCongestionEvent(eventTime, + ackedPackets, lostPackets, b.maxBandwidth.GetBest(), infBandwidth, b.roundTripCount) + if sample.lastPacketSendState.isValid { + b.lastSampleIsAppLimited = sample.lastPacketSendState.isAppLimited + b.hasNoAppLimitedSample = b.hasNoAppLimitedSample || !b.lastSampleIsAppLimited + } + // Avoid updating |max_bandwidth_| if a) this is a loss-only event, or b) all + // packets in |acked_packets| did not generate valid samples. (e.g. ack of + // ack-only packets). In both cases, sampler_.total_bytes_acked() will not + // change. + if totalBytesAckedBefore != b.sampler.TotalBytesAcked() { + if !sample.sampleIsAppLimited || sample.sampleMaxBandwidth > b.maxBandwidth.GetBest() { + b.maxBandwidth.Update(sample.sampleMaxBandwidth, b.roundTripCount) + } + } + + if sample.sampleRtt != infRTT { + minRttExpired = b.maybeUpdateMinRtt(eventTime, sample.sampleRtt) + } + bytesLost = b.sampler.TotalBytesLost() - totalBytesLostBefore + + excessAcked = sample.extraAcked + lastPacketSendState = sample.lastPacketSendState + + if len(lostPackets) != 0 { + b.numLossEventsInRound++ + b.bytesLostInRound += bytesLost + } + + // Handle logic specific to PROBE_BW mode. + if b.mode == bbrModeProbeBw { + b.updateGainCyclePhase(eventTime, priorInFlight, len(lostPackets) != 0) + } + + // Handle logic specific to STARTUP and DRAIN modes. + if isRoundStart && !b.isAtFullBandwidth { + b.checkIfFullBandwidthReached(&lastPacketSendState) + } + + b.maybeExitStartupOrDrain(eventTime) + + // Handle logic specific to PROBE_RTT. + b.maybeEnterOrExitProbeRtt(eventTime, isRoundStart, minRttExpired) + + // Calculate number of packets acked and lost. + bytesAcked := b.sampler.TotalBytesAcked() - totalBytesAckedBefore + + // After the model is updated, recalculate the pacing rate and congestion + // window. + b.calculatePacingRate(bytesLost) + b.calculateCongestionWindow(bytesAcked, excessAcked) + b.calculateRecoveryWindow(bytesAcked, bytesLost) + + // Cleanup internal state. + // This is where we clean up obsolete (acked or lost) packets from the bandwidth sampler. + // The "least unacked" should actually be FirstOutstanding, but since we are not passing + // that through OnCongestionEventEx, we will only do an estimate using acked/lost packets + // for now. Because of fast retransmission, they should differ by no more than 2 packets. + // (this is controlled by packetThreshold in quic-go's sentPacketHandler) + var leastUnacked congestion.PacketNumber + if len(ackedPackets) != 0 { + leastUnacked = ackedPackets[len(ackedPackets)-1].PacketNumber - 2 + } else { + leastUnacked = lostPackets[len(lostPackets)-1].PacketNumber + 1 + } + b.sampler.RemoveObsoletePackets(leastUnacked) + + if isRoundStart { + b.numLossEventsInRound = 0 + b.bytesLostInRound = 0 + } +} + +func (b *bbrSender) PacingRate() Bandwidth { + if b.pacingRate == 0 { + return Bandwidth(b.highGain * float64( + BandwidthFromDelta(b.initialCongestionWindow, b.getMinRtt()))) + } + + return b.pacingRate +} + +func (b *bbrSender) hasGoodBandwidthEstimateForResumption() bool { + return b.hasNonAppLimitedSample() +} + +func (b *bbrSender) hasNonAppLimitedSample() bool { + return b.hasNoAppLimitedSample +} + +// Sets the pacing gain used in STARTUP. Must be greater than 1. +func (b *bbrSender) setHighGain(highGain float64) { + b.highGain = highGain + if b.mode == bbrModeStartup { + b.pacingGain = highGain + } +} + +// Sets the CWND gain used in STARTUP. Must be greater than 1. +func (b *bbrSender) setHighCwndGain(highCwndGain float64) { + b.highCwndGain = highCwndGain + if b.mode == bbrModeStartup { + b.congestionWindowGain = highCwndGain + } +} + +// Sets the gain used in DRAIN. Must be less than 1. +func (b *bbrSender) setDrainGain(drainGain float64) { + b.drainGain = drainGain +} + +// What's the current estimated bandwidth in bytes per second. +func (b *bbrSender) bandwidthEstimate() Bandwidth { + return b.maxBandwidth.GetBest() +} + +func (b *bbrSender) bandwidthForPacer() congestion.ByteCount { + bps := congestion.ByteCount(float64(b.bandwidthEstimate()) * b.congestionWindowGain / float64(BytesPerSecond)) + if bps < minBps { + // We need to make sure that the bandwidth value for pacer is never zero, + // otherwise it will go into an edge case where HasPacingBudget = false + // but TimeUntilSend is before, causing the quic-go send loop to go crazy and get stuck. + return minBps + } + return bps +} + +// Returns the current estimate of the RTT of the connection. Outside of the +// edge cases, this is minimum RTT. +func (b *bbrSender) getMinRtt() time.Duration { + if b.minRtt != 0 { + return b.minRtt + } + // min_rtt could be available if the handshake packet gets neutered then + // gets acknowledged. This could only happen for QUIC crypto where we do not + // drop keys. + minRtt := b.rttStats.MinRTT() + if minRtt == 0 { + return 100 * time.Millisecond + } else { + return minRtt + } +} + +// Computes the target congestion window using the specified gain. +func (b *bbrSender) getTargetCongestionWindow(gain float64) congestion.ByteCount { + bdp := bdpFromRttAndBandwidth(b.getMinRtt(), b.bandwidthEstimate()) + congestionWindow := congestion.ByteCount(gain * float64(bdp)) + + // BDP estimate will be zero if no bandwidth samples are available yet. + if congestionWindow == 0 { + congestionWindow = congestion.ByteCount(gain * float64(b.initialCongestionWindow)) + } + + return Max(congestionWindow, b.minCongestionWindow) +} + +// The target congestion window during PROBE_RTT. +func (b *bbrSender) probeRttCongestionWindow() congestion.ByteCount { + return b.minCongestionWindow +} + +func (b *bbrSender) maybeUpdateMinRtt(now time.Time, sampleMinRtt time.Duration) bool { + // Do not expire min_rtt if none was ever available. + minRttExpired := b.minRtt != 0 && now.After(b.minRttTimestamp.Add(minRttExpiry)) + if minRttExpired || sampleMinRtt < b.minRtt || b.minRtt == 0 { + b.minRtt = sampleMinRtt + b.minRttTimestamp = now + } + + return minRttExpired +} + +// Enters the STARTUP mode. +func (b *bbrSender) enterStartupMode(now time.Time) { + b.mode = bbrModeStartup + // b.maybeTraceStateChange(logging.CongestionStateStartup) + b.pacingGain = b.highGain + b.congestionWindowGain = b.highCwndGain +} + +// Enters the PROBE_BW mode. +func (b *bbrSender) enterProbeBandwidthMode(now time.Time) { + b.mode = bbrModeProbeBw + // b.maybeTraceStateChange(logging.CongestionStateProbeBw) + b.congestionWindowGain = b.congestionWindowGainConstant + + // Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is + // excluded because in that case increased gain and decreased gain would not + // follow each other. + b.cycleCurrentOffset = int(fastrand.Int31n(congestion.PacketsPerConnectionID)) % (gainCycleLength - 1) + if b.cycleCurrentOffset >= 1 { + b.cycleCurrentOffset += 1 + } + + b.lastCycleStart = now + b.pacingGain = pacingGain[b.cycleCurrentOffset] +} + +// Updates the round-trip counter if a round-trip has passed. Returns true if +// the counter has been advanced. +func (b *bbrSender) updateRoundTripCounter(lastAckedPacket congestion.PacketNumber) bool { + if b.currentRoundTripEnd == invalidPacketNumber || lastAckedPacket > b.currentRoundTripEnd { + b.roundTripCount++ + b.currentRoundTripEnd = b.lastSentPacket + return true + } + return false +} + +// Updates the current gain used in PROBE_BW mode. +func (b *bbrSender) updateGainCyclePhase(now time.Time, priorInFlight congestion.ByteCount, hasLosses bool) { + // In most cases, the cycle is advanced after an RTT passes. + shouldAdvanceGainCycling := now.After(b.lastCycleStart.Add(b.getMinRtt())) + // If the pacing gain is above 1.0, the connection is trying to probe the + // bandwidth by increasing the number of bytes in flight to at least + // pacing_gain * BDP. Make sure that it actually reaches the target, as long + // as there are no losses suggesting that the buffers are not able to hold + // that much. + if b.pacingGain > 1.0 && !hasLosses && priorInFlight < b.getTargetCongestionWindow(b.pacingGain) { + shouldAdvanceGainCycling = false + } + + // If pacing gain is below 1.0, the connection is trying to drain the extra + // queue which could have been incurred by probing prior to it. If the number + // of bytes in flight falls down to the estimated BDP value earlier, conclude + // that the queue has been successfully drained and exit this cycle early. + if b.pacingGain < 1.0 && b.bytesInFlight <= b.getTargetCongestionWindow(1) { + shouldAdvanceGainCycling = true + } + + if shouldAdvanceGainCycling { + b.cycleCurrentOffset = (b.cycleCurrentOffset + 1) % gainCycleLength + b.lastCycleStart = now + // Stay in low gain mode until the target BDP is hit. + // Low gain mode will be exited immediately when the target BDP is achieved. + if b.drainToTarget && b.pacingGain < 1 && + pacingGain[b.cycleCurrentOffset] == 1 && + b.bytesInFlight > b.getTargetCongestionWindow(1) { + return + } + b.pacingGain = pacingGain[b.cycleCurrentOffset] + } +} + +// Tracks for how many round-trips the bandwidth has not increased +// significantly. +func (b *bbrSender) checkIfFullBandwidthReached(lastPacketSendState *sendTimeState) { + if b.lastSampleIsAppLimited { + return + } + + target := Bandwidth(float64(b.bandwidthAtLastRound) * startupGrowthTarget) + if b.bandwidthEstimate() >= target { + b.bandwidthAtLastRound = b.bandwidthEstimate() + b.roundsWithoutBandwidthGain = 0 + if b.expireAckAggregationInStartup { + // Expire old excess delivery measurements now that bandwidth increased. + b.sampler.ResetMaxAckHeightTracker(0, b.roundTripCount) + } + return + } + + b.roundsWithoutBandwidthGain++ + if b.roundsWithoutBandwidthGain >= b.numStartupRtts || + b.shouldExitStartupDueToLoss(lastPacketSendState) { + b.isAtFullBandwidth = true + } +} + +func (b *bbrSender) maybeApplimited(bytesInFlight congestion.ByteCount) { + congestionWindow := b.GetCongestionWindow() + if bytesInFlight >= congestionWindow { + return + } + availableBytes := congestionWindow - bytesInFlight + drainLimited := b.mode == bbrModeDrain && bytesInFlight > congestionWindow/2 + if !drainLimited || availableBytes > maxBbrBurstPackets*b.maxDatagramSize { + b.sampler.OnAppLimited() + } +} + +// Transitions from STARTUP to DRAIN and from DRAIN to PROBE_BW if +// appropriate. +func (b *bbrSender) maybeExitStartupOrDrain(now time.Time) { + if b.mode == bbrModeStartup && b.isAtFullBandwidth { + b.mode = bbrModeDrain + // b.maybeTraceStateChange(logging.CongestionStateDrain) + b.pacingGain = b.drainGain + b.congestionWindowGain = b.highCwndGain + } + if b.mode == bbrModeDrain && b.bytesInFlight <= b.getTargetCongestionWindow(1) { + b.enterProbeBandwidthMode(now) + } +} + +// Decides whether to enter or exit PROBE_RTT. +func (b *bbrSender) maybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRttExpired bool) { + if minRttExpired && !b.exitingQuiescence && b.mode != bbrModeProbeRtt { + b.mode = bbrModeProbeRtt + // b.maybeTraceStateChange(logging.CongestionStateProbRtt) + b.pacingGain = 1.0 + // Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight| + // is at the target small value. + b.exitProbeRttAt = time.Time{} + } + + if b.mode == bbrModeProbeRtt { + b.sampler.OnAppLimited() + // b.maybeTraceStateChange(logging.CongestionStateApplicationLimited) + + if b.exitProbeRttAt.IsZero() { + // If the window has reached the appropriate size, schedule exiting + // 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.bytesInFlight < b.probeRttCongestionWindow()+congestion.MaxPacketBufferSize { + b.exitProbeRttAt = now.Add(probeRttTime) + b.probeRttRoundPassed = false + } + } else { + if isRoundStart { + b.probeRttRoundPassed = true + } + if now.Sub(b.exitProbeRttAt) >= 0 && b.probeRttRoundPassed { + b.minRttTimestamp = now + if !b.isAtFullBandwidth { + b.enterStartupMode(now) + } else { + b.enterProbeBandwidthMode(now) + } + } + } + } + + b.exitingQuiescence = false +} + +// Determines whether BBR needs to enter, exit or advance state of the +// recovery. +func (b *bbrSender) updateRecoveryState(lastAckedPacket congestion.PacketNumber, hasLosses, isRoundStart bool) { + // Disable recovery in startup, if loss-based exit is enabled. + if !b.isAtFullBandwidth { + return + } + + // Exit recovery when there are no losses for a round. + if hasLosses { + b.endRecoveryAt = b.lastSentPacket + } + + switch b.recoveryState { + case bbrRecoveryStateNotInRecovery: + if hasLosses { + b.recoveryState = bbrRecoveryStateConservation + // This will cause the |recovery_window_| to be set to the correct + // value in CalculateRecoveryWindow(). + b.recoveryWindow = 0 + // Since the conservation phase is meant to be lasting for a whole + // round, extend the current round as if it were started right now. + b.currentRoundTripEnd = b.lastSentPacket + } + case bbrRecoveryStateConservation: + if isRoundStart { + b.recoveryState = bbrRecoveryStateGrowth + } + fallthrough + case bbrRecoveryStateGrowth: + // Exit recovery if appropriate. + if !hasLosses && lastAckedPacket > b.endRecoveryAt { + b.recoveryState = bbrRecoveryStateNotInRecovery + } + } +} + +// Determines the appropriate pacing rate for the connection. +func (b *bbrSender) calculatePacingRate(bytesLost congestion.ByteCount) { + if b.bandwidthEstimate() == 0 { + return + } + + targetRate := Bandwidth(b.pacingGain * float64(b.bandwidthEstimate())) + if b.isAtFullBandwidth { + b.pacingRate = targetRate + return + } + + // Pace at the rate of initial_window / RTT as soon as RTT measurements are + // available. + if b.pacingRate == 0 && b.rttStats.MinRTT() != 0 { + b.pacingRate = BandwidthFromDelta(b.initialCongestionWindow, b.rttStats.MinRTT()) + return + } + + if b.detectOvershooting { + b.bytesLostWhileDetectingOvershooting += bytesLost + // Check for overshooting with network parameters adjusted when pacing rate + // > target_rate and loss has been detected. + if b.pacingRate > targetRate && b.bytesLostWhileDetectingOvershooting > 0 { + if b.hasNoAppLimitedSample || + b.bytesLostWhileDetectingOvershooting*congestion.ByteCount(b.bytesLostMultiplierWhileDetectingOvershooting) > b.initialCongestionWindow { + // We are fairly sure overshoot happens if 1) there is at least one + // non app-limited bw sample or 2) half of IW gets lost. Slow pacing + // rate. + b.pacingRate = Max(targetRate, BandwidthFromDelta(b.cwndToCalculateMinPacingRate, b.rttStats.MinRTT())) + b.bytesLostWhileDetectingOvershooting = 0 + b.detectOvershooting = false + } + } + } + + // Do not decrease the pacing rate during startup. + b.pacingRate = Max(b.pacingRate, targetRate) +} + +// Determines the appropriate congestion window for the connection. +func (b *bbrSender) calculateCongestionWindow(bytesAcked, excessAcked congestion.ByteCount) { + if b.mode == bbrModeProbeRtt { + return + } + + targetWindow := b.getTargetCongestionWindow(b.congestionWindowGain) + if b.isAtFullBandwidth { + // Add the max recently measured ack aggregation to CWND. + targetWindow += b.sampler.MaxAckHeight() + } else if b.enableAckAggregationDuringStartup { + // Add the most recent excess acked. Because CWND never decreases in + // STARTUP, this will automatically create a very localized max filter. + targetWindow += excessAcked + } + + // Instead of immediately setting the target CWND as the new one, BBR grows + // the CWND towards |target_window| by only increasing it |bytes_acked| at a + // time. + if b.isAtFullBandwidth { + b.congestionWindow = Min(targetWindow, b.congestionWindow+bytesAcked) + } else if b.congestionWindow < targetWindow || + b.sampler.TotalBytesAcked() < b.initialCongestionWindow { + // If the connection is not yet out of startup phase, do not decrease the + // window. + b.congestionWindow += bytesAcked + } + + // Enforce the limits on the congestion window. + b.congestionWindow = Max(b.congestionWindow, b.minCongestionWindow) + b.congestionWindow = Min(b.congestionWindow, b.maxCongestionWindow) +} + +// Determines the appropriate window that constrains the in-flight during recovery. +func (b *bbrSender) calculateRecoveryWindow(bytesAcked, bytesLost congestion.ByteCount) { + if b.recoveryState == bbrRecoveryStateNotInRecovery { + return + } + + // Set up the initial recovery window. + if b.recoveryWindow == 0 { + b.recoveryWindow = b.bytesInFlight + bytesAcked + b.recoveryWindow = Max(b.minCongestionWindow, b.recoveryWindow) + return + } + + // Remove losses from the recovery window, while accounting for a potential + // integer underflow. + if b.recoveryWindow >= bytesLost { + b.recoveryWindow = b.recoveryWindow - bytesLost + } else { + b.recoveryWindow = b.maxDatagramSize + } + + // In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH, + // release additional |bytes_acked| to achieve a slow-start-like behavior. + if b.recoveryState == bbrRecoveryStateGrowth { + b.recoveryWindow += bytesAcked + } + + // Always allow sending at least |bytes_acked| in response. + b.recoveryWindow = Max(b.recoveryWindow, b.bytesInFlight+bytesAcked) + b.recoveryWindow = Max(b.minCongestionWindow, b.recoveryWindow) +} + +// Return whether we should exit STARTUP due to excessive loss. +func (b *bbrSender) shouldExitStartupDueToLoss(lastPacketSendState *sendTimeState) bool { + if b.numLossEventsInRound < defaultStartupFullLossCount || !lastPacketSendState.isValid { + return false + } + + inflightAtSend := lastPacketSendState.bytesInFlight + + if inflightAtSend > 0 && b.bytesLostInRound > 0 { + if b.bytesLostInRound > congestion.ByteCount(float64(inflightAtSend)*quicBbr2DefaultLossThreshold) { + return true + } + return false + } + return false +} + +func bdpFromRttAndBandwidth(rtt time.Duration, bandwidth Bandwidth) congestion.ByteCount { + return congestion.ByteCount(rtt) * congestion.ByteCount(bandwidth) / congestion.ByteCount(BytesPerSecond) / congestion.ByteCount(time.Second) +} + +func GetInitialPacketSize(addr net.Addr) congestion.ByteCount { + // 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 { + return congestion.InitialPacketSizeIPv4 + } else { + return congestion.InitialPacketSizeIPv6 + } + } else { + return congestion.MinInitialPacketSize + } +} diff --git a/transport/tuic/congestion_v2/clock.go b/transport/tuic/congestion_v2/clock.go new file mode 100644 index 00000000..405fae70 --- /dev/null +++ b/transport/tuic/congestion_v2/clock.go @@ -0,0 +1,18 @@ +package congestion + +import "time" + +// A Clock returns the current time +type Clock interface { + Now() time.Time +} + +// DefaultClock implements the Clock interface using the Go stdlib clock. +type DefaultClock struct{} + +var _ Clock = DefaultClock{} + +// Now gets the current time +func (DefaultClock) Now() time.Time { + return time.Now() +} diff --git a/transport/tuic/congestion_v2/minmax_go120.go b/transport/tuic/congestion_v2/minmax_go120.go new file mode 100644 index 00000000..1266edbc --- /dev/null +++ b/transport/tuic/congestion_v2/minmax_go120.go @@ -0,0 +1,19 @@ +//go:build !go1.21 + +package congestion + +import "golang.org/x/exp/constraints" + +func Max[T constraints.Ordered](a, b T) T { + if a < b { + return b + } + return a +} + +func Min[T constraints.Ordered](a, b T) T { + if a < b { + return a + } + return b +} diff --git a/transport/tuic/congestion_v2/minmax_go121.go b/transport/tuic/congestion_v2/minmax_go121.go new file mode 100644 index 00000000..65b06726 --- /dev/null +++ b/transport/tuic/congestion_v2/minmax_go121.go @@ -0,0 +1,13 @@ +//go:build go1.21 + +package congestion + +import "cmp" + +func Max[T cmp.Ordered](a, b T) T { + return max(a, b) +} + +func Min[T cmp.Ordered](a, b T) T { + return min(a, b) +} diff --git a/transport/tuic/congestion_v2/pacer.go b/transport/tuic/congestion_v2/pacer.go new file mode 100644 index 00000000..ecaf3d11 --- /dev/null +++ b/transport/tuic/congestion_v2/pacer.go @@ -0,0 +1,74 @@ +package congestion + +import ( + "math" + "time" + + "github.com/metacubex/quic-go/congestion" +) + +const ( + maxBurstPackets = 10 +) + +// Pacer implements a token bucket pacing algorithm. +type Pacer struct { + budgetAtLastSent congestion.ByteCount + maxDatagramSize congestion.ByteCount + lastSentTime time.Time + getBandwidth func() congestion.ByteCount // in bytes/s +} + +func NewPacer(getBandwidth func() congestion.ByteCount) *Pacer { + p := &Pacer{ + budgetAtLastSent: maxBurstPackets * congestion.InitialPacketSizeIPv4, + maxDatagramSize: congestion.InitialPacketSizeIPv4, + getBandwidth: getBandwidth, + } + return p +} + +func (p *Pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) { + budget := p.Budget(sendTime) + if size > budget { + p.budgetAtLastSent = 0 + } else { + p.budgetAtLastSent = budget - size + } + p.lastSentTime = sendTime +} + +func (p *Pacer) Budget(now time.Time) congestion.ByteCount { + if p.lastSentTime.IsZero() { + return p.maxBurstSize() + } + budget := p.budgetAtLastSent + (p.getBandwidth()*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9 + if budget < 0 { // protect against overflows + budget = congestion.ByteCount(1<<62 - 1) + } + return Min(p.maxBurstSize(), budget) +} + +func (p *Pacer) maxBurstSize() congestion.ByteCount { + return Max( + congestion.ByteCount((congestion.MinPacingDelay+time.Millisecond).Nanoseconds())*p.getBandwidth()/1e9, + maxBurstPackets*p.maxDatagramSize, + ) +} + +// TimeUntilSend returns when the next packet should be sent. +// It returns the zero value of time.Time if a packet can be sent immediately. +func (p *Pacer) TimeUntilSend() time.Time { + if p.budgetAtLastSent >= p.maxDatagramSize { + return time.Time{} + } + return p.lastSentTime.Add(Max( + congestion.MinPacingDelay, + time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/ + float64(p.getBandwidth())))*time.Nanosecond, + )) +} + +func (p *Pacer) SetMaxDatagramSize(s congestion.ByteCount) { + p.maxDatagramSize = s +} diff --git a/transport/tuic/congestion_v2/packet_number_indexed_queue.go b/transport/tuic/congestion_v2/packet_number_indexed_queue.go new file mode 100644 index 00000000..119d36f6 --- /dev/null +++ b/transport/tuic/congestion_v2/packet_number_indexed_queue.go @@ -0,0 +1,199 @@ +package congestion + +import ( + "github.com/metacubex/quic-go/congestion" +) + +// packetNumberIndexedQueue is a queue of mostly continuous numbered entries +// which supports the following operations: +// - adding elements to the end of the queue, or at some point past the end +// - removing elements in any order +// - retrieving elements +// If all elements are inserted in order, all of the operations above are +// amortized O(1) time. +// +// Internally, the data structure is a deque where each element is marked as +// present or not. The deque starts at the lowest present index. Whenever an +// element is removed, it's marked as not present, and the front of the deque is +// cleared of elements that are not present. +// +// The tail of the queue is not cleared due to the assumption of entries being +// inserted in order, though removing all elements of the queue will return it +// to its initial state. +// +// Note that this data structure is inherently hazardous, since an addition of +// just two entries will cause it to consume all of the memory available. +// Because of that, it is not a general-purpose container and should not be used +// as one. + +type entryWrapper[T any] struct { + present bool + entry T +} + +type packetNumberIndexedQueue[T any] struct { + entries RingBuffer[entryWrapper[T]] + numberOfPresentEntries int + firstPacket congestion.PacketNumber +} + +func newPacketNumberIndexedQueue[T any](size int) *packetNumberIndexedQueue[T] { + q := &packetNumberIndexedQueue[T]{ + firstPacket: invalidPacketNumber, + } + + q.entries.Init(size) + + return q +} + +// Emplace inserts data associated |packet_number| into (or past) the end of the +// queue, filling up the missing intermediate entries as necessary. Returns +// true if the element has been inserted successfully, false if it was already +// in the queue or inserted out of order. +func (p *packetNumberIndexedQueue[T]) Emplace(packetNumber congestion.PacketNumber, entry *T) bool { + if packetNumber == invalidPacketNumber || entry == nil { + return false + } + + if p.IsEmpty() { + p.entries.PushBack(entryWrapper[T]{ + present: true, + entry: *entry, + }) + p.numberOfPresentEntries = 1 + p.firstPacket = packetNumber + return true + } + + // Do not allow insertion out-of-order. + if packetNumber <= p.LastPacket() { + return false + } + + // Handle potentially missing elements. + offset := int(packetNumber - p.FirstPacket()) + if gap := offset - p.entries.Len(); gap > 0 { + for i := 0; i < gap; i++ { + p.entries.PushBack(entryWrapper[T]{}) + } + } + + p.entries.PushBack(entryWrapper[T]{ + present: true, + entry: *entry, + }) + p.numberOfPresentEntries++ + return true +} + +// GetEntry Retrieve the entry associated with the packet number. Returns the pointer +// to the entry in case of success, or nullptr if the entry does not exist. +func (p *packetNumberIndexedQueue[T]) GetEntry(packetNumber congestion.PacketNumber) *T { + ew := p.getEntryWraper(packetNumber) + if ew == nil { + return nil + } + + return &ew.entry +} + +// Remove, Same as above, but if an entry is present in the queue, also call f(entry) +// before removing it. +func (p *packetNumberIndexedQueue[T]) Remove(packetNumber congestion.PacketNumber, f func(T)) bool { + ew := p.getEntryWraper(packetNumber) + if ew == nil { + return false + } + if f != nil { + f(ew.entry) + } + ew.present = false + p.numberOfPresentEntries-- + + if packetNumber == p.FirstPacket() { + p.clearup() + } + + return true +} + +// RemoveUpTo, but not including |packet_number|. +// Unused slots in the front are also removed, which means when the function +// returns, |first_packet()| can be larger than |packet_number|. +func (p *packetNumberIndexedQueue[T]) RemoveUpTo(packetNumber congestion.PacketNumber) { + for !p.entries.Empty() && + p.firstPacket != invalidPacketNumber && + p.firstPacket < packetNumber { + if p.entries.Front().present { + p.numberOfPresentEntries-- + } + p.entries.PopFront() + p.firstPacket++ + } + p.clearup() + + return +} + +// IsEmpty return if queue is empty. +func (p *packetNumberIndexedQueue[T]) IsEmpty() bool { + return p.numberOfPresentEntries == 0 +} + +// NumberOfPresentEntries returns the number of entries in the queue. +func (p *packetNumberIndexedQueue[T]) NumberOfPresentEntries() int { + return p.numberOfPresentEntries +} + +// EntrySlotsUsed returns the number of entries allocated in the underlying deque. This is +// proportional to the memory usage of the queue. +func (p *packetNumberIndexedQueue[T]) EntrySlotsUsed() int { + return p.entries.Len() +} + +// LastPacket returns packet number of the first entry in the queue. +func (p *packetNumberIndexedQueue[T]) FirstPacket() (packetNumber congestion.PacketNumber) { + return p.firstPacket +} + +// LastPacket returns packet number of the last entry ever inserted in the queue. Note that the +// entry in question may have already been removed. Zero if the queue is +// empty. +func (p *packetNumberIndexedQueue[T]) LastPacket() (packetNumber congestion.PacketNumber) { + if p.IsEmpty() { + return invalidPacketNumber + } + + return p.firstPacket + congestion.PacketNumber(p.entries.Len()-1) +} + +func (p *packetNumberIndexedQueue[T]) clearup() { + for !p.entries.Empty() && !p.entries.Front().present { + p.entries.PopFront() + p.firstPacket++ + } + if p.entries.Empty() { + p.firstPacket = invalidPacketNumber + } +} + +func (p *packetNumberIndexedQueue[T]) getEntryWraper(packetNumber congestion.PacketNumber) *entryWrapper[T] { + if packetNumber == invalidPacketNumber || + p.IsEmpty() || + packetNumber < p.firstPacket { + return nil + } + + offset := int(packetNumber - p.firstPacket) + if offset >= p.entries.Len() { + return nil + } + + ew := p.entries.Offset(offset) + if ew == nil || !ew.present { + return nil + } + + return ew +} diff --git a/transport/tuic/congestion_v2/ringbuffer.go b/transport/tuic/congestion_v2/ringbuffer.go new file mode 100644 index 00000000..e110c00f --- /dev/null +++ b/transport/tuic/congestion_v2/ringbuffer.go @@ -0,0 +1,118 @@ +package congestion + +// A RingBuffer is a ring buffer. +// It acts as a heap that doesn't cause any allocations. +type RingBuffer[T any] struct { + ring []T + headPos, tailPos int + full bool +} + +// Init preallocs a buffer with a certain size. +func (r *RingBuffer[T]) Init(size int) { + r.ring = make([]T, size) +} + +// Len returns the number of elements in the ring buffer. +func (r *RingBuffer[T]) Len() int { + if r.full { + return len(r.ring) + } + if r.tailPos >= r.headPos { + return r.tailPos - r.headPos + } + return r.tailPos - r.headPos + len(r.ring) +} + +// Empty says if the ring buffer is empty. +func (r *RingBuffer[T]) Empty() bool { + return !r.full && r.headPos == r.tailPos +} + +// PushBack adds a new element. +// If the ring buffer is full, its capacity is increased first. +func (r *RingBuffer[T]) PushBack(t T) { + if r.full || len(r.ring) == 0 { + r.grow() + } + r.ring[r.tailPos] = t + r.tailPos++ + if r.tailPos == len(r.ring) { + r.tailPos = 0 + } + if r.tailPos == r.headPos { + r.full = true + } +} + +// PopFront returns the next element. +// It must not be called when the buffer is empty, that means that +// callers might need to check if there are elements in the buffer first. +func (r *RingBuffer[T]) PopFront() T { + if r.Empty() { + panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: pop from an empty queue") + } + r.full = false + t := r.ring[r.headPos] + r.ring[r.headPos] = *new(T) + r.headPos++ + if r.headPos == len(r.ring) { + r.headPos = 0 + } + return t +} + +// Offset returns the offset element. +// It must not be called when the buffer is empty, that means that +// callers might need to check if there are elements in the buffer first +// and check if the index larger than buffer length. +func (r *RingBuffer[T]) Offset(index int) *T { + if r.Empty() || index >= r.Len() { + panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: offset from invalid index") + } + offset := (r.headPos + index) % len(r.ring) + return &r.ring[offset] +} + +// Front returns the front element. +// It must not be called when the buffer is empty, that means that +// callers might need to check if there are elements in the buffer first. +func (r *RingBuffer[T]) Front() *T { + if r.Empty() { + panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: front from an empty queue") + } + return &r.ring[r.headPos] +} + +// Back returns the back element. +// It must not be called when the buffer is empty, that means that +// callers might need to check if there are elements in the buffer first. +func (r *RingBuffer[T]) Back() *T { + if r.Empty() { + panic("github.com/quic-go/quic-go/internal/utils/ringbuffer: back from an empty queue") + } + return r.Offset(r.Len() - 1) +} + +// Grow the maximum size of the queue. +// This method assume the queue is full. +func (r *RingBuffer[T]) grow() { + oldRing := r.ring + newSize := len(oldRing) * 2 + if newSize == 0 { + newSize = 1 + } + r.ring = make([]T, newSize) + headLen := copy(r.ring, oldRing[r.headPos:]) + copy(r.ring[headLen:], oldRing[:r.headPos]) + r.headPos, r.tailPos, r.full = 0, len(oldRing), false +} + +// Clear removes all elements. +func (r *RingBuffer[T]) Clear() { + var zeroValue T + for i := range r.ring { + r.ring[i] = zeroValue + } + r.headPos, r.tailPos, r.full = 0, 0, false +} diff --git a/transport/tuic/congestion_v2/windowed_filter.go b/transport/tuic/congestion_v2/windowed_filter.go new file mode 100644 index 00000000..2421b48b --- /dev/null +++ b/transport/tuic/congestion_v2/windowed_filter.go @@ -0,0 +1,162 @@ +package congestion + +import ( + "golang.org/x/exp/constraints" +) + +// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum) +// estimate of a stream of samples over some fixed time interval. (E.g., +// the minimum RTT over the past five minutes.) The algorithm keeps track of +// the best, second best, and third best min (or max) estimates, maintaining an +// invariant that the measurement time of the n'th best >= n-1'th best. + +// The algorithm works as follows. On a reset, all three estimates are set to +// the same sample. The second best estimate is then recorded in the second +// quarter of the window, and a third best estimate is recorded in the second +// half of the window, bounding the worst case error when the true min is +// monotonically increasing (or true max is monotonically decreasing) over the +// window. +// +// A new best sample replaces all three estimates, since the new best is lower +// (or higher) than everything else in the window and it is the most recent. +// The window thus effectively gets reset on every new min. The same property +// holds true for second best and third best estimates. Specifically, when a +// sample arrives that is better than the second best but not better than the +// best, it replaces the second and third best estimates but not the best +// estimate. Similarly, a sample that is better than the third best estimate +// but not the other estimates replaces only the third best estimate. +// +// Finally, when the best expires, it is replaced by the second best, which in +// turn is replaced by the third best. The newest sample replaces the third +// best. + +type WindowedFilterValue interface { + any +} + +type WindowedFilterTime interface { + constraints.Integer | constraints.Float +} + +type WindowedFilter[V WindowedFilterValue, T WindowedFilterTime] struct { + // Time length of window. + windowLength T + estimates []entry[V, T] + comparator func(V, V) int +} + +type entry[V WindowedFilterValue, T WindowedFilterTime] struct { + sample V + time T +} + +// Compares two values and returns true if the first is greater than or equal +// to the second. +func MaxFilter[O constraints.Ordered](a, b O) int { + if a > b { + return 1 + } else if a < b { + return -1 + } + return 0 +} + +// Compares two values and returns true if the first is less than or equal +// to the second. +func MinFilter[O constraints.Ordered](a, b O) int { + if a < b { + return 1 + } else if a > b { + return -1 + } + return 0 +} + +func NewWindowedFilter[V WindowedFilterValue, T WindowedFilterTime](windowLength T, comparator func(V, V) int) *WindowedFilter[V, T] { + return &WindowedFilter[V, T]{ + windowLength: windowLength, + estimates: make([]entry[V, T], 3, 3), + comparator: comparator, + } +} + +// Changes the window length. Does not update any current samples. +func (f *WindowedFilter[V, T]) SetWindowLength(windowLength T) { + f.windowLength = windowLength +} + +func (f *WindowedFilter[V, T]) GetBest() V { + return f.estimates[0].sample +} + +func (f *WindowedFilter[V, T]) GetSecondBest() V { + return f.estimates[1].sample +} + +func (f *WindowedFilter[V, T]) GetThirdBest() V { + return f.estimates[2].sample +} + +// Updates best estimates with |sample|, and expires and updates best +// estimates as necessary. +func (f *WindowedFilter[V, T]) Update(newSample V, newTime T) { + // Reset all estimates if they have not yet been initialized, if new sample + // is a new best, or if the newest recorded estimate is too old. + if f.comparator(f.estimates[0].sample, *new(V)) == 0 || + f.comparator(newSample, f.estimates[0].sample) >= 0 || + newTime-f.estimates[2].time > f.windowLength { + f.Reset(newSample, newTime) + return + } + + if f.comparator(newSample, f.estimates[1].sample) >= 0 { + f.estimates[1] = entry[V, T]{newSample, newTime} + f.estimates[2] = f.estimates[1] + } else if f.comparator(newSample, f.estimates[2].sample) >= 0 { + f.estimates[2] = entry[V, T]{newSample, newTime} + } + + // Expire and update estimates as necessary. + if newTime-f.estimates[0].time > f.windowLength { + // The best estimate hasn't been updated for an entire window, so promote + // second and third best estimates. + f.estimates[0] = f.estimates[1] + f.estimates[1] = f.estimates[2] + f.estimates[2] = entry[V, T]{newSample, newTime} + // Need to iterate one more time. Check if the new best estimate is + // outside the window as well, since it may also have been recorded a + // long time ago. Don't need to iterate once more since we cover that + // case at the beginning of the method. + if newTime-f.estimates[0].time > f.windowLength { + f.estimates[0] = f.estimates[1] + f.estimates[1] = f.estimates[2] + } + return + } + if f.comparator(f.estimates[1].sample, f.estimates[0].sample) == 0 && + newTime-f.estimates[1].time > f.windowLength/4 { + // A quarter of the window has passed without a better sample, so the + // second-best estimate is taken from the second quarter of the window. + f.estimates[1] = entry[V, T]{newSample, newTime} + f.estimates[2] = f.estimates[1] + return + } + + if f.comparator(f.estimates[2].sample, f.estimates[1].sample) == 0 && + newTime-f.estimates[2].time > f.windowLength/2 { + // We've passed a half of the window without a better estimate, so take + // a third-best estimate from the second half of the window. + f.estimates[2] = entry[V, T]{newSample, newTime} + } +} + +// Resets all estimates to new sample. +func (f *WindowedFilter[V, T]) Reset(newSample V, newTime T) { + f.estimates[2] = entry[V, T]{newSample, newTime} + f.estimates[1] = f.estimates[2] + f.estimates[0] = f.estimates[1] +} + +func (f *WindowedFilter[V, T]) Clear() { + f.estimates = make([]entry[V, T], 3, 3) +} diff --git a/transport/tuic/pool_client.go b/transport/tuic/pool_client.go index 4a779706..b4c319d8 100644 --- a/transport/tuic/pool_client.go +++ b/transport/tuic/pool_client.go @@ -8,12 +8,13 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/generics/list" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" "github.com/metacubex/quic-go" + + list "github.com/bahlo/generic-list-go" ) type dialResult struct { diff --git a/transport/tuic/server.go b/transport/tuic/server.go index cabc04e0..354533aa 100644 --- a/transport/tuic/server.go +++ b/transport/tuic/server.go @@ -7,14 +7,14 @@ import ( "net" "time" - "github.com/Dreamacro/clash/adapter/inbound" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/tuic/common" - v4 "github.com/Dreamacro/clash/transport/tuic/v4" - v5 "github.com/Dreamacro/clash/transport/tuic/v5" + "github.com/metacubex/mihomo/adapter/inbound" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/tuic/common" + v4 "github.com/metacubex/mihomo/transport/tuic/v4" + v5 "github.com/metacubex/mihomo/transport/tuic/v5" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" @@ -114,7 +114,7 @@ func (s *serverHandler) handle() { func (s *serverHandler) handleMessage() (err error) { for { var message []byte - message, err = s.quicConn.ReceiveMessage(context.Background()) + message, err = s.quicConn.ReceiveDatagram(context.Background()) if err != nil { return err } diff --git a/transport/tuic/tuic.go b/transport/tuic/tuic.go index 387a152c..02aaa3ad 100644 --- a/transport/tuic/tuic.go +++ b/transport/tuic/tuic.go @@ -1,10 +1,10 @@ package tuic import ( - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/tuic/common" - v4 "github.com/Dreamacro/clash/transport/tuic/v4" - v5 "github.com/Dreamacro/clash/transport/tuic/v5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/tuic/common" + v4 "github.com/metacubex/mihomo/transport/tuic/v4" + v5 "github.com/metacubex/mihomo/transport/tuic/v5" ) type ClientOptionV4 = v4.ClientOption diff --git a/transport/tuic/v4/client.go b/transport/tuic/v4/client.go index ce33b72b..67906959 100644 --- a/transport/tuic/v4/client.go +++ b/transport/tuic/v4/client.go @@ -11,18 +11,16 @@ import ( "sync" "sync/atomic" "time" - "unsafe" - atomic2 "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/tuic/common" + atomic2 "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/quic-go" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" "github.com/zhangyunhao116/fastrand" ) @@ -197,7 +195,7 @@ func (t *clientImpl) handleMessage(quicConn quic.Connection) (err error) { }() for { var message []byte - message, err = quicConn.ReceiveMessage(context.Background()) + message, err = quicConn.ReceiveDatagram(context.Background()) if err != nil { return err } @@ -329,75 +327,30 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta } bufConn := N.NewBufferedConn(stream) - conn := &earlyConn{ExtendedConn: bufConn, bufConn: bufConn, RequestTimeout: t.RequestTimeout} - if !t.FastOpen { - err = conn.Response() - if err != nil { - return nil, err + response := func() error { + if t.RequestTimeout > 0 { + _ = bufConn.SetReadDeadline(time.Now().Add(t.RequestTimeout)) } + response, err := ReadResponse(bufConn) + if err != nil { + _ = bufConn.Close() + return err + } + if response.IsFailed() { + _ = bufConn.Close() + return errors.New("connect failed") + } + _ = bufConn.SetReadDeadline(time.Time{}) + return nil } - return conn, nil -} - -type earlyConn struct { - N.ExtendedConn // only expose standard N.ExtendedConn function to outside - bufConn *N.BufferedConn - resOnce sync.Once - resErr error - - RequestTimeout time.Duration -} - -func (conn *earlyConn) response() error { - if conn.RequestTimeout > 0 { - _ = conn.SetReadDeadline(time.Now().Add(conn.RequestTimeout)) + if t.FastOpen { + return N.NewEarlyConn(bufConn, response), nil } - response, err := ReadResponse(conn.bufConn) + err = response() if err != nil { - _ = conn.Close() - return err + return nil, err } - if response.IsFailed() { - _ = conn.Close() - return errors.New("connect failed") - } - _ = conn.SetReadDeadline(time.Time{}) - return nil -} - -func (conn *earlyConn) Response() error { - conn.resOnce.Do(func() { - conn.resErr = conn.response() - }) - return conn.resErr -} - -func (conn *earlyConn) Read(b []byte) (n int, err error) { - err = conn.Response() - if err != nil { - return 0, err - } - return conn.bufConn.Read(b) -} - -func (conn *earlyConn) ReadBuffer(buffer *buf.Buffer) (err error) { - err = conn.Response() - if err != nil { - return err - } - return conn.bufConn.ReadBuffer(buffer) -} - -func (conn *earlyConn) Upstream() any { - return conn.bufConn -} - -func (conn *earlyConn) ReaderReplaceable() bool { - return atomic.LoadUint32((*uint32)(unsafe.Pointer(&conn.resOnce))) == 1 && conn.resErr == nil -} - -func (conn *earlyConn) WriterReplaceable() bool { - return true + return bufConn, nil } func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) { @@ -469,7 +422,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client ClientOption: clientOption, udp: udp, dialerRef: dialerRef, - udpInputMap: xsync.NewIntegerMapOf[uint32, net.Conn](), + udpInputMap: xsync.NewMapOf[uint32, net.Conn](), } c := &Client{ci} runtime.SetFinalizer(c, closeClient) diff --git a/transport/tuic/v4/packet.go b/transport/tuic/v4/packet.go index 2066ceb7..f282b3ed 100644 --- a/transport/tuic/v4/packet.go +++ b/transport/tuic/v4/packet.go @@ -5,10 +5,10 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/atomic" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/tuic/common" + "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/quic-go" ) @@ -161,7 +161,7 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro } default: // native data := buf.Bytes() - err = q.quicConn.SendMessage(data) + err = q.quicConn.SendDatagram(data) if err != nil { return } diff --git a/transport/tuic/v4/protocol.go b/transport/tuic/v4/protocol.go index bbdca67c..29536742 100644 --- a/transport/tuic/v4/protocol.go +++ b/transport/tuic/v4/protocol.go @@ -11,8 +11,8 @@ import ( "github.com/metacubex/quic-go" "lukechampine.com/blake3" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" ) type BufferedReader interface { diff --git a/transport/tuic/v4/server.go b/transport/tuic/v4/server.go index 56133fea..2430866f 100644 --- a/transport/tuic/v4/server.go +++ b/transport/tuic/v4/server.go @@ -7,17 +7,17 @@ import ( "net" "sync" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/atomic" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/tuic/common" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" ) type ServerOption struct { @@ -34,7 +34,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid quicConn: quicConn, uuid: uuid, authCh: make(chan struct{}), - udpInputMap: xsync.NewIntegerMapOf[uint32, *atomic.Bool](), + udpInputMap: xsync.NewMapOf[uint32, *atomic.Bool](), } } diff --git a/transport/tuic/v5/client.go b/transport/tuic/v5/client.go index c4ac25d4..8a4d6fb1 100644 --- a/transport/tuic/v5/client.go +++ b/transport/tuic/v5/client.go @@ -12,15 +12,15 @@ import ( "sync/atomic" "time" - atomic2 "github.com/Dreamacro/clash/common/atomic" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/tuic/common" + atomic2 "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/quic-go" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" "github.com/zhangyunhao116/fastrand" ) @@ -47,7 +47,7 @@ type clientImpl struct { openStreams atomic.Int64 closed atomic.Bool - udpInputMap xsync.MapOf[uint16, net.Conn] + udpInputMap *xsync.MapOf[uint16, net.Conn] // only ready for PoolClient dialerRef C.Dialer @@ -196,7 +196,7 @@ func (t *clientImpl) handleMessage(quicConn quic.Connection) (err error) { }() for { var message []byte - message, err = quicConn.ReceiveMessage(context.Background()) + message, err = quicConn.ReceiveDatagram(context.Background()) if err != nil { return err } @@ -270,7 +270,7 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) { if quicConn != nil { _ = quicConn.CloseWithError(ProtocolError, errStr) } - udpInputMap := &t.udpInputMap + udpInputMap := t.udpInputMap udpInputMap.Range(func(key uint16, value net.Conn) bool { conn := value _ = conn.Close() @@ -406,7 +406,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client ClientOption: clientOption, udp: udp, dialerRef: dialerRef, - udpInputMap: *xsync.NewIntegerMapOf[uint16, net.Conn](), + udpInputMap: xsync.NewMapOf[uint16, net.Conn](), } c := &Client{ci} runtime.SetFinalizer(c, closeClient) diff --git a/transport/tuic/v5/frag.go b/transport/tuic/v5/frag.go index 8df9f785..b0e1a174 100644 --- a/transport/tuic/v5/frag.go +++ b/transport/tuic/v5/frag.go @@ -4,7 +4,7 @@ import ( "bytes" "sync" - "github.com/Dreamacro/clash/common/cache" + "github.com/metacubex/mihomo/common/lru" "github.com/metacubex/quic-go" ) @@ -37,7 +37,7 @@ func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, return } data := buf.Bytes() - err = quicConn.SendMessage(data) + err = quicConn.SendDatagram(data) if err != nil { return } @@ -47,7 +47,7 @@ func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, } type deFragger struct { - lru *cache.LruCache[uint16, *packetBag] + lru *lru.LruCache[uint16, *packetBag] once sync.Once } @@ -63,9 +63,9 @@ func newPacketBag() *packetBag { func (d *deFragger) init() { if d.lru == nil { - d.lru = cache.New( - cache.WithAge[uint16, *packetBag](10), - cache.WithUpdateAgeOnGet[uint16, *packetBag](), + d.lru = lru.New( + lru.WithAge[uint16, *packetBag](10), + lru.WithUpdateAgeOnGet[uint16, *packetBag](), ) } } diff --git a/transport/tuic/v5/packet.go b/transport/tuic/v5/packet.go index efbe0bb9..a34e6a58 100644 --- a/transport/tuic/v5/packet.go +++ b/transport/tuic/v5/packet.go @@ -6,10 +6,10 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/common/atomic" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/tuic/common" + "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/pool" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/metacubex/quic-go" "github.com/zhangyunhao116/fastrand" @@ -184,7 +184,7 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro return } data := buf.Bytes() - err = q.quicConn.SendMessage(data) + err = q.quicConn.SendDatagram(data) } var tooLarge quic.ErrMessageTooLarge diff --git a/transport/tuic/v5/protocol.go b/transport/tuic/v5/protocol.go index 964401e1..de51a080 100644 --- a/transport/tuic/v5/protocol.go +++ b/transport/tuic/v5/protocol.go @@ -8,9 +8,9 @@ import ( "net/netip" "strconv" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/quic-go" ) diff --git a/transport/tuic/v5/server.go b/transport/tuic/v5/server.go index 10003a9d..8454b64c 100644 --- a/transport/tuic/v5/server.go +++ b/transport/tuic/v5/server.go @@ -7,16 +7,16 @@ import ( "net" "sync" - "github.com/Dreamacro/clash/adapter/inbound" - "github.com/Dreamacro/clash/common/atomic" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/tuic/common" + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/atomic" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/transport/socks5" + "github.com/metacubex/mihomo/transport/tuic/common" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" ) type ServerOption struct { @@ -33,7 +33,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid quicConn: quicConn, uuid: uuid, authCh: make(chan struct{}), - udpInputMap: xsync.NewIntegerMapOf[uint16, *serverUDPInput](), + udpInputMap: xsync.NewMapOf[uint16, *serverUDPInput](), } } diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 066a3e2a..90ff5efe 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -6,20 +6,22 @@ import ( "net" "net/http" - "github.com/Dreamacro/clash/component/ca" - "github.com/Dreamacro/clash/transport/vmess" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/transport/vmess" ) // Option is options of websocket obfs type Option struct { - Host string - Port string - Path string - Headers map[string]string - TLS bool - SkipCertVerify bool - Fingerprint string - Mux bool + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + Fingerprint string + Mux bool + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } // NewV2rayObfs return a HTTPObfs @@ -30,10 +32,12 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn, } config := &vmess.WebsocketConfig{ - Host: option.Host, - Port: option.Port, - Path: option.Path, - Headers: header, + Host: option.Host, + Port: option.Port, + Path: option.Path, + V2rayHttpUpgrade: option.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: option.V2rayHttpUpgradeFastOpen, + Headers: header, } if option.TLS { diff --git a/transport/vless/config.pb.go b/transport/vless/config.pb.go index 1407e4d7..14fcc5f9 100644 --- a/transport/vless/config.pb.go +++ b/transport/vless/config.pb.go @@ -108,7 +108,7 @@ func file_transport_vless_config_proto_rawDescGZIP() []byte { var file_transport_vless_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_transport_vless_config_proto_goTypes = []interface{}{ - (*Addons)(nil), // 0: clash.transport.vless.Addons + (*Addons)(nil), // 0: mihomo.transport.vless.Addons } var file_transport_vless_config_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type diff --git a/transport/vless/config.proto b/transport/vless/config.proto index 80900230..44cad479 100644 --- a/transport/vless/config.proto +++ b/transport/vless/config.proto @@ -1,9 +1,9 @@ syntax = "proto3"; -package clash.transport.vless; -option csharp_namespace = "Clash.Transport.Vless"; -option go_package = "github.com/Dreamacro/clash/transport/vless"; -option java_package = "com.clash.transport.vless"; +package mihomo.transport.vless; +option csharp_namespace = "Mihomo.Transport.Vless"; +option go_package = "github.com/metacubex/mihomo/transport/vless"; +option java_package = "com.mihomo.transport.vless"; option java_multiple_files = true; message Addons { diff --git a/transport/vless/conn.go b/transport/vless/conn.go index 33ecd97a..02224892 100644 --- a/transport/vless/conn.go +++ b/transport/vless/conn.go @@ -7,9 +7,9 @@ import ( "net" "sync" - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/transport/vless/vision" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/transport/vless/vision" "github.com/gofrs/uuid/v5" "google.golang.org/protobuf/proto" diff --git a/transport/vless/vision/conn.go b/transport/vless/vision/conn.go index 03f524aa..5ad28134 100644 --- a/transport/vless/vision/conn.go +++ b/transport/vless/vision/conn.go @@ -9,9 +9,9 @@ import ( "io" "net" - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/log" "github.com/gofrs/uuid/v5" utls "github.com/sagernet/utls" @@ -157,14 +157,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { func (vc *Conn) Write(p []byte) (int, error) { if vc.writeFilterApplicationData { - buffer := buf.New() - defer buffer.Release() - buffer.Write(p) - err := vc.WriteBuffer(buffer) - if err != nil { - return 0, err - } - return len(p), nil + return N.WriteBuffer(vc, buf.As(p)) } return vc.ExtendedWriter.Write(p) } @@ -266,6 +259,10 @@ func (vc *Conn) FrontHeadroom() int { return PaddingHeaderLen - uuid.Size } +func (vc *Conn) RearHeadroom() int { + return 500 + 900 +} + func (vc *Conn) NeedHandshake() bool { return vc.needHandshake } diff --git a/transport/vless/vision/filter.go b/transport/vless/vision/filter.go index e070de35..55b5663f 100644 --- a/transport/vless/vision/filter.go +++ b/transport/vless/vision/filter.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/binary" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/log" ) var ( diff --git a/transport/vless/vision/padding.go b/transport/vless/vision/padding.go index d5a230d1..e5f9dc85 100644 --- a/transport/vless/vision/padding.go +++ b/transport/vless/vision/padding.go @@ -4,8 +4,8 @@ import ( "bytes" "encoding/binary" - "github.com/Dreamacro/clash/common/buf" - "github.com/Dreamacro/clash/log" + "github.com/metacubex/mihomo/common/buf" + "github.com/metacubex/mihomo/log" "github.com/gofrs/uuid/v5" "github.com/zhangyunhao116/fastrand" diff --git a/transport/vless/vision/vision.go b/transport/vless/vision/vision.go index 3b52dd4b..09299b23 100644 --- a/transport/vless/vision/vision.go +++ b/transport/vless/vision/vision.go @@ -10,8 +10,8 @@ import ( "reflect" "unsafe" - N "github.com/Dreamacro/clash/common/net" - tlsC "github.com/Dreamacro/clash/component/tls" + N "github.com/metacubex/mihomo/common/net" + tlsC "github.com/metacubex/mihomo/component/tls" "github.com/gofrs/uuid/v5" "github.com/sagernet/sing/common" diff --git a/transport/vless/vless.go b/transport/vless/vless.go index 6c01b839..ce07cdb4 100644 --- a/transport/vless/vless.go +++ b/transport/vless/vless.go @@ -3,7 +3,7 @@ package vless import ( "net" - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "github.com/gofrs/uuid/v5" ) diff --git a/transport/vmess/aead.go b/transport/vmess/aead.go index d4fbf2d9..89ec6a3b 100644 --- a/transport/vmess/aead.go +++ b/transport/vmess/aead.go @@ -7,7 +7,7 @@ import ( "io" "sync" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) type aeadWriter struct { diff --git a/transport/vmess/chunk.go b/transport/vmess/chunk.go index ab1adb6d..f52fc82c 100644 --- a/transport/vmess/chunk.go +++ b/transport/vmess/chunk.go @@ -5,7 +5,7 @@ import ( "errors" "io" - "github.com/Dreamacro/clash/common/pool" + "github.com/metacubex/mihomo/common/pool" ) const ( diff --git a/transport/vmess/http.go b/transport/vmess/http.go index c4f27c4c..c77a7e9d 100644 --- a/transport/vmess/http.go +++ b/transport/vmess/http.go @@ -8,6 +8,8 @@ import ( "net/http" "net/textproto" + "github.com/metacubex/mihomo/common/utils" + "github.com/zhangyunhao116/fastrand" ) @@ -59,7 +61,7 @@ func (hc *httpConn) Write(b []byte) (int, error) { } u := fmt.Sprintf("http://%s%s", host, path) - req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) + req, _ := http.NewRequest(utils.EmptyOr(hc.cfg.Method, http.MethodGet), u, bytes.NewBuffer(b)) for key, list := range hc.cfg.Headers { req.Header.Set(key, list[fastrand.Intn(len(list))]) } diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 8bcb6513..bdaa8ccc 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -6,8 +6,8 @@ import ( "errors" "net" - "github.com/Dreamacro/clash/component/ca" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" ) type TLSConfig struct { diff --git a/transport/vmess/vmess.go b/transport/vmess/vmess.go index 2dd071eb..7c587c6a 100644 --- a/transport/vmess/vmess.go +++ b/transport/vmess/vmess.go @@ -5,7 +5,7 @@ import ( "net" "runtime" - "github.com/Dreamacro/clash/common/utils" + "github.com/metacubex/mihomo/common/utils" "github.com/gofrs/uuid/v5" "github.com/zhangyunhao116/fastrand" diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index a4ce99a9..f6914199 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -1,8 +1,10 @@ package vmess import ( + "bufio" "bytes" "context" + "crypto/sha1" "crypto/tls" "encoding/base64" "encoding/binary" @@ -14,27 +16,25 @@ import ( "net/url" "strconv" "strings" - "sync" "time" - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + tlsC "github.com/metacubex/mihomo/component/tls" + "github.com/metacubex/mihomo/log" - "github.com/gorilla/websocket" + "github.com/gobwas/ws" + "github.com/gobwas/ws/wsutil" "github.com/zhangyunhao116/fastrand" ) type websocketConn struct { - conn *websocket.Conn - reader io.Reader - remoteAddr net.Addr + net.Conn + state ws.State + reader *wsutil.Reader + controlHandler wsutil.FrameHandlerFunc rawWriter N.ExtendedWriter - - // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency - rMux sync.Mutex - wMux sync.Mutex } type websocketWithEarlyDataConn struct { @@ -49,44 +49,67 @@ type websocketWithEarlyDataConn struct { } type WebsocketConfig struct { - Host string - Port string - Path string - Headers http.Header - TLS bool - TLSConfig *tls.Config - MaxEarlyData int - EarlyDataHeaderName string - ClientFingerprint string + Host string + Port string + Path string + Headers http.Header + TLS bool + TLSConfig *tls.Config + MaxEarlyData int + EarlyDataHeaderName string + ClientFingerprint string + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool } // Read implements net.Conn.Read() -func (wsc *websocketConn) Read(b []byte) (int, error) { - wsc.rMux.Lock() - defer wsc.rMux.Unlock() - for { - reader, err := wsc.getReader() - if err != nil { - return 0, err +// modify from gobwas/ws/wsutil.readData +func (wsc *websocketConn) Read(b []byte) (n int, err error) { + defer func() { // avoid gobwas/ws pbytes.GetLen panic + if value := recover(); value != nil { + err = fmt.Errorf("websocket error: %s", value) } - - nBytes, err := reader.Read(b) - if err == io.EOF { - wsc.reader = nil + }() + var header ws.Header + for { + n, err = wsc.reader.Read(b) + // in gobwas/ws: "The error is io.EOF only if all of message bytes were read." + // but maybe next frame still have data, so drop it + if errors.Is(err, io.EOF) { + err = nil + } + if !errors.Is(err, wsutil.ErrNoFrameAdvance) { + return + } + header, err = wsc.reader.NextFrame() + if err != nil { + return + } + if header.OpCode.IsControl() { + err = wsc.controlHandler(header, wsc.reader) + if err != nil { + return + } + continue + } + if header.OpCode&(ws.OpBinary|ws.OpText) == 0 { + err = wsc.reader.Discard() + if err != nil { + return + } continue } - return nBytes, err } } // Write implements io.Writer. -func (wsc *websocketConn) Write(b []byte) (int, error) { - wsc.wMux.Lock() - defer wsc.wMux.Unlock() - if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { - return 0, err +func (wsc *websocketConn) Write(b []byte) (n int, err error) { + err = wsutil.WriteMessage(wsc.Conn, wsc.state, ws.OpBinary, b) + if err != nil { + return } - return len(b), nil + n = len(b) + return } func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error { @@ -104,12 +127,17 @@ func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error { var headerLen int headerLen += 1 // FIN / RSV / OPCODE headerLen += payloadBitLength - headerLen += 4 // MASK KEY + if wsc.state.ClientSide() { + headerLen += 4 // MASK KEY + } header := buffer.ExtendHeader(headerLen) - _ = header[2] // bounds check hint to compiler - header[0] = websocket.BinaryMessage | 1<<7 - header[1] = 1 << 7 + header[0] = byte(ws.OpBinary) | 0x80 + if wsc.state.ClientSide() { + header[1] = 1 << 7 + } else { + header[1] = 0 + } if dataLen < 126 { header[1] |= byte(dataLen) @@ -121,12 +149,12 @@ func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error { binary.BigEndian.PutUint64(header[2:], uint64(dataLen)) } - maskKey := fastrand.Uint32() - binary.LittleEndian.PutUint32(header[1+payloadBitLength:], maskKey) - N.MaskWebSocket(maskKey, data) + if wsc.state.ClientSide() { + maskKey := fastrand.Uint32() + binary.LittleEndian.PutUint32(header[1+payloadBitLength:], maskKey) + N.MaskWebSocket(maskKey, data) + } - wsc.wMux.Lock() - defer wsc.wMux.Unlock() return wsc.rawWriter.WriteBuffer(buffer) } @@ -135,59 +163,16 @@ func (wsc *websocketConn) FrontHeadroom() int { } func (wsc *websocketConn) Upstream() any { - return wsc.conn.UnderlyingConn() + return wsc.Conn } func (wsc *websocketConn) Close() error { - var e []string - if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { - e = append(e, err.Error()) - } - if err := wsc.conn.Close(); err != nil { - e = append(e, err.Error()) - } - if len(e) > 0 { - return fmt.Errorf("failed to close connection: %s", strings.Join(e, ",")) - } + _ = wsc.Conn.SetWriteDeadline(time.Now().Add(time.Second * 5)) + _ = wsutil.WriteMessage(wsc.Conn, wsc.state, ws.OpClose, ws.NewCloseFrameBody(ws.StatusNormalClosure, "")) + _ = wsc.Conn.Close() return nil } -func (wsc *websocketConn) getReader() (io.Reader, error) { - if wsc.reader != nil { - return wsc.reader, nil - } - - _, reader, err := wsc.conn.NextReader() - if err != nil { - return nil, err - } - wsc.reader = reader - return reader, nil -} - -func (wsc *websocketConn) LocalAddr() net.Addr { - return wsc.conn.LocalAddr() -} - -func (wsc *websocketConn) RemoteAddr() net.Addr { - return wsc.remoteAddr -} - -func (wsc *websocketConn) SetDeadline(t time.Time) error { - if err := wsc.SetReadDeadline(t); err != nil { - return err - } - return wsc.SetWriteDeadline(t) -} - -func (wsc *websocketConn) SetReadDeadline(t time.Time) error { - return wsc.conn.SetReadDeadline(t) -} - -func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { - return wsc.conn.SetWriteDeadline(t) -} - func (wsedc *websocketWithEarlyDataConn) Dial(earlyData []byte) error { base64DataBuf := &bytes.Buffer{} base64EarlyDataEncoder := base64.NewEncoder(base64.RawURLEncoding, base64DataBuf) @@ -341,82 +326,162 @@ func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Co } func streamWebsocketConn(ctx context.Context, 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 - }, - ReadBufferSize: 4 * 1024, - WriteBufferSize: 4 * 1024, - HandshakeTimeout: time.Second * 8, - } - - scheme := "ws" - 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) if err != nil { return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) } uri := url.URL{ - Scheme: scheme, + Scheme: "ws", Host: net.JoinHostPort(c.Host, c.Port), Path: u.Path, RawQuery: u.RawQuery, } - headers := http.Header{} - if c.Headers != nil { - for k := range c.Headers { - headers.Add(k, c.Headers.Get(k)) + if !strings.HasPrefix(uri.Path, "/") { + uri.Path = "/" + uri.Path + } + + if c.TLS { + uri.Scheme = "wss" + config := c.TLSConfig + if config == nil { // The config cannot be nil + config = &tls.Config{NextProtos: []string{"http/1.1"}} } + if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config. + config = config.Clone() + config.ServerName = uri.Host + } + + if len(c.ClientFingerprint) != 0 { + if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists { + utlsConn := tlsC.UClient(conn, config, fingerprint) + if err = utlsConn.BuildWebsocketHandshakeState(); err != nil { + return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) + } + conn = utlsConn + } + } else { + conn = tls.Client(conn, config) + } + + if tlsConn, ok := conn.(interface { + HandshakeContext(ctx context.Context) error + }); ok { + if err = tlsConn.HandshakeContext(ctx); err != nil { + return nil, err + } + } + } + + request := &http.Request{ + Method: http.MethodGet, + URL: &uri, + Header: c.Headers.Clone(), + Host: c.Host, + } + + request.Header.Set("Connection", "Upgrade") + request.Header.Set("Upgrade", "websocket") + + if host := request.Header.Get("Host"); host != "" { + // For client requests, Host optionally overrides the Host + // header to send. If empty, the Request.Write method uses + // the value of URL.Host. Host may contain an international + // domain name. + request.Host = host + } + request.Header.Del("Host") + + var secKey string + if !c.V2rayHttpUpgrade { + const nonceKeySize = 16 + // NOTE: bts does not escape. + bts := make([]byte, nonceKeySize) + if _, err = fastrand.Read(bts); err != nil { + return nil, fmt.Errorf("rand read error: %w", err) + } + secKey = base64.StdEncoding.EncodeToString(bts) + request.Header.Set("Sec-WebSocket-Version", "13") + request.Header.Set("Sec-WebSocket-Key", secKey) } if earlyData != nil { + earlyDataString := earlyData.String() if c.EarlyDataHeaderName == "" { - uri.Path += earlyData.String() + uri.Path += earlyDataString } else { - headers.Set(c.EarlyDataHeaderName, earlyData.String()) + request.Header.Set(c.EarlyDataHeaderName, earlyDataString) } } - wsConn, resp, err := dialer.DialContext(ctx, uri.String(), headers) + if ctx.Done() != nil { + done := N.SetupContextForConn(ctx, conn) + defer done(&err) + } + + err = request.Write(conn) if err != nil { - reason := err - if resp != nil { - reason = errors.New(resp.Status) - } - return nil, fmt.Errorf("dial %s error: %w", uri.Host, reason) + return nil, err + } + bufferedConn := N.NewBufferedConn(conn) + + if c.V2rayHttpUpgrade && c.V2rayHttpUpgradeFastOpen { + return N.NewEarlyConn(bufferedConn, func() error { + response, err := http.ReadResponse(bufferedConn.Reader(), request) + if err != nil { + return err + } + if response.StatusCode != http.StatusSwitchingProtocols || + !strings.EqualFold(response.Header.Get("Connection"), "upgrade") || + !strings.EqualFold(response.Header.Get("Upgrade"), "websocket") { + return fmt.Errorf("unexpected status: %s", response.Status) + } + return nil + }), nil } - conn = &websocketConn{ - conn: wsConn, - rawWriter: N.NewExtendedWriter(wsConn.UnderlyingConn()), - remoteAddr: conn.RemoteAddr(), + response, err := http.ReadResponse(bufferedConn.Reader(), request) + if err != nil { + return nil, err } + if response.StatusCode != http.StatusSwitchingProtocols || + !strings.EqualFold(response.Header.Get("Connection"), "upgrade") || + !strings.EqualFold(response.Header.Get("Upgrade"), "websocket") { + return nil, fmt.Errorf("unexpected status: %s", response.Status) + } + + if c.V2rayHttpUpgrade { + return bufferedConn, nil + } + + if log.Level() == log.DEBUG { // we might not check this for performance + secAccept := response.Header.Get("Sec-Websocket-Accept") + const acceptSize = 28 // base64.StdEncoding.EncodedLen(sha1.Size) + if lenSecAccept := len(secAccept); lenSecAccept != acceptSize { + return nil, fmt.Errorf("unexpected Sec-Websocket-Accept length: %d", lenSecAccept) + } + if getSecAccept(secKey) != secAccept { + return nil, errors.New("unexpected Sec-Websocket-Accept") + } + } + + conn = newWebsocketConn(conn, ws.StateClientSide) // websocketConn can't correct handle ReadDeadline - // gorilla/websocket will cache the os.ErrDeadlineExceeded from conn.Read() - // it will cause read fail and event panic in *websocket.Conn.NextReader() // so call N.NewDeadlineConn to add a safe wrapper return N.NewDeadlineConn(conn), nil } +func getSecAccept(secKey string) string { + const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + const nonceSize = 24 // base64.StdEncoding.EncodedLen(nonceKeySize) + p := make([]byte, nonceSize+len(magic)) + copy(p[:nonceSize], secKey) + copy(p[nonceSize:], magic) + sum := sha1.Sum(p) + return base64.StdEncoding.EncodeToString(sum[:]) +} + func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) (net.Conn, error) { if u, err := url.Parse(c.Path); err == nil { if q := u.Query(); q.Get("ed") != "" { @@ -436,3 +501,94 @@ func StreamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig) return streamWebsocketConn(ctx, conn, c, nil) } + +func newWebsocketConn(conn net.Conn, state ws.State) *websocketConn { + controlHandler := wsutil.ControlFrameHandler(conn, state) + return &websocketConn{ + Conn: conn, + state: state, + reader: &wsutil.Reader{ + Source: conn, + State: state, + SkipHeaderCheck: true, + CheckUTF8: false, + OnIntermediate: controlHandler, + }, + controlHandler: controlHandler, + rawWriter: N.NewExtendedWriter(conn), + } +} + +var replacer = strings.NewReplacer("+", "-", "/", "_", "=", "") + +func decodeEd(s string) ([]byte, error) { + return base64.RawURLEncoding.DecodeString(replacer.Replace(s)) +} + +func decodeXray0rtt(requestHeader http.Header) []byte { + // read inHeader's `Sec-WebSocket-Protocol` for Xray's 0rtt ws + if secProtocol := requestHeader.Get("Sec-WebSocket-Protocol"); len(secProtocol) > 0 { + if edBuf, err := decodeEd(secProtocol); err == nil { // sure could base64 decode + return edBuf + } + } + return nil +} + +func IsWebSocketUpgrade(r *http.Request) bool { + return r.Header.Get("Upgrade") == "websocket" +} + +func IsV2rayHttpUpdate(r *http.Request) bool { + return IsWebSocketUpgrade(r) && r.Header.Get("Sec-WebSocket-Key") == "" +} + +func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Conn, error) { + var conn net.Conn + var rw *bufio.ReadWriter + var err error + isRaw := IsV2rayHttpUpdate(r) + w.Header().Set("Connection", "upgrade") + w.Header().Set("Upgrade", "websocket") + if !isRaw { + w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key"))) + } + w.WriteHeader(http.StatusSwitchingProtocols) + if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher { + err = flusher.FlushError() + if err != nil { + return nil, fmt.Errorf("flush response: %w", err) + } + } + hijacker, canHijack := w.(http.Hijacker) + if !canHijack { + return nil, errors.New("invalid connection, maybe HTTP/2") + } + conn, rw, err = hijacker.Hijack() + if err != nil { + return nil, fmt.Errorf("hijack failed: %w", err) + } + + // rw.Writer was flushed, so we only need warp rw.Reader + conn = N.WarpConnWithBioReader(conn, rw.Reader) + + if !isRaw { + conn = newWebsocketConn(conn, ws.StateServerSide) + // websocketConn can't correct handle ReadDeadline + // so call N.NewDeadlineConn to add a safe wrapper + conn = N.NewDeadlineConn(conn) + } + + if edBuf := decodeXray0rtt(r.Header); len(edBuf) > 0 { + appendOk := false + if bufConn, ok := conn.(*N.BufferedConn); ok { + appendOk = bufConn.AppendData(edBuf) + } + if !appendOk { + conn = N.NewCachedConn(conn, edBuf) + } + + } + + return conn, nil +} diff --git a/tunnel/connection.go b/tunnel/connection.go index 9fc4f405..33cc4e8d 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -6,9 +6,9 @@ import ( "net/netip" "time" - N "github.com/Dreamacro/clash/common/net" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" ) func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { diff --git a/tunnel/statistic/manager.go b/tunnel/statistic/manager.go index 19ce58d9..08747118 100644 --- a/tunnel/statistic/manager.go +++ b/tunnel/statistic/manager.go @@ -4,9 +4,9 @@ import ( "os" "time" - "github.com/Dreamacro/clash/common/atomic" + "github.com/metacubex/mihomo/common/atomic" - "github.com/puzpuzpuz/xsync/v2" + "github.com/puzpuzpuz/xsync/v3" "github.com/shirou/gopsutil/v3/process" ) @@ -14,7 +14,7 @@ var DefaultManager *Manager func init() { DefaultManager = &Manager{ - connections: xsync.NewMapOf[Tracker](), + connections: xsync.NewMapOf[string, Tracker](), uploadTemp: atomic.NewInt64(0), downloadTemp: atomic.NewInt64(0), uploadBlip: atomic.NewInt64(0), @@ -29,12 +29,12 @@ func init() { type Manager struct { connections *xsync.MapOf[string, Tracker] - uploadTemp *atomic.Int64 - downloadTemp *atomic.Int64 - uploadBlip *atomic.Int64 - downloadBlip *atomic.Int64 - uploadTotal *atomic.Int64 - downloadTotal *atomic.Int64 + uploadTemp atomic.Int64 + downloadTemp atomic.Int64 + uploadBlip atomic.Int64 + downloadBlip atomic.Int64 + uploadTotal atomic.Int64 + downloadTotal atomic.Int64 process *process.Process memory uint64 } diff --git a/tunnel/statistic/patch_android.go b/tunnel/statistic/patch_android.go new file mode 100644 index 00000000..f1eee346 --- /dev/null +++ b/tunnel/statistic/patch_android.go @@ -0,0 +1,7 @@ +//go:build android && cmfa + +package statistic + +func (m *Manager) Total() (up, down int64) { + return m.uploadTotal.Load(), m.downloadTotal.Load() +} diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index f0f868de..0bf7995d 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -6,11 +6,11 @@ import ( "net/netip" "time" - "github.com/Dreamacro/clash/common/atomic" - "github.com/Dreamacro/clash/common/buf" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" - C "github.com/Dreamacro/clash/constant" + "github.com/metacubex/mihomo/common/atomic" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" + C "github.com/metacubex/mihomo/constant" "github.com/gofrs/uuid/v5" ) @@ -23,14 +23,14 @@ type Tracker interface { } type TrackerInfo struct { - UUID uuid.UUID `json:"id"` - Metadata *C.Metadata `json:"metadata"` - UploadTotal *atomic.Int64 `json:"upload"` - DownloadTotal *atomic.Int64 `json:"download"` - Start time.Time `json:"start"` - Chain C.Chain `json:"chains"` - Rule string `json:"rule"` - RulePayload string `json:"rulePayload"` + UUID uuid.UUID `json:"id"` + Metadata *C.Metadata `json:"metadata"` + UploadTotal atomic.Int64 `json:"upload"` + DownloadTotal atomic.Int64 `json:"download"` + Start time.Time `json:"start"` + Chain C.Chain `json:"chains"` + Rule string `json:"rule"` + RulePayload string `json:"rulePayload"` } type tcpTracker struct { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index ff64915a..391fe7c1 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -12,16 +12,17 @@ import ( "github.com/jpillora/backoff" - N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/component/nat" - P "github.com/Dreamacro/clash/component/process" - "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/component/sniffer" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/constant/provider" - icontext "github.com/Dreamacro/clash/context" - "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/tunnel/statistic" + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/nat" + P "github.com/metacubex/mihomo/component/process" + "github.com/metacubex/mihomo/component/resolver" + "github.com/metacubex/mihomo/component/sniffer" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" + "github.com/metacubex/mihomo/constant/provider" + icontext "github.com/metacubex/mihomo/context" + "github.com/metacubex/mihomo/log" + "github.com/metacubex/mihomo/tunnel/statistic" ) var ( @@ -49,6 +50,27 @@ var ( fakeIPRange netip.Prefix ) +type tunnel struct{} + +var Tunnel C.Tunnel = tunnel{} + +func (t tunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { + connCtx := icontext.NewConnContext(conn, metadata) + handleTCPConn(connCtx) +} + +func (t tunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) { + packetAdapter := C.NewPacketAdapter(packet, metadata) + select { + case udpQueue <- packetAdapter: + default: + } +} + +func (t tunnel) NatTable() C.NatTable { + return natTable +} + func OnSuspend() { status.Store(Suspend) } @@ -90,11 +112,13 @@ func init() { } // TCPIn return fan-in queue +// Deprecated: using Tunnel instead func TCPIn() chan<- C.ConnContext { return tcpQueue } // UDPIn return fan-in udp queue +// Deprecated: using Tunnel instead func UDPIn() chan<- C.PacketAdapter { return udpQueue } @@ -197,10 +221,6 @@ func isHandle(t C.Type) bool { func processUDP() { queue := udpQueue for conn := range queue { - if !isHandle(conn.Metadata().Type) { - conn.Drop() - continue - } handleUDPConn(conn) } } @@ -216,10 +236,6 @@ func process() { queue := tcpQueue for conn := range queue { - if !isHandle(conn.Metadata().Type) { - _ = conn.Conn().Close() - continue - } go handleTCPConn(conn) } } @@ -261,7 +277,7 @@ func preHandleMetadata(metadata *C.Metadata) error { return nil } -func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { +func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { if metadata.SpecialProxy != "" { var exist bool proxy, exist = proxies[metadata.SpecialProxy] @@ -284,6 +300,11 @@ func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, r } func handleUDPConn(packet C.PacketAdapter) { + if !isHandle(packet.Metadata().Type) { + packet.Drop() + return + } + metadata := packet.Metadata() if !metadata.Valid() { packet.Drop() @@ -303,11 +324,14 @@ func handleUDPConn(packet C.PacketAdapter) { return } + if sniffer.Dispatcher.Enable() && sniffingEnable { + sniffer.Dispatcher.UDPSniff(packet) + } + // local resolve UDP dns if !metadata.Resolved() { ip, err := resolver.ResolveIP(context.Background(), metadata.Host) if err != nil { - packet.Drop() return } metadata.DstIP = ip @@ -350,8 +374,7 @@ func handleUDPConn(packet C.PacketAdapter) { cond.Broadcast() }() - pCtx := icontext.NewPacketConnContext(metadata) - proxy, rule, err := resolveMetadata(pCtx, metadata) + proxy, rule, err := resolveMetadata(metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return @@ -377,7 +400,6 @@ func handleUDPConn(packet C.PacketAdapter) { if err != nil { return } - pCtx.InjectPacketConn(rawPc) pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true) @@ -387,6 +409,10 @@ func handleUDPConn(packet C.PacketAdapter) { 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()) + if rawPc.Chains().Last() == "REJECT-DROP" { + pc.Close() + return + } } else { log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), rule.Payload(), rawPc.Chains().String()) } @@ -409,6 +435,11 @@ func handleUDPConn(packet C.PacketAdapter) { } func handleTCPConn(connCtx C.ConnContext) { + if !isHandle(connCtx.Metadata().Type) { + _ = connCtx.Conn().Close() + return + } + defer func(conn net.Conn) { _ = conn.Close() }(connCtx.Conn()) @@ -454,7 +485,7 @@ func handleTCPConn(connCtx C.ConnContext) { }() } - proxy, rule, err := resolveMetadata(connCtx, metadata) + proxy, rule, err := resolveMetadata(metadata) if err != nil { log.Warnln("[Metadata] parse failed: %s", err.Error()) return @@ -591,13 +622,24 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { if attemptProcessLookup && !findProcessMode.Off() && (findProcessMode.Always() || rule.ShouldFindProcess()) { attemptProcessLookup = false - uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort)) - if err != nil { - log.Debugln("[Process] find process %s: %v", metadata.String(), err) + if !features.CMFA { + // normal check for process + uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(metadata.SrcPort)) + if err != nil { + log.Debugln("[Process] find process %s error: %v", metadata.String(), err) + } else { + metadata.Process = filepath.Base(path) + metadata.ProcessPath = path + metadata.Uid = uid + } } else { - metadata.Process = filepath.Base(path) - metadata.ProcessPath = path - metadata.Uid = uid + // check package names + pkg, err := P.FindPackageName(metadata) + if err != nil { + log.Debugln("[Process] find process %s error: %v", metadata.String(), err) + } else { + metadata.Process = pkg + } } }