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.
-[](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_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
+ }
}
}