Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c7e34bdc11 | ||
![]() |
24186a488a | ||
![]() |
5212aaf445 | ||
![]() |
e26bed43de | ||
![]() |
700ceed194 | ||
![]() |
154cb1d1f0 | ||
![]() |
295b0da0e5 | ||
![]() |
31fe77ee69 | ||
![]() |
9177645a89 | ||
![]() |
355eb491ad | ||
![]() |
18c666a1ab | ||
![]() |
13d9e960f7 | ||
![]() |
289025c6ee | ||
![]() |
369f2735a0 | ||
![]() |
2b6dd2a909 | ||
![]() |
ccd6d321cd | ||
![]() |
4d66da2277 | ||
![]() |
1ab615852e | ||
![]() |
46bb6c38ff | ||
![]() |
c244229ffb | ||
![]() |
e8b2d0ecc8 | ||
![]() |
acec0f5c89 | ||
![]() |
4655bd4da8 | ||
![]() |
6b17fd2595 | ||
![]() |
cbcbd0e085 | ||
![]() |
75c0254703 | ||
![]() |
e02d556bf4 | ||
![]() |
d006b0f2b4 | ||
![]() |
d9753efe23 | ||
![]() |
6ecd96e5ac | ||
![]() |
fdb1456c69 | ||
![]() |
7d9723662c | ||
![]() |
ca42ca2ca8 | ||
![]() |
10dcb7a3ad | ||
![]() |
4c3c64a34a | ||
![]() |
257fcef0b8 | ||
![]() |
7f1b50f4a7 | ||
![]() |
4f5e74dad9 | ||
![]() |
48b77b2847 | ||
![]() |
6eee226965 | ||
![]() |
765982e86a | ||
![]() |
c5fe5235f7 | ||
![]() |
63770b328f | ||
![]() |
85f4cb23fc | ||
![]() |
d71324069d | ||
![]() |
b7aade5e11 | ||
![]() |
df61a586c9 | ||
![]() |
8e05fbfd6d | ||
![]() |
9b2b7c662d | ||
![]() |
20a521f02d | ||
![]() |
95bbfe3945 | ||
![]() |
4cd4912749 | ||
![]() |
5045ca4574 | ||
![]() |
a7252a1576 | ||
![]() |
7e2974f02f | ||
![]() |
8f9b39c62e | ||
![]() |
d808576f98 | ||
![]() |
fcbe2f06cc | ||
![]() |
148ebccb60 | ||
![]() |
3b1d319820 | ||
![]() |
ff2f2b667b | ||
![]() |
e5a2dbd9b5 | ||
![]() |
4d14dd65fa | ||
![]() |
4ffc999617 | ||
![]() |
71f8f0667f | ||
![]() |
f78a7cb2cb | ||
![]() |
8173d6681b | ||
![]() |
fbf2f26516 | ||
![]() |
9af6d498e7 | ||
![]() |
81b1e9f931 |
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
76
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,76 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
如果你可以自己 debug 并解决的话,提交 PR 吧
|
||||
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
I have searched on the [issue tracker](……/) for a related issue.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经使用 dev 分支版本测试过,问题依旧存在
|
||||
I have tested using the dev branch, and the issue still exists.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||
"
|
||||
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.
|
||||
"
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Clash version
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: What OS are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
- OpenBSD/FreeBSD
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: yaml
|
||||
label: "Clash 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)
|
||||
"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: shell
|
||||
label: Clash log
|
||||
description: "
|
||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
124
.github/ISSUE_TEMPLATE/bug_report_en.yml
vendored
Normal file
124
.github/ISSUE_TEMPLATE/bug_report_en.yml
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
name: (English) Report a bug of the Clash core
|
||||
description: Create a bug report to help us improve
|
||||
labels:
|
||||
- bug
|
||||
title: "[Bug] <issue title>"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Welcome to the official Clash open-source community"
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to report an issue with the Clash core.
|
||||
|
||||
Prior to submitting this issue, please read and follow the guidelines below to ensure that your issue can be resolved as quickly as possible. Options marked with an asterisk (*) are required, while others are optional. If the information you provide does not comply with the requirements, the maintainers may not respond and may directly close the issue.
|
||||
|
||||
If you can debug and fix the issue yourself, we welcome you to submit a pull request to merge your changes upstream.
|
||||
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
description: "If any of the following options do not apply, please do not submit this issue as we will close it"
|
||||
options:
|
||||
- label: "I understand that this is the official open-source version of the Clash core, **only providing support for the open-source version or Premium version**"
|
||||
required: true
|
||||
- label: "I am submitting an issue with the Clash core, not Clash.Meta / OpenClash / ClashX / Clash For Windows or any other derivative version"
|
||||
required: true
|
||||
- label: "I am using the latest version of the Clash or Clash Premium core **in this repository**"
|
||||
required: true
|
||||
- label: "I have searched at the [Issue Tracker](……/) **and have not found any related issues**"
|
||||
required: true
|
||||
- label: "I have read the [official Wiki](https://dreamacro.github.io/clash/) **and was unable to solve the issue**"
|
||||
required: true
|
||||
- label: "(required for Premium core) I've tried the `dev` branch and the issue still exists"
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Environment"
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please provide the following information to help us locate the issue.
|
||||
The issue might be closed if there's not enough information provided.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: "Run `clash -v` or look at the bottom-left corner of the Clash Dashboard to find out"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: "Select all operating systems that apply to this issue"
|
||||
multiple: true
|
||||
options:
|
||||
- Linux
|
||||
- Windows
|
||||
- macOS (darwin)
|
||||
- Android
|
||||
- OpenBSD / FreeBSD
|
||||
|
||||
- type: dropdown
|
||||
id: arch
|
||||
attributes:
|
||||
label: Architecture
|
||||
description: "Select all architectures that apply to this issue"
|
||||
multiple: true
|
||||
options:
|
||||
- amd64
|
||||
- amd64-v3
|
||||
- arm64
|
||||
- "386"
|
||||
- armv5
|
||||
- armv6
|
||||
- armv7
|
||||
- mips-softfloat
|
||||
- mips-hardfloat
|
||||
- mipsle-softfloat
|
||||
- mipsle-hardfloat
|
||||
- mips64
|
||||
- mips64le
|
||||
- riscv64
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Clash related information"
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please provide relevant information about your Clash instance here. If you
|
||||
do not provide enough information, the issue may be closed.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: YAML
|
||||
label: Configuration File
|
||||
placeholder: "Ensure that there is no sensitive information (such as server addresses, passwords, or ports) in the configuration file, and provide the minimum reproducible configuration. Do not post configurations with thousands of lines."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: Text
|
||||
label: Log
|
||||
placeholder: "Please attach the corresponding core outout (setting `log-level: debug` in the configuration provides debugging information)."
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
placeholder: "Please describe your issue in detail here to help us understand (supports Markdown syntax)."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
placeholder: "Please provide the specific steps to reproduce the issue here (supports Markdown syntax)."
|
||||
|
121
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
Normal file
121
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
name: (中文)提交 Clash 核心的问题
|
||||
description: 如果 Clash 核心运作不符合预期,在这里提交问题
|
||||
labels:
|
||||
- bug
|
||||
title: "[Bug] <问题标题>"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 欢迎来到 Clash 官方开源社区!"
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你拨冗提交 Clash 内核的问题。在提交之前,请仔细阅读并遵守以下指引,以确保你的问题能够被尽快解决。
|
||||
带有星号(*)的选项为必填,其他可选填。**如果你填写的资料不符合规范,维护者可能不予回复,并直接关闭这个 issue。**
|
||||
如果你可以自行 debug 并且修正,我们随时欢迎你提交 Pull Request,将你的修改合并到上游。
|
||||
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: 先决条件
|
||||
description: "若以下任意选项不适用,请勿提交这个 issue,因为我们会把它关闭"
|
||||
options:
|
||||
- label: "我了解这里是官方开源版 Clash 核心仓库,**只提供开源版或者 Premium 内核的支持**"
|
||||
required: true
|
||||
- label: "我要提交 Clash 核心的问题,并非 Clash.Meta / OpenClash / ClashX / Clash For Windows 或其他任何衍生版本的问题"
|
||||
required: true
|
||||
- label: "我使用的是**本仓库**最新版本的 Clash 或 Clash Premium 内核"
|
||||
required: true
|
||||
- label: "我已经在 [Issue Tracker](……/) 中找过我要提出的 bug,**并且没有找到相关问题**"
|
||||
required: true
|
||||
- label: "我已经仔细阅读 [官方 Wiki](https://dreamacro.github.io/clash/) 并无法自行解决问题"
|
||||
required: true
|
||||
- label: "(非 Premium 内核必填)我已经使用 dev 分支版本测试过,问题依旧存在"
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 系统环境"
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请附上这个问题适用的环境,以帮助我们迅速定位问题并解决。若你提供的信息不足,我们将关闭
|
||||
这个 issue 并要求你提供更多信息。
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: 版本
|
||||
description: "运行 `clash -v` 或者查看 Clash Dashboard 的左下角来找到你现在使用的版本"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: 适用的作业系统
|
||||
description: "勾选所有适用于这个 issue 的系统"
|
||||
multiple: true
|
||||
options:
|
||||
- Linux
|
||||
- Windows
|
||||
- macOS (darwin)
|
||||
- Android
|
||||
- OpenBSD / FreeBSD
|
||||
|
||||
- type: dropdown
|
||||
id: arch
|
||||
attributes:
|
||||
label: 适用的硬件架构
|
||||
description: "勾选所有适用于这个 issue 的架构"
|
||||
multiple: true
|
||||
options:
|
||||
- amd64
|
||||
- amd64-v3
|
||||
- arm64
|
||||
- "386"
|
||||
- armv5
|
||||
- armv6
|
||||
- armv7
|
||||
- mips-softfloat
|
||||
- mips-hardfloat
|
||||
- mipsle-softfloat
|
||||
- mipsle-hardfloat
|
||||
- mips64
|
||||
- mips64le
|
||||
- riscv64
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Clash 相关信息"
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请附上与这个问题直接相关的相应信息,以帮助我们迅速定位问题并解决。
|
||||
若你提供的信息不足,我们将关闭这个 issue 并要求你提供更多信息。
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: YAML
|
||||
label: "配置文件"
|
||||
placeholder: "确保配置文件中没有敏感信息(如:服务器地址、密码、端口),并且提供最小可复现配置,严禁贴上上千行的配置"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: Text
|
||||
label: 日志输出
|
||||
placeholder: "在这里附上问题对应的内核日志(在配置中设置 `log-level: debug` 可获得调试信息)"
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 问题描述
|
||||
placeholder: "在这里详细叙述你的问题,帮助我们理解(支持 Markdown 语法)"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
placeholder: "在这里提供问题的具体重现步骤(支持 Markdown 语法)"
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,6 +1,9 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Get help in GitHub Discussions
|
||||
url: https://github.com/Dreamacro/clash/discussions
|
||||
about: Have a question? Not sure if your issue affects everyone reproducibly? The quickest way to get help is on Clash's GitHub Discussions!
|
||||
- name: (中文)阅读 Wiki
|
||||
url: https://dreamacro.github.io/clash/zh_CN/
|
||||
about: 如果你是新手,或者想要了解 Clash 的更多信息,请阅读我们撰写的官方 Wiki
|
||||
- name: (English) Read our Wiki page
|
||||
url: https://dreamacro.github.io/clash/
|
||||
about: If you are new to Clash, or want to know more about Clash, please read our Wiki page
|
||||
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,36 +0,0 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||
I have searched on the [issue tracker](……/) for a related feature request.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://github.com/Dreamacro/clash/wiki/) 并无法自行解决问题
|
||||
I have read the [documentation](https://github.com/Dreamacro/clash/wiki/) and was unable to solve the issue.
|
||||
"
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: "
|
||||
此项非必须,但是如果你有想法的话欢迎提出。
|
||||
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
|
||||
"
|
43
.github/ISSUE_TEMPLATE/feature_request_en.yml
vendored
Normal file
43
.github/ISSUE_TEMPLATE/feature_request_en.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
name: (English) Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels:
|
||||
- enhancement
|
||||
title: "[Feature] <title>"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Welcome to the official Clash open-source community"
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to make a suggestion to the Clash core.
|
||||
|
||||
Prior to submitting this issue, please read and follow the guidelines below to ensure that your issue can be resolved as quickly as possible. Options marked with an asterisk (*) are required, while others are optional. If the information you provide does not comply with the requirements, the maintainers may not respond and may directly close the issue.
|
||||
|
||||
If you can implement your idea by yourself, we welcome you to submit a pull request to merge your changes upstream.
|
||||
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
description: "If any of the following options do not apply, please do not submit this issue as we will close it"
|
||||
options:
|
||||
- label: "I understand that this is the official open-source version of the Clash core, **only providing support for the open-source version or Premium version**"
|
||||
required: true
|
||||
- label: "I have looked for my idea in [the issue tracker](https://github.com/Dreamacro/clash/issues?q=is%3Aissue+label%3Aenhancement), **and found none of which being related**"
|
||||
required: true
|
||||
- label: "I have read the [official Wiki](https://dreamacro.github.io/clash/)"
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
placeholder: "Please explain your suggestions in detail and in a clear manner. For instance, how does this issue impact you? What specific functionality are you hoping to achieve? Also, let us know what Clash Core is currently doing in terms of your suggestion, and what you would like it to do instead."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
placeholder: "Do you have any ideas on the implementation details?"
|
41
.github/ISSUE_TEMPLATE/feature_request_zh.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/feature_request_zh.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: (中文)建议一个新功能
|
||||
description: 在这里提供一个的想法或建议
|
||||
labels:
|
||||
- enhancement
|
||||
title: "[Feature] <标题>"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## 欢迎来到 Clash 官方开源社区!"
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你拨冗为 Clash 内核提供建议。在提交之前,请仔细阅读并遵守以下指引,以确保你的建议能够被顺利采纳。
|
||||
带有星号(*)的选项为必填,其他可选填。**如果你填写的资料不符合规范,维护者可能不予回复,并直接关闭这个 issue。**
|
||||
如果你可以自行添加这个功能,我们随时欢迎你提交 Pull Request,并将你的修改合并到上游。
|
||||
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: 先决条件
|
||||
description: "若以下任意选项不适用,请勿提交这个 issue,因为我们会把它关闭"
|
||||
options:
|
||||
- label: "我了解这里是 Clash 官方仓库,并非 Clash.Meta / OpenClash / ClashX / Clash For Windows 或其他任何衍生版本"
|
||||
required: true
|
||||
- label: "我已经在[这里](https://github.com/Dreamacro/clash/issues?q=is%3Aissue+label%3Aenhancement)找过我要提出的建议,**并且没有找到相关问题**"
|
||||
required: true
|
||||
- label: "我已经仔细阅读 [官方 Wiki](https://dreamacro.github.io/clash/) "
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 描述
|
||||
placeholder: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什么?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 可能的解决方案
|
||||
placeholder: 此项非必须,但是如果你有想法的话欢迎提出。
|
42
.github/workflows/deploy-docs.yml
vendored
Normal file
42
.github/workflows/deploy-docs.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
name: Deploy
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
working-directory: docs
|
||||
run: pnpm install --frozen-lockfile=false
|
||||
- name: Build
|
||||
working-directory: docs
|
||||
run: pnpm run docs:build
|
||||
- uses: actions/configure-pages@v2
|
||||
- uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Build dev branch and push
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
- name: Build release and push
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
|
4
.github/workflows/linter.yml
vendored
4
.github/workflows/linter.yml
vendored
@ -7,10 +7,10 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
check-latest: true
|
||||
go-version: '1.19'
|
||||
go-version: '1.20'
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -5,16 +5,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
check-latest: true
|
||||
go-version: '1.19'
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
NAME: clash
|
||||
BINDIR: bin
|
||||
run: make -j releases
|
||||
run: make -j $(go run ./test/main.go) releases
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
days-before-stale: 60
|
||||
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -23,3 +23,14 @@ vendor
|
||||
|
||||
# test suite
|
||||
test/config/cache*
|
||||
|
||||
# docs site generator
|
||||
node_modules
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
# docs site cache
|
||||
docs/.vitepress/cache
|
||||
|
||||
# docs site build files
|
||||
docs/.vitepress/dist
|
||||
|
@ -1,10 +1,16 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gofumpt
|
||||
- staticcheck
|
||||
- govet
|
||||
- gci
|
||||
- gofumpt
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
@ -14,4 +20,4 @@ linters-settings:
|
||||
- prefix(github.com/Dreamacro/clash)
|
||||
- default
|
||||
staticcheck:
|
||||
go: '1.19'
|
||||
go: '1.20'
|
||||
|
19
Makefile
19
Makefile
@ -24,6 +24,7 @@ PLATFORM_LIST = \
|
||||
linux-mips64 \
|
||||
linux-mips64le \
|
||||
linux-riscv64 \
|
||||
linux-loong64 \
|
||||
freebsd-386 \
|
||||
freebsd-amd64 \
|
||||
freebsd-amd64-v3 \
|
||||
@ -89,6 +90,9 @@ linux-mips64le:
|
||||
linux-riscv64:
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-loong64:
|
||||
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
freebsd-386:
|
||||
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
@ -130,12 +134,15 @@ all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
|
||||
|
||||
releases: $(gz_releases) $(zip_releases)
|
||||
|
||||
lint:
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=windows golangci-lint run ./...
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
GOOS=openbsd golangci-lint run ./...
|
||||
LINT_OS_LIST := darwin windows linux freebsd openbsd
|
||||
|
||||
lint: $(foreach os,$(LINT_OS_LIST),$(os)-lint)
|
||||
%-lint:
|
||||
GOOS=$* golangci-lint run ./...
|
||||
|
||||
lint-fix: $(foreach os,$(LINT_OS_LIST),$(os)-lint-fix)
|
||||
%-lint-fix:
|
||||
GOOS=$* golangci-lint run --fix ./...
|
||||
|
||||
clean:
|
||||
rm $(BINDIR)/*
|
||||
|
37
README.md
37
README.md
@ -23,35 +23,28 @@
|
||||
|
||||
## Features
|
||||
|
||||
- Local HTTP/HTTPS/SOCKS server with authentication support
|
||||
- Shadowsocks(R), VMess, Trojan, Snell, SOCKS5, HTTP(S) outbound support
|
||||
- Built-in [fake-ip](https://www.rfc-editor.org/rfc/rfc3089) DNS server that aims to minimize DNS pollution attack impact. DoH/DoT upstream supported.
|
||||
- Rules based off domains, GEOIP, IP-CIDR or process names to route packets to different destinations
|
||||
- Proxy groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select proxy based off latency
|
||||
- Remote providers, allowing users to get proxy lists remotely instead of hardcoding in config
|
||||
- Transparent proxy: Redirect TCP and TProxy TCP/UDP with automatic route table/rule management
|
||||
- Hot-reload via the comprehensive HTTP RESTful API controller
|
||||
This is a general overview of the features that comes with Clash.
|
||||
|
||||
## Premium
|
||||
- Inbound: HTTP, HTTPS, SOCKS5 server, TUN device
|
||||
- Outbound: Shadowsocks(R), VMess, Trojan, Snell, SOCKS5, HTTP(S), Wireguard
|
||||
- Rule-based Routing: dynamic scripting, domain, IP addresses, process name and more
|
||||
- Fake-IP DNS: minimises impact on DNS pollution and improves network performance
|
||||
- Transparent Proxy: Redirect TCP and TProxy TCP/UDP with automatic route table/rule management
|
||||
- Proxy Groups: automatic fallback, load balancing or latency testing
|
||||
- Remote Providers: load remote proxy lists dynamically
|
||||
- RESTful API: update configuration in-place via a comprehensive API
|
||||
|
||||
Premium core is proprietary. You can find their release notes and pre-built binaries [here](https://github.com/Dreamacro/clash/releases/tag/premium).
|
||||
*Some of the features may only be available in the [Premium core](https://dreamacro.github.io/clash/premium/introduction.html).*
|
||||
|
||||
- gvisor/system stack TUN device on macOS, Linux and Windows ([ref](https://github.com/Dreamacro/clash/wiki/Clash-Premium-Features#tun-device))
|
||||
- Policy routing with [Scripts](https://github.com/Dreamacro/clash/wiki/Clash-Premium-Features#script)
|
||||
- Load your rules with [Rule Providers](https://github.com/Dreamacro/clash/wiki/Clash-Premium-Features#rule-providers)
|
||||
- Monitor Clash usage with a built-in profiling engine. ([Dreamacro/clash-tracing](https://github.com/Dreamacro/clash-tracing))
|
||||
## Documentation
|
||||
|
||||
## Getting Started
|
||||
Documentations are available at [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||
|
||||
## Development
|
||||
If you want to build a Go application that uses Clash as a library, check out the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/Using-Clash-in-your-Golang-program).
|
||||
You can find the latest documentation at [https://dreamacro.github.io/clash/](https://dreamacro.github.io/clash/).
|
||||
|
||||
## Credits
|
||||
|
||||
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
||||
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
|
||||
- [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
|
||||
- [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
|
||||
- [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
|
||||
|
||||
## License
|
||||
|
||||
|
@ -94,6 +94,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping := map[string]any{}
|
||||
json.Unmarshal(inner, &mapping)
|
||||
mapping["history"] = p.DelayHistory()
|
||||
mapping["alive"] = p.Alive()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
return json.Marshal(mapping)
|
||||
@ -101,12 +102,13 @@ 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) (t uint16, err error) {
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (delay, meanDelay uint16, err error) {
|
||||
defer func() {
|
||||
p.alive.Store(err == nil)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if err == nil {
|
||||
record.Delay = t
|
||||
record.Delay = delay
|
||||
record.MeanDelay = meanDelay
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > 10 {
|
||||
@ -156,7 +158,16 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
t = uint16(time.Since(start) / time.Millisecond)
|
||||
delay = uint16(time.Since(start) / time.Millisecond)
|
||||
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
// ignore error because some server will hijack the connection and close immediately
|
||||
return delay, 0, nil
|
||||
}
|
||||
resp.Body.Close()
|
||||
meanDelay = uint16(time.Since(start) / time.Millisecond / 2)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
@ -9,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// NewHTTP receive normal http request and return HTTPContext
|
||||
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext {
|
||||
func NewHTTP(target socks5.Addr, source net.Addr, originTarget net.Addr, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.HTTP
|
||||
@ -17,5 +18,10 @@ func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnCo
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
if originTarget != nil {
|
||||
if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil {
|
||||
metadata.OriginDst = addrPort
|
||||
}
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package inbound
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
@ -16,5 +17,8 @@ func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
if addrPort, err := netip.ParseAddrPort(conn.LocalAddr().String()); err == nil {
|
||||
metadata.OriginDst = addrPort
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
@ -17,7 +20,7 @@ func (s *PacketAdapter) Metadata() *C.Metadata {
|
||||
}
|
||||
|
||||
// NewPacket is PacketAdapter generator
|
||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter {
|
||||
func NewPacket(target socks5.Addr, originTarget net.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.UDP
|
||||
metadata.Type = source
|
||||
@ -25,7 +28,11 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAda
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
|
||||
if originTarget != nil {
|
||||
if addrPort, err := netip.ParseAddrPort(originTarget.String()); err == nil {
|
||||
metadata.OriginDst = addrPort
|
||||
}
|
||||
}
|
||||
return &PacketAdapter{
|
||||
UDPPacket: packet,
|
||||
metadata: metadata,
|
||||
|
@ -2,6 +2,7 @@ package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
@ -17,6 +18,8 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
|
||||
if addrPort, err := netip.ParseAddrPort(conn.LocalAddr().String()); err == nil {
|
||||
metadata.OriginDst = addrPort
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ type Http struct {
|
||||
user string
|
||||
pass string
|
||||
tlsConfig *tls.Config
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
type HttpOption struct {
|
||||
@ -34,6 +35,7 @@ type HttpOption struct {
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConn implements C.ProxyAdapter
|
||||
@ -83,11 +85,11 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||
Host: addr,
|
||||
},
|
||||
Host: addr,
|
||||
Header: http.Header{
|
||||
"Proxy-Connection": []string{"Keep-Alive"},
|
||||
},
|
||||
Header: h.Headers.Clone(),
|
||||
}
|
||||
|
||||
req.Header.Add("Proxy-Connection", "Keep-Alive")
|
||||
|
||||
if h.user != "" && h.pass != "" {
|
||||
auth := h.user + ":" + h.pass
|
||||
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
|
||||
@ -134,6 +136,11 @@ func NewHttp(option HttpOption) *Http {
|
||||
}
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
for name, value := range option.Headers {
|
||||
headers.Add(name, value)
|
||||
}
|
||||
|
||||
return &Http{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
@ -145,5 +152,6 @@ func NewHttp(option HttpOption) *Http {
|
||||
user: option.UserName,
|
||||
pass: option.Password,
|
||||
tlsConfig: tlsConfig,
|
||||
Headers: headers,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -9,6 +8,8 @@ import (
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/Dreamacro/protobytes"
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
@ -19,24 +20,24 @@ func tcpKeepAlive(c net.Conn) {
|
||||
}
|
||||
|
||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||
var buf [][]byte
|
||||
buf := protobytes.BytesWriter{}
|
||||
|
||||
addrType := metadata.AddrType()
|
||||
aType := uint8(addrType)
|
||||
buf.PutUint8(uint8(addrType))
|
||||
|
||||
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||
switch addrType {
|
||||
case socks5.AtypDomainName:
|
||||
len := uint8(len(metadata.Host))
|
||||
host := []byte(metadata.Host)
|
||||
buf = [][]byte{{aType, len}, host, port}
|
||||
buf.PutUint8(uint8(len(metadata.Host)))
|
||||
buf.PutString(metadata.Host)
|
||||
case socks5.AtypIPv4:
|
||||
host := metadata.DstIP.To4()
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
buf.PutSlice(metadata.DstIP.To4())
|
||||
case socks5.AtypIPv6:
|
||||
host := metadata.DstIP.To16()
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
buf.PutSlice(metadata.DstIP.To16())
|
||||
}
|
||||
return bytes.Join(buf, nil)
|
||||
|
||||
buf.PutUint16be(uint16(p))
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
|
||||
|
@ -340,15 +340,15 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
||||
var addr []byte
|
||||
switch metadata.AddrType() {
|
||||
case socks5.AtypIPv4:
|
||||
addrType = byte(vmess.AtypIPv4)
|
||||
addrType = vmess.AtypIPv4
|
||||
addr = make([]byte, net.IPv4len)
|
||||
copy(addr[:], metadata.DstIP.To4())
|
||||
case socks5.AtypIPv6:
|
||||
addrType = byte(vmess.AtypIPv6)
|
||||
addrType = vmess.AtypIPv6
|
||||
addr = make([]byte, net.IPv6len)
|
||||
copy(addr[:], metadata.DstIP.To16())
|
||||
case socks5.AtypDomainName:
|
||||
addrType = byte(vmess.AtypDomainName)
|
||||
addrType = vmess.AtypDomainName
|
||||
addr = make([]byte, len(metadata.Host)+1)
|
||||
addr[0] = byte(len(metadata.Host))
|
||||
copy(addr[1:], []byte(metadata.Host))
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
regexp "github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -29,6 +31,7 @@ type GroupCommonOption struct {
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
@ -45,22 +48,33 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, errFormat
|
||||
}
|
||||
|
||||
groupName := groupOption.Name
|
||||
var (
|
||||
groupName = groupOption.Name
|
||||
filterReg *regexp.Regexp
|
||||
)
|
||||
|
||||
providers := []types.ProxyProvider{}
|
||||
if groupOption.Filter != "" {
|
||||
f, err := regexp.Compile(groupOption.Filter, regexp.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: invalid filter regex: %w", groupName, err)
|
||||
}
|
||||
filterReg = f
|
||||
}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
return nil, errMissProxy
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
|
||||
}
|
||||
|
||||
providers := []types.ProxyProvider{}
|
||||
|
||||
if len(groupOption.Proxies) != 0 {
|
||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
if _, ok := providersMap[groupName]; ok {
|
||||
return nil, errDuplicateProvider
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
@ -68,20 +82,20 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
} else {
|
||||
if groupOption.URL == "" || groupOption.Interval == 0 {
|
||||
return nil, errMissHealthCheck
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errMissHealthCheck)
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
@ -92,10 +106,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
if filterReg != nil {
|
||||
pd := provider.NewFilterableProvider(groupName, list, filterReg)
|
||||
providers = append(providers, pd)
|
||||
} else {
|
||||
providers = append(providers, list...)
|
||||
}
|
||||
}
|
||||
|
||||
var group C.ProxyAdapter
|
||||
switch groupOption.Type {
|
||||
@ -112,7 +131,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
case "relay":
|
||||
group = NewRelay(groupOption, providers)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
|
||||
return nil, fmt.Errorf("%s %w: %s", groupName, errType, groupOption.Type)
|
||||
}
|
||||
|
||||
return group, nil
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/batch"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
@ -31,13 +32,20 @@ type HealthCheck struct {
|
||||
func (hc *HealthCheck) process() {
|
||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||
|
||||
go hc.check()
|
||||
go hc.checkAll()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
hc.check()
|
||||
hc.checkAll()
|
||||
} else { // lazy but still need to check not alive proxies
|
||||
notAliveProxies := lo.Filter(hc.proxies, func(proxy C.Proxy, _ int) bool {
|
||||
return !proxy.Alive()
|
||||
})
|
||||
if len(notAliveProxies) != 0 {
|
||||
hc.check(notAliveProxies)
|
||||
}
|
||||
}
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
@ -58,9 +66,13 @@ func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
func (hc *HealthCheck) checkAll() {
|
||||
hc.check(hc.proxies)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check(proxies []C.Proxy) {
|
||||
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10))
|
||||
for _, proxy := range hc.proxies {
|
||||
for _, proxy := range proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
|
@ -10,7 +10,10 @@ import (
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var errVehicleType = errors.New("unsupport vehicle type")
|
||||
var (
|
||||
errVehicleType = errors.New("unsupport vehicle type")
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type healthCheckSchema struct {
|
||||
Enable bool `provider:"enable"`
|
||||
@ -53,6 +56,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
case "file":
|
||||
vehicle = NewFileVehicle(path)
|
||||
case "http":
|
||||
if !C.Path.IsSubPath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = NewHTTPVehicle(schema.URL, path)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
|
@ -4,17 +4,22 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
|
||||
regexp "github.com/dlclark/regexp2"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var reject = adapter.NewProxy(outbound.NewReject())
|
||||
|
||||
const (
|
||||
ReservedName = "default"
|
||||
)
|
||||
@ -49,7 +54,7 @@ func (pp *proxySetProvider) Name() string {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) HealthCheck() {
|
||||
pp.healthCheck.check()
|
||||
pp.healthCheck.checkAll()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Update() error {
|
||||
@ -86,7 +91,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.check()
|
||||
go pp.healthCheck.checkAll()
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +101,7 @@ func stopProxyProvider(pd *ProxySetProvider) {
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
filterReg, err := regexp.Compile(filter)
|
||||
filterReg, err := regexp.Compile(filter, regexp.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
@ -128,9 +133,15 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
|
||||
|
||||
proxies := []C.Proxy{}
|
||||
for idx, mapping := range schema.Proxies {
|
||||
if name, ok := mapping["name"].(string); ok && len(filter) > 0 && !filterReg.MatchString(name) {
|
||||
if name, ok := mapping["name"].(string); ok && len(filter) > 0 {
|
||||
matched, err := filterReg.MatchString(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("regex filter failed: %w", err)
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
|
||||
@ -181,7 +192,7 @@ func (cp *compatibleProvider) Name() string {
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheck() {
|
||||
cp.healthCheck.check()
|
||||
cp.healthCheck.checkAll()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Update() error {
|
||||
@ -231,3 +242,81 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
var _ types.ProxyProvider = (*FilterableProvider)(nil)
|
||||
|
||||
type FilterableProvider struct {
|
||||
name string
|
||||
providers []types.ProxyProvider
|
||||
filterReg *regexp.Regexp
|
||||
single *singledo.Single
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": fp.Name(),
|
||||
"type": fp.Type().String(),
|
||||
"vehicleType": fp.VehicleType().String(),
|
||||
"proxies": fp.Proxies(),
|
||||
})
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Name() string {
|
||||
return fp.name
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) HealthCheck() {
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Proxies() []C.Proxy {
|
||||
elm, _, _ := fp.single.Do(func() (any, error) {
|
||||
proxies := lo.FlatMap(
|
||||
fp.providers,
|
||||
func(item types.ProxyProvider, _ int) []C.Proxy {
|
||||
return lo.Filter(
|
||||
item.Proxies(),
|
||||
func(item C.Proxy, _ int) bool {
|
||||
matched, _ := fp.filterReg.MatchString(item.Name())
|
||||
return matched
|
||||
})
|
||||
})
|
||||
|
||||
if len(proxies) == 0 {
|
||||
proxies = append(proxies, reject)
|
||||
}
|
||||
return proxies, nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (fp *FilterableProvider) Touch() {
|
||||
for _, provider := range fp.providers {
|
||||
provider.Touch()
|
||||
}
|
||||
}
|
||||
|
||||
func NewFilterableProvider(name string, providers []types.ProxyProvider, filterReg *regexp.Regexp) *FilterableProvider {
|
||||
return &FilterableProvider{
|
||||
name: name,
|
||||
providers: providers,
|
||||
filterReg: filterReg,
|
||||
single: singledo.NewSingle(time.Second * 10),
|
||||
}
|
||||
}
|
||||
|
@ -32,10 +32,14 @@ func NewAllocator() *Allocator {
|
||||
|
||||
// Get a []byte from pool with most appropriate cap
|
||||
func (alloc *Allocator) Get(size int) []byte {
|
||||
if size <= 0 || size > 65536 {
|
||||
switch {
|
||||
case size < 0:
|
||||
panic("alloc.Get: len out of range")
|
||||
case size == 0:
|
||||
return nil
|
||||
}
|
||||
|
||||
case size > 65536:
|
||||
return make([]byte, size)
|
||||
default:
|
||||
bits := msb(size)
|
||||
if size == 1<<bits {
|
||||
return alloc.buffers[bits].Get().([]byte)[:size]
|
||||
@ -43,12 +47,17 @@ func (alloc *Allocator) Get(size int) []byte {
|
||||
|
||||
return alloc.buffers[bits+1].Get().([]byte)[:size]
|
||||
}
|
||||
}
|
||||
|
||||
// Put returns a []byte to pool for future use,
|
||||
// which the cap must be exactly 2^n
|
||||
func (alloc *Allocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bits := msb(cap(buf))
|
||||
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||
if cap(buf) != 1<<bits {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
|
@ -19,17 +19,17 @@ func TestAllocGet(t *testing.T) {
|
||||
assert.Equal(t, 1024, cap(alloc.Get(1023)))
|
||||
assert.Equal(t, 1024, len(alloc.Get(1024)))
|
||||
assert.Equal(t, 65536, len(alloc.Get(65536)))
|
||||
assert.Nil(t, alloc.Get(65537))
|
||||
assert.Equal(t, 65537, len(alloc.Get(65537)))
|
||||
}
|
||||
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.Nil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
|
@ -3,9 +3,14 @@ package pool
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/protobytes"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
|
||||
var (
|
||||
bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
|
||||
bytesBufferPool = sync.Pool{New: func() any { return &protobytes.BytesWriter{} }}
|
||||
)
|
||||
|
||||
func GetBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
@ -15,3 +20,12 @@ func PutBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
|
||||
func GetBytesBuffer() *protobytes.BytesWriter {
|
||||
return bytesBufferPool.Get().(*protobytes.BytesWriter)
|
||||
}
|
||||
|
||||
func PutBytesBuffer(buf *protobytes.BytesWriter) {
|
||||
buf.Reset()
|
||||
bytesBufferPool.Put(buf)
|
||||
}
|
||||
|
@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
||||
listenAddr = "255.255.255.255:68"
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
|
||||
options := []dialer.Option{
|
||||
dialer.WithInterface(ifaceName),
|
||||
dialer.WithAddrReuse(true),
|
||||
}
|
||||
|
||||
// fallback bind on windows, because syscall bind can not receive broadcast
|
||||
if runtime.GOOS == "windows" {
|
||||
options = append(options, dialer.WithFallbackBind(true))
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
|
||||
}
|
||||
|
@ -1,57 +1,12 @@
|
||||
//go:build !linux && !darwin
|
||||
//go:build !linux && !darwin && !windows
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *net.IPNet
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination != nil {
|
||||
if destination.To4() != nil {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.IP,
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.IP,
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
|
98
component/dialer/bind_windows.go
Normal file
98
component/dialer/bind_windows.go
Normal file
@ -0,0 +1,98 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
IP_UNICAST_IF = 31
|
||||
IPV6_UNICAST_IF = 31
|
||||
)
|
||||
|
||||
type controlFn = func(network, address string, c syscall.RawConn) error
|
||||
|
||||
func bindControl(ifaceIdx int, chain controlFn) controlFn {
|
||||
return func(network, address string, c syscall.RawConn) (err error) {
|
||||
defer func() {
|
||||
if err == nil && chain != nil {
|
||||
err = chain(network, address, c)
|
||||
}
|
||||
}()
|
||||
|
||||
ipStr, _, err := net.SplitHostPort(address)
|
||||
if err == nil {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip != nil && !ip.IsGlobalUnicast() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var innerErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
if ipStr == "" && strings.HasPrefix(network, "udp") {
|
||||
// When listening udp ":0", we should bind socket to interface4 and interface6 at the same time
|
||||
// and ignore the error of bind6
|
||||
_ = bindSocketToInterface6(windows.Handle(fd), ifaceIdx)
|
||||
innerErr = bindSocketToInterface4(windows.Handle(fd), ifaceIdx)
|
||||
return
|
||||
}
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
innerErr = bindSocketToInterface4(windows.Handle(fd), ifaceIdx)
|
||||
case "tcp6", "udp6":
|
||||
innerErr = bindSocketToInterface6(windows.Handle(fd), ifaceIdx)
|
||||
}
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
err = innerErr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func bindSocketToInterface4(handle windows.Handle, ifaceIdx int) error {
|
||||
// MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros.
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
|
||||
var bytes [4]byte
|
||||
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
|
||||
index := *(*uint32)(unsafe.Pointer(&bytes[0]))
|
||||
err := windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindSocketToInterface6(handle windows.Handle, ifaceIdx int) error {
|
||||
return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lc.Control = bindControl(ifaceObj.Index, lc.Control)
|
||||
return address, nil
|
||||
}
|
@ -51,7 +51,15 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||
var (
|
||||
addr string
|
||||
err error
|
||||
)
|
||||
if cfg.fallbackBind {
|
||||
addr, err = fallbackBindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||
} else {
|
||||
addr, err = bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,10 +91,16 @@ func dialContext(ctx context.Context, network string, destination net.IP, port s
|
||||
|
||||
dialer := &net.Dialer{}
|
||||
if opt.interfaceName != "" {
|
||||
if opt.fallbackBind {
|
||||
if err := fallbackBindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
|
90
component/dialer/fallbackbind.go
Normal file
90
component/dialer/fallbackbind.go
Normal file
@ -0,0 +1,90 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func lookupLocalAddr(ifaceName string, network string, destination net.IP, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *net.IPNet
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination != nil {
|
||||
if destination.To4() != nil {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.IP,
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.IP,
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination net.IP) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
|
||||
local := uint64(0)
|
||||
if dialer.LocalAddr != nil {
|
||||
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||
if err == nil {
|
||||
local, _ = strconv.ParseUint(port, 10, 16)
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.LocalAddr = addr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
port = "0"
|
||||
}
|
||||
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, nil, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addr.String(), nil
|
||||
}
|
@ -10,6 +10,7 @@ var (
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
fallbackBind bool
|
||||
addrReuse bool
|
||||
routingMark int
|
||||
}
|
||||
@ -22,6 +23,12 @@ func WithInterface(name string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithFallbackBind(fallback bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.fallbackBind = fallback
|
||||
}
|
||||
}
|
||||
|
||||
func WithAddrReuse(reuse bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.addrReuse = reuse
|
||||
|
22
component/ipset/ipset_linux.go
Normal file
22
component/ipset/ipset_linux.go
Normal file
@ -0,0 +1,22 @@
|
||||
//go:build linux
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// Test whether the ip is in the set or not
|
||||
func Test(setName string, ip net.IP) (bool, error) {
|
||||
return netlink.IpsetTest(setName, &netlink.IPSetEntry{
|
||||
IP: ip,
|
||||
})
|
||||
}
|
||||
|
||||
// Verify dumps a specific ipset to check if we can use the set normally
|
||||
func Verify(setName string) error {
|
||||
_, err := netlink.IpsetList(setName)
|
||||
return err
|
||||
}
|
17
component/ipset/ipset_others.go
Normal file
17
component/ipset/ipset_others.go
Normal file
@ -0,0 +1,17 @@
|
||||
//go:build !linux
|
||||
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// Always return false in non-linux
|
||||
func Test(setName string, ip net.IP) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Always pass in non-linux
|
||||
func Verify(setName string) error {
|
||||
return nil
|
||||
}
|
@ -2,7 +2,7 @@ package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -16,6 +16,6 @@ const (
|
||||
UDP = "udp"
|
||||
)
|
||||
|
||||
func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) {
|
||||
return findProcessName(network, srcIP, srcPort)
|
||||
func FindProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
||||
return findProcessPath(network, from, to)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package process
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -33,7 +33,7 @@ var structSize = func() int {
|
||||
}
|
||||
}()
|
||||
|
||||
func findProcessName(network string, ip net.IP, port int) (string, error) {
|
||||
func findProcessPath(network string, from netip.AddrPort, _ netip.AddrPort) (string, error) {
|
||||
var spath string
|
||||
switch network {
|
||||
case TCP:
|
||||
@ -44,7 +44,7 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
isIPv4 := ip.To4() != nil
|
||||
isIPv4 := from.Addr().Is4()
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
@ -65,30 +65,36 @@ func findProcessName(network string, ip net.IP, port int) (string, error) {
|
||||
inp, so := i, i+104
|
||||
|
||||
srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
|
||||
if uint16(port) != srcPort {
|
||||
if from.Port() != srcPort {
|
||||
continue
|
||||
}
|
||||
|
||||
// FIXME: add dstPort check
|
||||
|
||||
// xinpcb_n.inp_vflag
|
||||
flag := buf[inp+44]
|
||||
|
||||
var (
|
||||
srcIP net.IP
|
||||
srcIP netip.Addr
|
||||
srcIPOk bool
|
||||
srcIsIPv4 bool
|
||||
)
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = net.IP(buf[inp+76 : inp+80])
|
||||
srcIP, srcIPOk = netip.AddrFromSlice(buf[inp+76 : inp+80])
|
||||
srcIsIPv4 = true
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
srcIP = net.IP(buf[inp+64 : inp+80])
|
||||
srcIP, srcIPOk = netip.AddrFromSlice(buf[inp+64 : inp+80])
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if !srcIPOk {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.Equal(srcIP) {
|
||||
if from.Addr() == srcIP { // FIXME: add dstIP check
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
|
217
component/process/process_freebsd.go
Normal file
217
component/process/process_freebsd.go
Normal file
@ -0,0 +1,217 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Xinpgen12 [64]byte // size 64
|
||||
|
||||
type InEndpoints12 struct {
|
||||
FPort [2]byte
|
||||
LPort [2]byte
|
||||
FAddr [16]byte
|
||||
LAddr [16]byte
|
||||
ZoneID uint32
|
||||
} // size 40
|
||||
|
||||
type XTcpcb12 struct {
|
||||
Len uint32 // offset 0
|
||||
_ [20]byte // offset 4
|
||||
SocketAddr uint64 // offset 24
|
||||
_ [84]byte // offset 32
|
||||
Family uint32 // offset 116
|
||||
_ [140]byte // offset 120
|
||||
InEndpoints InEndpoints12 // offset 260
|
||||
_ [444]byte // offset 300
|
||||
} // size 744
|
||||
|
||||
type XInpcb12 struct {
|
||||
Len uint32 // offset 0
|
||||
_ [12]byte // offset 4
|
||||
SocketAddr uint64 // offset 16
|
||||
_ [84]byte // offset 24
|
||||
Family uint32 // offset 108
|
||||
_ [140]byte // offset 112
|
||||
InEndpoints InEndpoints12 // offset 252
|
||||
_ [108]byte // offset 292
|
||||
} // size 400
|
||||
|
||||
type XFile12 struct {
|
||||
Size uint64 // offset 0
|
||||
Pid uint32 // offset 8
|
||||
_ [44]byte // offset 12
|
||||
DataAddr uint64 // offset 56
|
||||
_ [64]byte // offset 64
|
||||
} // size 128
|
||||
|
||||
var majorVersion = func() int {
|
||||
releaseVersion, err := unix.Sysctl("kern.osrelease")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
majorVersionText, _, _ := strings.Cut(releaseVersion, ".")
|
||||
|
||||
majorVersion, err := strconv.Atoi(majorVersionText)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return majorVersion
|
||||
}()
|
||||
|
||||
func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
||||
switch majorVersion {
|
||||
case 12, 13:
|
||||
return findProcessPath12(network, from, to)
|
||||
}
|
||||
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
func findProcessPath12(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
||||
switch network {
|
||||
case TCP:
|
||||
data, err := unix.SysctlRaw("net.inet.tcp.pcblist")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(data) < int(unsafe.Sizeof(Xinpgen12{})) {
|
||||
return "", fmt.Errorf("invalid sysctl data len: %d", len(data))
|
||||
}
|
||||
|
||||
data = data[unsafe.Sizeof(Xinpgen12{}):]
|
||||
|
||||
for len(data) > int(unsafe.Sizeof(XTcpcb12{}.Len)) {
|
||||
tcb := (*XTcpcb12)(unsafe.Pointer(&data[0]))
|
||||
if tcb.Len < uint32(unsafe.Sizeof(XTcpcb12{})) || uint32(len(data)) < tcb.Len {
|
||||
break
|
||||
}
|
||||
|
||||
data = data[tcb.Len:]
|
||||
|
||||
var (
|
||||
connFromAddr netip.Addr
|
||||
connToAddr netip.Addr
|
||||
)
|
||||
if tcb.Family == unix.AF_INET {
|
||||
connFromAddr = netip.AddrFrom4([4]byte(tcb.InEndpoints.LAddr[12:16]))
|
||||
connToAddr = netip.AddrFrom4([4]byte(tcb.InEndpoints.FAddr[12:16]))
|
||||
} else if tcb.Family == unix.AF_INET6 {
|
||||
connFromAddr = netip.AddrFrom16(tcb.InEndpoints.LAddr)
|
||||
connToAddr = netip.AddrFrom16(tcb.InEndpoints.FAddr)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
connFrom := netip.AddrPortFrom(connFromAddr, binary.BigEndian.Uint16(tcb.InEndpoints.LPort[:]))
|
||||
connTo := netip.AddrPortFrom(connToAddr, binary.BigEndian.Uint16(tcb.InEndpoints.FPort[:]))
|
||||
|
||||
if connFrom == from && connTo == to {
|
||||
pid, err := findPidBySocketAddr12(tcb.SocketAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return findExecutableByPid(pid)
|
||||
}
|
||||
}
|
||||
case UDP:
|
||||
data, err := unix.SysctlRaw("net.inet.udp.pcblist")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(data) < int(unsafe.Sizeof(Xinpgen12{})) {
|
||||
return "", fmt.Errorf("invalid sysctl data len: %d", len(data))
|
||||
}
|
||||
|
||||
data = data[unsafe.Sizeof(Xinpgen12{}):]
|
||||
|
||||
for len(data) > int(unsafe.Sizeof(XInpcb12{}.Len)) {
|
||||
icb := (*XInpcb12)(unsafe.Pointer(&data[0]))
|
||||
if icb.Len < uint32(unsafe.Sizeof(XInpcb12{})) || uint32(len(data)) < icb.Len {
|
||||
break
|
||||
}
|
||||
data = data[icb.Len:]
|
||||
|
||||
var connFromAddr netip.Addr
|
||||
if icb.Family == unix.AF_INET {
|
||||
connFromAddr = netip.AddrFrom4([4]byte(icb.InEndpoints.LAddr[12:16]))
|
||||
} else if icb.Family == unix.AF_INET6 {
|
||||
connFromAddr = netip.AddrFrom16(icb.InEndpoints.LAddr)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
connFromPort := binary.BigEndian.Uint16(icb.InEndpoints.LPort[:])
|
||||
|
||||
if (connFromAddr == from.Addr() || connFromAddr.IsUnspecified()) && connFromPort == from.Port() {
|
||||
pid, err := findPidBySocketAddr12(icb.SocketAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return findExecutableByPid(pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
func findPidBySocketAddr12(socketAddr uint64) (uint32, error) {
|
||||
buf, err := unix.SysctlRaw("kern.file")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
filesLen := len(buf) / int(unsafe.Sizeof(XFile12{}))
|
||||
files := unsafe.Slice((*XFile12)(unsafe.Pointer(&buf[0])), filesLen)
|
||||
|
||||
for _, file := range files {
|
||||
if file.Size != uint64(unsafe.Sizeof(XFile12{})) {
|
||||
return 0, fmt.Errorf("invalid xfile size: %d", file.Size)
|
||||
}
|
||||
|
||||
if file.DataAddr == socketAddr {
|
||||
return file.Pid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func findExecutableByPid(pid uint32) (string, error) {
|
||||
buf := make([]byte, unix.PathMax)
|
||||
size := uint64(len(buf))
|
||||
mib := [4]uint32{
|
||||
unix.CTL_KERN,
|
||||
14, // KERN_PROC
|
||||
12, // KERN_PROC_PATHNAME
|
||||
pid,
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall6(
|
||||
unix.SYS___SYSCTL,
|
||||
uintptr(unsafe.Pointer(&mib[0])),
|
||||
uintptr(len(mib)),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if errno != 0 || size == 0 {
|
||||
return "", fmt.Errorf("sysctl: get proc name: %w", errno)
|
||||
}
|
||||
|
||||
return string(buf[:size-1]), nil
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
// store process name for when dealing with multiple PROCESS-NAME rules
|
||||
var (
|
||||
defaultSearcher *searcher
|
||||
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
once.Do(func() {
|
||||
if err := initSearcher(); err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAME rules will be skipped")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if defaultSearcher == nil {
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
||||
var spath string
|
||||
isTCP := network == TCP
|
||||
switch network {
|
||||
case TCP:
|
||||
spath = "net.inet.tcp.pcblist"
|
||||
case UDP:
|
||||
spath = "net.inet.udp.pcblist"
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
value, err := syscall.Sysctl(spath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
buf := make([]byte, 2048)
|
||||
size := uint64(len(buf))
|
||||
// CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid
|
||||
mib := [4]uint32{1, 14, 12, pid}
|
||||
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS___SYSCTL,
|
||||
uintptr(unsafe.Pointer(&mib[0])),
|
||||
uintptr(len(mib)),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
0,
|
||||
0)
|
||||
if errno != 0 || size == 0 {
|
||||
return "", errno
|
||||
}
|
||||
|
||||
return string(buf[:size-1]), nil
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
// sizeof(struct xinpgen)
|
||||
headSize int
|
||||
// sizeof(struct xtcpcb)
|
||||
tcpItemSize int
|
||||
// sizeof(struct xinpcb)
|
||||
udpItemSize int
|
||||
udpInpOffset int
|
||||
port int
|
||||
ip int
|
||||
vflag int
|
||||
socket int
|
||||
|
||||
// sizeof(struct xfile)
|
||||
fileItemSize int
|
||||
data int
|
||||
pid int
|
||||
}
|
||||
|
||||
func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) {
|
||||
var itemSize int
|
||||
var inpOffset int
|
||||
|
||||
if isTCP {
|
||||
// struct xtcpcb
|
||||
itemSize = s.tcpItemSize
|
||||
inpOffset = 8
|
||||
} else {
|
||||
// struct xinpcb
|
||||
itemSize = s.udpItemSize
|
||||
inpOffset = s.udpInpOffset
|
||||
}
|
||||
|
||||
isIPv4 := ip.To4() != nil
|
||||
// skip the first xinpgen block
|
||||
for i := s.headSize; i+itemSize <= len(buf); i += itemSize {
|
||||
inp := i + inpOffset
|
||||
|
||||
srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2])
|
||||
|
||||
if port != srcPort {
|
||||
continue
|
||||
}
|
||||
|
||||
// xinpcb.inp_vflag
|
||||
flag := buf[inp+s.vflag]
|
||||
|
||||
var srcIP net.IP
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4])
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4])
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if !ip.Equal(srcIP) {
|
||||
continue
|
||||
}
|
||||
|
||||
// xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison
|
||||
socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8])
|
||||
return s.searchSocketPid(socket)
|
||||
}
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func (s *searcher) searchSocketPid(socket uint64) (uint32, error) {
|
||||
value, err := syscall.Sysctl("kern.file")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
buf := []byte(value)
|
||||
|
||||
// struct xfile
|
||||
itemSize := s.fileItemSize
|
||||
for i := 0; i+itemSize <= len(buf); i += itemSize {
|
||||
// xfile.xf_data
|
||||
data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8])
|
||||
if data == socket {
|
||||
// xfile.xf_pid
|
||||
pid := readNativeUint32(buf[i+s.pid : i+s.pid+4])
|
||||
return pid, nil
|
||||
}
|
||||
}
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func newSearcher(major int) *searcher {
|
||||
var s *searcher
|
||||
switch major {
|
||||
case 11:
|
||||
s = &searcher{
|
||||
headSize: 32,
|
||||
tcpItemSize: 1304,
|
||||
udpItemSize: 632,
|
||||
port: 198,
|
||||
ip: 228,
|
||||
vflag: 116,
|
||||
socket: 88,
|
||||
fileItemSize: 80,
|
||||
data: 56,
|
||||
pid: 8,
|
||||
udpInpOffset: 8,
|
||||
}
|
||||
case 12:
|
||||
fallthrough
|
||||
case 13:
|
||||
s = &searcher{
|
||||
headSize: 64,
|
||||
tcpItemSize: 744,
|
||||
udpItemSize: 400,
|
||||
port: 254,
|
||||
ip: 284,
|
||||
vflag: 392,
|
||||
socket: 16,
|
||||
fileItemSize: 128,
|
||||
data: 56,
|
||||
pid: 8,
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func initSearcher() error {
|
||||
osRelease, err := syscall.Sysctl("kern.osrelease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dot := strings.Index(osRelease, ".")
|
||||
if dot != -1 {
|
||||
osRelease = osRelease[:dot]
|
||||
}
|
||||
major, err := strconv.Atoi(osRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defaultSearcher = newSearcher(major)
|
||||
if defaultSearcher == nil {
|
||||
return fmt.Errorf("unsupported freebsd version %d", major)
|
||||
}
|
||||
return nil
|
||||
}
|
35
component/process/process_freebsd_test.go
Normal file
35
component/process/process_freebsd_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
//go:build freebsd
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnforceStructValid12(t *testing.T) {
|
||||
if majorVersion != 12 && majorVersion != 13 {
|
||||
t.Skipf("Unsupported freebsd version: %d", majorVersion)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, int(unsafe.Offsetof(XTcpcb12{}.Len)))
|
||||
assert.Equal(t, 24, int(unsafe.Offsetof(XTcpcb12{}.SocketAddr)))
|
||||
assert.Equal(t, 116, int(unsafe.Offsetof(XTcpcb12{}.Family)))
|
||||
assert.Equal(t, 260, int(unsafe.Offsetof(XTcpcb12{}.InEndpoints)))
|
||||
assert.Equal(t, 0, int(unsafe.Offsetof(XInpcb12{}.Len)))
|
||||
assert.Equal(t, 16, int(unsafe.Offsetof(XInpcb12{}.SocketAddr)))
|
||||
assert.Equal(t, 108, int(unsafe.Offsetof(XInpcb12{}.Family)))
|
||||
assert.Equal(t, 252, int(unsafe.Offsetof(XInpcb12{}.InEndpoints)))
|
||||
assert.Equal(t, 0, int(unsafe.Offsetof(XFile12{}.Size)))
|
||||
assert.Equal(t, 8, int(unsafe.Offsetof(XFile12{}.Pid)))
|
||||
assert.Equal(t, 56, int(unsafe.Offsetof(XFile12{}.DataAddr)))
|
||||
assert.Equal(t, 64, int(unsafe.Sizeof(Xinpgen12{})))
|
||||
assert.Equal(t, 744, int(unsafe.Sizeof(XTcpcb12{})))
|
||||
assert.Equal(t, 400, int(unsafe.Sizeof(XInpcb12{})))
|
||||
assert.Equal(t, 40, int(unsafe.Sizeof(InEndpoints12{})))
|
||||
assert.Equal(t, 128, int(unsafe.Sizeof(XFile12{})))
|
||||
}
|
@ -5,23 +5,16 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
|
||||
"github.com/mdlayher/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
SOCK_DIAG_BY_FAMILY = 20
|
||||
inetDiagRequestSize = int(unsafe.Sizeof(inetDiagRequest{}))
|
||||
inetDiagResponseSize = int(unsafe.Sizeof(inetDiagResponse{}))
|
||||
)
|
||||
|
||||
type inetDiagRequest struct {
|
||||
Family byte
|
||||
Protocol byte
|
||||
@ -57,42 +50,74 @@ type inetDiagResponse struct {
|
||||
INode uint32
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
|
||||
func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
||||
inode, uid, err := resolveSocketByNetlink(network, from, to)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resolveProcessNameByProcSearch(inode, uid)
|
||||
return resolveProcessPathByProcSearch(inode, uid)
|
||||
}
|
||||
|
||||
func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (uint32, uint32, error) {
|
||||
func resolveSocketByNetlink(network string, from netip.AddrPort, to netip.AddrPort) (inode uint32, uid uint32, err error) {
|
||||
var families []byte
|
||||
if from.Addr().Unmap().Is4() {
|
||||
families = []byte{unix.AF_INET, unix.AF_INET6}
|
||||
} else {
|
||||
families = []byte{unix.AF_INET6, unix.AF_INET}
|
||||
}
|
||||
|
||||
var protocol byte
|
||||
switch network {
|
||||
case TCP:
|
||||
protocol = unix.IPPROTO_TCP
|
||||
case UDP:
|
||||
protocol = unix.IPPROTO_UDP
|
||||
default:
|
||||
return 0, 0, ErrInvalidNetwork
|
||||
}
|
||||
|
||||
if protocol == unix.IPPROTO_UDP {
|
||||
// Swap from & to for udp
|
||||
// See also https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
|
||||
from, to = to, from
|
||||
}
|
||||
|
||||
for _, family := range families {
|
||||
inode, uid, err = resolveSocketByNetlinkExact(family, protocol, from, to, netlink.Request)
|
||||
if err == nil {
|
||||
return inode, uid, err
|
||||
}
|
||||
}
|
||||
|
||||
return 0, 0, ErrNotFound
|
||||
}
|
||||
|
||||
func resolveSocketByNetlinkExact(family byte, protocol byte, from netip.AddrPort, to netip.AddrPort, flags netlink.HeaderFlags) (inode uint32, uid uint32, err error) {
|
||||
request := &inetDiagRequest{
|
||||
Family: family,
|
||||
Protocol: protocol,
|
||||
States: 0xffffffff,
|
||||
Cookie: [2]uint32{0xffffffff, 0xffffffff},
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
request.Family = unix.AF_INET
|
||||
var (
|
||||
fromAddr []byte
|
||||
toAddr []byte
|
||||
)
|
||||
if family == unix.AF_INET {
|
||||
fromAddr = net.IP(from.Addr().AsSlice()).To4()
|
||||
toAddr = net.IP(to.Addr().AsSlice()).To4()
|
||||
} else {
|
||||
request.Family = unix.AF_INET6
|
||||
fromAddr = net.IP(from.Addr().AsSlice()).To16()
|
||||
toAddr = net.IP(to.Addr().AsSlice()).To16()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
request.Protocol = unix.IPPROTO_TCP
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
request.Protocol = unix.IPPROTO_UDP
|
||||
} else {
|
||||
return 0, 0, ErrInvalidNetwork
|
||||
}
|
||||
copy(request.Src[:], fromAddr)
|
||||
copy(request.Dst[:], toAddr)
|
||||
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
copy(request.Src[:], v4)
|
||||
} else {
|
||||
copy(request.Src[:], ip)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(request.SrcPort[:], uint16(srcPort))
|
||||
binary.BigEndian.PutUint16(request.SrcPort[:], from.Port())
|
||||
binary.BigEndian.PutUint16(request.DstPort[:], to.Port())
|
||||
|
||||
conn, err := netlink.Dial(unix.NETLINK_INET_DIAG, nil)
|
||||
if err != nil {
|
||||
@ -102,10 +127,10 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (uint32, uin
|
||||
|
||||
message := netlink.Message{
|
||||
Header: netlink.Header{
|
||||
Type: SOCK_DIAG_BY_FAMILY,
|
||||
Flags: netlink.Request | netlink.Dump,
|
||||
Type: 20, // SOCK_DIAG_BY_FAMILY
|
||||
Flags: flags,
|
||||
},
|
||||
Data: (*(*[inetDiagRequestSize]byte)(unsafe.Pointer(request)))[:],
|
||||
Data: (*(*[unsafe.Sizeof(*request)]byte)(unsafe.Pointer(request)))[:],
|
||||
}
|
||||
|
||||
messages, err := conn.Execute(message)
|
||||
@ -114,7 +139,7 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (uint32, uin
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
if len(msg.Data) < inetDiagResponseSize {
|
||||
if len(msg.Data) < int(unsafe.Sizeof(inetDiagResponse{})) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -126,53 +151,82 @@ func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (uint32, uin
|
||||
return 0, 0, ErrNotFound
|
||||
}
|
||||
|
||||
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
|
||||
files, err := os.ReadDir("/proc")
|
||||
func resolveProcessPathByProcSearch(inode, uid uint32) (string, error) {
|
||||
procDir, err := os.Open("/proc")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer procDir.Close()
|
||||
|
||||
pids, err := procDir.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer := make([]byte, unix.PathMax)
|
||||
socket := fmt.Appendf(nil, "socket:[%d]", inode)
|
||||
expectedSocketName := fmt.Appendf(nil, "socket:[%d]", inode)
|
||||
|
||||
for _, f := range files {
|
||||
if !f.IsDir() || !isPid(f.Name()) {
|
||||
pathBuffer := pool.Get(64)
|
||||
defer pool.Put(pathBuffer)
|
||||
|
||||
readlinkBuffer := pool.Get(32)
|
||||
defer pool.Put(readlinkBuffer)
|
||||
|
||||
copy(pathBuffer, "/proc/")
|
||||
|
||||
for _, pid := range pids {
|
||||
if !isPid(pid) {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := f.Info()
|
||||
pathBuffer = append(pathBuffer[:len("/proc/")], pid...)
|
||||
|
||||
stat := &unix.Stat_t{}
|
||||
err = unix.Stat(string(pathBuffer), stat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Sys().(*syscall.Stat_t).Uid != uid {
|
||||
continue
|
||||
} else if stat.Uid != uid {
|
||||
continue
|
||||
}
|
||||
|
||||
processPath := filepath.Join("/proc", f.Name())
|
||||
fdPath := filepath.Join(processPath, "fd")
|
||||
pathBuffer = append(pathBuffer, "/fd/"...)
|
||||
fdsPrefixLength := len(pathBuffer)
|
||||
|
||||
fds, err := os.ReadDir(fdPath)
|
||||
fdDir, err := os.Open(string(pathBuffer))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fds, err := fdDir.Readdirnames(-1)
|
||||
fdDir.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, fd := range fds {
|
||||
n, err := unix.Readlink(filepath.Join(fdPath, fd.Name()), buffer)
|
||||
pathBuffer = pathBuffer[:fdsPrefixLength]
|
||||
|
||||
pathBuffer = append(pathBuffer, fd...)
|
||||
|
||||
n, err := unix.Readlink(string(pathBuffer), readlinkBuffer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(buffer[:n], socket) {
|
||||
return os.Readlink(filepath.Join(processPath, "exe"))
|
||||
if bytes.Equal(readlinkBuffer[:n], expectedSocketName) {
|
||||
return os.Readlink("/proc/" + pid + "/exe")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
|
||||
return "", fmt.Errorf("inode %d of uid %d not found", inode, uid)
|
||||
}
|
||||
|
||||
func isPid(s string) bool {
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !unicode.IsDigit(r)
|
||||
}) == -1
|
||||
func isPid(name string) bool {
|
||||
for _, c := range name {
|
||||
if c < '0' || c > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
//go:build !darwin && !linux && !windows && (!freebsd || !amd64)
|
||||
//go:build !darwin && !linux && !windows && !freebsd
|
||||
|
||||
package process
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
func findProcessPath(_ string, _, _ netip.AddrPort) (string, error) {
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
||||
|
112
component/process/process_test.go
Normal file
112
component/process/process_test.go
Normal file
@ -0,0 +1,112 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testConn(t *testing.T, network, address string) {
|
||||
l, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Listen failed", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
conn, err := net.Dial("tcp", l.Addr().String())
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Dial failed", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
rConn, err := l.Accept()
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Accept conn failed", err)
|
||||
}
|
||||
defer rConn.Close()
|
||||
|
||||
path, err := FindProcessPath(TCP, conn.LocalAddr().(*net.TCPAddr).AddrPort(), conn.RemoteAddr().(*net.TCPAddr).AddrPort())
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Find process path failed", err)
|
||||
}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Get executable failed", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, exePath, path)
|
||||
}
|
||||
|
||||
func TestFindProcessPathTCP(t *testing.T) {
|
||||
t.Run("v4", func(t *testing.T) {
|
||||
testConn(t, "tcp4", "127.0.0.1:0")
|
||||
})
|
||||
t.Run("v6", func(t *testing.T) {
|
||||
testConn(t, "tcp6", "[::1]:0")
|
||||
})
|
||||
}
|
||||
|
||||
func testPacketConn(t *testing.T, network, lAddress, rAddress string) {
|
||||
lConn, err := net.ListenPacket(network, lAddress)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "ListenPacket failed", err)
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
rConn, err := net.ListenPacket(network, rAddress)
|
||||
if err != nil {
|
||||
assert.FailNow(t, "ListenPacket failed", err)
|
||||
}
|
||||
defer rConn.Close()
|
||||
|
||||
_, err = lConn.WriteTo([]byte{0}, rConn.LocalAddr())
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Send message failed", err)
|
||||
}
|
||||
|
||||
_, lAddr, err := rConn.ReadFrom([]byte{0})
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Receive message failed", err)
|
||||
}
|
||||
|
||||
path, err := FindProcessPath(UDP, lAddr.(*net.UDPAddr).AddrPort(), rConn.LocalAddr().(*net.UDPAddr).AddrPort())
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Find process path", err)
|
||||
}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
assert.FailNow(t, "Find executable", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, exePath, path)
|
||||
}
|
||||
|
||||
func TestFindProcessPathUDP(t *testing.T) {
|
||||
t.Run("v4", func(t *testing.T) {
|
||||
testPacketConn(t, "udp4", "127.0.0.1:0", "127.0.0.1:0")
|
||||
})
|
||||
t.Run("v6", func(t *testing.T) {
|
||||
testPacketConn(t, "udp6", "[::1]:0", "[::1]:0")
|
||||
})
|
||||
t.Run("v4AnyLocal", func(t *testing.T) {
|
||||
testPacketConn(t, "udp4", "0.0.0.0:0", "127.0.0.1:0")
|
||||
})
|
||||
t.Run("v6AnyLocal", func(t *testing.T) {
|
||||
testPacketConn(t, "udp6", "[::]:0", "[::1]:0")
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkFindProcessName(b *testing.B) {
|
||||
from := netip.MustParseAddrPort("127.0.0.1:11447")
|
||||
to := netip.MustParseAddrPort("127.0.0.1:33669")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
FindProcessPath(TCP, from, to)
|
||||
}
|
||||
}
|
@ -1,194 +1,204 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
"net/netip"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
tcpTableFunc = "GetExtendedTcpTable"
|
||||
tcpTablePidConn = 4
|
||||
udpTableFunc = "GetExtendedUdpTable"
|
||||
udpTablePid = 1
|
||||
queryProcNameFunc = "QueryFullProcessImageNameW"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
getExTCPTable uintptr
|
||||
getExUDPTable uintptr
|
||||
queryProcName uintptr
|
||||
modIphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
|
||||
once sync.Once
|
||||
procGetExtendedTcpTable = modIphlpapi.NewProc("GetExtendedTcpTable")
|
||||
procGetExtendedUdpTable = modIphlpapi.NewProc("GetExtendedUdpTable")
|
||||
)
|
||||
|
||||
func initWin32API() error {
|
||||
h, err := windows.LoadLibrary("iphlpapi.dll")
|
||||
if err != nil {
|
||||
return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error())
|
||||
}
|
||||
|
||||
getExTCPTable, err = windows.GetProcAddress(h, tcpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error())
|
||||
}
|
||||
|
||||
getExUDPTable, err = windows.GetProcAddress(h, udpTableFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error())
|
||||
}
|
||||
|
||||
h, err = windows.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return fmt.Errorf("LoadLibrary kernel32.dll failed: %s", err.Error())
|
||||
}
|
||||
|
||||
queryProcName, err = windows.GetProcAddress(h, queryProcNameFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetProcAddress of %s failed: %s", queryProcNameFunc, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip net.IP, srcPort int) (string, error) {
|
||||
once.Do(func() {
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
||||
return
|
||||
}
|
||||
})
|
||||
family := windows.AF_INET
|
||||
if ip.To4() == nil {
|
||||
func findProcessPath(network string, from netip.AddrPort, to netip.AddrPort) (string, error) {
|
||||
family := uint32(windows.AF_INET)
|
||||
if from.Addr().Is6() {
|
||||
family = windows.AF_INET6
|
||||
}
|
||||
|
||||
var class int
|
||||
var fn uintptr
|
||||
var protocol uint32
|
||||
switch network {
|
||||
case TCP:
|
||||
fn = getExTCPTable
|
||||
class = tcpTablePidConn
|
||||
protocol = windows.IPPROTO_TCP
|
||||
case UDP:
|
||||
fn = getExUDPTable
|
||||
class = udpTablePid
|
||||
protocol = windows.IPPROTO_UDP
|
||||
default:
|
||||
return "", ErrInvalidNetwork
|
||||
}
|
||||
|
||||
buf, err := getTransportTable(fn, family, class)
|
||||
pid, err := findPidByConnectionEndpoint(family, protocol, from, to)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s := newSearcher(family == windows.AF_INET, network == TCP)
|
||||
|
||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
itemSize int
|
||||
port int
|
||||
ip int
|
||||
ipSize int
|
||||
pid int
|
||||
tcpState int
|
||||
}
|
||||
func findPidByConnectionEndpoint(family uint32, protocol uint32, from netip.AddrPort, to netip.AddrPort) (uint32, error) {
|
||||
buf := pool.Get(0)
|
||||
defer pool.Put(buf)
|
||||
|
||||
func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) {
|
||||
n := int(readNativeUint32(b[:4]))
|
||||
itemSize := s.itemSize
|
||||
for i := 0; i < n; i++ {
|
||||
row := b[4+itemSize*i : 4+itemSize*(i+1)]
|
||||
bufSize := uint32(len(buf))
|
||||
|
||||
if s.tcpState >= 0 {
|
||||
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
|
||||
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
|
||||
if tcpState != 5 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
loop:
|
||||
for {
|
||||
var ret uintptr
|
||||
|
||||
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
|
||||
// this field can be illustrated as follows depends on different machine endianess:
|
||||
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
|
||||
// big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
|
||||
// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
|
||||
srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
|
||||
if srcPort != port {
|
||||
continue
|
||||
}
|
||||
|
||||
srcIP := net.IP(row[s.ip : s.ip+s.ipSize])
|
||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||
if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
pid := readNativeUint32(row[s.pid : s.pid+4])
|
||||
return pid, nil
|
||||
}
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func newSearcher(isV4, isTCP bool) *searcher {
|
||||
var itemSize, port, ip, ipSize, pid int
|
||||
tcpState := -1
|
||||
switch {
|
||||
case isV4 && isTCP:
|
||||
// struct MIB_TCPROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
|
||||
case isV4 && !isTCP:
|
||||
// struct MIB_UDPROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
|
||||
case !isV4 && isTCP:
|
||||
// struct MIB_TCP6ROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
|
||||
case !isV4 && !isTCP:
|
||||
// struct MIB_UDP6ROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
|
||||
}
|
||||
|
||||
return &searcher{
|
||||
itemSize: itemSize,
|
||||
port: port,
|
||||
ip: ip,
|
||||
ipSize: ipSize,
|
||||
pid: pid,
|
||||
tcpState: tcpState,
|
||||
}
|
||||
}
|
||||
|
||||
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
||||
for size, buf := uint32(8), make([]byte, 8); ; {
|
||||
ptr := unsafe.Pointer(&buf[0])
|
||||
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
||||
|
||||
switch err {
|
||||
case 0:
|
||||
return buf, nil
|
||||
case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
|
||||
buf = make([]byte, size)
|
||||
switch protocol {
|
||||
case windows.IPPROTO_TCP:
|
||||
ret, _, _ = procGetExtendedTcpTable.Call(
|
||||
uintptr(unsafe.Pointer(unsafe.SliceData(buf))),
|
||||
uintptr(unsafe.Pointer(&bufSize)),
|
||||
0,
|
||||
uintptr(family),
|
||||
4, // TCP_TABLE_OWNER_PID_CONNECTIONS
|
||||
0,
|
||||
)
|
||||
case windows.IPPROTO_UDP:
|
||||
ret, _, _ = procGetExtendedUdpTable.Call(
|
||||
uintptr(unsafe.Pointer(unsafe.SliceData(buf))),
|
||||
uintptr(unsafe.Pointer(&bufSize)),
|
||||
0,
|
||||
uintptr(family),
|
||||
1, // UDP_TABLE_OWNER_PID
|
||||
0,
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("syscall error: %d", err)
|
||||
return 0, errors.New("unsupported network")
|
||||
}
|
||||
|
||||
switch ret {
|
||||
case 0:
|
||||
buf = buf[:bufSize]
|
||||
|
||||
break loop
|
||||
case uintptr(windows.ERROR_INSUFFICIENT_BUFFER):
|
||||
pool.Put(buf)
|
||||
buf = pool.Get(int(bufSize))
|
||||
|
||||
continue loop
|
||||
default:
|
||||
return 0, fmt.Errorf("syscall error: %d", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
if len(buf) < int(unsafe.Sizeof(uint32(0))) {
|
||||
return 0, fmt.Errorf("invalid table size: %d", len(buf))
|
||||
}
|
||||
|
||||
entriesSize := *(*uint32)(unsafe.Pointer(&buf[0]))
|
||||
|
||||
switch protocol {
|
||||
case windows.IPPROTO_TCP:
|
||||
if family == windows.AF_INET {
|
||||
type MibTcpRowOwnerPid struct {
|
||||
State uint32
|
||||
LocalAddr [4]byte
|
||||
LocalPort uint32
|
||||
RemoteAddr [4]byte
|
||||
RemotePort uint32
|
||||
OwningPid uint32
|
||||
}
|
||||
|
||||
if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibTcpRowOwnerPid{})) {
|
||||
return 0, fmt.Errorf("invalid tables size: %d", len(buf))
|
||||
}
|
||||
|
||||
entries := unsafe.Slice((*MibTcpRowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
|
||||
for _, entry := range entries {
|
||||
localAddr := netip.AddrFrom4(entry.LocalAddr)
|
||||
localPort := windows.Ntohs(uint16(entry.LocalPort))
|
||||
remoteAddr := netip.AddrFrom4(entry.RemoteAddr)
|
||||
remotePort := windows.Ntohs(uint16(entry.RemotePort))
|
||||
|
||||
if localAddr == from.Addr() && remoteAddr == to.Addr() && localPort == from.Port() && remotePort == to.Port() {
|
||||
return entry.OwningPid, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type MibTcp6RowOwnerPid struct {
|
||||
LocalAddr [16]byte
|
||||
LocalScopeID uint32
|
||||
LocalPort uint32
|
||||
RemoteAddr [16]byte
|
||||
RemoteScopeID uint32
|
||||
RemotePort uint32
|
||||
State uint32
|
||||
OwningPid uint32
|
||||
}
|
||||
|
||||
if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibTcp6RowOwnerPid{})) {
|
||||
return 0, fmt.Errorf("invalid tables size: %d", len(buf))
|
||||
}
|
||||
|
||||
entries := unsafe.Slice((*MibTcp6RowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
|
||||
for _, entry := range entries {
|
||||
localAddr := netip.AddrFrom16(entry.LocalAddr)
|
||||
localPort := windows.Ntohs(uint16(entry.LocalPort))
|
||||
remoteAddr := netip.AddrFrom16(entry.RemoteAddr)
|
||||
remotePort := windows.Ntohs(uint16(entry.RemotePort))
|
||||
|
||||
if localAddr == from.Addr() && remoteAddr == to.Addr() && localPort == from.Port() && remotePort == to.Port() {
|
||||
return entry.OwningPid, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case windows.IPPROTO_UDP:
|
||||
if family == windows.AF_INET {
|
||||
type MibUdpRowOwnerPid struct {
|
||||
LocalAddr [4]byte
|
||||
LocalPort uint32
|
||||
OwningPid uint32
|
||||
}
|
||||
|
||||
if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibUdpRowOwnerPid{})) {
|
||||
return 0, fmt.Errorf("invalid tables size: %d", len(buf))
|
||||
}
|
||||
|
||||
entries := unsafe.Slice((*MibUdpRowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
|
||||
for _, entry := range entries {
|
||||
localAddr := netip.AddrFrom4(entry.LocalAddr)
|
||||
localPort := windows.Ntohs(uint16(entry.LocalPort))
|
||||
|
||||
if (localAddr == from.Addr() || localAddr.IsUnspecified()) && localPort == from.Port() {
|
||||
return entry.OwningPid, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type MibUdp6RowOwnerPid struct {
|
||||
LocalAddr [16]byte
|
||||
LocalScopeId uint32
|
||||
LocalPort uint32
|
||||
OwningPid uint32
|
||||
}
|
||||
|
||||
if uint32(len(buf))-4 < entriesSize*uint32(unsafe.Sizeof(MibUdp6RowOwnerPid{})) {
|
||||
return 0, fmt.Errorf("invalid tables size: %d", len(buf))
|
||||
}
|
||||
|
||||
entries := unsafe.Slice((*MibUdp6RowOwnerPid)(unsafe.Pointer(&buf[4])), entriesSize)
|
||||
for _, entry := range entries {
|
||||
localAddr := netip.AddrFrom16(entry.LocalAddr)
|
||||
localPort := windows.Ntohs(uint16(entry.LocalPort))
|
||||
|
||||
if (localAddr == from.Addr() || localAddr.IsUnspecified()) && localPort == from.Port() {
|
||||
return entry.OwningPid, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return 0, ErrInvalidNetwork
|
||||
}
|
||||
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
@ -207,17 +217,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
}
|
||||
defer windows.CloseHandle(h)
|
||||
|
||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||
buf := make([]uint16, windows.MAX_LONG_PATH)
|
||||
size := uint32(len(buf))
|
||||
r1, _, err := syscall.SyscallN(
|
||||
queryProcName,
|
||||
uintptr(h),
|
||||
uintptr(1),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
if r1 == 0 {
|
||||
|
||||
err = windows.QueryFullProcessImageName(h, 0, &buf[0], &size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return syscall.UTF16ToString(buf[:size]), nil
|
||||
|
||||
return windows.UTF16ToString(buf[:size]), nil
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ type DNS struct {
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
NameServerPolicy map[string]dns.NameServer
|
||||
SearchDomains []string
|
||||
}
|
||||
|
||||
// FallbackFilter config
|
||||
@ -117,6 +118,7 @@ type RawDNS struct {
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||
NameServerPolicy map[string]string `yaml:"nameserver-policy"`
|
||||
SearchDomains []string `yaml:"search-domains"`
|
||||
}
|
||||
|
||||
type RawFallbackFilter struct {
|
||||
@ -564,7 +566,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
dnsNetType = "tcp-tls" // DNS over TLS
|
||||
case "https":
|
||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
|
||||
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path, User: u.User}
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
case "dhcp":
|
||||
@ -702,6 +704,18 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
|
||||
dnsCfg.Hosts = hosts
|
||||
}
|
||||
|
||||
if len(cfg.SearchDomains) != 0 {
|
||||
for _, domain := range cfg.SearchDomains {
|
||||
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
|
||||
return nil, errors.New("search domains should not start or end with '.'")
|
||||
}
|
||||
if strings.Contains(domain, ":") {
|
||||
return nil, errors.New("search domains are for ipv4 only and should not contain ports")
|
||||
}
|
||||
}
|
||||
dnsCfg.SearchDomains = cfg.SearchDomains
|
||||
}
|
||||
|
||||
return dnsCfg, nil
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,7 @@ type ProxyAdapter interface {
|
||||
type DelayHistory struct {
|
||||
Time time.Time `json:"time"`
|
||||
Delay uint16 `json:"delay"`
|
||||
MeanDelay uint16 `json:"meanDelay"`
|
||||
}
|
||||
|
||||
type Proxy interface {
|
||||
@ -110,7 +111,7 @@ type Proxy interface {
|
||||
Alive() bool
|
||||
DelayHistory() []DelayHistory
|
||||
LastDelay() uint16
|
||||
URLTest(ctx context.Context, url string) (uint16, error)
|
||||
URLTest(ctx context.Context, url string) (uint16, uint16, error)
|
||||
|
||||
// Deprecated: use DialContext instead.
|
||||
Dial(metadata *Metadata) (Conn, error)
|
||||
|
@ -3,7 +3,7 @@ package constant
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type PlainContext interface {
|
||||
|
@ -3,13 +3,13 @@ package constant
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DNSModeMapping is a mapping for EnhancedMode enum
|
||||
var DNSModeMapping = map[string]DNSMode{
|
||||
DNSNormal.String(): DNSNormal,
|
||||
DNSFakeIP.String(): DNSFakeIP,
|
||||
DNSMapping.String(): DNSMapping,
|
||||
}
|
||||
|
||||
const (
|
||||
@ -28,7 +28,7 @@ func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
}
|
||||
mode, exist := DNSModeMapping[tp]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
return fmt.Errorf("invalid mode: %s", tp)
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
|
@ -3,6 +3,7 @@ package constant
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
@ -72,6 +73,8 @@ type Metadata struct {
|
||||
DNSMode DNSMode `json:"dnsMode"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
SpecialProxy string `json:"specialProxy"`
|
||||
|
||||
OriginDst netip.AddrPort `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const Name = "clash"
|
||||
@ -51,6 +52,18 @@ func (p *path) Resolve(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// IsSubPath return true if path is a subpath of homedir
|
||||
func (p *path) IsSubPath(path string) bool {
|
||||
homedir := p.HomeDir()
|
||||
path = p.Resolve(path)
|
||||
rel, err := filepath.Rel(homedir, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.Contains(rel, "..")
|
||||
}
|
||||
|
||||
func (p *path) MMDB() string {
|
||||
return P.Join(p.homeDir, "Country.mmdb")
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ const (
|
||||
DstPort
|
||||
Process
|
||||
ProcessPath
|
||||
IPSet
|
||||
MATCH
|
||||
)
|
||||
|
||||
@ -39,6 +40,8 @@ func (rt RuleType) String() string {
|
||||
return "Process"
|
||||
case ProcessPath:
|
||||
return "ProcessPath"
|
||||
case IPSet:
|
||||
return "IPSet"
|
||||
case MATCH:
|
||||
return "Match"
|
||||
default:
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type ConnContext struct {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type PacketConnContext struct {
|
||||
|
@ -87,9 +87,15 @@ func withMapping(mapping *cache.LruCache) middleware {
|
||||
case *D.A:
|
||||
ip = a.A
|
||||
ttl = a.Hdr.Ttl
|
||||
if !ip.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
case *D.AAAA:
|
||||
ip = a.AAAA
|
||||
ttl = a.Hdr.Ttl
|
||||
if !ip.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -181,9 +187,6 @@ func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
||||
|
||||
if mapper.mode == C.DNSFakeIP {
|
||||
middlewares = append(middlewares, withFakeIP(mapper.fakePool))
|
||||
}
|
||||
|
||||
if mapper.mode != C.DNSNormal {
|
||||
middlewares = append(middlewares, withMapping(mapper.mapping))
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ type Resolver struct {
|
||||
group singleflight.Group
|
||||
lruCache *cache.LruCache
|
||||
policy *trie.DomainTrie
|
||||
searchDomains []string
|
||||
}
|
||||
|
||||
// LookupIP request with TypeA and TypeAAAA, priority return TypeA
|
||||
@ -140,9 +141,14 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
|
||||
msg = cache.(*D.Msg).Copy()
|
||||
if expireTime.Before(now) {
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
go r.exchangeWithoutCache(ctx, m)
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||
r.exchangeWithoutCache(ctx, m)
|
||||
cancel()
|
||||
}()
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
// updating TTL by subtracting common delta time from each DNS record
|
||||
updateMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -161,7 +167,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
msg := result.(*D.Msg)
|
||||
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
putMsgToCache(r.lruCache, q.String(), q, msg)
|
||||
}()
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
@ -285,17 +291,34 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) ([
|
||||
query := &D.Msg{}
|
||||
query.SetQuestion(D.Fqdn(host), dnsType)
|
||||
|
||||
msg, err := r.Exchange(query)
|
||||
msg, err := r.ExchangeContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ips := msgToIP(msg)
|
||||
if len(ips) == 0 {
|
||||
if len(ips) != 0 {
|
||||
return ips, nil
|
||||
} else if len(r.searchDomains) == 0 {
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
// query provided search domains serially
|
||||
for _, domain := range r.searchDomains {
|
||||
q := &D.Msg{}
|
||||
q.SetQuestion(D.Fqdn(fmt.Sprintf("%s.%s", host, domain)), dnsType)
|
||||
msg, err := r.ExchangeContext(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := msgToIP(msg)
|
||||
if len(ips) != 0 {
|
||||
return ips, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
func (r *Resolver) msgToDomain(msg *D.Msg) string {
|
||||
if len(msg.Question) > 0 {
|
||||
@ -336,6 +359,7 @@ type Config struct {
|
||||
Pool *fakeip.Pool
|
||||
Hosts *trie.DomainTrie
|
||||
Policy map[string]NameServer
|
||||
SearchDomains []string
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
@ -349,6 +373,7 @@ func NewResolver(config Config) *Resolver {
|
||||
main: transform(config.Main, defaultResolver),
|
||||
lruCache: cache.New(cache.WithSize(4096), cache.WithStale(true)),
|
||||
hosts: config.Hosts,
|
||||
searchDomains: config.SearchDomains,
|
||||
}
|
||||
|
||||
if len(config.Fallback) != 0 {
|
||||
|
33
dns/util.go
33
dns/util.go
@ -14,11 +14,28 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
||||
func minimalTTL(records []D.RR) uint32 {
|
||||
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return r1.Header().Ttl < r2.Header().Ttl
|
||||
}).Header().Ttl
|
||||
}
|
||||
|
||||
func updateTTL(records []D.RR, ttl uint32) {
|
||||
if len(records) == 0 {
|
||||
return
|
||||
}
|
||||
delta := minimalTTL(records) - ttl
|
||||
for i := range records {
|
||||
records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache, key string, q D.Question, msg *D.Msg) {
|
||||
// skip dns cache for acme challenge
|
||||
if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_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
|
||||
}
|
||||
@ -26,11 +43,11 @@ func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) {
|
||||
var ttl uint32
|
||||
switch {
|
||||
case len(msg.Answer) != 0:
|
||||
ttl = msg.Answer[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Answer)
|
||||
case len(msg.Ns) != 0:
|
||||
ttl = msg.Ns[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Ns)
|
||||
case len(msg.Extra) != 0:
|
||||
ttl = msg.Extra[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Extra)
|
||||
default:
|
||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||
return
|
||||
@ -53,6 +70,12 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
updateTTL(msg.Answer, ttl)
|
||||
updateTTL(msg.Ns, ttl)
|
||||
updateTTL(msg.Extra, ttl)
|
||||
}
|
||||
|
||||
func isIPRequest(q D.Question) bool {
|
||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
|
||||
}
|
||||
|
47
docs/.vitepress/config.ts
Normal file
47
docs/.vitepress/config.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import locales from './locales'
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: 'Clash',
|
||||
|
||||
base: '/clash/',
|
||||
|
||||
head: [
|
||||
[
|
||||
'link',
|
||||
{ rel: 'icon', type: "image/x-icon", href: '/clash/logo.png' }
|
||||
],
|
||||
],
|
||||
|
||||
locales: locales.locales,
|
||||
|
||||
lastUpdated: true,
|
||||
|
||||
themeConfig: {
|
||||
search: {
|
||||
provider: 'local',
|
||||
options: {
|
||||
locales: {
|
||||
zh_CN: {
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: '搜索文档',
|
||||
buttonAriaLabel: '搜索文档'
|
||||
},
|
||||
modal: {
|
||||
noResultsText: '无法找到相关结果',
|
||||
resetButtonTitle: '清除查询条件',
|
||||
footer: {
|
||||
selectText: '选择',
|
||||
navigateText: '切换'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
60
docs/.vitepress/locales/en_US.ts
Normal file
60
docs/.vitepress/locales/en_US.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { createRequire } from 'module'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { generateSidebarChapter } from './side_bar.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const chapters = generateSidebarChapter('en_US', new Map([
|
||||
['introduction', 'Introduction'],
|
||||
['configuration', 'Configuration'],
|
||||
['premium', 'Premium'],
|
||||
['runtime', 'Runtime'],
|
||||
['advanced-usages', 'Advanced Usages'],
|
||||
]))
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'en-US',
|
||||
|
||||
description: 'A rule-based tunnel in Go.',
|
||||
|
||||
themeConfig: {
|
||||
nav: nav(),
|
||||
|
||||
logo: '/logo.png',
|
||||
|
||||
lastUpdatedText: 'Last updated at',
|
||||
|
||||
sidebar: chapters,
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/Dreamacro/clash' },
|
||||
],
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path',
|
||||
text: 'Edit this page on GitHub'
|
||||
},
|
||||
|
||||
outline: {
|
||||
level: 'deep',
|
||||
label: 'On this page',
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
function nav() {
|
||||
return [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Configuration', link: '/configuration/configuration-reference' },
|
||||
{
|
||||
text: 'Download',
|
||||
items: [
|
||||
{ text: 'Open-source Edition', link: 'https://github.com/Dreamacro/clash/releases/' },
|
||||
{ text: 'Premium Edition', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
20
docs/.vitepress/locales/index.ts
Normal file
20
docs/.vitepress/locales/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import en_US from './en_US'
|
||||
import zh_CN from './zh_CN'
|
||||
|
||||
export default defineConfig({
|
||||
locales: {
|
||||
root: {
|
||||
label: 'English',
|
||||
lang: en_US.lang,
|
||||
themeConfig: en_US.themeConfig,
|
||||
description: en_US.description
|
||||
},
|
||||
zh_CN: {
|
||||
label: '简体中文',
|
||||
lang: zh_CN.lang,
|
||||
themeConfig: zh_CN.themeConfig,
|
||||
description: zh_CN.description
|
||||
}
|
||||
}
|
||||
})
|
78
docs/.vitepress/locales/side_bar.ts
Normal file
78
docs/.vitepress/locales/side_bar.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import directoryTree from 'directory-tree'
|
||||
import fs from 'fs'
|
||||
import metadataParser from 'markdown-yaml-metadata-parser'
|
||||
|
||||
function getMetadataFromDoc(path: string): { sidebarTitle?: string, sidebarOrder?: number } {
|
||||
const fileContents = fs.readFileSync(path, 'utf8')
|
||||
|
||||
return metadataParser(fileContents).metadata
|
||||
}
|
||||
|
||||
export function generateSidebarChapter(locale:string, chapterDirName: Map<string,string>): any[] {
|
||||
if (chapterDirName.size < 1) {
|
||||
console.error(chapterDirName)
|
||||
throw new Error(`Could not genereate sidebar: chapterDirName is empty`)
|
||||
}
|
||||
|
||||
var chapterPath = ''
|
||||
var sidebar: any[] = []
|
||||
|
||||
for (const chapterDirKey of chapterDirName.keys()) {
|
||||
if (locale !== 'en_US') {
|
||||
chapterPath = `./${locale}/${chapterDirKey}`
|
||||
} else {
|
||||
chapterPath = `./${chapterDirKey}`
|
||||
}
|
||||
|
||||
const tree = directoryTree(chapterPath)
|
||||
|
||||
if (!tree || !tree.children) {
|
||||
console.error(tree)
|
||||
throw new Error(`Could not genereate sidebar: invalid chapter at ${chapterPath}`)
|
||||
}
|
||||
|
||||
let items: { sidebarOrder: number, text: string, link: string }[] = []
|
||||
|
||||
// Look into files in the chapter
|
||||
for (const doc of tree.children) {
|
||||
// make sure it's a .md file
|
||||
if (doc.children || !doc.name.endsWith('.md'))
|
||||
continue
|
||||
|
||||
const { sidebarOrder, sidebarTitle } = getMetadataFromDoc(doc.path)
|
||||
|
||||
if (!sidebarOrder)
|
||||
throw new Error('Cannot find sidebarOrder in doc metadata: ' + doc.path)
|
||||
|
||||
if (!sidebarTitle)
|
||||
throw new Error('Cannot find sidebarTitle in doc metadata: ' + doc.path)
|
||||
|
||||
if (chapterDirKey === 'introduction' && doc.name === '_dummy-index.md') {
|
||||
// Override index page link
|
||||
items.push({
|
||||
sidebarOrder,
|
||||
text: sidebarTitle,
|
||||
link: '/' + (locale === 'en_US' ? '' : locale + '/')
|
||||
})
|
||||
} else {
|
||||
items.push({
|
||||
sidebarOrder,
|
||||
text: sidebarTitle,
|
||||
link: "/" + doc.path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
items = items.sort((a, b) => a.sidebarOrder - b.sidebarOrder)
|
||||
|
||||
// remove dash and capitalize first character of each word as chapter title
|
||||
const text = chapterDirName.get(chapterDirKey) || chapterDirKey.split('-').join(' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||
sidebar.push({
|
||||
text,
|
||||
collapsed: false,
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
return sidebar
|
||||
}
|
60
docs/.vitepress/locales/zh_CN.ts
Normal file
60
docs/.vitepress/locales/zh_CN.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { createRequire } from 'module'
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { generateSidebarChapter } from './side_bar.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const chapters = generateSidebarChapter('zh_CN', new Map([
|
||||
['introduction', '简介'],
|
||||
['configuration', '配置'],
|
||||
['premium', 'Premium 版本'],
|
||||
['runtime', '运行时'],
|
||||
['advanced-usages', '高级用法'],
|
||||
]))
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'zh-CN',
|
||||
|
||||
description: '基于规则的 Go 网络隧道. ',
|
||||
|
||||
themeConfig: {
|
||||
nav: nav(),
|
||||
|
||||
logo: '/logo.png',
|
||||
|
||||
lastUpdatedText: '最后更新于',
|
||||
|
||||
sidebar: chapters,
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/Dreamacro/clash' },
|
||||
],
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/Dreamacro/clash/edit/master/docs/:path',
|
||||
text: '在 GitHub 中编辑此页面'
|
||||
},
|
||||
|
||||
docFooter: { prev: '上一篇', next: '下一篇' },
|
||||
|
||||
outline: {
|
||||
level: 'deep',
|
||||
label: '页面导航',
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
function nav() {
|
||||
return [
|
||||
{ text: '主页', link: '/zh_CN/' },
|
||||
{ text: '配置', link: '/zh_CN/configuration/configuration-reference' },
|
||||
{
|
||||
text: '下载',
|
||||
items: [
|
||||
{ text: 'GitHub 开源版', link: 'https://github.com/Dreamacro/clash/releases/' },
|
||||
{ text: 'Premium 版本', link: 'https://github.com/Dreamacro/clash/releases/tag/premium' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
59
docs/advanced-usages/golang-api.md
Normal file
59
docs/advanced-usages/golang-api.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebarTitle: Integrating Clash in Golang Programs
|
||||
sidebarOrder: 3
|
||||
---
|
||||
|
||||
# Integrating Clash in Golang Programs
|
||||
|
||||
If clash does not fit your own usage, you can use Clash in your own Golang code.
|
||||
|
||||
There is already basic support:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
)
|
||||
|
||||
func main() {
|
||||
in := make(chan constant.ConnContext, 100)
|
||||
defer close(in)
|
||||
|
||||
l, err := socks.New("127.0.0.1:10000", in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
println("listen at:", l.Address())
|
||||
|
||||
direct := outbound.NewDirect()
|
||||
|
||||
for c := range in {
|
||||
conn := c
|
||||
metadata := conn.Metadata()
|
||||
fmt.Printf("request incoming from %s to %s\n", metadata.SourceAddress(), metadata.RemoteAddress())
|
||||
go func () {
|
||||
remote, err := direct.DialContext(context.Background(), metadata)
|
||||
if err != nil {
|
||||
fmt.Printf("dial error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
relay(remote, conn.Conn())
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func relay(l, r net.Conn) {
|
||||
go io.Copy(l, r)
|
||||
io.Copy(r, l)
|
||||
}
|
||||
```
|
93
docs/advanced-usages/openconnect.md
Normal file
93
docs/advanced-usages/openconnect.md
Normal file
@ -0,0 +1,93 @@
|
||||
---
|
||||
sidebarTitle: Rule-based OpenConnect
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# Rule-based OpenConnect
|
||||
|
||||
OpenConnect supports Cisco AnyConnect SSL VPN, Juniper Network Connect, Palo Alto Networks (PAN) GlobalProtect SSL VPN, Pulse Connect Secure SSL VPN, F5 BIG-IP SSL VPN, FortiGate SSL VPN and Array Networks SSL VPN.
|
||||
|
||||
For example, there would be a use case where your company uses Cisco AnyConnect for internal network access. Here I'll show you how you can use OpenConnect with policy routing powered by Clash.
|
||||
|
||||
First, [install vpn-slice](https://github.com/dlenski/vpn-slice#requirements). This tool overrides default routing table behaviour of OpenConnect. Simply saying, it stops the VPN from overriding your default routes.
|
||||
|
||||
Next you would have a script (let's say `tun0.sh`) similar to this:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
ANYCONNECT_HOST="vpn.example.com"
|
||||
ANYCONNECT_USER="john"
|
||||
ANYCONNECT_PASSWORD="foobar"
|
||||
ROUTING_TABLE_ID="6667"
|
||||
TUN_INTERFACE="tun0"
|
||||
|
||||
# Add --no-dtls if the server is in mainland China. UDP in China is choppy.
|
||||
echo "$ANYCONNECT_PASSWORD" | \
|
||||
openconnect \
|
||||
--non-inter \
|
||||
--passwd-on-stdin \
|
||||
--protocol=anyconnect \
|
||||
--interface $TUN_INTERFACE \
|
||||
--script "vpn-slice
|
||||
if [ \"\$reason\" = 'connect' ]; then
|
||||
ip rule add from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
|
||||
ip route add default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
|
||||
elif [ \"\$reason\" = 'disconnect' ]; then
|
||||
ip rule del from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
|
||||
ip route del default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
|
||||
fi" \
|
||||
--user $ANYCONNECT_USER \
|
||||
https://$ANYCONNECT_HOST
|
||||
```
|
||||
|
||||
After that, we configure it as a systemd service. Create `/etc/systemd/system/tun0.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Cisco AnyConnect VPN
|
||||
After=network-online.target
|
||||
Conflicts=shutdown.target sleep.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/path/to/tun0.sh
|
||||
KillSignal=SIGINT
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Then we enable & start the service.
|
||||
|
||||
```shell
|
||||
chmod +x /path/to/tun0.sh
|
||||
systemctl daemon-reload
|
||||
systemctl enable tun0
|
||||
systemctl start tun0
|
||||
```
|
||||
|
||||
From here you can look at the logs to see if it's running properly. Simple way is to look at if `tun0` interface has been created.
|
||||
|
||||
Similar to the Wireguard one, having an outbound to a TUN device is simple as adding a proxy group:
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: Cisco AnyConnect VPN
|
||||
type: select
|
||||
interface-name: tun0
|
||||
proxies:
|
||||
- DIRECT
|
||||
```
|
||||
|
||||
... and it's ready to use! Add the desired rules:
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- DOMAIN-SUFFIX,internal.company.com,Cisco AnyConnect VPN
|
||||
```
|
||||
|
||||
You should look at the debug level logs when something does not seem right.
|
||||
|
40
docs/advanced-usages/wireguard.md
Normal file
40
docs/advanced-usages/wireguard.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
sidebarTitle: Rule-based Wireguard
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
# Rule-based Wireguard
|
||||
|
||||
Suppose your kernel supports Wireguard and you have it enabled. The `Table` option stops _wg-quick_ from overriding default routes.
|
||||
|
||||
Example `wg0.conf`:
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = ...
|
||||
Address = 172.16.0.1/32
|
||||
MTU = ...
|
||||
Table = off
|
||||
PostUp = ip rule add from 172.16.0.1/32 table 6666
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
AllowedIPs = ::/0
|
||||
PublicKey = ...
|
||||
Endpoint = ...
|
||||
```
|
||||
|
||||
Then in Clash you would only need to have a DIRECT proxy group that has a specific outbound interface:
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: Wireguard
|
||||
type: select
|
||||
interface-name: wg0
|
||||
proxies:
|
||||
- DIRECT
|
||||
rules:
|
||||
- DOMAIN,google.com,Wireguard
|
||||
```
|
||||
|
||||
This should perform better than whereas if Clash implemented its own userspace Wireguard client. Wireguard is supported in the kernel.
|
BIN
docs/assets/connection-flow.png
Normal file
BIN
docs/assets/connection-flow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
480
docs/configuration/configuration-reference.md
Normal file
480
docs/configuration/configuration-reference.md
Normal file
@ -0,0 +1,480 @@
|
||||
---
|
||||
sidebarTitle: Configuration Reference
|
||||
sidebarOrder: 7
|
||||
---
|
||||
|
||||
# Configuration Reference
|
||||
|
||||
```yaml
|
||||
# Port of HTTP(S) proxy server on the local end
|
||||
port: 7890
|
||||
|
||||
# Port of SOCKS5 proxy server on the local end
|
||||
socks-port: 7891
|
||||
|
||||
# Transparent proxy server port for Linux and macOS (Redirect TCP and TProxy UDP)
|
||||
# redir-port: 7892
|
||||
|
||||
# Transparent proxy server port for Linux (TProxy TCP and TProxy UDP)
|
||||
# tproxy-port: 7893
|
||||
|
||||
# HTTP(S) and SOCKS4(A)/SOCKS5 server on the same port
|
||||
# mixed-port: 7890
|
||||
|
||||
# authentication of local SOCKS5/HTTP(S) server
|
||||
# authentication:
|
||||
# - "user1:pass1"
|
||||
# - "user2:pass2"
|
||||
|
||||
# Set to true to allow connections to the local-end server from
|
||||
# other LAN IP addresses
|
||||
# allow-lan: false
|
||||
|
||||
# This is only applicable when `allow-lan` is `true`
|
||||
# '*': bind all IP addresses
|
||||
# 192.168.122.11: bind a single IPv4 address
|
||||
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
|
||||
# bind-address: '*'
|
||||
|
||||
# Clash router working mode
|
||||
# rule: rule-based packet routing
|
||||
# global: all packets will be forwarded to a single endpoint
|
||||
# direct: directly forward the packets to the Internet
|
||||
mode: rule
|
||||
|
||||
# Clash by default prints logs to STDOUT
|
||||
# info / warning / error / debug / silent
|
||||
# log-level: info
|
||||
|
||||
# When set to false, resolver won't translate hostnames to IPv6 addresses
|
||||
# ipv6: false
|
||||
|
||||
# RESTful web API listening address
|
||||
external-controller: 127.0.0.1:9090
|
||||
|
||||
# A relative path to the configuration directory or an absolute path to a
|
||||
# directory in which you put some static web resource. Clash core will then
|
||||
# serve it at `http://{{external-controller}}/ui`.
|
||||
# external-ui: folder
|
||||
|
||||
# Secret for the RESTful API (optional)
|
||||
# Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||
# ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
||||
# secret: ""
|
||||
|
||||
# Outbound interface name
|
||||
# interface-name: en0
|
||||
|
||||
# fwmark on Linux only
|
||||
# routing-mark: 6666
|
||||
|
||||
# Static hosts for DNS server and connection establishment (like /etc/hosts)
|
||||
#
|
||||
# Wildcard hostnames are supported (e.g. *.clash.dev, *.foo.*.example.com)
|
||||
# Non-wildcard domain names have a higher priority than wildcard domain names
|
||||
# e.g. foo.example.com > *.example.com > .example.com
|
||||
# P.S. +.foo.com equals to .foo.com and foo.com
|
||||
# hosts:
|
||||
# '*.clash.dev': 127.0.0.1
|
||||
# '.dev': 127.0.0.1
|
||||
# 'alpha.clash.dev': '::1'
|
||||
|
||||
# profile:
|
||||
# Store the `select` results in $HOME/.config/clash/.cache
|
||||
# set false If you don't want this behavior
|
||||
# when two different configurations have groups with the same name, the selected values are shared
|
||||
# store-selected: true
|
||||
|
||||
# persistence fakeip
|
||||
# store-fake-ip: false
|
||||
|
||||
# DNS server settings
|
||||
# This section is optional. When not present, the DNS server will be disabled.
|
||||
dns:
|
||||
enable: false
|
||||
listen: 0.0.0.0:53
|
||||
# ipv6: false # when the false, response to AAAA questions will be empty
|
||||
|
||||
# These nameservers are used to resolve the DNS nameserver hostnames below.
|
||||
# Specify IP addresses only
|
||||
default-nameserver:
|
||||
- 114.114.114.114
|
||||
- 8.8.8.8
|
||||
# enhanced-mode: fake-ip
|
||||
fake-ip-range: 198.18.0.1/16 # Fake IP addresses pool CIDR
|
||||
# use-hosts: true # lookup hosts and return IP record
|
||||
|
||||
# search-domains: [local] # search domains for A/AAAA record
|
||||
|
||||
# Hostnames in this list will not be resolved with fake IPs
|
||||
# i.e. questions to these domain names will always be answered with their
|
||||
# real IP addresses
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
# Supports UDP, TCP, DoT, DoH. You can specify the port to connect to.
|
||||
# All DNS questions are sent directly to the nameserver, without proxies
|
||||
# involved. Clash answers the DNS question with the first result gathered.
|
||||
nameserver:
|
||||
- 114.114.114.114 # default value
|
||||
- 8.8.8.8 # default value
|
||||
- tls://dns.rubyfish.cn:853 # DNS over TLS
|
||||
- https://1.1.1.1/dns-query # DNS over HTTPS
|
||||
- dhcp://en0 # dns from dhcp
|
||||
# - '8.8.8.8#en0'
|
||||
|
||||
# When `fallback` is present, the DNS server will send concurrent requests
|
||||
# to the servers in this section along with servers in `nameservers`.
|
||||
# The answers from fallback servers are used when the GEOIP country
|
||||
# is not `CN`.
|
||||
# fallback:
|
||||
# - tcp://1.1.1.1
|
||||
# - 'tcp://1.1.1.1#en0'
|
||||
|
||||
# If IP addresses resolved with servers in `nameservers` are in the specified
|
||||
# subnets below, they are considered invalid and results from `fallback`
|
||||
# servers are used instead.
|
||||
#
|
||||
# IP address resolved with servers in `nameserver` is used when
|
||||
# `fallback-filter.geoip` is true and when GEOIP of the IP address is `CN`.
|
||||
#
|
||||
# If `fallback-filter.geoip` is false, results from `nameserver` nameservers
|
||||
# are always used if not match `fallback-filter.ipcidr`.
|
||||
#
|
||||
# This is a countermeasure against DNS pollution attacks.
|
||||
# fallback-filter:
|
||||
# geoip: true
|
||||
# geoip-code: CN
|
||||
# ipcidr:
|
||||
# - 240.0.0.0/4
|
||||
# domain:
|
||||
# - '+.google.com'
|
||||
# - '+.facebook.com'
|
||||
# - '+.youtube.com'
|
||||
|
||||
# Lookup domains via specific nameservers
|
||||
# nameserver-policy:
|
||||
# 'www.baidu.com': '114.114.114.114'
|
||||
# '+.internal.crop.com': '10.0.0.1'
|
||||
|
||||
proxies:
|
||||
# Shadowsocks
|
||||
# The supported ciphers (encryption methods):
|
||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||
# aes-128-cfb aes-192-cfb aes-256-cfb
|
||||
# aes-128-ctr aes-192-ctr aes-256-ctr
|
||||
# rc4-md5 chacha20-ietf xchacha20
|
||||
# chacha20-ietf-poly1305 xchacha20-ietf-poly1305
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
# udp: true
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: obfs
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
# vmess
|
||||
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# 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
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: h2
|
||||
tls: true
|
||||
h2-opts:
|
||||
host:
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# network: http
|
||||
# http-opts:
|
||||
# # method: "GET"
|
||||
# # path:
|
||||
# # - '/'
|
||||
# # - '/video'
|
||||
# # headers:
|
||||
# # Connection:
|
||||
# # - keep-alive
|
||||
|
||||
- name: vmess-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: vmess
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: grpc
|
||||
tls: true
|
||||
servername: example.com
|
||||
# skip-cert-verify: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
# socks5
|
||||
- name: "socks"
|
||||
type: socks5
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true # https
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
|
||||
# Snell
|
||||
# Beware that there's currently no UDP support yet
|
||||
- name: "snell"
|
||||
type: snell
|
||||
server: server
|
||||
port: 44046
|
||||
psk: yourpsk
|
||||
# version: 2
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
# ShadowsocksR
|
||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||
# The supported obfses:
|
||||
# plain http_simple http_post
|
||||
# random_head tls1.2_ticket_auth tls1.2_ticket_fastauth
|
||||
# The supported supported protocols:
|
||||
# origin auth_sha1_v4 auth_aes128_md5
|
||||
# auth_aes128_sha1 auth_chain_a auth_chain_b
|
||||
- name: "ssr"
|
||||
type: ssr
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf
|
||||
password: "password"
|
||||
obfs: tls1.2_ticket_auth
|
||||
protocol: auth_sha1_v4
|
||||
# obfs-param: domain.tld
|
||||
# protocol-param: "#"
|
||||
# udp: true
|
||||
|
||||
proxy-groups:
|
||||
# relay chains the proxies. proxies shall not contain a relay. No UDP support.
|
||||
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||
- name: "relay"
|
||||
type: relay
|
||||
proxies:
|
||||
- http
|
||||
- vmess
|
||||
- ss1
|
||||
- ss2
|
||||
|
||||
# url-test select which proxy will be used by benchmarking speed to a URL.
|
||||
- name: "auto"
|
||||
type: url-test
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
# tolerance: 150
|
||||
# lazy: true
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
|
||||
# fallback selects an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
|
||||
- name: "fallback-auto"
|
||||
type: fallback
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
|
||||
# load-balance: The request of the same eTLD+1 will be dial to the same proxy.
|
||||
- name: "load-balance"
|
||||
type: load-balance
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
# strategy: consistent-hashing # or round-robin
|
||||
|
||||
# select is used for selecting proxy or proxy group
|
||||
# you can use RESTful API to switch proxy is recommended for use in GUI.
|
||||
- name: Proxy
|
||||
type: select
|
||||
# disable-udp: true
|
||||
# filter: 'someregex'
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
# direct to another interfacename or fwmark, also supported on proxy
|
||||
- name: en1
|
||||
type: select
|
||||
interface-name: en1
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
use:
|
||||
- provider1
|
||||
proxies:
|
||||
- Proxy
|
||||
- DIRECT
|
||||
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
# lazy: true
|
||||
url: http://www.gstatic.com/generate_204
|
||||
test:
|
||||
type: file
|
||||
path: /test.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 36000
|
||||
url: http://www.gstatic.com/generate_204
|
||||
|
||||
tunnels:
|
||||
# one line config
|
||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||
# full yaml config
|
||||
- network: [tcp, udp]
|
||||
address: 127.0.0.1:7777
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
rules:
|
||||
- DOMAIN-SUFFIX,google.com,auto
|
||||
- DOMAIN-KEYWORD,google,auto
|
||||
- DOMAIN,google.com,auto
|
||||
- DOMAIN-SUFFIX,ad.com,REJECT
|
||||
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
|
||||
# optional param "no-resolve" for IP rules (GEOIP, IP-CIDR, IP-CIDR6)
|
||||
- IP-CIDR,127.0.0.0/8,DIRECT
|
||||
- GEOIP,CN,DIRECT
|
||||
- DST-PORT,80,DIRECT
|
||||
- SRC-PORT,7777,DIRECT
|
||||
- RULE-SET,apple,REJECT # Premium only
|
||||
- MATCH,auto
|
||||
```
|
72
docs/configuration/dns.md
Normal file
72
docs/configuration/dns.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
sidebarTitle: Clash DNS
|
||||
sidebarOrder: 6
|
||||
---
|
||||
|
||||
# Clash DNS
|
||||
|
||||
Since some parts of Clash run on the Layer 3 (Network Layer), they would've been impossible to obtain domain names of the packets for rule-based routing.
|
||||
|
||||
*Enter fake-ip*. It enables rule-based routing, minimises the impact of DNS pollution attack and improves network performance, sometimes drastically.
|
||||
|
||||
## fake-ip
|
||||
|
||||
The concept of "fake IP" addresses is originated from [RFC 3089](https://tools.ietf.org/rfc/rfc3089):
|
||||
|
||||
> A "fake IP" address is used as a key to look up the corresponding "FQDN" information.
|
||||
|
||||
The default CIDR for the fake-ip pool is `198.18.0.1/16`, a reserved IPv4 address space, which can be changed in `dns.fake-ip-range`.
|
||||
|
||||
When a DNS request is sent to the Clash DNS, the core allocates a *free* fake-ip address from the pool, by managing an internal mapping of domain names and their fake-ip addresses.
|
||||
|
||||
Take an example of accessing `http://google.com` with your browser.
|
||||
|
||||
1. The browser asks Clash DNS for the IP address of `google.com`
|
||||
2. Clash checks the internal mapping and returned `198.18.1.5`
|
||||
3. The browser sends an HTTP request to `198.18.1.5` on `80/tcp`
|
||||
4. When receiving the inbound packet for `198.18.1.5`, Clash looks up the internal mapping and realises the client is actually sending a packet to `google.com`
|
||||
5. Depending on the rules:
|
||||
|
||||
1. Clash may just send the domain name to an outbound proxy like SOCKS5 or shadowsocks and establish the connection with the proxy server
|
||||
|
||||
2. or Clash might look for the real IP address of `google.com`, in the case of encountering a `SCRIPT`, `GEOIP`, `IP-CIDR` rule, or the case of DIRECT outbound
|
||||
|
||||
Being a confusing concept, I'll take another example of accessing `http://google.com` with the cURL utility:
|
||||
|
||||
```txt{2,3,5,6,8,9}
|
||||
$ curl -v http://google.com
|
||||
<---- cURL asks your system DNS (Clash) about the IP address of google.com
|
||||
----> Clash decided 198.18.1.70 should be used as google.com and remembers it
|
||||
* Trying 198.18.1.70:80...
|
||||
<---- cURL connects to 198.18.1.70 tcp/80
|
||||
----> Clash will accept the connection immediately, and..
|
||||
* Connected to google.com (198.18.1.70) port 80 (#0)
|
||||
----> Clash looks up in its memory and found 198.18.1.70 being google.com
|
||||
----> Clash looks up in the rules and sends the packet via the matching outbound
|
||||
> GET / HTTP/1.1
|
||||
> Host: google.com
|
||||
> User-Agent: curl/8.0.1
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 301 Moved Permanently
|
||||
< Location: http://www.google.com/
|
||||
< Content-Type: text/html; charset=UTF-8
|
||||
< Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-ahELFt78xOoxhySY2lQ34A' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
|
||||
< Date: Thu, 11 May 2023 06:52:19 GMT
|
||||
< Expires: Sat, 10 Jun 2023 06:52:19 GMT
|
||||
< Cache-Control: public, max-age=2592000
|
||||
< Server: gws
|
||||
< Content-Length: 219
|
||||
< X-XSS-Protection: 0
|
||||
< X-Frame-Options: SAMEORIGIN
|
||||
<
|
||||
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<TITLE>301 Moved</TITLE></HEAD><BODY>
|
||||
<H1>301 Moved</H1>
|
||||
The document has moved
|
||||
<A HREF="http://www.google.com/">here</A>.
|
||||
</BODY></HTML>
|
||||
* Connection #0 to host google.com left intact
|
||||
```
|
||||
|
||||
<!-- TODO: nameserver, fallback, fallback-filter, hosts, search-domains, fake-ip-filter, nameserver-policy -->
|
76
docs/configuration/getting-started.md
Normal file
76
docs/configuration/getting-started.md
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
sidebarTitle: Getting Started
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
It's recommended that you read the [Introduction](/configuration/introduction) before proceeding. After you have a brief understanding of how Clash works, you can start writing your own configuration.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The main configuration file is called `config.yaml`. By default, Clash reads the configuration files at `$HOME/.config/clash`. If it doesn't exist, Clash will generate a minimal configuration file at that location.
|
||||
|
||||
If you want to place your configurations elsewhere (e.g. `/etc/clash`), you can use command-line option `-d` to specify a configuration directory:
|
||||
|
||||
```shell
|
||||
clash -d . # current directory
|
||||
clash -d /etc/clash
|
||||
```
|
||||
|
||||
Or, you can use option `-f` to specify a configuration file:
|
||||
|
||||
```shell
|
||||
clash -f ./config.yaml
|
||||
clash -f /etc/clash/config.yaml
|
||||
```
|
||||
|
||||
## Special Syntaxes
|
||||
|
||||
There are some special syntaxes in Clash configuration files, of which you might want to be aware:
|
||||
|
||||
### IPv6 Addresses
|
||||
|
||||
You should wrap IPv6 addresses in square brackets, for example:
|
||||
|
||||
```txt
|
||||
[aaaa::a8aa:ff:fe09:57d8]
|
||||
```
|
||||
|
||||
### DNS Wildcard Domain Matching
|
||||
|
||||
In some cases, you will need to match against wildcard domains. For example, when you're setting up [Clash DNS](/configuration/dns), you might want to match against all subdomains of `localdomain`.
|
||||
|
||||
Clash do offer support on matching different levels of wildcard domains in the DNS configuration, while the syntaxes defined below:
|
||||
|
||||
::: tip
|
||||
Any domain with these characters should be wrapped with single quotes (`'`). For example, `'*.google.com'`.
|
||||
Static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com).
|
||||
:::
|
||||
|
||||
Use an asterisk (`*`) to match against a single-level wildcard subdomain.
|
||||
|
||||
| Expression | Matches | Does Not Match |
|
||||
| ---------- | ------- | -------------- |
|
||||
| `*.google.com` | `www.google.com` | `google.com` |
|
||||
| `*.bar.google.com` | `foo.bar.google.com` | `bar.google.com` |
|
||||
| `*.*.google.com` | `thoughtful.sandbox.google.com` | `one.two.three.google.com` |
|
||||
|
||||
Use a dot sign (`.`) to match against multi-level wildcard subdomains.
|
||||
|
||||
| Expression | Matches | Does Not Match |
|
||||
| ---------- | ------- | -------------- |
|
||||
| `.google.com` | `www.google.com` | `google.com` |
|
||||
| `.google.com` | `thoughtful.sandbox.google.com` | `google.com` |
|
||||
| `.google.com` | `one.two.three.google.com` | `google.com` |
|
||||
|
||||
Use a plus sign (`+`) to match against multi-level wildcard subdomains.
|
||||
|
||||
`+` wildcard works like DOMAIN-SUFFIX, you can quickly match multi level at a time.
|
||||
|
||||
| Expression | Matches |
|
||||
| ---------- | ------- |
|
||||
| `+.google.com` | `google.com` |
|
||||
| `+.google.com` | `www.google.com` |
|
||||
| `+.google.com` | `thoughtful.sandbox.google.com` |
|
||||
| `+.google.com` | `one.two.three.google.com` |
|
69
docs/configuration/inbound.md
Normal file
69
docs/configuration/inbound.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
sidebarTitle: Inbound
|
||||
sidebarOrder: 3
|
||||
---
|
||||
|
||||
# Inbound
|
||||
|
||||
Clash supports multiple inbound protocols, including:
|
||||
|
||||
- SOCKS5
|
||||
- HTTP(S)
|
||||
- Redirect TCP
|
||||
- TProxy TCP
|
||||
- TProxy UDP
|
||||
- Linux TUN device (Premium only)
|
||||
|
||||
Connections to any inbound protocol listed above will be handled by the same internal rule-matching engine. That is to say, Clash does not (currently) support different rule sets for different inbounds.
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# Port of HTTP(S) proxy server on the local end
|
||||
# port: 7890
|
||||
|
||||
# Port of SOCKS5 proxy server on the local end
|
||||
# socks-port: 7891
|
||||
|
||||
# HTTP(S) and SOCKS4(A)/SOCKS5 server on the same port
|
||||
mixed-port: 7890
|
||||
|
||||
# Transparent proxy server port for Linux and macOS (Redirect TCP and TProxy UDP)
|
||||
# redir-port: 7892
|
||||
|
||||
# Transparent proxy server port for Linux (TProxy TCP and TProxy UDP)
|
||||
# tproxy-port: 7893
|
||||
|
||||
# Allow clients other than 127.0.0.1 to connect to the inbounds
|
||||
allow-lan: false
|
||||
```
|
||||
|
||||
## The Mixed Port
|
||||
|
||||
The mixed port is a special port that supports both HTTP(S) and SOCKS5 protocols. You can have any programs that support either HTTP or SOCKS proxy to connect to this port, for example:
|
||||
|
||||
```shell
|
||||
$ curl -x socks5h://127.0.0.1:7890 -v http://connect.rom.miui.com/generate_204
|
||||
* Trying 127.0.0.1:7890...
|
||||
* SOCKS5 connect to connect.rom.miui.com:80 (remotely resolved)
|
||||
* SOCKS5 request granted.
|
||||
* Connected to (nil) (127.0.0.1) port 7890 (#0)
|
||||
> GET /generate_204 HTTP/1.1
|
||||
> Host: connect.rom.miui.com
|
||||
> User-Agent: curl/7.81.0
|
||||
> Accept: */*
|
||||
>
|
||||
* Mark bundle as not supporting multiuse
|
||||
< HTTP/1.1 204 No Content
|
||||
< Date: Thu, 11 May 2023 06:18:22 GMT
|
||||
< Connection: keep-alive
|
||||
< Content-Type: text/plain
|
||||
<
|
||||
* Connection #0 to host (nil) left intact
|
||||
```
|
||||
|
||||
## Redirect and TProxy
|
||||
|
||||
Redirect and TProxy are two different ways of implementing transparent proxying. They are both supported by Clash.
|
||||
|
||||
However, you most likely don't need to mess with these two inbounds - we recommend using [Clash Premium](/premium/introduction) if you want to use transparent proxying, as it has built-in support of the automatic management of the route table, rules and nftables.
|
38
docs/configuration/introduction.md
Normal file
38
docs/configuration/introduction.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
sidebarTitle: Introduction
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
In this chapter, we'll cover the common features of Clash and how they should be used and configured.
|
||||
|
||||
Clash uses [YAML](https://yaml.org), _YAML Ain't Markup Language_, for configuration files. YAML is designed to be easy to be read, be written, and be interpreted by computers, and is commonly used for exact configuration files.
|
||||
|
||||
## Understanding how Clash works
|
||||
|
||||
Before proceeding, it's important to understand how Clash works, in which there are two critical components:
|
||||
|
||||

|
||||
|
||||
<!-- https://excalidraw.com/clash-connection-flow#json=OHsOdaqAUPuuN7VPvdZ9Z,NT7rRrtzRgbVIM0tpkPnGA -->
|
||||
|
||||
### Inbound
|
||||
|
||||
Inbound is the component that listens on the local end. It works by opening a local port and listening for incoming connections. When a connection comes in, Clash looks up the rules that are configured in the configuration file, and decides which outbound that the connection should go next.
|
||||
|
||||
### Outbound
|
||||
|
||||
Outbound is the component that connects to the remote end. Depending on the configuration, it can be a specific network interface, a proxy server, or a [proxy group](./outbound#proxy-groups).
|
||||
|
||||
## Rule-based Routing
|
||||
|
||||
Clash supports rule-based routing, which means you can route packets to different outbounds based on the a variety of contraints. The rules can be defined in the `rules` section of the configuration file.
|
||||
|
||||
There's a number of available rule types, and each rule type has its own syntax. The general syntax of a rule is:
|
||||
|
||||
```txt
|
||||
TYPE,ARGUMENT,POLICY(,no-resolve)
|
||||
```
|
||||
|
||||
In the upcoming guides, you will learn more about how rules can be configured.
|
437
docs/configuration/outbound.md
Normal file
437
docs/configuration/outbound.md
Normal file
@ -0,0 +1,437 @@
|
||||
---
|
||||
sidebarTitle: Outbound
|
||||
sidebarOrder: 4
|
||||
---
|
||||
|
||||
# Outbound
|
||||
|
||||
There are several types of outbound targets in Clash. Each type has its own features and usage scenarios. In this page, we'll cover the common features of each type and how they should be used and configured.
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Proxies
|
||||
|
||||
Proxies are some outbound targets that you can configure. Like proxy servers, you define destinations for the packets here.
|
||||
|
||||
### Shadowsocks
|
||||
|
||||
Clash supports the following ciphers (encryption methods) for Shadowsocks:
|
||||
|
||||
| Family | Ciphers |
|
||||
| ------ | ------- |
|
||||
| AEAD | aes-128-gcm, aes-192-gcm, aes-256-gcm, chacha20-ietf-poly1305, xchacha20-ietf-poly1305 |
|
||||
| Stream | aes-128-cfb, aes-192-cfb, aes-256-cfb, rc4-md5, chacha20-ietf, xchacha20 |
|
||||
| Block | aes-128-ctr, aes-192-ctr, aes-256-ctr |
|
||||
|
||||
In addition, Clash also supports popular Shadowsocks plugins `obfs` and `v2ray-plugin`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [basic]
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
# udp: true
|
||||
```
|
||||
|
||||
```yaml [obfs]
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: obfs
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
```
|
||||
|
||||
```yaml [ws (websocket)]
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### ShadowsocksR
|
||||
|
||||
Clash supports the infamous anti-censorship protocol ShadowsocksR as well. The supported ciphers:
|
||||
|
||||
| Family | Ciphers |
|
||||
| ------ | ------- |
|
||||
| Stream | aes-128-cfb, aes-192-cfb, aes-256-cfb, rc4-md5, chacha20-ietf, xchacha20 |
|
||||
|
||||
Supported obfuscation methods:
|
||||
|
||||
- plain
|
||||
- http_simple
|
||||
- http_post
|
||||
- random_head
|
||||
- tls1.2_ticket_auth
|
||||
- tls1.2_ticket_fastauth
|
||||
|
||||
Supported protocols:
|
||||
|
||||
- origin
|
||||
- auth_sha1_v4
|
||||
- auth_aes128_md5
|
||||
- auth_aes128_sha1
|
||||
- auth_chain_a
|
||||
- auth_chain_b
|
||||
|
||||
```yaml
|
||||
- name: "ssr"
|
||||
type: ssr
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf
|
||||
password: "password"
|
||||
obfs: tls1.2_ticket_auth
|
||||
protocol: auth_sha1_v4
|
||||
# obfs-param: domain.tld
|
||||
# protocol-param: "#"
|
||||
# udp: true
|
||||
```
|
||||
|
||||
### Vmess
|
||||
|
||||
Clash supports the following ciphers (encryption methods) for Vmess:
|
||||
|
||||
- auto
|
||||
- aes-128-gcm
|
||||
- chacha20-poly1305
|
||||
- none
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [basic]
|
||||
- name: "vmess"
|
||||
type: vmess
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# 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
|
||||
```
|
||||
|
||||
```yaml [HTTP]
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# network: http
|
||||
# http-opts:
|
||||
# # method: "GET"
|
||||
# # path:
|
||||
# # - '/'
|
||||
# # - '/video'
|
||||
# # headers:
|
||||
# # Connection:
|
||||
# # - keep-alive
|
||||
```
|
||||
|
||||
```yaml [HTTP/2]
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: h2
|
||||
tls: true
|
||||
h2-opts:
|
||||
host:
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
```
|
||||
|
||||
```yaml [gRPC]
|
||||
- name: vmess-grpc
|
||||
type: vmess
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: grpc
|
||||
tls: true
|
||||
servername: example.com
|
||||
# skip-cert-verify: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### SOCKS5
|
||||
|
||||
In addition, Clash supports SOCKS5 outbound as well:
|
||||
|
||||
```yaml
|
||||
- name: "socks"
|
||||
type: socks5
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
```
|
||||
|
||||
### HTTP
|
||||
|
||||
Clash also supports HTTP outbound:
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [HTTP]
|
||||
- name: "http"
|
||||
type: http
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
```
|
||||
|
||||
```yaml [HTTPS]
|
||||
- name: "http"
|
||||
type: http
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
tls: true
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
# username: username
|
||||
# password: password
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Snell
|
||||
|
||||
Being an alternative protocol for anti-censorship, Clash has integrated support for Snell as well.
|
||||
|
||||
::: tip
|
||||
Clash does not support Snell v4. ([#2466](https://github.com/Dreamacro/clash/issues/2466))
|
||||
:::
|
||||
|
||||
```yaml
|
||||
# No UDP support yet
|
||||
- name: "snell"
|
||||
type: snell
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 44046
|
||||
psk: yourpsk
|
||||
# version: 2
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
```
|
||||
|
||||
### Trojan
|
||||
|
||||
Clash has built support for the popular protocol Trojan:
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [basic]
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# udp: true
|
||||
# sni: example.com # aka server name
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
```
|
||||
|
||||
```yaml [gRPC]
|
||||
- name: trojan-grpc
|
||||
type: trojan
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
password: "example"
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
```
|
||||
|
||||
```yaml [ws (websocket)]
|
||||
- name: trojan-ws
|
||||
type: trojan
|
||||
# interface-name: eth0
|
||||
# routing-mark: 1234
|
||||
server: server
|
||||
port: 443
|
||||
password: "example"
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Proxy Groups
|
||||
|
||||
Proxy Groups are groups of proxies that you can use directly as a rule policy.
|
||||
|
||||
### relay
|
||||
|
||||
The request sent to this proxy group will be relayed through the specified proxy servers sequently. There's currently no UDP support on this. The specified proxy servers should not contain another relay.
|
||||
|
||||
### url-test
|
||||
|
||||
Clash benchmarks each proxy servers in the list, by sending HTTP HEAD requests to a specified URL through these servers periodically. It's possible to set a maximum tolerance value, benchmarking interval, and the target URL.
|
||||
|
||||
### fallback
|
||||
|
||||
Clash periodically tests the availability of servers in the list with the same mechanism of `url-test`. The first available server will be used.
|
||||
|
||||
### load-balance
|
||||
|
||||
The request to the same eTLD+1 will be dialed with the same proxy.
|
||||
|
||||
### select
|
||||
|
||||
The first server is by default used when Clash starts up. Users can choose the server to use with the RESTful API. In this mode, you can hardcode servers in the config or use [Proxy Providers](#proxy-providers).
|
||||
|
||||
Either way, sometimes you might as well just route packets with a direct connection. In this case, you can use the `DIRECT` outbound.
|
||||
|
||||
To use a different network interface, you will need to use a Proxy Group that contains a `DIRECT` outbound with the `interface-name` option set.
|
||||
|
||||
```yaml
|
||||
- name: "My Wireguard Outbound"
|
||||
type: select
|
||||
interface-name: wg0
|
||||
proxies: [ 'DIRECT' ]
|
||||
```
|
||||
|
||||
## Proxy Providers
|
||||
|
||||
Proxy Providers give users the power to load proxy server lists dynamically, instead of hardcoding them in the configuration file. There are currently two sources for a proxy provider to load server list from:
|
||||
|
||||
- `http`: Clash loads the server list from a specified URL on startup. Clash periodically pulls the server list from remote if the `interval` option is set.
|
||||
- `file`: Clash loads the server list from a specified location on the filesystem on startup.
|
||||
|
||||
Health check is available for both modes, and works exactly like `fallback` in Proxy Groups. The configuration format for the server list files is also exactly the same in the main configuration file:
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [config.yaml]
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml
|
||||
# filter: 'a|b' # golang regex string
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
# lazy: true
|
||||
url: http://www.gstatic.com/generate_204
|
||||
test:
|
||||
type: file
|
||||
path: /test.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 36000
|
||||
url: http://www.gstatic.com/generate_204
|
||||
```
|
||||
|
||||
```yaml [test.yaml]
|
||||
proxies:
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: obfs
|
||||
plugin-opts:
|
||||
mode: tls
|
||||
```
|
||||
|
||||
:::
|
159
docs/configuration/rules.md
Normal file
159
docs/configuration/rules.md
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
sidebarTitle: Rules
|
||||
sidebarOrder: 5
|
||||
---
|
||||
|
||||
# Rules
|
||||
|
||||
In the Getting Started guide, we covered the basics of rule-based matching in Clash. In this chapter, we'll cover all available rule types in the latest version of Clash.
|
||||
|
||||
```txt
|
||||
TYPE,ARGUMENT,POLICY(,no-resolve)
|
||||
```
|
||||
|
||||
The `no-resolve` option is optional, and it's used to skip DNS resolution for the rule. It's useful when you want to use `GEOIP`, `IP-CIDR`, `IP-CIDR6`, `SCRIPT` rules, but don't want to resolve the domain name to an IP address just yet.
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Policy
|
||||
|
||||
There are four types of POLICY for now, in which:
|
||||
|
||||
- DIRECT: directly connects to the target through `interface-name` (does not lookup system route table)
|
||||
- REJECT: drops the packet
|
||||
- Proxy: routes the packet to the specified proxy server
|
||||
- Proxy Group: routes the packet to the specified proxy group
|
||||
|
||||
## Types of rules
|
||||
|
||||
There are a number of rules where one might find useful. The following section covers each rule type and how they should be used.
|
||||
|
||||
### DOMAIN
|
||||
|
||||
`DOMAIN,www.google.com,policy` routes only `www.google.com` to `policy`.
|
||||
|
||||
### DOMAIN-SUFFIX
|
||||
|
||||
`DOMAIN-SUFFIX,youtube.com,policy` routes any domain names that ends with `youtube.com`.
|
||||
|
||||
In this case, `www.youtube.com` and `foo.bar.youtube.com` will be routed to `policy`.
|
||||
|
||||
### DOMAIN-KEYWORD
|
||||
|
||||
`DOMAIN-KEYWORD,google,policy` routes any domain names to policy that contains `google`.
|
||||
|
||||
In this case, `www.google.com` or `googleapis.com` are routed to `policy`.
|
||||
|
||||
### GEOIP
|
||||
|
||||
GEOIP rules are used to route packets based on the **country code** of the target IP address. Clash uses [MaxMind GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) database for this feature.
|
||||
|
||||
::: warning
|
||||
When encountering this rule, Clash will resolve the domain name to an IP address and then look up the country code of the IP address. If you want to skip the DNS resolution, use `no-resolve` option.
|
||||
:::
|
||||
|
||||
`GEOIP,CN,policy` routes any packets destined to a China IP address to `policy`.
|
||||
|
||||
### IP-CIDR
|
||||
|
||||
IP-CIDR rules are used to route packets based on the **destination IPv4 address** of the packet.
|
||||
|
||||
::: warning
|
||||
When encountering this rule, Clash will resolve the domain name to an IP address. If you want to skip the DNS resolution, use `no-resolve` option.
|
||||
:::
|
||||
|
||||
`IP-CIDR,127.0.0.0/8,DIRECT` routes any packets destined to `127.0.0.0/8` to the `DIRECT` outbound.
|
||||
|
||||
### IP-CIDR6
|
||||
|
||||
IP-CIDR6 rules are used to route packets based on the **destination IPv6 address** of the packet.
|
||||
|
||||
::: warning
|
||||
When encountering this rule, Clash will resolve the domain name to an IP address. If you want to skip the DNS resolution, use `no-resolve` option.
|
||||
:::
|
||||
|
||||
`IP-CIDR6,2620:0:2d0:200::7/32,policy` routes any packets destined to `2620:0:2d0:200::7/32` to `policy`.
|
||||
|
||||
### SRC-IP-CIDR
|
||||
|
||||
SRC-IP-CIDR rules are used to route packets based on the **source IPv4 address** of the packet.
|
||||
|
||||
`SRC-IP-CIDR,192.168.1.201/32,DIRECT` routes any packets **from** `192.168.1.201/32` to the `DIRECT` policy.
|
||||
|
||||
### SRC-PORT
|
||||
|
||||
SRC-PORT rules are used to route packets based on the **source port** of the packet.
|
||||
|
||||
`SRC-PORT,80,policy` routes any packets **from** the port 80 to `policy`.
|
||||
|
||||
### DST-PORT
|
||||
|
||||
DST-PORT rules are used to route packets based on the **destination port** of the packet.
|
||||
|
||||
`DST-PORT,80,policy` routes any packets **to** the port 80 to `policy`.
|
||||
|
||||
### PROCESS-NAME
|
||||
|
||||
PROCESS-NAME rules are used to route packets based on the name of process that is sending the packet.
|
||||
|
||||
::: warning
|
||||
Currently, only macOS, Linux, FreeBSD and Windows are supported.
|
||||
:::
|
||||
|
||||
`PROCESS-NAME,nc,DIRECT` routes all packets from the process `nc` to the `DIRECT` outbound.
|
||||
|
||||
### PROCESS-PATH
|
||||
|
||||
PROCESS-PATH rules are used to route packets based on the PATH of process that is sending the packet.
|
||||
|
||||
::: warning
|
||||
Currently, only macOS, Linux, FreeBSD and Windows are supported.
|
||||
:::
|
||||
|
||||
`PROCESS-PATH,/bin/sh,DIRECT` routes all packets from the process `/bin/sh` to the `DIRECT` outbound.
|
||||
|
||||
### IPSET
|
||||
|
||||
IPSET rules are used to match against an IP set and route packets based on the result. According to the [official website of IPSET](https://ipset.netfilter.org/):
|
||||
|
||||
> IP sets are a framework inside the Linux kernel, which can be administered by the ipset utility. Depending on the type, an IP set may store IP addresses, networks, (TCP/UDP) port numbers, MAC addresses, interface names or combinations of them in a way, which ensures lightning speed when matching an entry against a set.
|
||||
|
||||
Therefore, this feature only works on Linux and requires `ipset` to be installed.
|
||||
|
||||
::: warning
|
||||
When encountering this rule, Clash will resolve the domain name to an IP address. If you want to skip the DNS resolution, use `no-resolve` option.
|
||||
:::
|
||||
|
||||
`IPSET,chinaip,DIRECT` routes all packets with destination IPs matching the `chinaip` IPSET to DIRECT outbound.
|
||||
|
||||
### RULE-SET
|
||||
|
||||
::: info
|
||||
This feature is only available in the [Premium](/premium/introduction) edtion.
|
||||
:::
|
||||
|
||||
RULE-SET rules are used to route packets based on the result of a [rule provider](/premium/rule-providers). When Clash encounters this rule, it loads the rules from the specified rule provider and then matches the packet against the rules. If the packet matches any of the rules, the packet will be routed to the specified policy, otherwise the rule is skipped.
|
||||
|
||||
::: warning
|
||||
When encountering RULE-SET, Clash will resolve the domain name to an IP address **when the ruleset is of type IPCIDR**. If you want to skip the DNS resolution, use `no-resolve` option for the RULE-SET entry.
|
||||
:::
|
||||
|
||||
`RULE-SET,my-rule-provider,DIRECT` loads all rules from `my-rule-provider` and sends the matched packets to the `DIRECT` outbound.
|
||||
|
||||
### SCRIPT
|
||||
|
||||
::: info
|
||||
This feature is only available in the [Premium](/premium/introduction) edtion.
|
||||
:::
|
||||
|
||||
SCRIPT rules are special rules that are used to route packets based on the result of a [script shortcut](/premium/script-shortcuts). When Clash encounters this rule, it evaluates the expression. If it returns `true`, the packet will be routed to the specified policy, otherwise the rule is skipped.
|
||||
|
||||
::: warning
|
||||
When encountering this rule, Clash will resolve the domain name to an IP address. If you want to skip the DNS resolution, use `no-resolve` option.
|
||||
:::
|
||||
|
||||
`SCRIPT,SHORTCUT-NAME,policy` routes any packets to `policy` if they have the shortcut evaluated `true`.
|
||||
|
||||
### MATCH
|
||||
|
||||
`MATCH,policy` routes the rest of the packets to `policy`. This rule is **required** and is usually used as the last rule.
|
38
docs/index.md
Normal file
38
docs/index.md
Normal file
@ -0,0 +1,38 @@
|
||||
<!-- This is the index page, linked by the dummy sidebar item at Introduction/_dummy-index.md -->
|
||||
# What is Clash?
|
||||
|
||||
Welcome to the official knowledge base of the Clash core project ("Clash").
|
||||
|
||||
Clash is a cross-platform rule-based proxy utility that runs on the network and application layer, supporting various proxy and anti-censorship protocols out-of-the-box.
|
||||
|
||||
It has been adopted widely by the Internet users in some countries and regions where the Internet is heavily censored or blocked. Either way, Clash can be used by anyone who wants to improve their Internet experience.
|
||||
|
||||
There are currently two editions of Clash:
|
||||
|
||||
- [Clash](https://github.com/Dreamacro/clash): the open-source version released at [github.com/Dreamacro/clash](https://github.com/Dreamacro/clash)
|
||||
- [Clash Premium](https://github.com/Dreamacro/clash/releases/tag/premium): proprietary core with [TUN support and more](/premium/introduction) (free of charge)
|
||||
|
||||
While this wiki covers both, however, the use of Clash could be challenging for the average users. Those might want to consider using a GUI client instead, and we do have some recommendations:
|
||||
|
||||
- [Clash for Windows](https://github.com/Fndroid/clash_for_windows_pkg/releases) (Windows and macOS)
|
||||
- [Clash for Android](https://github.com/Kr328/ClashForAndroid)
|
||||
- [ClashX](https://github.com/yichengchen/clashX) or [ClashX Pro](https://install.appcenter.ms/users/clashx/apps/clashx-pro/distribution_groups/public) (macOS)
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- Inbound: HTTP, HTTPS, SOCKS5 server, TUN device*
|
||||
- Outbound: Shadowsocks(R), VMess, Trojan, Snell, SOCKS5, HTTP(S), Wireguard*
|
||||
- Rule-based Routing: dynamic scripting, domain, IP addresses, process name and more*
|
||||
- Fake-IP DNS: minimises impact on DNS pollution and improves network performance
|
||||
- Transparent Proxy: Redirect TCP and TProxy TCP/UDP with automatic route table/rule management*
|
||||
- Proxy Groups: automatic fallback, load balancing or latency testing
|
||||
- Remote Providers: load remote proxy lists dynamically
|
||||
- RESTful API: update configuration in-place via a comprehensive API
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<small>\*: Only available in the free-of-charge Premium edition.</small>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
## License
|
||||
|
||||
Clash is released under the [GPL-3.0](https://github.com/Dreamacro/clash/blob/master/LICENSE) open-source license. Prior to [v0.16.0](https://github.com/Dreamacro/clash/releases/tag/v0.16.0) or commit [e5284c](https://github.com/Dreamacro/clash/commit/e5284cf647717a8087a185d88d15a01096274bc2), it was licensed under the MIT license.
|
6
docs/introduction/_dummy-index.md
Normal file
6
docs/introduction/_dummy-index.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
sidebarTitle: What is Clash?
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
<!-- This file is used as a dummy sidebar item that always links to / -->
|
95
docs/introduction/faq.md
Normal file
95
docs/introduction/faq.md
Normal file
@ -0,0 +1,95 @@
|
||||
---
|
||||
sidebarTitle: Frequently Asked Questions
|
||||
sidebarOrder: 4
|
||||
---
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
Here we have some common questions people ask. If you have any questions not listed here, feel free to [open an issue](https://github.com/Dreamacro/clash/issues/new/choose).
|
||||
|
||||
[[toc]]
|
||||
|
||||
## What is the difference between amd64 and amd64-v3?
|
||||
|
||||
Quoting from [golang/go](https://github.com/golang/go/wiki/MinimumRequirements#amd64):
|
||||
|
||||
> Until Go 1.17, the Go compiler always generated x86 binaries that could be executed by any 64-bit x86 processor.
|
||||
>
|
||||
> Go 1.18 introduced [4 architectural levels](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels) for AMD64.
|
||||
> Each level differs in the set of x86 instructions that the compiler can include in the generated binaries:
|
||||
>
|
||||
> * GOAMD64=v1 (default): The baseline. Exclusively generates instructions that all 64-bit x86 processors can execute.
|
||||
> * GOAMD64=v2: all v1 instructions, plus CMPXCHG16B, LAHF, SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3.
|
||||
> * GOAMD64=v3: all v2 instructions, plus AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, OSXSAVE.
|
||||
> * GOAMD64=v4: all v3 instructions, plus AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL.
|
||||
>
|
||||
> Setting, for example, GOAMD64=v3, will allow the Go compiler to use AVX2 instructions in the generated binaries (which may improve performance in some cases); but these binaries will not run on older x86 processors that don't support AVX2.
|
||||
>
|
||||
> The Go toolchain may also generate newer instructions, but guarded by dynamic checks to ensure they're only executed on capable processors. For example, with GOAMD64=v1, [math/bits.OnesCount](https://pkg.go.dev/math/bits#OnesCount) will still use the [POPCNT](https://www.felixcloutier.com/x86/popcnt) instruction if [CPUID](https://www.felixcloutier.com/x86/cpuid) reports that it's available. Otherwise, it falls back to a generic implementation.
|
||||
>
|
||||
> The Go toolchain does not currently generate any AVX512 instructions.
|
||||
>
|
||||
> Note that *processor* is a simplification in this context. In practice, support from the entire system (firmware, hypervisor, kernel) is needed.
|
||||
|
||||
## Which release should I use for my system?
|
||||
|
||||
Here are some common systems that people use Clash on, and the recommended release for each of them:
|
||||
|
||||
- NETGEAR WNDR3700v2: mips-hardfloat [#846](https://github.com/Dreamacro/clash/issues/846)
|
||||
- NETGEAR WNDR3800: mips-softfloat [#579](https://github.com/Dreamacro/clash/issues/579)
|
||||
- ASUS RT-AC5300: armv5 [#2356](https://github.com/Dreamacro/clash/issues/2356)
|
||||
- MediaTek MT7620A, MT7621A: mipsle-softfloat ([#136](https://github.com/Dreamacro/clash/issues/136))
|
||||
- mips_24kc: [#192](https://github.com/Dreamacro/clash/issues/192)
|
||||
|
||||
If your device is not listed here, you can check the CPU architecture of your device with `uname -m` and find the corresponding release in the release page.
|
||||
|
||||
## List of wontfix
|
||||
|
||||
The official Clash core project will not implement/fix these things:
|
||||
|
||||
- [Snell](https://github.com/Dreamacro/clash/issues/2466)
|
||||
- [Custom CA](https://github.com/Dreamacro/clash/issues/2333)
|
||||
- [VMess Mux](https://github.com/Dreamacro/clash/issues/450)
|
||||
- [VLess](https://github.com/Dreamacro/clash/issues/1185)
|
||||
- [KCP](https://github.com/Dreamacro/clash/issues/16)
|
||||
- [mKCP](https://github.com/Dreamacro/clash/issues/2308)
|
||||
- [TLS Encrypted Client Hello](https://github.com/Dreamacro/clash/issues/2295)
|
||||
- [TCP support for Clash DNS server](https://github.com/Dreamacro/clash/issues/368)
|
||||
- [MITM](https://github.com/Dreamacro/clash/issues/227#issuecomment-508693628)
|
||||
|
||||
The following will be considered implementing when the official Go QUIC library releases.
|
||||
|
||||
- [TUIC](https://github.com/Dreamacro/clash/issues/2222)
|
||||
- [Hysteria](https://github.com/Dreamacro/clash/issues/1863)
|
||||
|
||||
## Proxies work on my local machine, but not on my router or in a container
|
||||
|
||||
Your system might be out of sync in time. Refer to your platform documentations about time synchronisation - things will break if time is not in sync.
|
||||
|
||||
## Time complexity of rule matching
|
||||
|
||||
Refer to this discussion: [#422](https://github.com/Dreamacro/clash/issues/422)
|
||||
|
||||
## Clash Premium unable to access Internet
|
||||
|
||||
You can refer to these relevant discussions:
|
||||
|
||||
- [#432](https://github.com/Dreamacro/clash/issues/432#issuecomment-571634905)
|
||||
- [#2480](https://github.com/Dreamacro/clash/issues/2480)
|
||||
|
||||
## error: unsupported rule type RULE-SET
|
||||
|
||||
If you stumbled on this error message:
|
||||
|
||||
```txt
|
||||
FATA[0000] Parse config error: Rules[0] [RULE-SET,apple,REJECT] error: unsupported rule type RULE-SET
|
||||
```
|
||||
|
||||
You're using Clash open-source edition. Rule Providers is currently only available in the [Premium core](https://github.com/Dreamacro/clash/releases/tag/premium). (it's free)
|
||||
|
||||
## DNS Hijack does not work
|
||||
|
||||
Since `tun.auto-route` does not intercept LAN traffic, if your system DNS is set to servers in private subnets, DNS hijack will not work. You can either:
|
||||
|
||||
1. Use a non-private DNS server as your system DNS like `1.1.1.1`
|
||||
2. Or manually set up your system DNS to the Clash DNS (by default, `198.18.0.1`)
|
50
docs/introduction/getting-started.md
Normal file
50
docs/introduction/getting-started.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
sidebarTitle: Getting Started
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
To get started with Clash, you can either build it from source or download pre-built binaries.
|
||||
|
||||
## Using pre-built binaries
|
||||
|
||||
You can download Clash core binaries here: [https://github.com/Dreamacro/clash/releases](https://github.com/Dreamacro/clash/releases)
|
||||
|
||||
## Install from source
|
||||
|
||||
You can build Clash on your own device with Golang 1.19+:
|
||||
|
||||
```shell
|
||||
$ go install github.com/Dreamacro/clash@latest
|
||||
go: downloading github.com/Dreamacro/clash v1.15.1
|
||||
```
|
||||
|
||||
The binary is built under `$GOPATH/bin`:
|
||||
|
||||
```shell
|
||||
$ $GOPATH/bin/clash -v
|
||||
Clash unknown version darwin arm64 with go1.20.3 unknown time
|
||||
```
|
||||
|
||||
## Build for a different arch/os
|
||||
|
||||
Golang supports cross-compilation, so you can build for a device on a different architecture or operating system. You can use _make_ to build them easily - for example:
|
||||
|
||||
```shell
|
||||
$ git clone --depth 1 https://github.com/Dreamacro/clash
|
||||
Cloning into 'clash'...
|
||||
remote: Enumerating objects: 359, done.
|
||||
remote: Counting objects: 100% (359/359), done.
|
||||
remote: Compressing objects: 100% (325/325), done.
|
||||
remote: Total 359 (delta 25), reused 232 (delta 17), pack-reused 0
|
||||
Receiving objects: 100% (359/359), 248.99 KiB | 1.63 MiB/s, done.
|
||||
Resolving deltas: 100% (25/25), done.
|
||||
$ cd clash && make darwin-arm64
|
||||
fatal: No names found, cannot describe anything.
|
||||
GOARCH=arm64 GOOS=darwin CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=unknown version" -X "github.com/Dreamacro/clash/constant.BuildTime=Mon May 8 16:47:10 UTC 2023" -w -s -buildid=' -o bin/clash-darwin-arm64
|
||||
$ file bin/clash-darwin-arm64
|
||||
bin/clash-darwin-arm64: Mach-O 64-bit executable arm64
|
||||
```
|
||||
|
||||
For other build targets, check out the [Makefile](https://github.com/Dreamacro/clash/blob/master/Makefile).
|
132
docs/introduction/service.md
Normal file
132
docs/introduction/service.md
Normal file
@ -0,0 +1,132 @@
|
||||
---
|
||||
sidebarTitle: Clash as a Service
|
||||
sidebarOrder: 3
|
||||
---
|
||||
|
||||
# Clash as a Service
|
||||
|
||||
While Clash is meant to be run in the background, there's currently no elegant way to implement daemons with Golang, hence we recommend you to daemonize Clash with third-party tools.
|
||||
|
||||
## systemd
|
||||
|
||||
Copy Clash binary to `/usr/local/bin` and configuration files to `/etc/clash`:
|
||||
|
||||
```shell
|
||||
cp clash /usr/local/bin
|
||||
cp config.yaml /etc/clash/
|
||||
cp Country.mmdb /etc/clash/
|
||||
```
|
||||
|
||||
Create the systemd configuration file at `/etc/systemd/system/clash.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Clash daemon, A rule-based proxy in Go.
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
ExecStart=/usr/local/bin/clash -d /etc/clash
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
After that you're supposed to reload systemd:
|
||||
|
||||
```shell
|
||||
systemctl daemon-reload
|
||||
```
|
||||
|
||||
Launch clashd on system startup with:
|
||||
|
||||
```shell
|
||||
systemctl enable clash
|
||||
```
|
||||
|
||||
Launch clashd immediately with:
|
||||
|
||||
```shell
|
||||
systemctl start clash
|
||||
```
|
||||
|
||||
Check the health and logs of Clash with:
|
||||
|
||||
```shell
|
||||
systemctl status clash
|
||||
journalctl -xe
|
||||
```
|
||||
|
||||
Credits to [ktechmidas](https://github.com/ktechmidas) for this guide. ([#754](https://github.com/Dreamacro/clash/issues/754))
|
||||
|
||||
## Docker
|
||||
|
||||
We provide pre-built images of Clash and Clash Premium. Therefore you can deploy Clash with [Docker Compose](https://docs.docker.com/compose/) if you're on Linux. However, you should be advised that it's [not recommended](https://github.com/Dreamacro/clash/issues/2249#issuecomment-1203494599) to run **Clash Premium** in a container.
|
||||
|
||||
::: warning
|
||||
This setup will not work on macOS systems due to the lack of [host networking and TUN support](https://github.com/Dreamacro/clash/issues/770#issuecomment-650951876) in Docker for Mac.
|
||||
:::
|
||||
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [Clash]
|
||||
services:
|
||||
clash:
|
||||
image: ghcr.io/dreamacro/clash
|
||||
restart: always
|
||||
volumes:
|
||||
- ./config.yaml:/root/.config/clash/config.yaml:ro
|
||||
# - ./ui:/ui:ro # dashboard volume
|
||||
ports:
|
||||
- "7890:7890"
|
||||
- "7891:7891"
|
||||
# - "8080:8080" # The External Controller (RESTful API)
|
||||
network_mode: "bridge"
|
||||
```
|
||||
|
||||
```yaml [Clash Premium]
|
||||
services:
|
||||
clash:
|
||||
image: ghcr.io/dreamacro/clash-premium
|
||||
restart: always
|
||||
volumes:
|
||||
- ./config.yaml:/root/.config/clash/config.yaml:ro
|
||||
# - ./ui:/ui:ro # dashboard volume
|
||||
ports:
|
||||
- "7890:7890"
|
||||
- "7891:7891"
|
||||
# - "8080:8080" # The External Controller (RESTful API)
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun
|
||||
network_mode: "host"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Save as `docker-compose.yaml` and place your `config.yaml` in the same directory.
|
||||
|
||||
::: tip
|
||||
Before proceeding, refer to your platform documentations about time synchronisation - things will break if time is not in sync.
|
||||
:::
|
||||
|
||||
When you're ready, run the following commands to bring up Clash:
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
You can view the logs with:
|
||||
|
||||
```shell
|
||||
docker-compose logs
|
||||
```
|
||||
|
||||
Stop Clash with:
|
||||
|
||||
```shell
|
||||
docker-compose stop
|
||||
```
|
13
docs/package.json
Normal file
13
docs/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"scripts": {
|
||||
"docs:dev": "vitepress dev",
|
||||
"docs:build": "vitepress build",
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.3.2",
|
||||
"directory-tree": "^3.5.1",
|
||||
"markdown-yaml-metadata-parser": "^3.0.0",
|
||||
"vitepress": "1.0.0-beta.3"
|
||||
}
|
||||
}
|
26
docs/premium/ebpf.md
Normal file
26
docs/premium/ebpf.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebarTitle: "Feature: eBPF Redirect to TUN"
|
||||
sidebarOrder: 3
|
||||
---
|
||||
|
||||
# eBPF Redirect to TUN
|
||||
|
||||
eBPF redirect to TUN is a feature that intercepts all network traffic on a specific network interface and redirects it to the TUN interface. [Support from your kernel](https://github.com/iovisor/bcc/blob/master/INSTALL.md#kernel-configuration) is required.
|
||||
|
||||
::: warning
|
||||
This feature conflicts with `tun.auto-route`.
|
||||
:::
|
||||
|
||||
While it usually brings better performance compared to `tun.auto-redir` and `tun.auto-route`, it's less tested compared to `auto-route`. Therefore, you should proceed with caution.
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
ebpf:
|
||||
redirect-to-tun:
|
||||
- eth0
|
||||
```
|
||||
|
||||
## Known Issues
|
||||
|
||||
- This feature breaks Tailscaled, so you should use `tun.auto-route` instead.
|
19
docs/premium/experimental-features.md
Normal file
19
docs/premium/experimental-features.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebarTitle: Experimental Features
|
||||
sidebarOrder: 9
|
||||
---
|
||||
|
||||
# Experimental Features
|
||||
|
||||
Occasionally we make new features that would require a fair amount of testing before having it in the main release. These features are marked as experimental and are disabled by default.
|
||||
|
||||
::: warning
|
||||
Some features listed here can be unstable, and might get removed in any future version - we do not recommend using them unless you have a specific reason to do so.
|
||||
:::
|
||||
|
||||
## Sniff TLS SNI
|
||||
|
||||
```yaml
|
||||
experimental:
|
||||
sniff-tls-sni: true
|
||||
```
|
26
docs/premium/introduction.md
Normal file
26
docs/premium/introduction.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebarTitle: Introduction
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
In the past, there was only one open-source version of Clash, until some [improper uses and redistributions](https://github.com/Dreamacro/clash/issues/541#issuecomment-672029110) of Clash arose. From that, we decided to fork Clash and develop the more advanced features in a private GitHub repository.
|
||||
|
||||
Don't worry just yet - the Premium core will stay free of charge, and the security is enforced with peer reviews from multiple credible developers.
|
||||
|
||||
## What's the difference?
|
||||
|
||||
The Premium core is a fork of the open-source Clash core with the addition of the following features:
|
||||
|
||||
- [TUN Device](/premium/tun-device) with the support of `auto-redir` and `auto-route`
|
||||
- [eBPF Redirect to TUN](/premium/ebpf)
|
||||
- [Rule Providers](/premium/rule-providers)
|
||||
- [Script](/premium/script)
|
||||
- [Script Shortcuts](/premium/script-shortcuts)
|
||||
- [Userspace Wireguard](/premium/userspace-wireguard)
|
||||
- [The Profiling Engine](/premium/the-profiling-engine)
|
||||
|
||||
## Obtaining a Copy
|
||||
|
||||
You can download the latest Clash Premium binaries from [GitHub Releases](https://github.com/Dreamacro/clash/releases/tag/premium).
|
100
docs/premium/rule-providers.md
Normal file
100
docs/premium/rule-providers.md
Normal file
@ -0,0 +1,100 @@
|
||||
---
|
||||
sidebarTitle: "Feature: Rule Providers"
|
||||
sidebarOrder: 4
|
||||
---
|
||||
|
||||
# Rule Providers
|
||||
|
||||
Rule Providers are pretty much the same compared to Proxy Providers. It enables users to load rules from external sources and overall cleaner configuration. This feature is currently Premium core only.
|
||||
|
||||
To define a Rule Provider, add the `rule-providers` field to the main configuration:
|
||||
|
||||
```yaml
|
||||
rule-providers:
|
||||
apple:
|
||||
behavior: "domain" # domain, ipcidr or classical (premium core only)
|
||||
type: http
|
||||
url: "url"
|
||||
# format: 'yaml' # or 'text'
|
||||
interval: 3600
|
||||
path: ./apple.yaml
|
||||
microsoft:
|
||||
behavior: "domain"
|
||||
type: file
|
||||
path: /microsoft.yaml
|
||||
|
||||
rules:
|
||||
- RULE-SET,apple,REJECT
|
||||
- RULE-SET,microsoft,policy
|
||||
```
|
||||
|
||||
There are three behavior types available:
|
||||
|
||||
## `domain`
|
||||
|
||||
yaml:
|
||||
|
||||
```yaml
|
||||
payload:
|
||||
- '.blogger.com'
|
||||
- '*.*.microsoft.com'
|
||||
- 'books.itunes.apple.com'
|
||||
```
|
||||
|
||||
text:
|
||||
|
||||
```txt
|
||||
# comment
|
||||
.blogger.com
|
||||
*.*.microsoft.com
|
||||
books.itunes.apple.com
|
||||
```
|
||||
|
||||
## `ipcidr`
|
||||
|
||||
yaml
|
||||
|
||||
```yaml
|
||||
payload:
|
||||
- '192.168.1.0/24'
|
||||
- '10.0.0.0.1/32'
|
||||
```
|
||||
|
||||
text:
|
||||
|
||||
```txt
|
||||
# comment
|
||||
192.168.1.0/24
|
||||
10.0.0.0.1/32
|
||||
```
|
||||
|
||||
## `classical`
|
||||
|
||||
yaml:
|
||||
|
||||
```yaml
|
||||
payload:
|
||||
- DOMAIN-SUFFIX,google.com
|
||||
- DOMAIN-KEYWORD,google
|
||||
- DOMAIN,ad.com
|
||||
- SRC-IP-CIDR,192.168.1.201/32
|
||||
- IP-CIDR,127.0.0.0/8
|
||||
- GEOIP,CN
|
||||
- DST-PORT,80
|
||||
- SRC-PORT,7777
|
||||
# MATCH is not necessary here
|
||||
```
|
||||
|
||||
text:
|
||||
|
||||
```txt
|
||||
# comment
|
||||
DOMAIN-SUFFIX,google.com
|
||||
DOMAIN-KEYWORD,google
|
||||
DOMAIN,ad.com
|
||||
SRC-IP-CIDR,192.168.1.201/32
|
||||
IP-CIDR,127.0.0.0/8
|
||||
GEOIP,CN
|
||||
DST-PORT,80
|
||||
SRC-PORT,7777
|
||||
```
|
59
docs/premium/script-shortcuts.md
Normal file
59
docs/premium/script-shortcuts.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebarTitle: "Feature: Script Shortcuts"
|
||||
sidebarOrder: 6
|
||||
---
|
||||
|
||||
# Script Shortcuts
|
||||
|
||||
Clash Premium implements the Scripting feature powered by Python3, enableing users to programmatically select policies for the packets with dynamic flexibility.
|
||||
|
||||
You can either controll the entire rule-matching engine with a single Python script, or define a number of shortcuts and use them in companion with the regular rules. This page refers to the latter feature. For the former, see [Script](./script.md).
|
||||
|
||||
This feature enables the use of script in `rules` mode. By default, DNS resolution takes place for SCRIPT rules. `no-resolve` can be appended to the rule to prevent the resolution. (i.e.: `SCRIPT,quic,DIRECT,no-resolve`)
|
||||
|
||||
```yaml
|
||||
mode: Rule
|
||||
|
||||
script:
|
||||
engine: expr # or starlark (10x to 20x slower)
|
||||
shortcuts:
|
||||
quic: network == 'udp' and dst_port == 443
|
||||
curl: resolve_process_name() == 'curl'
|
||||
# curl: resolve_process_path() == '/usr/bin/curl'
|
||||
|
||||
rules:
|
||||
- SCRIPT,quic,REJECT
|
||||
```
|
||||
|
||||
## Evaluation Engines
|
||||
|
||||
[Expr](https://expr.medv.io/) is used as the default engine for Script Shortcuts, offering 10x to 20x performance boost compared to Starlark.
|
||||
|
||||
[Starlark](https://github.com/google/starlark-go) is a Python-like langauge for configuration purposes, you can also use it for Script Shortcuts.
|
||||
|
||||
## Variables
|
||||
|
||||
- network: string
|
||||
- type: string
|
||||
- src_ip: string
|
||||
- dst_ip: string
|
||||
- src_port: uint16
|
||||
- dst_port: uint16
|
||||
- host: string
|
||||
- process_path: string
|
||||
|
||||
::: warning
|
||||
Starlark is missing `process_path` variable for now.
|
||||
:::
|
||||
|
||||
## Functions
|
||||
|
||||
```ts
|
||||
type resolve_ip = (host: string) => string // ip string
|
||||
type in_cidr = (ip: string, cidr: string) => boolean // ip in cidr
|
||||
type in_ipset = (name: string, ip: string) => boolean // ip in ipset
|
||||
type geoip = (ip: string) => string // country code
|
||||
type match_provider = (name: string) => boolean // in rule provider
|
||||
type resolve_process_name = () => string // find process name (curl .e.g)
|
||||
type resolve_process_path = () => string // find process path (/usr/bin/curl .e.g)
|
||||
```
|
70
docs/premium/script.md
Normal file
70
docs/premium/script.md
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
sidebarTitle: "Feature: Script"
|
||||
sidebarOrder: 5
|
||||
---
|
||||
|
||||
# Script
|
||||
|
||||
Clash Premium implements the Scripting feature powered by Python3, enableing users to programmatically select policies for the packets with dynamic flexibility.
|
||||
|
||||
You can either controll the entire rule-matching engine with a single Python script, or define a number of shortcuts and use them in companion with the regular rules. This page refers to the first feature, for the latter, see [Script Shortcuts](./script-shortcuts.md).
|
||||
|
||||
## Scripting the entire rule-matching engine
|
||||
|
||||
```yaml
|
||||
mode: Script
|
||||
|
||||
# https://lancellc.gitbook.io/clash/clash-config-file/script
|
||||
script:
|
||||
code: |
|
||||
def main(ctx, metadata):
|
||||
ip = metadata["dst_ip"] = ctx.resolve_ip(metadata["host"])
|
||||
if ip == "":
|
||||
return "DIRECT"
|
||||
|
||||
code = ctx.geoip(ip)
|
||||
if code == "LAN" or code == "CN":
|
||||
return "DIRECT"
|
||||
|
||||
return "Proxy" # default policy for requests which are not matched by any other script
|
||||
```
|
||||
|
||||
If you want to use ip rules (i.e.: IP-CIDR, GEOIP, etc), you will first need to manually resolve IP addresses and assign them to metadata:
|
||||
|
||||
```python
|
||||
def main(ctx, metadata):
|
||||
# ctx.rule_providers["geoip"].match(metadata) return false
|
||||
|
||||
ip = ctx.resolve_ip(metadata["host"])
|
||||
if ip == "":
|
||||
return "DIRECT"
|
||||
metadata["dst_ip"] = ip
|
||||
|
||||
# ctx.rule_providers["iprule"].match(metadata) return true
|
||||
|
||||
return "Proxy"
|
||||
```
|
||||
|
||||
Interface definition for Metadata and Context:
|
||||
|
||||
```ts
|
||||
interface Metadata {
|
||||
type: string // socks5、http
|
||||
network: string // tcp
|
||||
host: string
|
||||
src_ip: string
|
||||
src_port: string
|
||||
dst_ip: string
|
||||
dst_port: string
|
||||
}
|
||||
|
||||
interface Context {
|
||||
resolve_ip: (host: string) => string // ip string
|
||||
resolve_process_name: (metadata: Metadata) => string
|
||||
resolve_process_path: (metadata: Metadata) => string
|
||||
geoip: (ip: string) => string // country code
|
||||
log: (log: string) => void
|
||||
proxy_providers: Record<string, Array<{ name: string, alive: boolean, delay: number }>>
|
||||
rule_providers: Record<string, { match: (metadata: Metadata) => boolean }>
|
||||
}
|
||||
```
|
13
docs/premium/the-profiling-engine.md
Normal file
13
docs/premium/the-profiling-engine.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
sidebarTitle: "Feature: The Profiling Engine"
|
||||
sidebarOrder: 8
|
||||
---
|
||||
|
||||
# The Profiling Engine
|
||||
|
||||
https://github.com/Dreamacro/clash-tracing
|
||||
|
||||
```yaml
|
||||
profile:
|
||||
tracing: true
|
||||
```
|
65
docs/premium/tun-device.md
Normal file
65
docs/premium/tun-device.md
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
sidebarTitle: "Feature: TUN Device"
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# TUN Device
|
||||
|
||||
The Premium core has out-of-the-box support of TUN device. Being a Network layer device, it can be used to handle TCP, UDP, ICMP traffic. It has been extensively tested and used in production environments - you can even play competitive games with it.
|
||||
|
||||
One of the biggest advantage of using Clash TUN is the built-in support of the *automagic* management of the route table, routing rules and nftable. You can enable it with the options `tun.auto-route` and `tun.auto-redir`. It's a drop-in replacement of the ancient configuration option `redir-port` (TCP) for the sake of easier configuration and better stability.
|
||||
|
||||
::: tip
|
||||
`tun.auto-route` and `tun.auto-redir` are only available on macOS, Windows, Linux and Android, and only receives IPv4 traffic.
|
||||
:::
|
||||
|
||||
There are two options of TCP/IP stack available: `system` or `gvisor`. In order to get the best performance available, we recommend that you always use `system` stack unless you have a specific reason or compatibility issue to use `gvisor`. If that's the case, do not hesitate to [submit an issue](https://github.com/Dreamacro/clash/issues/new/choose).
|
||||
|
||||
## Technical Limitations
|
||||
|
||||
* For Android, the control device is at `/dev/tun` instead of `/dev/net/tun`, you will need to create a symbolic link first (i.e. `ln -sf /dev/tun /dev/net/tun`)
|
||||
|
||||
* DNS hijacking might result in a failure, if the system DNS is at a private IP address (since `auto-route` does not capture private network traffic).
|
||||
|
||||
## Linux, macOS or Android
|
||||
|
||||
This is an example configuration of the TUN feature:
|
||||
|
||||
```yaml
|
||||
interface-name: en0 # conflict with `tun.auto-detect-interface`
|
||||
|
||||
tun:
|
||||
enable: true
|
||||
stack: system # or gvisor
|
||||
# dns-hijack:
|
||||
# - 8.8.8.8:53
|
||||
# - tcp://8.8.8.8:53
|
||||
# - any:53
|
||||
# - tcp://any:53
|
||||
auto-route: true # manage `ip route` and `ip rules`
|
||||
auto-redir: true # manage nftable REDIRECT
|
||||
auto-detect-interface: true # conflict with `interface-name`
|
||||
```
|
||||
|
||||
Be advised, since the use of TUN device and manipulation of system route/nft settings, Clash will need superuser privileges to run.
|
||||
|
||||
```shell
|
||||
sudo ./clash
|
||||
```
|
||||
|
||||
If your device already has some TUN device, Clash TUN might not work - you will have to check the route table and routing rules manually. In this case, `fake-ip-filter` may helpful as well.
|
||||
|
||||
## Windows
|
||||
|
||||
You will need to visit the [WinTUN website](https://www.wintun.net) and download the latest release. After that, copy `wintun.dll` into Clash home directory. Example configuration:
|
||||
|
||||
```yaml
|
||||
tun:
|
||||
enable: true
|
||||
stack: gvisor # or system
|
||||
dns-hijack:
|
||||
- 198.18.0.2:53 # when `fake-ip-range` is 198.18.0.1/16, should hijack 198.18.0.2:53
|
||||
auto-route: true # auto set global route for Windows
|
||||
# It is recommended to use `interface-name`
|
||||
auto-detect-interface: true # auto detect interface, conflict with `interface-name`
|
||||
```
|
25
docs/premium/userspace-wireguard.md
Normal file
25
docs/premium/userspace-wireguard.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebarTitle: "Feature: Userspace Wireguard"
|
||||
sidebarOrder: 7
|
||||
---
|
||||
|
||||
# Userspace Wireguard
|
||||
|
||||
Due to the dependency on gvisor TCP/IP stack, Wireguard outbound is currently only available in the Premium core.
|
||||
|
||||
```yaml
|
||||
proxies:
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
server: 127.0.0.1
|
||||
port: 443
|
||||
ip: 172.16.0.2
|
||||
# ipv6: your_ipv6
|
||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||
# preshared-key: base64
|
||||
# remote-dns-resolve: true # remote resolve DNS with `dns` field, default is true
|
||||
# dns: [1.1.1.1, 8.8.8.8]
|
||||
# mtu: 1420
|
||||
udp: true
|
||||
```
|
1
docs/public/logo.png
Symbolic link
1
docs/public/logo.png
Symbolic link
@ -0,0 +1 @@
|
||||
../logo.png
|
130
docs/runtime/external-controller.md
Normal file
130
docs/runtime/external-controller.md
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
sidebarTitle: The External Controller
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
# The External Controller
|
||||
|
||||
## Introduction
|
||||
|
||||
External Controller enables users to control Clash programmatically with the HTTP RESTful API. The third-party Clash GUIs are heavily based on this feature. Enable this feature by specifying an address in `external-controller`.
|
||||
|
||||
## Authentication
|
||||
|
||||
- External Controllers Accept `Bearer Tokens` as access authentication method.
|
||||
- Use `Authorization: Bearer <Your Secret>` as your request header in order to pass credentials.
|
||||
|
||||
## RESTful API Documentation
|
||||
|
||||
### Logs
|
||||
|
||||
- `/logs`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /logs`
|
||||
- Description: Get real-time logs
|
||||
|
||||
### Traffic
|
||||
|
||||
- `/traffic`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /traffic`
|
||||
- Description: Get real-time traffic data
|
||||
|
||||
### Version
|
||||
|
||||
- `/version`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /version`
|
||||
- Description: Get clash version
|
||||
|
||||
### Configs
|
||||
|
||||
- `/configs`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /configs`
|
||||
- Description: Get base configs
|
||||
|
||||
- Method: `PUT`
|
||||
- Full Path: `PUT /configs`
|
||||
- Description: Reloading base configs
|
||||
|
||||
- Method: `PATCH`
|
||||
- Full Path: `PATCH /configs`
|
||||
- Description: Update base configs
|
||||
|
||||
### Proxies
|
||||
|
||||
- `/proxies`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /proxies`
|
||||
- Description: Get proxies information
|
||||
|
||||
- `/proxies/:name`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /proxies/:name`
|
||||
- Description: Get specific proxy information
|
||||
|
||||
- Method: `PUT`
|
||||
- Full Path: `PUT /proxies/:name`
|
||||
- Description: Select specific proxy
|
||||
|
||||
- `/proxies/:name/delay`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /proxies/:name/delay`
|
||||
- Description: Get specific proxy delay test information
|
||||
|
||||
### Rules
|
||||
|
||||
- `/rules`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /rules`
|
||||
- Description: Get rules information
|
||||
|
||||
### Connections
|
||||
|
||||
- `/connections`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /connections`
|
||||
- Description: Get connections information
|
||||
|
||||
- Method: `DELETE`
|
||||
- Full Path: `DELETE /connections`
|
||||
- Description: Close all connections
|
||||
|
||||
- `/connections/:id`
|
||||
- Method: `DELETE`
|
||||
- Full Path: `DELETE /connections/:id`
|
||||
- Description: Close specific connection
|
||||
|
||||
### Providers
|
||||
|
||||
- `/providers/proxies`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /providers/proxies`
|
||||
- Description: Get all proxies information for all proxy-providers
|
||||
|
||||
- `/providers/proxies/:name`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /providers/proxies/:name`
|
||||
- Description: Get proxies information for specific proxy-provider
|
||||
|
||||
- Method: `PUT`
|
||||
- Full Path: `PUT /providers/proxies/:name`
|
||||
- Description: Select specific proxy-provider
|
||||
|
||||
- `/providers/proxies/:name/healthcheck`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /providers/proxies/:name/healthcheck`
|
||||
- Description: Get proxies information for specific proxy-provider
|
||||
|
||||
### DNS Query
|
||||
|
||||
- `/dns/query`
|
||||
- Method: `GET`
|
||||
- Full Path: `GET /dns/query?name={name}[&type={type}]`
|
||||
- Description: Get DNS query data for a specified name and type.
|
||||
- Parameters:
|
||||
- `name` (required): The domain name to query.
|
||||
- `type` (optional): The DNS record type to query (e.g., A, MX, CNAME, etc.). Defaults to `A` if not provided.
|
||||
|
||||
- Example: `GET /dns/query?name=example.com&type=A`
|
59
docs/zh_CN/advanced-usages/golang-api.md
Normal file
59
docs/zh_CN/advanced-usages/golang-api.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebarTitle: 在 Golang 程序中集成 Clash
|
||||
sidebarOrder: 3
|
||||
---
|
||||
|
||||
# 在 Golang 程序中集成 Clash
|
||||
|
||||
如果 Clash 不能满足您的需求, 您可以在自己的 Golang 代码中使用 Clash.
|
||||
|
||||
目前已经有基本的支持:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
)
|
||||
|
||||
func main() {
|
||||
in := make(chan constant.ConnContext, 100)
|
||||
defer close(in)
|
||||
|
||||
l, err := socks.New("127.0.0.1:10000", in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
println("listen at:", l.Address())
|
||||
|
||||
direct := outbound.NewDirect()
|
||||
|
||||
for c := range in {
|
||||
conn := c
|
||||
metadata := conn.Metadata()
|
||||
fmt.Printf("请求从 %s 传入到 %s\n", metadata.SourceAddress(), metadata.RemoteAddress())
|
||||
go func () {
|
||||
remote, err := direct.DialContext(context.Background(), metadata)
|
||||
if err != nil {
|
||||
fmt.Printf("Dial 错误: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
relay(remote, conn.Conn())
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func relay(l, r net.Conn) {
|
||||
go io.Copy(l, r)
|
||||
io.Copy(r, l)
|
||||
}
|
||||
```
|
102
docs/zh_CN/advanced-usages/openconnect.md
Normal file
102
docs/zh_CN/advanced-usages/openconnect.md
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
sidebarTitle: 基于规则的 OpenConnect
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# 基于规则的 OpenConnect
|
||||
|
||||
支持以下 OpenConnect:
|
||||
|
||||
- Cisco AnyConnect SSL VPN
|
||||
- Juniper Network Connect
|
||||
- Palo Alto Networks (PAN) GlobalProtect SSL VPN
|
||||
- Pulse Connect Secure SSL VPN
|
||||
- F5 BIG-IP SSL VPN
|
||||
- FortiGate SSL VPN
|
||||
- Array Networks SSL VPN
|
||||
|
||||
例如, 您的公司使用 Cisco AnyConnect 作为内部网络访问的方式. 这里我将向您展示如何使用 Clash 提供的策略路由来使用 OpenConnect.
|
||||
|
||||
首先, [安装 vpn-slice](https://github.com/dlenski/vpn-slice#requirements). 这个工具会覆写 OpenConnect 的默认路由表行为. 简单来说, 它会阻止 VPN 覆写您的默认路由.
|
||||
|
||||
接下来您需要一个脚本 (比如 `tun0.sh`) 类似于这样:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
ANYCONNECT_HOST="vpn.example.com"
|
||||
ANYCONNECT_USER="john"
|
||||
ANYCONNECT_PASSWORD="foobar"
|
||||
ROUTING_TABLE_ID="6667"
|
||||
TUN_INTERFACE="tun0"
|
||||
|
||||
# 如果服务器在中国大陆, 请添加 --no-dtls. 中国大陆的 UDP 会很卡.
|
||||
echo "$ANYCONNECT_PASSWORD" | \
|
||||
openconnect \
|
||||
--non-inter \
|
||||
--passwd-on-stdin \
|
||||
--protocol=anyconnect \
|
||||
--interface $TUN_INTERFACE \
|
||||
--script "vpn-slice
|
||||
if [ \"\$reason\" = 'connect' ]; then
|
||||
ip rule add from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
|
||||
ip route add default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
|
||||
elif [ \"\$reason\" = 'disconnect' ]; then
|
||||
ip rule del from \$INTERNAL_IP4_ADDRESS table $ROUTING_TABLE_ID
|
||||
ip route del default dev \$TUNDEV scope link table $ROUTING_TABLE_ID
|
||||
fi" \
|
||||
--user $ANYCONNECT_USER \
|
||||
https://$ANYCONNECT_HOST
|
||||
```
|
||||
|
||||
之后, 我们将其配置成一个 systemd 服务. 创建 `/etc/systemd/system/tun0.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Cisco AnyConnect VPN
|
||||
After=network-online.target
|
||||
Conflicts=shutdown.target sleep.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/path/to/tun0.sh
|
||||
KillSignal=SIGINT
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
然后我们启用并启动服务.
|
||||
|
||||
```shell
|
||||
chmod +x /path/to/tun0.sh
|
||||
systemctl daemon-reload
|
||||
systemctl enable tun0
|
||||
systemctl start tun0
|
||||
```
|
||||
|
||||
这里您可以查看日志来查看它是否正常运行. 简单的方法是查看 `tun0` 接口是否已经创建.
|
||||
|
||||
和 Wireguard 类似, 将 TUN 设备作为出站很简单, 只需要添加一个策略组:
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: Cisco AnyConnect VPN
|
||||
type: select
|
||||
interface-name: tun0
|
||||
proxies:
|
||||
- DIRECT
|
||||
```
|
||||
|
||||
... 然后就可以使用了!
|
||||
|
||||
添加您想要的规则:
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- DOMAIN-SUFFIX,internal.company.com,Cisco AnyConnect VPN
|
||||
```
|
||||
|
||||
当您发现有问题时, 您应该查看 debug 级别的日志.
|
40
docs/zh_CN/advanced-usages/wireguard.md
Normal file
40
docs/zh_CN/advanced-usages/wireguard.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
sidebarTitle: 基于规则的 Wireguard
|
||||
sidebarOrder: 1
|
||||
---
|
||||
|
||||
# 基于规则的 Wireguard
|
||||
|
||||
假设您的内核支持 Wireguard 并且您已经启用了它. `Table` 选项可以阻止 _wg-quick_ 覆写默认路由.
|
||||
|
||||
例如 `wg0.conf`:
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = ...
|
||||
Address = 172.16.0.1/32
|
||||
MTU = ...
|
||||
Table = off
|
||||
PostUp = ip rule add from 172.16.0.1/32 table 6666
|
||||
|
||||
[Peer]
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
AllowedIPs = ::/0
|
||||
PublicKey = ...
|
||||
Endpoint = ...
|
||||
```
|
||||
|
||||
然后在 Clash 中您只需要有一个 DIRECT 策略组, 它包含一个指定的出站接口:
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: Wireguard
|
||||
type: select
|
||||
interface-name: wg0
|
||||
proxies:
|
||||
- DIRECT
|
||||
rules:
|
||||
- DOMAIN,google.com,Wireguard
|
||||
```
|
||||
|
||||
这通常比 Clash 自己实现的用户空间 Wireguard 客户端性能更好. Wireguard 在内核中支持.
|
476
docs/zh_CN/configuration/configuration-reference.md
Normal file
476
docs/zh_CN/configuration/configuration-reference.md
Normal file
@ -0,0 +1,476 @@
|
||||
---
|
||||
sidebarTitle: 参考配置
|
||||
sidebarOrder: 7
|
||||
---
|
||||
|
||||
# 参考配置
|
||||
|
||||
```yaml
|
||||
# HTTP(S) 代理服务端口
|
||||
port: 7890
|
||||
|
||||
# SOCKS5 代理服务端口
|
||||
socks-port: 7891
|
||||
|
||||
# Linux 和 macOS 的透明代理服务端口 (TCP 和 TProxy UDP 重定向)
|
||||
# redir-port: 7892
|
||||
|
||||
# Linux 的透明代理服务端口 (TProxy TCP 和 TProxy UDP)
|
||||
# tproxy-port: 7893
|
||||
|
||||
# HTTP(S) 和 SOCKS4(A)/SOCKS5 代理服务共用一个端口
|
||||
# mixed-port: 7890
|
||||
|
||||
# 本地 SOCKS5/HTTP(S) 代理服务的认证
|
||||
# authentication:
|
||||
# - "user1:pass1"
|
||||
# - "user2:pass2"
|
||||
|
||||
# 设置为 true 以允许来自其他 LAN IP 地址的连接
|
||||
# allow-lan: false
|
||||
|
||||
# 仅当 `allow-lan` 为 `true` 时有效
|
||||
# '*': 绑定所有 IP 地址
|
||||
# 192.168.122.11: 绑定单个 IPv4 地址
|
||||
# "[aaaa::a8aa:ff:fe09:57d8]": 绑定单个 IPv6 地址
|
||||
# bind-address: '*'
|
||||
|
||||
# Clash 路由工作模式
|
||||
# rule: 基于规则的数据包路由
|
||||
# global: 所有数据包将被转发到单个节点
|
||||
# direct: 直接将数据包转发到互联网
|
||||
mode: rule
|
||||
|
||||
# 默认情况下, Clash 将日志打印到 STDOUT
|
||||
# 日志级别: info / warning / error / debug / silent
|
||||
# log-level: info
|
||||
|
||||
# 当设置为 false 时, 解析器不会将主机名解析为 IPv6 地址
|
||||
# ipv6: false
|
||||
|
||||
# RESTful Web API 监听地址
|
||||
external-controller: 127.0.0.1:9090
|
||||
|
||||
# 配置目录的相对路径或静态 Web 资源目录的绝对路径. Clash core 将在
|
||||
# `http://{{external-controller}}/ui` 中提供服务.
|
||||
# external-ui: folder
|
||||
|
||||
# RESTful API 密钥 (可选)
|
||||
# 通过指定 HTTP 头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||
# 如果RESTful API在 0.0.0.0 上监听, 务必设置一个 secret 密钥.
|
||||
# secret: ""
|
||||
|
||||
# 出站接口名称
|
||||
# interface-name: en0
|
||||
|
||||
# fwmark (仅在 Linux 上有效)
|
||||
# routing-mark: 6666
|
||||
|
||||
# 用于DNS服务器和连接建立的静态主机 (如/etc/hosts) .
|
||||
#
|
||||
# 支持通配符主机名 (例如 *.clash.dev, *.foo.*.example.com)
|
||||
# 非通配符域名优先级高于通配符域名
|
||||
# 例如 foo.example.com > *.example.com > .example.com
|
||||
# P.S. +.foo.com 等于 .foo.com 和 foo.com
|
||||
# hosts:
|
||||
# '*.clash.dev': 127.0.0.1
|
||||
# '.dev': 127.0.0.1
|
||||
# 'alpha.clash.dev': '::1'
|
||||
|
||||
# profile:
|
||||
# 将 `select` 手动选择 结果存储在 $HOME/.config/clash/.cache 中
|
||||
# 如果不需要此行为, 请设置为 false
|
||||
# 当两个不同的配置具有同名的组时, 将共享所选值
|
||||
# store-selected: true
|
||||
|
||||
# 持久化 fakeip
|
||||
# store-fake-ip: false
|
||||
|
||||
# DNS 服务设置
|
||||
# 此部分是可选的. 当不存在时, DNS 服务将被禁用.
|
||||
dns:
|
||||
enable: false
|
||||
listen: 0.0.0.0:53
|
||||
# ipv6: false # 当为 false 时, AAAA 查询的响应将为空
|
||||
|
||||
# 这些 名称服务器(nameservers) 用于解析下列 DNS 名称服务器主机名.
|
||||
# 仅指定 IP 地址
|
||||
default-nameserver:
|
||||
- 114.114.114.114
|
||||
- 8.8.8.8
|
||||
# enhanced-mode: fake-ip
|
||||
fake-ip-range: 198.18.0.1/16 # Fake IP 地址池 CIDR
|
||||
# use-hosts: true # 查找 hosts 并返回 IP 记录
|
||||
|
||||
# search-domains: [local] # A/AAAA 记录的搜索域
|
||||
|
||||
# 此列表中的主机名将不会使用 Fake IP 解析
|
||||
# 即, 对这些域名的请求将始终使用其真实 IP 地址进行响应
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
# 支持 UDP、TCP、DoT、DoH. 您可以指定要连接的端口.
|
||||
# 所有 DNS 查询都直接发送到名称服务器, 无需代理
|
||||
# Clash 使用第一个收到的响应作为 DNS 查询的结果.
|
||||
nameserver:
|
||||
- 114.114.114.114 # 默认值
|
||||
- 8.8.8.8 # 默认值
|
||||
- tls://dns.rubyfish.cn:853 # DNS over TLS
|
||||
- https://1.1.1.1/dns-query # DNS over HTTPS
|
||||
- dhcp://en0 # 来自 dhcp 的 dns
|
||||
# - '8.8.8.8#en0'
|
||||
|
||||
# 当 `fallback` 存在时, DNS 服务器将向此部分中的服务器
|
||||
# 与 `nameservers` 中的服务器发送并发请求
|
||||
# 当 GEOIP 国家不是 `CN` 时, 将使用 fallback 服务器的响应
|
||||
# fallback:
|
||||
# - tcp://1.1.1.1
|
||||
# - 'tcp://1.1.1.1#en0'
|
||||
|
||||
# 如果使用 `nameservers` 解析的 IP 地址在下面指定的子网中,
|
||||
# 则认为它们无效, 并使用 `fallback` 服务器的结果.
|
||||
#
|
||||
# 当 `fallback-filter.geoip` 为 true 且 IP 地址的 GEOIP 为 `CN` 时,
|
||||
# 将使用 `nameservers` 服务器解析的 IP 地址.
|
||||
#
|
||||
# 如果 `fallback-filter.geoip` 为 false, 且不匹配 `fallback-filter.ipcidr`,
|
||||
# 则始终使用 `nameservers` 服务器的结果
|
||||
#
|
||||
# 这是对抗 DNS 污染攻击的一种措施.
|
||||
# fallback-filter:
|
||||
# geoip: true
|
||||
# geoip-code: CN
|
||||
# ipcidr:
|
||||
# - 240.0.0.0/4
|
||||
# domain:
|
||||
# - '+.google.com'
|
||||
# - '+.facebook.com'
|
||||
# - '+.youtube.com'
|
||||
|
||||
# 通过特定的名称服务器查找域名
|
||||
# nameserver-policy:
|
||||
# 'www.baidu.com': '114.114.114.114'
|
||||
# '+.internal.crop.com': '10.0.0.1'
|
||||
|
||||
proxies:
|
||||
# Shadowsocks
|
||||
# 支持的加密方法:
|
||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||
# aes-128-cfb aes-192-cfb aes-256-cfb
|
||||
# aes-128-ctr aes-192-ctr aes-256-ctr
|
||||
# rc4-md5 chacha20-ietf xchacha20
|
||||
# chacha20-ietf-poly1305 xchacha20-ietf-poly1305
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
# udp: true
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: obfs
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # 暂不支持 QUIC
|
||||
# tls: true # wss
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
# vmess
|
||||
# 支持的加密方法:
|
||||
# auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# servername: example.com # 优先于 wss 主机
|
||||
# network: ws
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: h2
|
||||
tls: true
|
||||
h2-opts:
|
||||
host:
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
# udp: true
|
||||
# network: http
|
||||
# http-opts:
|
||||
# # method: "GET"
|
||||
# # path:
|
||||
# # - '/'
|
||||
# # - '/video'
|
||||
# # headers:
|
||||
# # Connection:
|
||||
# # - keep-alive
|
||||
|
||||
- name: vmess-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: vmess
|
||||
uuid: uuid
|
||||
alterId: 32
|
||||
cipher: auto
|
||||
network: grpc
|
||||
tls: true
|
||||
servername: example.com
|
||||
# skip-cert-verify: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
# socks5
|
||||
- name: "socks"
|
||||
type: socks5
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
server: server
|
||||
port: 443
|
||||
# username: username
|
||||
# password: password
|
||||
# tls: true # https
|
||||
# skip-cert-verify: true
|
||||
# sni: custom.com
|
||||
|
||||
# Snell
|
||||
# 请注意, 目前还没有UDP支持.
|
||||
- name: "snell"
|
||||
type: snell
|
||||
server: server
|
||||
port: 44046
|
||||
psk: yourpsk
|
||||
# version: 2
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
server: server
|
||||
port: 443
|
||||
password: yourpsk
|
||||
# udp: true
|
||||
# sni: example.com # aka 服务器名称
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: grpc
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
type: trojan
|
||||
password: "example"
|
||||
network: ws
|
||||
sni: example.com
|
||||
# skip-cert-verify: true
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
# ShadowsocksR
|
||||
# 支持的加密方法: ss 中的所有流加密方法
|
||||
# 支持的混淆方式:
|
||||
# plain http_simple http_post
|
||||
# random_head tls1.2_ticket_auth tls1.2_ticket_fastauth
|
||||
# 支持的协议:
|
||||
# origin auth_sha1_v4 auth_aes128_md5
|
||||
# auth_aes128_sha1 auth_chain_a auth_chain_b
|
||||
- name: "ssr"
|
||||
type: ssr
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf
|
||||
password: "password"
|
||||
obfs: tls1.2_ticket_auth
|
||||
protocol: auth_sha1_v4
|
||||
# obfs-param: domain.tld
|
||||
# protocol-param: "#"
|
||||
# udp: true
|
||||
|
||||
proxy-groups:
|
||||
# 中继链路代理节点. 节点不应包含中继. 不支持 UDP.
|
||||
# 流量节点链路: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||
- name: "relay"
|
||||
type: relay
|
||||
proxies:
|
||||
- http
|
||||
- vmess
|
||||
- ss1
|
||||
- ss2
|
||||
|
||||
# url-test 通过对 指定URL 进行基准速度测试来选择将使用哪个代理.
|
||||
- name: "auto"
|
||||
type: url-test
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
# tolerance: 150
|
||||
# lazy: true
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
|
||||
# fallback-auto 基于优先级选择可用策略. 可用性通过访问 指定URL 来测试, 就像自动 url-test 组一样.
|
||||
- name: "fallback-auto"
|
||||
type: fallback
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
|
||||
# 负载均衡: 同一 eTLD+1 的请求将拨号到同一代理.
|
||||
- name: "load-balance"
|
||||
type: load-balance
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
url: 'http://www.gstatic.com/generate_204'
|
||||
interval: 300
|
||||
# strategy: consistent-hashing # or round-robin
|
||||
|
||||
# select 手动选择, 用于选择代理或策略组
|
||||
# 您可以使用 RESTful API 来切换代理, 建议在GUI中切换.
|
||||
- name: Proxy
|
||||
type: select
|
||||
# disable-udp: true
|
||||
# filter: 'someregex'
|
||||
proxies:
|
||||
- ss1
|
||||
- ss2
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
# 直接连接到另一个接口名称或 fwmark, 也支持代理
|
||||
- name: en1
|
||||
type: select
|
||||
interface-name: en1
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
use:
|
||||
- provider1
|
||||
proxies:
|
||||
- Proxy
|
||||
- DIRECT
|
||||
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
# lazy: true
|
||||
url: http://www.gstatic.com/generate_204
|
||||
test:
|
||||
type: file
|
||||
path: /test.yaml
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 36000
|
||||
url: http://www.gstatic.com/generate_204
|
||||
|
||||
tunnels:
|
||||
# 单行配置
|
||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||
# 全 yaml 配置
|
||||
- network: [tcp, udp]
|
||||
address: 127.0.0.1:7777
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
rules:
|
||||
- DOMAIN-SUFFIX,google.com,auto
|
||||
- DOMAIN-KEYWORD,google,auto
|
||||
- DOMAIN,google.com,auto
|
||||
- DOMAIN-SUFFIX,ad.com,REJECT
|
||||
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
|
||||
# 用于 IP 规则 (GEOIP, IP-CIDR, IP-CIDR6) 的可选参数 "no-resolve"
|
||||
- IP-CIDR,127.0.0.0/8,DIRECT
|
||||
- GEOIP,CN,DIRECT
|
||||
- DST-PORT,80,DIRECT
|
||||
- SRC-PORT,7777,DIRECT
|
||||
- RULE-SET,apple,REJECT # 仅 Premium 版本支持
|
||||
- MATCH,auto
|
||||
```
|
72
docs/zh_CN/configuration/dns.md
Normal file
72
docs/zh_CN/configuration/dns.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
sidebarTitle: Clash DNS
|
||||
sidebarOrder: 6
|
||||
---
|
||||
|
||||
# Clash DNS
|
||||
|
||||
由于 Clash 的某些部分运行在第 3 层 (网络层) , 因此其数据包的域名是无法获取的, 也就无法进行基于规则的路由.
|
||||
|
||||
*Enter fake-ip*: 它支持基于规则的路由, 最大程度地减少了 DNS 污染攻击的影响, 并且提高了网络性能, 有时甚至是显著的.
|
||||
|
||||
## fake-ip
|
||||
|
||||
"fake IP" 的概念源自 [RFC 3089](https://tools.ietf.org/rfc/rfc3089):
|
||||
|
||||
> 一个 "fake IP" 地址被用于查询相应的 "FQDN" 信息的关键字.
|
||||
|
||||
fake-ip 池的默认 CIDR 是 `198.18.0.1/16` (一个保留的 IPv4 地址空间, 可以在 `dns.fake-ip-range` 中进行更改).
|
||||
|
||||
当 DNS 请求被发送到 Clash DNS 时, Clash 内核会通过管理内部的域名和其 fake-ip 地址的映射, 从池中分配一个 *空闲* 的 fake-ip 地址.
|
||||
|
||||
以使用浏览器访问 `http://google.com` 为例.
|
||||
|
||||
1. 浏览器向 Clash DNS 请求 `google.com` 的 IP 地址
|
||||
2. Clash 检查内部映射并返回 `198.18.1.5`
|
||||
3. 浏览器向 `198.18.1.5` 的 `80/tcp` 端口发送 HTTP 请求
|
||||
4. 当收到 `198.18.1.5` 的入站数据包时, Clash 查询内部映射, 发现客户端实际上是在向 `google.com` 发送数据包
|
||||
5. 根据规则的不同:
|
||||
|
||||
1. Clash 可能仅将域名发送到 SOCKS5 或 shadowsocks 等出站代理, 并与代理服务器建立连接
|
||||
|
||||
2. 或者 Clash 可能会基于 `SCRIPT`、`GEOIP`、`IP-CIDR` 规则或者使用 DIRECT 直连出口查询 `google.com` 的真实 IP 地址
|
||||
|
||||
由于这是一个令人困惑的概念, 我将以使用 cURL 程序访问 `http://google.com` 为例:
|
||||
|
||||
```txt{2,3,5,6,8,9}
|
||||
$ curl -v http://google.com
|
||||
<---- cURL 向您的系统 DNS (Clash) 询问 google.com 的 IP 地址
|
||||
----> Clash 决定使用 198.18.1.70 作为 google.com 的 IP 地址, 并记住它
|
||||
* Trying 198.18.1.70:80...
|
||||
<---- cURL 连接到 198.18.1.70 tcp/80
|
||||
----> Clash 将立即接受连接, 并且..
|
||||
* Connected to google.com (198.18.1.70) port 80 (#0)
|
||||
----> Clash 在其内存中查找到 198.18.1.70 对应于 google.com
|
||||
----> Clash 查询对应的规则, 并通过匹配的出口发送数据包
|
||||
> GET / HTTP/1.1
|
||||
> Host: google.com
|
||||
> User-Agent: curl/8.0.1
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 301 Moved Permanently
|
||||
< Location: http://www.google.com/
|
||||
< Content-Type: text/html; charset=UTF-8
|
||||
< Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-ahELFt78xOoxhySY2lQ34A' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
|
||||
< Date: Thu, 11 May 2023 06:52:19 GMT
|
||||
< Expires: Sat, 10 Jun 2023 06:52:19 GMT
|
||||
< Cache-Control: public, max-age=2592000
|
||||
< Server: gws
|
||||
< Content-Length: 219
|
||||
< X-XSS-Protection: 0
|
||||
< X-Frame-Options: SAMEORIGIN
|
||||
<
|
||||
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<TITLE>301 Moved</TITLE></HEAD><BODY>
|
||||
<H1>301 Moved</H1>
|
||||
The document has moved
|
||||
<A HREF="http://www.google.com/">here</A>.
|
||||
</BODY></HTML>
|
||||
* Connection #0 to host google.com left intact
|
||||
```
|
||||
|
||||
<!-- TODO: nameserver, fallback, fallback-filter, hosts, search-domains, fake-ip-filter, nameserver-policy -->
|
76
docs/zh_CN/configuration/getting-started.md
Normal file
76
docs/zh_CN/configuration/getting-started.md
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
sidebarTitle: 快速入手
|
||||
sidebarOrder: 2
|
||||
---
|
||||
|
||||
# 快速入手
|
||||
|
||||
建议您在继续阅读本节之前, 先阅读[介绍](/zh_CN/configuration/introduction). 在您对Clash的工作原理有了简单的了解后, 您可以开始编写您自己的配置.
|
||||
|
||||
## 配置文件
|
||||
|
||||
主配置文件名为 `config.yaml`. 默认情况下, Clash会在 `$HOME/.config/clash` 目录读取配置文件. 如果该目录不存在, Clash会在该位置生成一个最小的配置文件.
|
||||
|
||||
如果您想将配置文件放在其他地方 (例如 `/etc/clash`) , 您可以使用命令行选项 `-d` 来指定配置目录:
|
||||
|
||||
```shell
|
||||
clash -d . # current directory
|
||||
clash -d /etc/clash
|
||||
```
|
||||
|
||||
或者, 您可以使用选项 `-f` 来指定配置文件:
|
||||
|
||||
```shell
|
||||
clash -f ./config.yaml
|
||||
clash -f /etc/clash/config.yaml
|
||||
```
|
||||
|
||||
## 特殊语法
|
||||
|
||||
Clash 配置文件中有一些特殊的语法, 您可能需要了解:
|
||||
|
||||
### IPv6 地址
|
||||
|
||||
您应该使用方括号 (`[]`) 来包裹 IPv6 地址, 例如:
|
||||
|
||||
```txt
|
||||
[aaaa::a8aa:ff:fe09:57d8]
|
||||
```
|
||||
|
||||
### DNS 通配符域名匹配
|
||||
|
||||
在某些情况下, 您需要匹配通配符域名. 例如, 当您设置 [Clash DNS](/zh_CN/configuration/dns) 时, 您可能想要匹配 `localdomain` 的所有子域名.
|
||||
|
||||
Clash 在 DNS 配置中提供了匹配不同级别通配符域名的支持, 其语法如下:
|
||||
|
||||
::: tip
|
||||
任何包含这些字符的域名都应该用单引号 (`'`) 包裹. 例如, `'*.google.com'`.
|
||||
静态域名的优先级高于通配符域名 (foo.example.com > *.example.com > .example.com) .
|
||||
:::
|
||||
|
||||
使用星号 (`*`) 来匹配单级通配符子域名.
|
||||
|
||||
| 表达式 | 匹配 | 不匹配 |
|
||||
| ---------- | ------- | -------------- |
|
||||
| `*.google.com` | `www.google.com` | `google.com` |
|
||||
| `*.bar.google.com` | `foo.bar.google.com` | `bar.google.com` |
|
||||
| `*.*.google.com` | `thoughtful.sandbox.google.com` | `one.two.three.google.com` |
|
||||
|
||||
使用点号 (`.`) 来匹配多级通配符子域名.
|
||||
|
||||
| 表达式 | 匹配 | 不匹配 |
|
||||
| ---------- | ------- | -------------- |
|
||||
| `.google.com` | `www.google.com` | `google.com` |
|
||||
| `.google.com` | `thoughtful.sandbox.google.com` | `google.com` |
|
||||
| `.google.com` | `one.two.three.google.com` | `google.com` |
|
||||
|
||||
使用加号 (`+`) 来匹配多级通配符子域名.
|
||||
|
||||
`+` 通配符的工作方式类似于 `DOMAIN-SUFFIX`, 您可以一次进行多级的快速匹配.
|
||||
|
||||
| 表达式 | 匹配 |
|
||||
| ---------- | ------- |
|
||||
| `+.google.com` | `google.com` |
|
||||
| `+.google.com` | `www.google.com` |
|
||||
| `+.google.com` | `thoughtful.sandbox.google.com` |
|
||||
| `+.google.com` | `one.two.three.google.com` |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user