Compare commits

...

116 Commits

Author SHA1 Message Date
80fabce99c fix: config override error 2023-11-01 20:49:58 +08:00
347e606853 chore: add optional provider path 2023-10-23 16:52:21 +08:00
7d58238e06 fix: find pkg not work 2023-10-23 16:52:21 +08:00
a883faae9d chore: Try to restore process rules 2023-10-23 16:52:21 +08:00
a7040af346 Android: patch 2023-10-23 16:52:19 +08:00
5cca3569e9 chore: cleanup unneed patch 2023-10-23 16:51:51 +08:00
f2857b3243 fix: inbound metadata 2023-10-23 16:51:48 +08:00
8e84210852 fix: missing config member in patch 2023-10-23 16:50:48 +08:00
8c2df79536 chore: Update default Geodata URL 2023-10-23 16:50:48 +08:00
ebe33cff18 fix: GeoIP side-load 2023-10-23 16:50:48 +08:00
a551d7ebd9 Android: patch 2023-10-23 16:50:43 +08:00
79a29826a4 Android: patch 2023-10-23 16:46:00 +08:00
3564e96a00 chore: share some code 2023-10-23 16:45:22 +08:00
f6f8f27668 action: update sync 2023-10-23 15:39:56 +08:00
dff54464c6 Add auto sync Alpha rebase android-open -> android-real (#817)
* chore: add android branch auto sync

* chore: fix

* chore: fix missing

* chore: fix actions

* chore: write branch auto sync
2023-10-23 15:39:56 +08:00
e987cdaaae chore: add CMFA auto update-dependencies trigger 2023-10-23 15:39:56 +08:00
6cd0e58fd0 fix: ssr panic 2023-10-23 15:39:56 +08:00
f794c090a5 chore: update sing-tun 2023-10-23 15:39:56 +08:00
0d3197e437 chore: fix sniffer log error 2023-10-20 22:36:29 +08:00
150bf7fc65 chore: decrease memory copy in sing listener 2023-10-20 08:39:04 +08:00
51004b14d9 docs: update readme.md 2023-10-20 00:34:10 +08:00
ea7e15b447 chore: decrease memory copy in quic sniffer 2023-10-19 23:51:37 +08:00
8e637a2ec7 chore: code cleanup 2023-10-19 20:44:49 +08:00
96d886380a Merge pull request #810 from 5aaee9/Alpha
feat: add quic sniffer
2023-10-19 19:34:45 +08:00
981c69040f docs: update about quic sniffer 2023-10-19 19:09:13 +08:00
de90c276af feat(sniffer): add quic sniffer 2023-10-19 18:30:20 +08:00
0129a8579f chore: merge some quic-go fix 2023-10-19 11:08:14 +08:00
11ed4a56bd chore: code cleanup 2023-10-17 12:46:41 +08:00
d75a0e69a0 chore: Update dependencies 2023-10-16 09:56:41 +08:00
1faad73381 fix: socks5 udp associate 2023-10-16 09:27:55 +08:00
d2499cd69d feature: add xdg base support (#2913) 2023-10-16 09:23:31 +08:00
98df77439c feature: add environs startup option support (#2909) 2023-10-16 09:22:16 +08:00
81bbbe4eec fix: DNS NCACHE TTL and OPT RRs (#2900)
* Fix: DNS NCACHE TTL and OPT RRs

1. DNS NCACHE was not correctly implemented.
2. OPT RRs must not be cached or forwarded.

Closes #2889.
2023-10-16 09:21:06 +08:00
9f530525d7 fix: method in vmess http-opts is not used 2023-10-16 09:16:36 +08:00
129283066f chore: code cleanup 2023-10-11 22:54:19 +08:00
0dc6a726c1 fix: unmap 4in6 ip 2023-10-11 18:17:39 +08:00
4636499439 chore: support reject proxy type 2023-10-11 13:01:14 +08:00
9a16eb2895 fix: BBR memory leak
from: 7c46e845a6
2023-10-11 11:01:17 +08:00
270a080b55 fix: sing listener panic 2023-10-11 10:55:12 +08:00
1cf9a55e3e chore: code cleanup 2023-10-10 21:29:12 +08:00
6bcd91a801 feat: add skip-auth-prefixes 2023-10-10 21:29:12 +08:00
7ed25ddc74 chore: better atomic using 2023-10-10 21:28:46 +08:00
ae557c30d3 fix: quic-go min MTU 2023-10-08 13:15:17 +08:00
5a1800d642 fix: BBR bandwidth estimation edge case
from 89429598bf
2023-10-08 07:26:28 +08:00
d8fe7a52d6 feat: add certificate and private-key to vmess listener 2023-10-08 07:26:28 +08:00
791ecfbb32 feat: add ws-path to vmess listener 2023-10-08 07:26:28 +08:00
5ff4473083 chore: migrate from gorilla/websocket to gobwas/ws 2023-10-06 17:44:36 +08:00
d1e88a30cb fix: gVisor UDP 6to4 check 2023-10-03 16:00:03 +08:00
7eae7756f5 chore: update gvisor 2023-10-01 19:15:26 +08:00
4e3cd01aad chore: merge some quic-go fix 2023-10-01 13:44:56 +08:00
dbaee284e4 fix: hy2/tuic inbound cert isn't path
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-10-01 12:04:34 +08:00
8253bfe2e0 add quic-go-disable-ecn to experimental 2023-10-01 09:10:11 +08:00
828b5ad8bb chore: add new bbr implementation 2023-10-01 00:01:32 +08:00
fedad26c13 chore: support relative path for hy2/tuic inbound cert 2023-10-01 00:01:32 +08:00
a526bb70ea chore: fix bbr bugs 2023-09-30 13:40:07 +08:00
5f6de610e1 Fix: should check all ips need to fallback (#2915) 2023-09-29 13:42:22 +08:00
02397868fc docs: support reload in service 2023-09-29 13:26:59 +08:00
265a6b9b68 chore: reduce string split immediately after string concat (#773) 2023-09-29 08:51:13 +08:00
10e7c533d7 feat: support clash premium's structured log stream (#735)
* feat: support clash premium's structured log stream

New version of Clash for Windows uses `ws://external-controller/logs?token=&level=info&format=structured` to get real time log. When Clash Premium Core reveices `format=structured`, it returns a different form of JSON log entry. Supporting this feature will allow better Clash for Windows integration

Signed-off-by: Misty <gyc990326@gmail.com>
2023-09-29 08:50:50 +08:00
0ed3c5a5ec chore: improve subscription userinfo parsing (#781)
do not use regex parsing for `Subscription-UserInfo` header field
2023-09-29 08:42:57 +08:00
c2b06a02bf feat: add reload signal support (#780)
Backport Clash feature by @septs, see Dreamacro/clash#2908
2023-09-29 08:36:25 +08:00
e0458a8fde chore: decrease goroutine used in core tunnel 2023-09-28 18:59:31 +08:00
21fb5f75b8 fix: gvisor panic 2023-09-26 09:06:00 +08:00
fb99412193 chore: update quic-go to 0.39.0 2023-09-26 08:51:25 +08:00
fdd327d58d fix: fail to set KeepAliveIntervall #715 2023-09-25 14:05:13 +08:00
0dfe696300 chore: ntp service support dialer-proxy 2023-09-25 09:11:35 +08:00
c0ba798708 chore: share N.dialer code 2023-09-25 09:11:35 +08:00
67d7e53f7a feat: recovering preHandleMetadata failure from sniffing (#769) 2023-09-24 19:27:55 +08:00
e6366f7442 chore: fix typo 2023-09-24 19:00:51 +08:00
89d9cb0539 Merge pull request #767 from PuerNya/fix-delay
chore: handle provider proxies  in proxies api
2023-09-24 15:55:33 +08:00
0d300a3540 chore: handle provider proxies in proxies api 2023-09-24 15:39:14 +08:00
7c59916c22 chore: update provider proxies api 2023-09-24 00:19:10 +08:00
8f515ecc05 chore: updateUI API return 501 when config incomplete 2023-09-23 18:00:07 +08:00
34f62a0919 feat: add provider proxies api 2023-09-23 17:54:20 +08:00
0207a7ac96 chore: resolver read system hosts file 2023-09-23 14:01:18 +08:00
bf619d8586 fix: socks5 udp not working on loopback 2023-09-22 23:33:24 +08:00
d48f9c2a6c chore: rebuild ca parsing 2023-09-22 14:45:34 +08:00
90a5aa609a fix: uot read failed 2023-09-22 00:11:57 +08:00
4fe7a463c5 chore: limit tuicv5's maxUdpRelayPacketSize up to 1200-PacketOverHead 2023-09-21 23:49:45 +08:00
7f49c91267 fix: hy2 udp not working 2023-09-21 23:36:40 +08:00
f6bf9c0857 feat: converter support hysteria2 2023-09-21 17:25:15 +08:00
da24810da2 chore: support set cwnd for hy2 too 2023-09-21 16:41:31 +08:00
ee3213c28f fix: tuicv5 panic in ReadFrom 2023-09-21 15:10:35 +08:00
233eeb0b38 feat: inbound support Hysteria2 2023-09-21 15:10:35 +08:00
6c3b973748 doc: add Hysteria2 doc 2023-09-21 10:43:45 +08:00
9b8e2d9343 feat: support Hysteria2 2023-09-21 10:28:28 +08:00
24fd577767 chore: Update dependencies 2023-09-21 08:57:38 +08:00
42b85de83e chore: Restore go1.20 support 2023-09-21 08:29:28 +08:00
62266010ac Revert "migration: go 1.21"
This reverts commit 33d41338ef.
2023-09-21 08:29:28 +08:00
0d7a57fa9d Chore: update github issue template 2023-09-21 03:40:46 +08:00
f909b3c0dc chore: Update android-ndk 2023-09-20 15:26:36 +08:00
8b518161a3 chore: update external-ui 2023-09-20 14:23:58 +08:00
20fafdca65 chore: cleanup code 2023-09-18 19:42:08 +08:00
fd96efd456 chore: ignore PR when Pre-releasing 2023-09-18 19:36:11 +08:00
7c21768e99 feat: update external-ui 2023-09-18 19:21:30 +08:00
6a5a94f48f chore: DNS cache policy follow upstream 2023-09-17 17:18:35 +08:00
33d41338ef migration: go 1.21 2023-09-17 17:05:13 +08:00
2d3b9364bf fix: caceh dns result 2023-09-16 12:30:11 +08:00
fa49fd7ba2 chore: use cmp in go 1.21
Co-authored-by: H1JK <hell0jack@protonmail.com>
2023-09-16 12:06:58 +08:00
c3d72f6883 feat: download/upgrade XD to external-ui 2023-09-16 11:44:15 +08:00
af99b52527 docs(README): update dashboard section 2023-09-09 13:06:49 +08:00
f241e1f81a chore: Update dependencies 2023-09-09 09:53:14 +08:00
90acce7fa1 feat: Add disable quic-go GSO to experimental 2023-09-08 22:58:59 +08:00
7286391883 feat: support users to customize download ua 2023-09-07 18:44:58 +08:00
a1eab125ee fix: ntp service panic 2023-09-04 18:35:06 +08:00
1d4af2d92b chore: TCPKeepAlive interval set to 15s by default 2023-09-03 20:42:54 +08:00
d6cf2a837f chore: ntp service dep with sing, optional synchronize system time 2023-09-03 17:49:56 +08:00
d6b80acfbc chore: Use xsync provided map size calculation 2023-09-02 20:17:43 +08:00
1cad615b25 chore: using xsync.MapOf replace sync.Map 2023-09-02 16:54:48 +08:00
73fa79bf3f feat: configurable TCPKeepAlive interval 2023-09-02 16:45:16 +08:00
d79c13064e chore: cleanup codes 2023-09-02 14:12:53 +08:00
427a377c2a refactor: Decouple .Cleanup from ReCreateTun
The listener.Cleanup method will be called during
executor.Shutdown and route.restart, so it should serve
all kinds of listeners rather than a single tun device.

Currently listener.ReCreateTun will call it to handle
some internal affairs, This should be decoupled.

In this way, the cleanup tasks for data outside the
process life cycle that other listeners will add here
in the future will not be accidentally triggered
by configuring tun.
2023-09-02 14:12:53 +08:00
9feb4d6668 fix: RESTful api missing TunConf.device
In commit 54fee7b, due to failure to take into account that
not all required parameters of `sing_tun.server.New` have
default values provided by `LC.Tun`, the name of the tun device
cannot be obtained when `TunConf.device` is not explicitly
configured. This commit fixed the issue.
2023-09-02 14:12:53 +08:00
a366e9a4b5 fix: ntp service panic 2023-09-02 12:37:43 +08:00
cbdf33c42c feat: ntp service 2023-09-02 02:15:46 +08:00
9ceaf20584 fix: concurrent map writes #707 2023-09-01 10:43:04 +08:00
193 changed files with 6464 additions and 2144 deletions

82
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,82 @@
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: "
确保你使用的是**本仓库**最新的的 clash 或 clash Alpha 版本
Ensure you are using the latest version of Clash or Clash Premium from **this repository**.
"
required: true
- label: "
如果你可以自己 debug 并解决的话,提交 PR 吧
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
"
required: false
- label: "
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
I have searched on the [issue tracker](……/) for a related issue.
"
required: true
- label: "
我已经使用 Alpha 分支版本测试过,问题依旧存在
I have tested using the dev branch, and the issue still exists.
"
required: true
- label: "
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法自行解决问题
I have read the [documentation](https://wiki.metacubex.one/) 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
description: "use `clash -v`"
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

View File

@ -0,0 +1,36 @@
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://wiki.metacubex.one/) 并无法找到这个功能
I have read the [documentation](https://wiki.metacubex.one/) 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
"

View File

@ -0,0 +1,51 @@
name: Android Branch Auto Sync
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
- android-open
tags:
- "v*"
pull_request_target:
branches:
- Alpha
- android-open
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com'
- name: Sync android-real with Alpha rebase android-open
run: |
git fetch origin
git checkout origin/Alpha -b android-real
git merge --squash origin/android-open
git commit -m "Android: patch"
- name: Check for conflicts
run: |
CONFLICTS=$(git diff --name-only --diff-filter=U)
if [ ! -z "$CONFLICTS" ]; then
echo "There are conflicts in the following files:"
echo $CONFLICTS
exit 1
fi
- name: Push changes
run: |
git push origin android-real --force

View File

@ -147,7 +147,7 @@ jobs:
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk
with:
ndk-version: r25b
ndk-version: r26
add-to-path: false
local-cache: true
@ -209,7 +209,7 @@ jobs:
Upload-Prerelease:
permissions: write-all
if: ${{ github.ref_type=='branch' }}
if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }}
needs: [Build]
runs-on: ubuntu-latest
steps:

View File

@ -0,0 +1,28 @@
name: CMFA auto update-dependencies trigger
on:
workflow_dispatch:
push:
tags:
- "v*"
pull_request_target:
branches:
- Alpha
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.MAINTAINER_APPID }}
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
- name: Trigger update-dependencies
run: |
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
-d '{"event_type": "core-updated"}'
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies

318
README.md
View File

@ -21,261 +21,52 @@
## Features
- Local HTTP/HTTPS/SOCKS server with authentication support
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
- VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
based off latency
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller
## Wiki
Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki).
## Dashboard
## Build
A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd).
You should install [golang](https://go.dev) first.
## Configration example
Then get the source code of Clash.Meta:
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
## Docs
Documentation can be found in [Clash.Meta Docs](https://clash-meta.wiki).
## For development
Requirements:
[Go 1.20 or newer](https://go.dev/dl/)
Build Clash.Meta:
```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download
go build
```
If you can't visit github,you should set proxy first:
Set go proxy if a connection to GitHub is not possible:
```shell
go env -w GOPROXY=https://goproxy.io,direct
```
Now you can build it:
```shell
go build
```
If you need gvisor for tun stack, build with:
Build with gvisor tun stack:
```shell
go build -tags with_gvisor
```
<!-- ## Advanced usage of this fork -->
<!-- ### DNS configuration
Support `geosite` with `fallback-filter`.
Restore `Redir remote resolution`.
Support resolve ip with a `Proxy Tunnel`.
```yaml
proxy-groups:
- name: DNS
type: url-test
use:
- HK
url: http://cp.cloudflare.com
interval: 180
lazy: true
```
```yaml
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: redir-host
fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868
default-nameserver:
- 119.29.29.29
- 114.114.114.114
nameserver:
- https://doh.pub/dns-query
- tls://223.5.5.5:853
fallback:
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
- "tls://8.8.4.4:853#DNS"
fallback-filter:
geoip: false
geosite:
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
domain:
- +.example.com
ipcidr:
- 0.0.0.0/32
```
### TUN configuration
Supports macOS, Linux and Windows.
Built-in [Wintun](https://www.wintun.net) driver.
```yaml
# Enable the TUN listener
tun:
enable: true
stack: system # system/gvisor
dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route
```
### Rules configuration
- Support rule `GEOSITE`.
- Support rule-providers `RULE-SET`.
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
- Support `network` condition for all rules.
- Support source IPCIDR condition for all rules, just append to the end.
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
```yaml
rules:
# network(tcp/udp) condition for all rules
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
# multiport condition for rules SRC-PORT and DST-PORT
- DST-PORT,123/136/137-139,DIRECT,udp
# rule GEOSITE
- GEOSITE,category-ads-all,REJECT
- GEOSITE,icloud@cn,DIRECT
- GEOSITE,apple@cn,DIRECT
- GEOSITE,apple-cn,DIRECT
- GEOSITE,microsoft@cn,DIRECT
- GEOSITE,facebook,PROXY
- GEOSITE,youtube,PROXY
- GEOSITE,geolocation-cn,DIRECT
- GEOSITE,geolocation-!cn,PROXY
# source IPCIDR condition for all rules in gateway proxy
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
- GEOIP,telegram,PROXY,no-resolve
- GEOIP,private,DIRECT,no-resolve
- GEOIP,cn,DIRECT
- MATCH,PROXY
```
### Proxies configuration
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
Support `Policy Group Filter`
```yaml
proxy-groups:
- name: 🚀 HK Group
type: select
use:
- ALL
filter: "HK"
- name: 🚀 US Group
type: select
use:
- ALL
filter: "US"
proxy-providers:
ALL:
type: http
url: "xxxxx"
interval: 3600
path: "xxxxx"
health-check:
enable: true
interval: 600
url: http://www.gstatic.com/generate_204
```
Support outbound transport protocol `VLESS`.
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml
proxies:
- name: "vless"
type: vless
server: server
port: 443
uuid: uuid
servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true
- name: "vless-ws"
type: vless
server: server
port: 443
uuid: uuid
tls: true
udp: true
network: ws
servername: example.com # priority over wss host
# skip-cert-verify: true
ws-opts:
path: /path
headers: { Host: example.com, Edge: "12a00c4.fm.huawei.com:82897" }
- name: "vless-grpc"
type: vless
server: server
port: 443
uuid: uuid
tls: true
udp: true
network: grpc
servername: example.com # priority over wss host
# skip-cert-verify: true
grpc-opts:
grpc-service-name: grpcname
```
Support outbound transport protocol `Wireguard`
```yaml
proxies:
- name: "wg"
type: wireguard
server: 162.159.192.1
port: 2480
ip: 172.16.0.2
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
udp: true
```
Support outbound transport protocol `Tuic`
```yaml
proxies:
- name: "tuic"
server: www.example.com
port: 10443
type: tuic
token: TOKEN
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
# heartbeat-interval: 10000
# alpn: [h3]
# disable-sni: true
reduce-rtt: true
# request-timeout: 8000
udp-relay-mode: native # Available: "native", "quic". Default: "native"
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
# max-udp-relay-packet-size: 1500
# fast-open: true
# skip-cert-verify: true
``` -->
### IPTABLES configuration
Work on Linux OS which supported `iptables`
@ -289,71 +80,10 @@ iptables:
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
```
### General installation guide for Linux
- Create user given name `clash-meta`
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
- Create folder `/etc/Clash-Meta/` as working directory
Run Meta Kernel by user `clash-meta` as a daemon.
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
```
[Unit]
Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
User=clash-meta
Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install]
WantedBy=multi-user.target
```
Launch clashd on system startup with:
```shell
$ systemctl enable Clash-Meta
```
Launch clashd immediately with:
```shell
$ systemctl start Clash-Meta
```
### Display Process name
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
### Dashboard
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
## Development
If you want to build an application that uses clash as a library, check out the
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Debugging
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
API.
## Credits

View File

@ -18,6 +18,8 @@ import (
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/puzpuzpuz/xsync/v2"
)
var UnifiedDelay = atomic.NewBool(false)
@ -28,15 +30,15 @@ const (
type extraProxyState struct {
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
alive atomic.Bool
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
alive atomic.Bool
url string
extra map[string]*extraProxyState
extra *xsync.MapOf[string, *extraProxyState]
}
// Alive implements C.Proxy
@ -46,10 +48,8 @@ func (p *Proxy) Alive() bool {
// AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool {
if p.extra != nil {
if state, ok := p.extra[url]; ok {
return state.alive.Load()
}
if state, ok := p.extra.Load(url); ok {
return state.alive.Load()
}
return p.alive.Load()
@ -88,16 +88,16 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
// DelayHistoryForTestUrl implements C.Proxy
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
var queueM []C.DelayHistory
if p.extra != nil {
if state, ok := p.extra[url]; ok {
queueM = state.history.Copy()
}
if state, ok := p.extra.Load(url); ok {
queueM = state.history.Copy()
}
if queueM == nil {
@ -112,19 +112,25 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
}
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
extra := map[string][]C.DelayHistory{}
if p.extra != nil && len(p.extra) != 0 {
for testUrl, option := range p.extra {
histories := []C.DelayHistory{}
queueM := option.history.Copy()
for _, item := range queueM {
histories = append(histories, item)
}
extraHistory := map[string][]C.DelayHistory{}
extra[testUrl] = histories
p.extra.Range(func(k string, v *extraProxyState) bool {
testUrl := k
state := v
histories := []C.DelayHistory{}
queueM := state.history.Copy()
for _, item := range queueM {
histories = append(histories, item)
}
}
return extra
extraHistory[testUrl] = histories
return true
})
return extraHistory
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
@ -149,11 +155,9 @@ func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
alive := p.alive.Load()
history := p.history.Last()
if p.extra != nil {
if state, ok := p.extra[url]; ok {
alive = state.alive.Load()
history = state.history.Last()
}
if state, ok := p.extra.Load(url); ok {
alive = state.alive.Load()
history = state.history.Last()
}
if !alive {
@ -213,18 +217,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if alive {
record.Delay = t
}
if p.extra == nil {
p.extra = map[string]*extraProxyState{}
p.history.Put(record)
if p.history.Len() > defaultHistoriesNum {
p.history.Pop()
}
state, ok := p.extra[url]
state, ok := p.extra.Load(url)
if !ok {
state = &extraProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
p.extra[url] = state
p.extra.Store(url, state)
}
state.alive.Store(alive)
@ -307,7 +311,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
return &Proxy{
ProxyAdapter: adapter,
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
url: "",
extra: xsync.NewMapOf[*extraProxyState]()}
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
@ -350,14 +359,14 @@ func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url strin
return C.OriginalHistory
}
if p.extra == nil {
store = C.ExtraHistory
} else {
if _, ok := p.extra[url]; ok {
store = C.ExtraHistory
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
store = C.ExtraHistory
}
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
return C.ExtraHistory
}
_, ok := p.extra.Load(url)
if ok {
return C.ExtraHistory
}
return store
}

View File

@ -1,13 +1,17 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
)
type Addition func(metadata *C.Metadata)
func (a Addition) Apply(metadata *C.Metadata) {
a(metadata)
func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
for _, addition := range additions {
addition(metadata)
}
}
func WithInName(name string) Addition {
@ -33,3 +37,29 @@ func WithSpecialProxy(specialProxy string) Addition {
metadata.SpecialProxy = specialProxy
}
}
func WithDstAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
_ = metadata.SetRemoteAddr(addr)
}
}
func WithSrcAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.SrcIP = m.DstIP
metadata.SrcPort = m.DstPort
}
}
}
func WithInAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.InIP = m.DstIP
metadata.InPort = m.DstPort
}
}
}

45
adapter/inbound/auth.go Normal file
View File

@ -0,0 +1,45 @@
package inbound
import (
"net"
"net/netip"
C "github.com/Dreamacro/clash/constant"
)
var skipAuthPrefixes []netip.Prefix
func SetSkipAuthPrefixes(prefixes []netip.Prefix) {
skipAuthPrefixes = prefixes
}
func SkipAuthPrefixes() []netip.Prefix {
return skipAuthPrefixes
}
func SkipAuthRemoteAddr(addr net.Addr) bool {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func SkipAuthRemoteAddress(addr string) bool {
m := C.Metadata{}
if err := m.SetRemoteAddress(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func skipAuth(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range skipAuthPrefixes {
if prefix.Contains(addr.Unmap()) {
return true
}
}
}
return false
}

View File

@ -4,25 +4,17 @@ import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewHTTP receive normal http request and return HTTPContext
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext {
func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = C.HTTP
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(source); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@ -5,23 +5,13 @@ import (
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
)
// NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext {
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPS
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@ -5,38 +5,18 @@ import (
"github.com/Dreamacro/clash/transport/socks5"
)
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
type PacketAdapter struct {
C.UDPPacket
metadata *C.Metadata
}
// Metadata returns destination metadata
func (s *PacketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter {
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.UDP
metadata.Type = source
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
metadata.RawSrcAddr = packet.LocalAddr()
metadata.RawDstAddr = metadata.UDPAddr()
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
}
ApplyAdditions(metadata, additions...)
return &PacketAdapter{
packet,
metadata,
}
return packet, metadata
}

View File

@ -2,51 +2,17 @@ package inbound
import (
"net"
"net/netip"
"strconv"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewSocket receive TCP inbound and return ConnContext
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext {
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
for _, addition := range additions {
addition.Apply(metadata)
}
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
}
func NewInner(conn net.Conn, address string) *context.ConnContext {
metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal
metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(address); err == nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
metadata.DstPort = uint16(port)
}
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
} else {
metadata.Host = h
}
}
return context.NewConnContext(conn, metadata)
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

View File

@ -1,7 +1,6 @@
package inbound
import (
"errors"
"net"
"net/http"
"net/netip"
@ -62,29 +61,3 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata
}
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
// Filter when net.Addr interface is nil
if addr == nil {
return netip.Addr{}, 0, errors.New("nil addr")
}
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
ip, port, err := parseAddr(rawAddr.RawAddr())
if err == nil {
return ip, port, err
}
}
addrStr := addr.String()
host, port, err := net.SplitHostPort(addrStr)
if err != nil {
return netip.Addr{}, 0, err
}
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
}
ip, err := netip.ParseAddr(host)
return ip, uint16Port, err
}

View File

@ -3,6 +3,9 @@ package outbound
import (
"context"
"errors"
"net/netip"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
@ -24,7 +27,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
if err != nil {
return nil, err
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
return NewConn(c, d), nil
}
@ -38,7 +41,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
}
metadata.DstIP = ip
}
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
if err != nil {
return nil, err
}

View File

@ -7,14 +7,16 @@ import (
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
)
@ -74,7 +76,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@ -155,19 +157,13 @@ func NewHttp(option HttpOption) (*Http, error) {
if option.SNI != "" {
sni = option.SNI
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
})
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
}, option.Fingerprint); err != nil {
return nil, err
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
}, option.Fingerprint)
if err != nil {
return nil, err
}
}

View File

@ -2,16 +2,11 @@ package outbound
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
"net/netip"
"os"
"regexp"
"strconv"
"time"
@ -19,9 +14,9 @@ import (
"github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
@ -43,8 +38,6 @@ const (
DefaultHopInterval = 10
)
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type Hysteria struct {
*Base
@ -53,7 +46,7 @@ type Hysteria struct {
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...))
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}
@ -120,12 +113,12 @@ type HysteriaOption struct {
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
var up, down uint64
up = stringToBps(c.Up)
up = StringToBps(c.Up)
if up == 0 {
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
}
down = stringToBps(c.Down)
down = StringToBps(c.Down)
if down == 0 {
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
}
@ -153,37 +146,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
MinVersion: tls.VersionTLS13,
}
var bs []byte
var err error
if len(option.CustomCA) > 0 {
bs, err = os.ReadFile(option.CustomCA)
if err != nil {
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
}
} else if option.CustomCAString != "" {
bs = []byte(option.CustomCAString)
}
if len(bs) > 0 {
block, _ := pem.Decode(bs)
if block == nil {
return nil, fmt.Errorf("CA cert is not PEM")
}
fpBytes := sha256.Sum256(block.Bytes)
if len(option.Fingerprint) == 0 {
option.Fingerprint = hex.EncodeToString(fpBytes[:])
}
}
if len(option.Fingerprint) != 0 {
var err error
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
} else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if len(option.ALPN) > 0 {
@ -268,42 +234,6 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
}, nil
}
func stringToBps(s string) uint64 {
if s == "" {
return 0
}
// when have not unit, use Mbps
if v, err := strconv.Atoi(s); err == nil {
return stringToBps(fmt.Sprintf("%d Mbps", v))
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}
type hyPacketConn struct {
core.UDPConn
}

View File

@ -0,0 +1,157 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"runtime"
"strconv"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
tuicCommon "github.com/Dreamacro/clash/transport/tuic/common"
"github.com/metacubex/sing-quic/hysteria2"
M "github.com/sagernet/sing/common/metadata"
)
func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
}
type Hysteria2 struct {
*Base
option *Hysteria2Option
client *hysteria2.Client
dialer proxydialer.SingDialer
}
type Hysteria2Option struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
ObfsPassword string `proxy:"obfs-password,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
}
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...))
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, h), h), nil
}
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...))
pc, err := h.client.ListenPacket(ctx)
if err != nil {
return nil, err
}
if pc == nil {
return nil, errors.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
}
func closeHysteria2(h *Hysteria2) {
if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed"))
}
}
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
var salamanderPassword string
if len(option.Obfs) > 0 {
if option.ObfsPassword == "" {
return nil, errors.New("missing obfs password")
}
switch option.Obfs {
case hysteria2.ObfsTypeSalamander:
salamanderPassword = option.ObfsPassword
default:
return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
}
}
serverName := option.Server
if option.SNI != "" {
serverName = option.SNI
}
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
var err error
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
clientOptions := hysteria2.ClientOptions{
Context: context.TODO(),
Dialer: singDialer,
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword,
Password: option.Password,
TLSConfig: tlsConfig,
UDPDisabled: false,
CWND: option.CWND,
}
client, err := hysteria2.NewClient(clientOptions)
if err != nil {
return nil, err
}
outbound := &Hysteria2{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Hysteria2,
udp: true,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: client,
dialer: singDialer,
}
runtime.SetFinalizer(outbound, closeHysteria2)
return outbound, nil
}

View File

@ -15,6 +15,10 @@ type Reject struct {
*Base
}
type RejectOption struct {
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return NewConn(nopConn{}, r), nil
@ -25,6 +29,16 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
return newPacketConn(nopPacketConn{}, r), nil
}
func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
},
}
}
func NewReject() *Reject {
return &Reject{
Base: &Base{

View File

@ -123,9 +123,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
}
}
if useEarly {
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil
} else {
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)

View File

@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)

View File

@ -3,7 +3,6 @@ package outbound
import (
"context"
"errors"
"net"
"runtime"
CN "github.com/Dreamacro/clash/common/net"
@ -15,14 +14,13 @@ import (
mux "github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type SingMux struct {
C.ProxyAdapter
base ProxyBase
client *mux.Client
dialer *muxSingDialer
dialer proxydialer.SingDialer
onlyTcp bool
}
@ -41,28 +39,10 @@ type ProxyBase interface {
DialOptions(opts ...dialer.Option) []dialer.Option
}
type muxSingDialer struct {
dialer dialer.Dialer
proxy C.ProxyAdapter
statistic bool
}
var _ N.Dialer = (*muxSingDialer)(nil)
func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...)
s.dialer.dialer = dialer.NewDialer(options...)
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
s.dialer.SetDialer(dialer.NewDialer(options...))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
@ -74,7 +54,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
options := s.base.DialOptions(opts...)
s.dialer.dialer = dialer.NewDialer(options...)
s.dialer.SetDialer(dialer.NewDialer(options...))
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
@ -114,7 +94,7 @@ func closeSingMux(s *SingMux) {
}
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
client, err := mux.NewClient(mux.Options{
Dialer: singDialer,
Protocol: option.Protocol,

View File

@ -6,6 +6,7 @@ import (
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
@ -93,7 +94,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@ -121,7 +122,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, err
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
@ -207,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, err
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}

View File

@ -7,11 +7,13 @@ import (
"fmt"
"io"
"net"
"net/netip"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
@ -80,7 +82,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@ -126,7 +128,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err)
}(c)
tcpKeepAlive(c)
N.TCPKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
@ -135,7 +137,8 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
}
}
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return
@ -155,7 +158,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
bindUDPAddr.IP = serverAddr.IP
}
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
if err != nil {
return
}
@ -179,13 +182,10 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
ServerName: option.Server,
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
return nil, err
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
}

View File

@ -8,6 +8,8 @@ import (
"net/http"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
@ -131,7 +133,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
@ -184,7 +186,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
tcpKeepAlive(c)
N.TCPKeepAlive(c)
c, err = t.plainStream(ctx, c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@ -268,7 +270,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
return c, nil
}
@ -279,13 +281,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ServerName: tOption.ServerName,
}
if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
return nil, err
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)

View File

@ -2,22 +2,18 @@ package outbound
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"math"
"net"
"os"
"strconv"
"time"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic"
@ -162,37 +158,10 @@ func NewTuic(option TuicOption) (*Tuic, error) {
tlsConfig.ServerName = option.SNI
}
var bs []byte
var err error
if len(option.CustomCA) > 0 {
bs, err = os.ReadFile(option.CustomCA)
if err != nil {
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
}
} else if option.CustomCAString != "" {
bs = []byte(option.CustomCAString)
}
if len(bs) > 0 {
block, _ := pem.Decode(bs)
if block == nil {
return nil, fmt.Errorf("CA cert is not PEM")
}
fpBytes := sha256.Sum256(block.Bytes)
if len(option.Fingerprint) == 0 {
option.Fingerprint = hex.EncodeToString(fpBytes[:])
}
}
if len(option.Fingerprint) != 0 {
var err error
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
} else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
@ -323,6 +292,10 @@ func NewTuic(option TuicOption) (*Tuic, error) {
t.client = tuic.NewPoolClientV4(clientOption)
} else {
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
}
clientOption := &tuic.ClientOptionV5{
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
@ -331,7 +304,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
UdpRelayMode: udpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
}

View File

@ -4,10 +4,12 @@ import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/netip"
"regexp"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
@ -19,13 +21,6 @@ var (
once sync.Once
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
@ -128,3 +123,41 @@ func safeConnClose(c net.Conn, err error) {
_ = c.Close()
}
}
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
func StringToBps(s string) uint64 {
if s == "" {
return 0
}
// when have not unit, use Mbps
if v, err := strconv.Atoi(s); err == nil {
return StringToBps(fmt.Sprintf("%d Mbps", v))
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}

View File

@ -15,6 +15,7 @@ import (
"github.com/Dreamacro/clash/common/convert"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
@ -110,13 +111,9 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
NextProtos: []string{"http/1.1"},
}
if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
@ -263,7 +260,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@ -328,7 +325,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@ -578,7 +575,7 @@ func NewVless(option VlessOption) (*Vless, error) {
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
return c, nil
}
@ -592,7 +589,7 @@ func NewVless(option VlessOption) (*Vless, error) {
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
})

View File

@ -13,11 +13,13 @@ import (
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/ntp"
"github.com/Dreamacro/clash/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess"
@ -126,12 +128,9 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
NextProtos: []string{"http/1.1"},
}
if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else {
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
return nil, err
}
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
@ -261,10 +260,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c,
M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else {
conn, err = v.client.DialConn(c,
M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
if err != nil {
@ -285,7 +284,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err)
}(c)
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
@ -307,7 +306,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@ -368,7 +367,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
@ -416,6 +415,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if option.AuthenticatedLength {
options = append(options, vmess.ClientWithAuthenticatedLength())
}
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
if err != nil {
return nil, err
@ -467,7 +467,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
N.TCPKeepAlive(c)
return c, nil
}
@ -481,7 +481,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
})

View File

@ -27,7 +27,6 @@ import (
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/wireguard-go/device"
)
@ -36,7 +35,7 @@ type WireGuard struct {
bind *wireguard.ClientBind
device *device.Device
tunDevice wireguard.Device
dialer *wgSingDialer
dialer proxydialer.SingDialer
startOnce sync.Once
startErr error
resolver *dns.Resolver
@ -70,37 +69,6 @@ type WireGuardPeerOption struct {
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
}
type wgSingDialer struct {
dialer dialer.Dialer
proxyName string
}
var _ N.Dialer = (*wgSingDialer)(nil)
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
type wgSingErrorHandler struct {
name string
}
@ -168,7 +136,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()),
}
runtime.SetFinalizer(outbound, closeWireGuard)
@ -355,7 +323,7 @@ func closeWireGuard(w *WireGuard) {
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := w.Base.DialOptions(opts...)
w.dialer.dialer = dialer.NewDialer(options...)
w.dialer.SetDialer(dialer.NewDialer(options...))
var conn net.Conn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()
@ -387,7 +355,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
options := w.Base.DialOptions(opts...)
w.dialer.dialer = dialer.NewDialer(options...)
w.dialer.SetDialer(dialer.NewDialer(options...))
var pc net.PacketConn
w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start()

View File

@ -28,7 +28,7 @@ type GroupBase struct {
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
failedTesting atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
}

View File

@ -0,0 +1,62 @@
package outboundgroup
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type ProxyGroup interface {
C.ProxyAdapter
Providers() []provider.ProxyProvider
Proxies() []C.Proxy
Now() string
}
func (f *Fallback) Providers() []provider.ProxyProvider {
return f.providers
}
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
return lb.providers
}
func (f *Fallback) Proxies() []C.Proxy {
return f.GetProxies(false)
}
func (lb *LoadBalance) Proxies() []C.Proxy {
return lb.GetProxies(false)
}
func (lb *LoadBalance) Now() string {
return ""
}
func (r *Relay) Providers() []provider.ProxyProvider {
return r.providers
}
func (r *Relay) Proxies() []C.Proxy {
return r.GetProxies(false)
}
func (r *Relay) Now() string {
return ""
}
func (s *Selector) Providers() []provider.ProxyProvider {
return s.providers
}
func (s *Selector) Proxies() []C.Proxy {
return s.GetProxies(false)
}
func (u *URLTest) Providers() []provider.ProxyProvider {
return u.providers
}
func (u *URLTest) Proxies() []C.Proxy {
return u.GetProxies(false)
}

View File

@ -1,17 +1,5 @@
package outboundgroup
import (
"net"
"time"
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
type SelectAble interface {
Set(string) error
ForceSet(name string)

View File

@ -92,6 +92,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewHysteria(*hyOption)
case "hysteria2":
hyOption := &outbound.Hysteria2Option{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewHysteria2(*hyOption)
case "wireguard":
wgOption := &outbound.WireGuardOption{}
err = decoder.Decode(mapping, wgOption)
@ -113,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy = outbound.NewDirectWithOption(*directOption)
case "reject":
rejectOption := &outbound.RejectOption{}
err = decoder.Decode(mapping, rejectOption)
if err != nil {
break
}
proxy = outbound.NewRejectWithOption(*rejectOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}

View File

@ -18,6 +18,7 @@ import (
const (
defaultURLTestTimeout = time.Second * 5
defaultURLTestURL = "https://www.gstatic.com/generate_204"
)
type HealthCheckOption struct {
@ -34,12 +35,12 @@ type HealthCheck struct {
url string
extra map[string]*extraOption
mu sync.Mutex
started *atomic.Bool
started atomic.Bool
proxies []C.Proxy
interval uint
interval time.Duration
lazy bool
expectedStatus utils.IntRanges[uint16]
lastTouch *atomic.Int64
lastTouch atomic.TypedValue[time.Time]
done chan struct{}
singleDo *singledo.Single[struct{}]
}
@ -50,13 +51,14 @@ func (hc *HealthCheck) process() {
return
}
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
ticker := time.NewTicker(hc.interval)
hc.start()
for {
select {
case <-ticker.C:
now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
lastTouch := hc.lastTouch.Load()
since := time.Since(lastTouch)
if !hc.lazy || since < hc.interval {
hc.check()
} else {
log.Debugln("Skip once health check because we are lazy")
@ -85,7 +87,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
// if the provider has not set up health checks, then modify it to be the same as the group's interval
if hc.interval == 0 {
hc.interval = interval
hc.interval = time.Duration(interval) * time.Second
}
if hc.extra == nil {
@ -135,7 +137,7 @@ func (hc *HealthCheck) auto() bool {
}
func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix())
hc.lastTouch.Store(time.Now())
}
func (hc *HealthCheck) start() {
@ -147,6 +149,11 @@ func (hc *HealthCheck) stop() {
}
func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
@ -222,17 +229,16 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
if len(url) == 0 {
interval = 0
expectedStatus = nil
url = defaultURLTestURL
}
return &HealthCheck{
proxies: proxies,
url: url,
extra: map[string]*extraOption{},
started: atomic.NewBool(false),
interval: interval,
interval: time.Duration(interval) * time.Second,
lazy: lazy,
expectedStatus: expectedStatus,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second),
}

View File

@ -68,9 +68,6 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
case "http":
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)

34
adapter/provider/patch.go Normal file
View File

@ -0,0 +1,34 @@
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@ -1,7 +1,6 @@
package provider
import (
"github.com/dlclark/regexp2"
"strconv"
"strings"
)
@ -13,45 +12,24 @@ type SubscriptionInfo struct {
Expire int64
}
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
si = &SubscriptionInfo{}
str = strings.ToLower(str)
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
match, err := reTraffic.FindStringMatch(str)
if err != nil || match == nil {
return nil, err
}
group := match.Groups()
si.Upload, err = str2uint64(group[1].String())
if err != nil {
return nil, err
}
si.Download, err = str2uint64(group[2].String())
if err != nil {
return nil, err
}
si.Total, err = str2uint64(group[3].String())
if err != nil {
return nil, err
}
match, _ = reExpire.FindStringMatch(str)
if match != nil {
group = match.Groups()
si.Expire, err = str2uint64(group[1].String())
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
userinfo = strings.ToLower(userinfo)
userinfo = strings.ReplaceAll(userinfo, " ", "")
si = new(SubscriptionInfo)
for _, field := range strings.Split(userinfo, ";") {
switch name, value, _ := strings.Cut(field, "="); name {
case "upload":
si.Upload, err = strconv.ParseInt(value, 10, 64)
case "download":
si.Download, err = strconv.ParseInt(value, 10, 64)
case "total":
si.Total, err = strconv.ParseInt(value, 10, 64)
case "expire":
si.Expire, err = strconv.ParseInt(value, 10, 64)
}
if err != nil {
return nil, err
return
}
}
return
}
func str2uint64(str string) (int64, error) {
i, err := strconv.ParseInt(str, 10, 64)
return i, err
}

View File

@ -11,10 +11,9 @@ type Bool struct {
atomic.Bool
}
func NewBool(val bool) *Bool {
i := &Bool{}
func NewBool(val bool) (i Bool) {
i.Store(val)
return i
return
}
func (i *Bool) MarshalJSON() ([]byte, error) {
@ -39,12 +38,11 @@ type Pointer[T any] struct {
atomic.Pointer[T]
}
func NewPointer[T any](v *T) *Pointer[T] {
var p Pointer[T]
func NewPointer[T any](v *T) (p Pointer[T]) {
if v != nil {
p.Store(v)
}
return &p
return
}
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
@ -68,10 +66,9 @@ type Int32 struct {
atomic.Int32
}
func NewInt32(val int32) *Int32 {
i := &Int32{}
func NewInt32(val int32) (i Int32) {
i.Store(val)
return i
return
}
func (i *Int32) MarshalJSON() ([]byte, error) {
@ -96,10 +93,9 @@ type Int64 struct {
atomic.Int64
}
func NewInt64(val int64) *Int64 {
i := &Int64{}
func NewInt64(val int64) (i Int64) {
i.Store(val)
return i
return
}
func (i *Int64) MarshalJSON() ([]byte, error) {
@ -124,10 +120,9 @@ type Uint32 struct {
atomic.Uint32
}
func NewUint32(val uint32) *Uint32 {
i := &Uint32{}
func NewUint32(val uint32) (i Uint32) {
i.Store(val)
return i
return
}
func (i *Uint32) MarshalJSON() ([]byte, error) {
@ -152,10 +147,9 @@ type Uint64 struct {
atomic.Uint64
}
func NewUint64(val uint64) *Uint64 {
i := &Uint64{}
func NewUint64(val uint64) (i Uint64) {
i.Store(val)
return i
return
}
func (i *Uint64) MarshalJSON() ([]byte, error) {
@ -180,10 +174,9 @@ type Uintptr struct {
atomic.Uintptr
}
func NewUintptr(val uintptr) *Uintptr {
i := &Uintptr{}
func NewUintptr(val uintptr) (i Uintptr) {
i.Store(val)
return i
return
}
func (i *Uintptr) MarshalJSON() ([]byte, error) {

View File

@ -51,8 +51,7 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil
}
func NewTypedValue[T any](t T) *TypedValue[T] {
v := &TypedValue[T]{}
func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t)
return v
return
}

View File

@ -10,6 +10,7 @@ const BufferSize = buf.BufferSize
type Buffer = buf.Buffer
var New = buf.New
var NewPacket = buf.NewPacket
var NewSize = buf.NewSize
var With = buf.With
var As = buf.As

View File

@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
func TestExecCmd(t *testing.T) {
if runtime.GOOS == "windows" {
_, err := ExecCmd("dir")
_, err := ExecCmd("cmd -c 'dir'")
assert.Nil(t, err)
return
}

View File

@ -68,7 +68,39 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
proxies = append(proxies, hysteria)
case "hysteria2":
urlHysteria2, err := url.Parse(line)
if err != nil {
continue
}
query := urlHysteria2.Query()
name := uniqueName(names, urlHysteria2.Fragment)
hysteria2 := make(map[string]any, 20)
hysteria2["name"] = name
hysteria2["type"] = scheme
hysteria2["server"] = urlHysteria2.Hostname()
if port := urlHysteria2.Port(); port != "" {
hysteria2["port"] = port
} else {
hysteria2["port"] = "443"
}
hysteria2["obfs"] = query.Get("obfs")
hysteria2["obfs-password"] = query.Get("obfs-password")
hysteria2["sni"] = query.Get("sni")
hysteria2["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
if alpn := query.Get("alpn"); alpn != "" {
hysteria2["alpn"] = strings.Split(alpn, ",")
}
if auth := urlHysteria2.User.String(); auth != "" {
hysteria2["password"] = auth
}
hysteria2["fingerprint"] = query.Get("pinSHA256")
hysteria2["down"] = query.Get("down")
hysteria2["up"] = query.Get("up")
proxies = append(proxies, hysteria2)
case "tuic":
// A temporary unofficial TUIC share link standard
// Modified from https://github.com/daeuniverse/dae/discussions/182

View File

@ -0,0 +1,35 @@
package convert
import (
"testing"
"github.com/stretchr/testify/assert"
)
// https://v2.hysteria.network/zh/docs/developers/URI-Scheme/
func TestConvertsV2Ray_normal(t *testing.T) {
hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test"
expected := []map[string]interface{}{
{
"name": "hy2test",
"type": "hysteria2",
"server": "example.com",
"port": "8443",
"sni": "real.example.com",
"obfs": "salamander",
"obfs-password": "gawrgura",
"alpn": []string{"h3", "h4"},
"password": "letmein",
"up": "114",
"down": "514",
"skip-cert-verify": true,
"fingerprint": "deadbeef",
},
}
proxies, err := ConvertsV2Ray([]byte(hy2test))
assert.Nil(t, err)
assert.Equal(t, expected, proxies)
}

View File

@ -4,8 +4,11 @@ import (
"fmt"
"net"
"strings"
"time"
)
var KeepAliveInterval = 15 * time.Second
func SplitNetworkType(s string) (string, string, error) {
var (
shecme string
@ -44,3 +47,10 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
host, port, err = net.SplitHostPort(temp)
return
}
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
}
}

View File

@ -10,7 +10,11 @@ import (
"math/big"
)
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
type Path interface {
Resolve(path string) string
}
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
if certificate == "" && privateKey == "" {
return newRandomTLSKeyPair()
}
@ -19,6 +23,8 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
return cert, nil
}
certificate = path.Resolve(certificate)
privateKey = path.Resolve(privateKey)
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())

View File

@ -0,0 +1,8 @@
package util
import "github.com/samber/lo"
func EmptyOr[T comparable](v T, def T) T {
ret, _ := lo.Coalesce(v, def)
return ret
}

View File

@ -1,7 +1,7 @@
package auth
import (
"sync"
"github.com/puzpuzpuz/xsync/v2"
)
type Authenticator interface {
@ -15,7 +15,7 @@ type AuthUser struct {
}
type inMemoryAuthenticator struct {
storage *sync.Map
storage *xsync.MapOf[string, string]
usernames []string
}
@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
return nil
}
au := &inMemoryAuthenticator{storage: &sync.Map{}}
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
for _, user := range users {
au.storage.Store(user.User, user.Pass)
}
usernames := make([]string, 0, len(users))
au.storage.Range(func(key, value any) bool {
usernames = append(usernames, key.(string))
au.storage.Range(func(key string, value string) bool {
usernames = append(usernames, key)
return true
})
au.usernames = usernames

View File

@ -1,4 +1,4 @@
package tls
package ca
import (
"bytes"
@ -8,12 +8,13 @@ import (
"encoding/hex"
"errors"
"fmt"
"os"
"strings"
"sync"
)
var trustCerts []*x509.Certificate
var certPool *x509.CertPool
var globalCertPool *x509.CertPool
var mutex sync.RWMutex
var errNotMatch = errors.New("certificate fingerprints do not match")
@ -33,12 +34,12 @@ func AddCertificate(certificate string) error {
func initializeCertPool() {
var err error
certPool, err = x509.SystemCertPool()
globalCertPool, err = x509.SystemCertPool()
if err != nil {
certPool = x509.NewCertPool()
globalCertPool = x509.NewCertPool()
}
for _, cert := range trustCerts {
certPool.AddCert(cert)
globalCertPool.AddCert(cert)
}
}
@ -53,15 +54,15 @@ func getCertPool() *x509.CertPool {
if len(trustCerts) == 0 {
return nil
}
if certPool == nil {
if globalCertPool == nil {
mutex.Lock()
defer mutex.Unlock()
if certPool != nil {
return certPool
if globalCertPool != nil {
return globalCertPool
}
initializeCertPool()
}
return certPool
return globalCertPool
}
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
@ -94,29 +95,49 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) {
return (*[32]byte)(fpByte), nil
}
func GetDefaultTLSConfig() *tls.Config {
return GetGlobalTLSConfig(nil)
// GetTLSConfig specified fingerprint, customCA and customCAString
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
var certificate []byte
var err error
if len(customCA) > 0 {
certificate, err = os.ReadFile(customCA)
if err != nil {
return nil, fmt.Errorf("load ca error: %w", err)
}
} else if customCAString != "" {
certificate = []byte(customCAString)
}
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
}
tlsConfig.RootCAs = certPool
} else {
tlsConfig.RootCAs = getCertPool()
}
if len(fingerprint) > 0 {
var fingerprintBytes *[32]byte
fingerprintBytes, err = convertFingerprint(fingerprint)
if err != nil {
return nil, err
}
tlsConfig = GetGlobalTLSConfig(tlsConfig)
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
tlsConfig.InsecureSkipVerify = true
}
return tlsConfig, nil
}
// GetSpecifiedFingerprintTLSConfig specified fingerprint
func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) {
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
return nil, err
} else {
tlsConfig = GetGlobalTLSConfig(tlsConfig)
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
return GetTLSConfig(tlsConfig, fingerprint, "", "")
}
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
certPool := getCertPool()
if tlsConfig == nil {
return &tls.Config{
RootCAs: certPool,
}
}
tlsConfig.RootCAs = certPool
tlsConfig, _ = GetTLSConfig(tlsConfig, "", "", "")
return tlsConfig
}

View File

@ -70,6 +70,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option
}
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
if DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address)
}
cfg := applyOptions(options...)
lc := &net.ListenConfig{}
@ -110,6 +113,9 @@ func GetTcpConcurrent() bool {
}
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
if DefaultSocketHook != nil {
return dialContextHooked(ctx, network, destination, port)
}
address := net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer

37
component/dialer/patch.go Normal file
View File

@ -0,0 +1,37 @@
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultSocketHook SocketControl
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
dialer := &net.Dialer{
Control: DefaultSocketHook,
}
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
if err != nil {
return nil, err
}
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
return conn, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: DefaultSocketHook,
}
return lc.ListenPacket(ctx, network, address)
}

View File

@ -2,22 +2,22 @@ package http
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
URL "net/url"
"runtime"
"strings"
"time"
"github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/ca"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/inner"
)
const (
UA = "clash.meta"
)
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
UA := C.UA
method = strings.ToUpper(method)
urlRes, err := URL.Parse(url)
if err != nil {
@ -48,6 +48,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
transport := &http.Transport{
// from http.DefaultTransport
DisableKeepAlives: runtime.GOOS == "android",
MaxIdleConns: 100,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
@ -60,7 +61,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
return d.DialContext(ctx, network, address)
}
},
TLSClientConfig: tls.GetDefaultTLSConfig(),
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
}
client := http.Client{Transport: transport}

16
component/mmdb/patch.go Normal file
View File

@ -0,0 +1,16 @@
package mmdb
import "github.com/oschwald/maxminddb-golang"
func InstallOverride(override *maxminddb.Reader) {
newReader := Reader{Reader: override}
switch override.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
}
reader = newReader
}

View File

@ -5,23 +5,28 @@ import (
"sync"
C "github.com/Dreamacro/clash/constant"
"github.com/puzpuzpuz/xsync/v2"
)
type Table struct {
mapping sync.Map
mapping *xsync.MapOf[string, *Entry]
lockMap *xsync.MapOf[string, *sync.Cond]
}
type Entry struct {
PacketConn C.PacketConn
WriteBackProxy C.WriteBackProxy
LocalUDPConnMap sync.Map
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
LocalLockMap *xsync.MapOf[string, *sync.Cond]
}
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
t.mapping.Store(key, &Entry{
PacketConn: e,
WriteBackProxy: w,
LocalUDPConnMap: sync.Map{},
LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](),
LocalLockMap: xsync.NewMapOf[*sync.Cond](),
})
}
@ -34,15 +39,19 @@ func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
}
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.Cond), loaded
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
return item, loaded
}
func (t *Table) Delete(key string) {
t.mapping.Delete(key)
}
func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
func (t *Table) DeleteLock(lockKey string) {
t.lockMap.Delete(lockKey)
}
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
entry, exist := t.getEntry(lAddr)
if !exist {
return nil
@ -51,10 +60,10 @@ func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
if !exist {
return nil
}
return item.(*net.UDPConn)
return item
}
func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
entry, exist := t.getEntry(lAddr)
if !exist {
return false
@ -63,7 +72,7 @@ func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
return true
}
func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) {
entry, exist := t.getEntry(lAddr)
if !exist {
return
@ -76,11 +85,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
if !loaded {
return nil, false
}
item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.Cond), loaded
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock)
return item, loaded
}
func (t *Table) DeleteLocalConnMap(lAddr, key string) {
func (t *Table) DeleteForLocalConn(lAddr, key string) {
entry, loaded := t.getEntry(lAddr)
if !loaded {
return
@ -88,17 +97,26 @@ func (t *Table) DeleteLocalConnMap(lAddr, key string) {
entry.LocalUDPConnMap.Delete(key)
}
func (t *Table) getEntry(key string) (*Entry, bool) {
item, ok := t.mapping.Load(key)
// This should not happen usually since this function called after PacketConn created
if !ok {
return nil, false
func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
entry, loaded := t.getEntry(lAddr)
if !loaded {
return
}
entry, ok := item.(*Entry)
return entry, ok
entry.LocalLockMap.Delete(key)
}
func (t *Table) getEntry(key string) (*Entry, bool) {
return t.mapping.Load(key)
}
func makeLock() *sync.Cond {
return sync.NewCond(&sync.Mutex{})
}
// New return *Cache
func New() *Table {
return &Table{}
return &Table{
mapping: xsync.NewMapOf[*Entry](),
lockMap: xsync.NewMapOf[*sync.Cond](),
}
}

View File

@ -0,0 +1,14 @@
package process
import "github.com/Dreamacro/clash/constant"
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
var DefaultPackageNameResolver PackageNameResolver
func FindPackageName(metadata *constant.Metadata) (string, error) {
if resolver := DefaultPackageNameResolver; resolver != nil {
return resolver(metadata)
}
return "", ErrPlatformNotSupport
}

View File

@ -70,10 +70,7 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
}
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
currentMeta := &C.Metadata{Type: C.INNER}
if err := currentMeta.SetRemoteAddress(rAddrPort.String()); err != nil {
return nil, err
}
currentMeta := &C.Metadata{Type: C.INNER, DstIP: rAddrPort.Addr(), DstPort: rAddrPort.Port()}
return p.listenPacket(ctx, currentMeta)
}

View File

@ -0,0 +1,82 @@
package proxydialer
import (
"context"
"net"
C "github.com/Dreamacro/clash/constant"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type SingDialer interface {
N.Dialer
SetDialer(dialer C.Dialer)
}
type singDialer proxyDialer
var _ N.Dialer = (*singDialer)(nil)
func (d *singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return (*proxyDialer)(d).DialContext(ctx, network, destination.String())
}
func (d *singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return (*proxyDialer)(d).ListenPacket(ctx, "udp", "", destination.AddrPort())
}
func (d *singDialer) SetDialer(dialer C.Dialer) {
(*proxyDialer)(d).dialer = dialer
}
func NewSingDialer(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) SingDialer {
return (*singDialer)(&proxyDialer{
proxy: proxy,
dialer: dialer,
statistic: statistic,
})
}
type byNameSingDialer struct {
dialer C.Dialer
proxyName string
}
var _ N.Dialer = (*byNameSingDialer)(nil)
func (d *byNameSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.DialContext(ctx, network, destination.String())
}
func (d *byNameSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = d.dialer
if len(d.proxyName) > 0 {
pd, err := NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
func (d *byNameSingDialer) SetDialer(dialer C.Dialer) {
d.dialer = dialer
}
func NewByNameSingDialer(proxyName string, dialer C.Dialer) SingDialer {
return &byNameSingDialer{
dialer: dialer,
proxyName: proxyName,
}
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"net/netip"
"strings"
_ "unsafe"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie"
@ -20,28 +21,39 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
}
}
// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
//
//go:linkname lookupStaticHost net.lookupStaticHost
func lookupStaticHost(host string) ([]string, string)
// Return the search result and whether to match the parameter `isDomain`
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
value := h.DomainTrie.Search(domain)
if value == nil {
return nil, false
}
hostValue := value.Data()
for {
if isDomain && hostValue.IsDomain {
return &hostValue, true
} else {
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
hostValue = node.Data()
if value := h.DomainTrie.Search(domain); value != nil {
hostValue := value.Data()
for {
if isDomain && hostValue.IsDomain {
return &hostValue, true
} else {
break
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
hostValue = node.Data()
} else {
break
}
}
}
if isDomain == hostValue.IsDomain {
return &hostValue, true
}
return &hostValue, false
}
if isDomain == hostValue.IsDomain {
return &hostValue, true
if !isDomain {
addr, _ := lookupStaticHost(domain)
if hostValue, err := NewHostValue(addr); err == nil {
return &hostValue, true
}
}
return &hostValue, false
return nil, false
}
type HostValue struct {

View File

@ -0,0 +1,19 @@
//go:build !go1.22
// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
package resolver
import (
"golang.org/x/sys/windows"
_ "unsafe"
)
//go:linkname testHookHostsPath net.testHookHostsPath
var testHookHostsPath string
func init() {
if dir, err := windows.GetSystemDirectory(); err == nil {
testHookHostsPath = dir + "/Drivers/etc/hosts"
}
}

View File

@ -13,6 +13,10 @@ import (
"github.com/samber/lo"
)
const (
minInterval = time.Minute * 5
)
var (
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
@ -24,8 +28,7 @@ type Fetcher[V any] struct {
resourceType string
name string
vehicle types.Vehicle
UpdatedAt *time.Time
ticker *time.Ticker
UpdatedAt time.Time
done chan struct{}
hash [16]byte
parser Parser[V]
@ -56,7 +59,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
f.UpdatedAt = &modTime
f.UpdatedAt = modTime
isLocal = true
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
@ -64,6 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
}
} else {
buf, err = f.vehicle.Read()
f.UpdatedAt = time.Now()
}
if err != nil {
@ -113,7 +117,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
f.hash = md5.Sum(buf)
// pull contents automatically
if f.ticker != nil {
if f.interval > 0 {
go f.pullLoop()
}
@ -129,7 +133,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.UpdatedAt = &now
f.UpdatedAt = now
_ = os.Chtimes(f.vehicle.Path(), now, now)
return lo.Empty[V](), true, nil
}
@ -145,23 +149,31 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
}
}
f.UpdatedAt = &now
f.UpdatedAt = now
f.hash = hash
return contents, false, nil
}
func (f *Fetcher[V]) Destroy() error {
if f.ticker != nil {
if f.interval > 0 {
f.done <- struct{}{}
}
return nil
}
func (f *Fetcher[V]) pullLoop() {
initialInterval := f.interval - time.Since(f.UpdatedAt)
if initialInterval < minInterval {
initialInterval = minInterval
}
timer := time.NewTimer(initialInterval)
defer timer.Stop()
for {
select {
case <-f.ticker.C:
case <-timer.C:
timer.Reset(f.interval)
elm, same, err := f.Update()
if err != nil {
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
@ -178,7 +190,6 @@ func (f *Fetcher[V]) pullLoop() {
f.OnUpdate(elm)
}
case <-f.done:
f.ticker.Stop()
return
}
}
@ -197,17 +208,12 @@ func safeWrite(path string, buf []byte) error {
}
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &Fetcher[V]{
name: name,
ticker: ticker,
vehicle: vehicle,
parser: parser,
done: make(chan struct{}, 1),
done: make(chan struct{}, 8),
OnUpdate: onUpdate,
interval: interval,
}

View File

@ -23,8 +23,8 @@ func (*BaseSniffer) Protocol() string {
return "unknown"
}
// SniffTCP implements sniffer.Sniffer
func (*BaseSniffer) SniffTCP(bytes []byte) (string, error) {
// SniffData implements sniffer.Sniffer
func (*BaseSniffer) SniffData(bytes []byte) (string, error) {
return "", errors.New("TODO")
}

View File

@ -35,8 +35,40 @@ type SnifferDispatcher struct {
parsePureIp bool
}
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
return (metadata.Host == "" && sd.parsePureIp) ||
sd.forceDomain.Has(metadata.Host) ||
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping)
}
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
metadata := packet.Metadata()
if sd.shouldOverride(packet.Metadata()) {
for sniffer, config := range sd.sniffers {
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet {
inWhitelist := sniffer.SupportPort(metadata.DstPort)
overrideDest := config.OverrideDest
if inWhitelist {
host, err := sniffer.SniffData(packet.Data())
if err != nil {
continue
}
sd.replaceDomain(metadata, host, overrideDest)
return true
}
}
}
}
return false
}
// TCPSniff returns true if the connection is sniffed to have a domain
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool {
if sd.shouldOverride(metadata) {
inWhitelist := false
overrideDest := false
for sniffer, config := range sd.sniffers {
@ -50,7 +82,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
}
if !inWhitelist {
return
return false
}
sd.rwMux.RLock()
@ -58,18 +90,18 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
defer sd.rwMux.RUnlock()
return
return false
}
sd.rwMux.RUnlock()
if host, err := sd.sniffDomain(conn, metadata); err != nil {
sd.cacheSniffFailed(metadata)
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return
return false
} else {
if sd.skipSNI.Has(host) {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return
return false
}
sd.rwMux.RLock()
@ -77,20 +109,24 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
sd.rwMux.RUnlock()
sd.replaceDomain(metadata, host, overrideDest)
return true
}
}
return false
}
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
// show log early, since the following code may mutate `metadata.Host`
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
metadata.NetWork,
metadata.SourceDetail(),
metadata.RemoteAddress(),
metadata.Host, host)
metadata.SniffHost = host
if overrideDest {
metadata.Host = host
}
metadata.DNSMode = C.DNSNormal
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
metadata.SourceDetail(),
metadata.RemoteAddress(),
metadata.Host, host)
}
func (sd *SnifferDispatcher) Enable() bool {
@ -121,7 +157,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
continue
}
host, err := s.SniffTCP(bytes)
host, err := s.SniffData(bytes)
if err != nil {
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
continue
@ -190,6 +226,8 @@ func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer
return NewTLSSniffer(snifferConfig)
case sniffer.HTTP:
return NewHTTPSniffer(snifferConfig)
case sniffer.QUIC:
return NewQuicSniffer(snifferConfig)
default:
return nil, ErrorUnsupportedSniffer
}

View File

@ -58,7 +58,7 @@ func (http *HTTPSniffer) SupportNetwork() C.NetWork {
return C.TCP
}
func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) {
func (http *HTTPSniffer) SniffData(bytes []byte) (string, error) {
domain, err := SniffHTTP(bytes)
if err == nil {
return *domain, nil

View File

@ -1,3 +1,287 @@
package sniffer
//TODO
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"errors"
"io"
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
"github.com/metacubex/quic-go/quicvarint"
"golang.org/x/crypto/hkdf"
)
// Modified from https://github.com/v2fly/v2ray-core/blob/master/common/protocol/quic/sniff.go
const (
versionDraft29 uint32 = 0xff00001d
version1 uint32 = 0x1
)
var (
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
errNotQuic = errors.New("not QUIC")
errNotQuicInitial = errors.New("not QUIC initial packet")
)
type QuicSniffer struct {
*BaseSniffer
}
func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) {
ports := snifferConfig.Ports
if len(ports) == 0 {
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)}
}
return &QuicSniffer{
BaseSniffer: NewBaseSniffer(ports, C.UDP),
}, nil
}
func (quic QuicSniffer) Protocol() string {
return "quic"
}
func (quic QuicSniffer) SupportNetwork() C.NetWork {
return C.UDP
}
func (quic QuicSniffer) SniffData(b []byte) (string, error) {
buffer := buf.As(b)
typeByte, err := buffer.ReadByte()
if err != nil {
return "", errNotQuic
}
isLongHeader := typeByte&0x80 > 0
if !isLongHeader || typeByte&0x40 == 0 {
return "", errNotQuicInitial
}
vb, err := buffer.ReadBytes(4)
if err != nil {
return "", errNotQuic
}
versionNumber := binary.BigEndian.Uint32(vb)
if versionNumber != 0 && typeByte&0x40 == 0 {
return "", errNotQuic
} else if versionNumber != versionDraft29 && versionNumber != version1 {
return "", errNotQuic
}
if (typeByte&0x30)>>4 != 0x0 {
return "", errNotQuicInitial
}
var destConnID []byte
if l, err := buffer.ReadByte(); err != nil {
return "", errNotQuic
} else if destConnID, err = buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic
}
if l, err := buffer.ReadByte(); err != nil {
return "", errNotQuic
} else if _, err := buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic
}
tokenLen, err := quicvarint.Read(buffer)
if err != nil || tokenLen > uint64(len(b)) {
return "", errNotQuic
}
if _, err = buffer.ReadBytes(int(tokenLen)); err != nil {
return "", errNotQuic
}
packetLen, err := quicvarint.Read(buffer)
if err != nil {
return "", errNotQuic
}
hdrLen := len(b) - buffer.Len()
var salt []byte
if versionNumber == version1 {
salt = quicSalt
} else {
salt = quicSaltOld
}
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16)
block, err := aes.NewCipher(hpKey)
if err != nil {
return "", err
}
cache := buf.NewPacket()
defer cache.Release()
mask := cache.Extend(block.BlockSize())
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
firstByte := b[0]
// Encrypt/decrypt first byte.
if isLongHeader {
// Long header: 4 bits masked
// High 4 bits are not protected.
firstByte ^= mask[0] & 0x0f
} else {
// Short header: 5 bits masked
// High 3 bits are not protected.
firstByte ^= mask[0] & 0x1f
}
packetNumberLength := int(firstByte&0x3 + 1) // max = 4 (64-bit sequence number)
extHdrLen := hdrLen + packetNumberLength
// copy to avoid modify origin data
extHdr := cache.Extend(extHdrLen)
copy(extHdr, b)
extHdr[0] = firstByte
packetNumber := extHdr[hdrLen:extHdrLen]
// Encrypt/decrypt packet number.
for i := range packetNumber {
packetNumber[i] ^= mask[1+i]
}
if packetNumber[0] != 0 && packetNumber[0] != 1 {
return "", errNotQuicInitial
}
data := b[extHdrLen : int(packetLen)+hdrLen]
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
aesCipher, err := aes.NewCipher(key)
if err != nil {
return "", err
}
aead, err := cipher.NewGCM(aesCipher)
if err != nil {
return "", err
}
// We only decrypt once, so we do not need to XOR it back.
// https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496
for i, b := range packetNumber {
iv[len(iv)-len(packetNumber)+i] ^= b
}
dst := cache.Extend(len(data))
decrypted, err := aead.Open(dst[:0], iv, data, extHdr)
if err != nil {
return "", err
}
buffer = buf.As(decrypted)
cryptoLen := uint(0)
cryptoData := cache.Extend(buffer.Len())
for i := 0; !buffer.IsEmpty(); i++ {
frameType := byte(0x0) // Default to PADDING frame
for frameType == 0x0 && !buffer.IsEmpty() {
frameType, _ = buffer.ReadByte()
}
switch frameType {
case 0x00: // PADDING frame
case 0x01: // PING frame
case 0x02, 0x03: // ACK frame
if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay
return "", io.ErrUnexpectedEOF
}
ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count
if err != nil {
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range
return "", io.ErrUnexpectedEOF
}
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length
return "", io.ErrUnexpectedEOF
}
}
if frameType == 0x03 {
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
return "", io.ErrUnexpectedEOF
}
}
case 0x06: // CRYPTO frame, we will use this frame
offset, err := quicvarint.Read(buffer) // Field: Offset
if err != nil {
return "", io.ErrUnexpectedEOF
}
length, err := quicvarint.Read(buffer) // Field: Length
if err != nil || length > uint64(buffer.Len()) {
return "", io.ErrUnexpectedEOF
}
if cryptoLen < uint(offset+length) {
cryptoLen = uint(offset + length)
}
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data
return "", io.ErrUnexpectedEOF
}
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
return "", io.ErrUnexpectedEOF
}
if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type
return "", io.ErrUnexpectedEOF
}
length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length
if err != nil {
return "", io.ErrUnexpectedEOF
}
if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase
return "", io.ErrUnexpectedEOF
}
default:
// Only above frame types are permitted in initial packet.
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
return "", errNotQuicInitial
}
}
domain, err := ReadClientHello(cryptoData[:cryptoLen])
if err != nil {
return "", err
}
return *domain, nil
}
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
b := make([]byte, 3, 3+6+len(label)+1+len(context))
binary.BigEndian.PutUint16(b, uint16(length))
b[2] = uint8(6 + len(label))
b = append(b, []byte("tls13 ")...)
b = append(b, []byte(label)...)
b = b[:3+6+len(label)+1]
b[3+6+len(label)] = uint8(len(context))
b = append(b, context...)
out := make([]byte, length)
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
if err != nil || n != length {
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
}
return out
}

View File

@ -1,9 +1,40 @@
package sniffer
import (
"bytes"
"encoding/hex"
"github.com/stretchr/testify/assert"
"testing"
)
func TestQuicHeaders(t *testing.T) {
cases := []struct {
input string
domain string
}{
{
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b",
domain: "www.google.com",
},
{
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738",
domain: "cloudflare-dns.com",
},
}
q, err := NewQuicSniffer(SnifferConfig{})
assert.NoError(t, err)
for _, test := range cases {
pkt, err := hex.DecodeString(test.input)
assert.NoError(t, err)
oriPkt := bytes.Clone(pkt)
domain, err := q.SniffData(pkt)
assert.NoError(t, err)
assert.Equal(t, test.domain, domain)
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
}
}
func TestTLSHeaders(t *testing.T) {
cases := []struct {
input []byte
@ -142,6 +173,7 @@ func TestTLSHeaders(t *testing.T) {
}
for _, test := range cases {
input := bytes.Clone(test.input)
domain, err := SniffTLS(test.input)
if test.err {
if err == nil {
@ -155,5 +187,6 @@ func TestTLSHeaders(t *testing.T) {
t.Error("expect domain ", test.domain, " but got ", domain)
}
}
assert.Equal(t, input, test.input)
}
}

View File

@ -39,7 +39,7 @@ func (tls *TLSSniffer) SupportNetwork() C.NetWork {
return C.TCP
}
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) {
func (tls *TLSSniffer) SniffData(bytes []byte) (string, error) {
domain, err := SniffTLS(bytes)
if err == nil {
return *domain, nil

View File

@ -22,6 +22,7 @@ import (
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/ntp"
utls "github.com/sagernet/utls"
"github.com/zhangyunhao116/fastrand"
@ -70,7 +71,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
rawSessionID[i] = 0
}
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
copy(hello.SessionId[8:], realityConfig.ShortID[:])
hello.SessionId[0] = 1

View File

@ -99,10 +99,9 @@ func copyConfig(c *tls.Config) *utls.Config {
}
}
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
// http/1.1 in its ALPN.
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
func (c *UConn) WebsocketHandshake() error {
func (c *UConn) BuildWebsocketHandshakeState() error {
// Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil {
@ -120,11 +119,11 @@ func (c *UConn) WebsocketHandshake() error {
if !hasALPNExtension { // Append extension if doesn't exists
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
}
// Rebuild the client hello and do the handshake
// Rebuild the client hello
if err := c.BuildHandshakeState(); err != nil {
return err
}
return c.Handshake()
return nil
}
func SetGlobalUtlsClient(Client string) {

View File

@ -8,6 +8,7 @@ import (
"net/netip"
"net/url"
"os"
"path"
"regexp"
"strings"
"time"
@ -16,6 +17,7 @@ import (
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
@ -57,26 +59,27 @@ type General struct {
TCPConcurrent bool `json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
Sniffing bool `json:"sniffing"`
EBpf EBpf `json:"-"`
GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"`
}
// Inbound config
type Inbound struct {
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
Tun LC.Tun `json:"tun"`
TuicServer LC.TuicServer `json:"tuic-server"`
ShadowSocksConfig string `json:"ss-config"`
VmessConfig string `json:"vmess-config"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
InboundTfo bool `json:"inbound-tfo"`
InboundMPTCP bool `json:"inbound-mptcp"`
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
Tun LC.Tun `json:"tun"`
TuicServer LC.TuicServer `json:"tuic-server"`
ShadowSocksConfig string `json:"ss-config"`
VmessConfig string `json:"vmess-config"`
Authentication []string `json:"authentication"`
SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
InboundTfo bool `json:"inbound-tfo"`
InboundMPTCP bool `json:"inbound-mptcp"`
}
// Controller config
@ -87,6 +90,16 @@ type Controller struct {
Secret string `json:"-"`
}
// NTP config
type NTP struct {
Enable bool `yaml:"enable"`
Server string `yaml:"server"`
Port int `yaml:"port"`
Interval int `yaml:"interval"`
DialerProxy string `yaml:"dialer-proxy"`
WriteToSystem bool `yaml:"write-to-system"`
}
// DNS config
type DNS struct {
Enable bool `yaml:"enable"`
@ -144,13 +157,16 @@ type Sniffer struct {
// Experimental config
type Experimental struct {
Fingerprints []string `yaml:"fingerprints"`
Fingerprints []string `yaml:"fingerprints"`
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
}
// Config is clash config manager
type Config struct {
General *General
IPTables *IPTables
NTP *NTP
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie[resolver.HostValue]
@ -167,30 +183,44 @@ type Config struct {
TLS *TLS
}
type RawNTP struct {
Enable bool `yaml:"enable"`
Server string `yaml:"server"`
ServerPort int `yaml:"server-port"`
Interval int `yaml:"interval"`
DialerProxy string `yaml:"dialer-proxy"`
WriteToSystem bool `yaml:"write-to-system"`
}
type RawDNS struct {
Enable bool `yaml:"enable"`
PreferH3 bool `yaml:"prefer-h3"`
IPv6 bool `yaml:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]any `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
Enable bool `yaml:"enable" json:"enable"`
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
NameServer []string `yaml:"nameserver" json:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
Listen string `yaml:"listen" json:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
NameServerPolicy map[string]any `yaml:"nameserver-policy" json:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
}
type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"`
IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
GeoSite []string `yaml:"geosite"`
GeoIP bool `yaml:"geoip" json:"geoip"`
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"`
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"`
Domain []string `yaml:"domain" json:"domain"`
GeoSite []string `yaml:"geosite" json:"geosite"`
}
type RawClashForAndroid struct {
AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"`
UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"`
}
type RawTun struct {
@ -203,21 +233,21 @@ type RawTun struct {
RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address []LC.ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []LC.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
Inet4RouteAddress []LC.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []LC.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
Inet4RouteAddress []netip.Prefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
}
type RawTuicServer struct {
@ -236,40 +266,46 @@ type RawTuicServer struct {
}
type RawConfig struct {
Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port"`
Port int `yaml:"port" json:"port"`
SocksPort int `yaml:"socks-port" json:"socks-port"`
RedirPort int `yaml:"redir-port" json:"redir-port"`
TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"`
MixedPort int `yaml:"mixed-port" json:"mixed-port"`
ShadowSocksConfig string `yaml:"ss-config"`
VmessConfig string `yaml:"vmess-config"`
InboundTfo bool `yaml:"inbound-tfo"`
InboundMPTCP bool `yaml:"inbound-mptcp"`
Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode"`
UnifiedDelay bool `yaml:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level"`
IPv6 bool `yaml:"ipv6"`
Authentication []string `yaml:"authentication" json:"authentication"`
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
AllowLan bool `yaml:"allow-lan" json:"allow-lan"`
BindAddress string `yaml:"bind-address" json:"bind-address"`
Mode T.TunnelMode `yaml:"mode" json:"mode"`
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
ExternalController string `yaml:"external-controller"`
ExternalControllerTLS string `yaml:"external-controller-tls"`
ExternalUI string `yaml:"external-ui"`
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"`
Tunnels []LC.Tunnel `yaml:"tunnels"`
GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"`
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
GlobalUA string `yaml:"global-ua"`
KeepAliveInterval int `yaml:"keep-alive-interval"`
Sniffer RawSniffer `yaml:"sniffer"`
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]any `yaml:"hosts"`
DNS RawDNS `yaml:"dns"`
Hosts map[string]any `yaml:"hosts" json:"hosts"`
NTP RawNTP `yaml:"ntp" json:"ntp"`
DNS RawDNS `yaml:"dns" json:"dns"`
Tun RawTun `yaml:"tun"`
TuicServer RawTuicServer `yaml:"tuic-server"`
EBpf EBpf `yaml:"ebpf"`
@ -283,6 +319,8 @@ type RawConfig struct {
SubRules map[string][]string `yaml:"sub-rules"`
RawTLS TLS `yaml:"tls"`
Listeners []map[string]any `yaml:"listeners"`
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
}
type GeoXUrl struct {
@ -348,6 +386,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
ProxyGroup: []map[string]any{},
TCPConcurrent: false,
FindProcessMode: P.FindProcessStrict,
GlobalUA: "clash.meta",
Tun: RawTun{
Enable: false,
Device: "",
@ -355,7 +394,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true,
AutoDetectInterface: true,
Inet6Address: []LC.ListenPrefix{LC.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")},
},
TuicServer: RawTuicServer{
Enable: false,
@ -379,6 +418,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
InboundInterface: "lo",
Bypass: []string{},
},
NTP: RawNTP{
Enable: false,
WriteToSystem: false,
Server: "time.apple.com",
ServerPort: 123,
Interval: 30,
},
DNS: RawDNS{
Enable: false,
IPv6: false,
@ -426,6 +472,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
},
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
}
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
@ -441,7 +488,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
startTime := time.Now()
config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile
config.IPTables = &rawCfg.IPTables
config.TLS = &rawCfg.RawTLS
general, err := parseGeneral(rawCfg)
@ -493,17 +539,15 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.Hosts = hosts
ntpCfg := paresNTP(rawCfg)
config.NTP = ntpCfg
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
if err != nil {
return nil, err
}
config.DNS = dnsCfg
err = parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
err = parseTuicServer(rawCfg.TuicServer, config.General)
if err != nil {
return nil, err
@ -533,19 +577,40 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
func parseGeneral(cfg *RawConfig) (*General, error) {
externalUI := cfg.ExternalUI
geodata.SetLoader(cfg.GeodataLoader)
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
C.MmdbUrl = cfg.GeoXUrl.Mmdb
C.GeodataMode = cfg.GeodataMode
C.UA = cfg.GlobalUA
if cfg.KeepAliveInterval != 0 {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
}
ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist
if externalUI != "" {
externalUI = C.Path.Resolve(externalUI)
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
if ExternalUIPath != "" {
ExternalUIPath = C.Path.Resolve(ExternalUIPath)
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath)
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err
}
ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath
}
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
ExternalUIName = cfg.ExternalUIName
} else {
ExternalUIFolder = ExternalUIPath
}
if cfg.ExternalUIURL != "" {
ExternalUIURL = cfg.ExternalUIURL
}
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
return &General{
Inbound: Inbound{
@ -557,6 +622,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
ShadowSocksConfig: cfg.ShadowSocksConfig,
VmessConfig: cfg.VmessConfig,
AllowLan: cfg.AllowLan,
SkipAuthPrefixes: cfg.SkipAuthPrefixes,
BindAddress: cfg.BindAddress,
InboundTfo: cfg.InboundTfo,
InboundMPTCP: cfg.InboundMPTCP,
@ -578,8 +644,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA,
}, nil
}
@ -1132,6 +1198,19 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
return sites, nil
}
func paresNTP(rawCfg *RawConfig) *NTP {
cfg := rawCfg.NTP
ntpCfg := &NTP{
Enable: cfg.Enable,
Server: cfg.Server,
Port: cfg.ServerPort,
Interval: cfg.Interval,
DialerProxy: cfg.DialerProxy,
WriteToSystem: cfg.WriteToSystem,
}
return ntpCfg
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
@ -1284,7 +1363,7 @@ func parseTun(rawTun RawTun, general *General) error {
RedirectToTun: rawTun.RedirectToTun,
MTU: rawTun.MTU,
Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)},
Inet4Address: []netip.Prefix{tunAddressPrefix},
Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute,
Inet4RouteAddress: rawTun.Inet4RouteAddress,

View File

@ -1,17 +1,11 @@
package config
import (
"context"
"fmt"
"io"
"net/http"
"os"
"runtime"
"time"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/maxminddb-golang"
@ -72,19 +66,3 @@ func UpdateGeoDatabases() error {
return nil
}
func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func saveFile(bytes []byte, path string) error {
return os.WriteFile(path, bytes, 0o644)
}

145
config/update_ui.go Normal file
View File

@ -0,0 +1,145 @@
package config
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"sync"
C "github.com/Dreamacro/clash/constant"
)
var (
ExternalUIURL string
ExternalUIPath string
ExternalUIFolder string
ExternalUIName string
)
var (
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
)
var xdMutex sync.Mutex
func UpdateUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepare()
if err != nil {
return err
}
data, err := downloadForBytes(ExternalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
}
saved := path.Join(C.Path.HomeDir(), "download.zip")
if saveFile(data, saved) != nil {
return fmt.Errorf("can't save zip file: %w", err)
}
defer os.Remove(saved)
err = cleanup(ExternalUIFolder)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
}
}
unzipFolder, err := unzip(saved, C.Path.HomeDir())
if err != nil {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIFolder)
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
}
return nil
}
func prepare() error {
if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf
}
if ExternalUIName != "" {
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
return err
}
}
} else {
ExternalUIFolder = ExternalUIPath
}
return nil
}
func unzip(src, dest string) (string, error) {
r, err := zip.OpenReader(src)
if err != nil {
return "", err
}
defer r.Close()
var extractedFolder string
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return "", fmt.Errorf("invalid file path: %s", fpath)
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return "", err
}
rc, err := f.Open()
if err != nil {
return "", err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return "", err
}
if extractedFolder == "" {
extractedFolder = filepath.Dir(fpath)
}
}
return extractedFolder, nil
}
func cleanup(root string) error {
if _, err := os.Stat(root); os.IsNotExist(err) {
return nil
}
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if err := os.RemoveAll(path); err != nil {
return err
}
} else {
if err := os.Remove(path); err != nil {
return err
}
}
return nil
})
}

View File

@ -1,15 +1,37 @@
package config
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"os"
"strings"
"time"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/common/structure"
clashHttp "github.com/Dreamacro/clash/component/http"
)
func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func saveFile(bytes []byte, path string) error {
return os.WriteFile(path, bytes, 0o644)
}
func trimArr(arr []string) (r []string) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))

View File

@ -36,6 +36,7 @@ const (
Vless
Trojan
Hysteria
Hysteria2
WireGuard
Tuic
)
@ -200,6 +201,8 @@ func (at AdapterType) String() string {
return "Trojan"
case Hysteria:
return "Hysteria"
case Hysteria2:
return "Hysteria2"
case WireGuard:
return "WireGuard"
case Tuic:
@ -249,6 +252,23 @@ type PacketAdapter interface {
Metadata() *Metadata
}
type packetAdapter struct {
UDPPacket
metadata *Metadata
}
// Metadata returns destination metadata
func (s *packetAdapter) Metadata() *Metadata {
return s.metadata
}
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
return &packetAdapter{
packet,
metadata,
}
}
type WriteBack interface {
WriteBack(b []byte, addr net.Addr) (n int, err error)
}
@ -267,13 +287,17 @@ type NatTable interface {
Delete(key string)
GetLocalConn(lAddr, rAddr string) *net.UDPConn
DeleteLock(key string)
AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
GetForLocalConn(lAddr, rAddr string) *net.UDPConn
RangeLocalConn(lAddr string, f func(key, value any) bool)
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool)
DeleteLocalConnMap(lAddr, key string)
GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool)
DeleteForLocalConn(lAddr, key string)
DeleteLockForLocalConn(lAddr, key string)
}

View File

@ -2,7 +2,7 @@ package constant
var (
GeodataMode bool
GeoIpUrl string
MmdbUrl string
GeoSiteUrl string
GeoIpUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
MmdbUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
GeoSiteUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
)

5
constant/http.go Normal file
View File

@ -0,0 +1,5 @@
package constant
var (
UA string
)

View File

@ -16,7 +16,7 @@ type MultiAddrListener interface {
type InboundListener interface {
Name() string
Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error
Listen(tunnel Tunnel) error
Close() error
Address() string
RawAddress() string

View File

@ -30,6 +30,7 @@ const (
TUNNEL
TUN
TUIC
HYSTERIA2
INNER
)
@ -78,6 +79,8 @@ func (t Type) String() string {
return "Tun"
case TUIC:
return "Tuic"
case HYSTERIA2:
return "Hysteria2"
case INNER:
return "Inner"
default:
@ -110,6 +113,8 @@ func ParseType(t string) (*Type, error) {
res = TUN
case "TUIC":
res = TUIC
case "HYSTERIA2":
res = HYSTERIA2
case "INNER":
res = INNER
default:
@ -142,6 +147,9 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"`
RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"`
// Only domain rule
SniffHost string `json:"sniffHost"`
}
@ -235,6 +243,34 @@ func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP.IsValid()
}
func (m *Metadata) SetRemoteAddr(addr net.Addr) error {
if addr == nil {
return nil
}
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
if rawAddr := rawAddr.RawAddr(); rawAddr != nil {
if err := m.SetRemoteAddr(rawAddr); err == nil {
return nil
}
}
}
if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok { // *net.TCPAddr, *net.UDPAddr, M.Socksaddr
if addrPort := addr.AddrPort(); addrPort.Port() != 0 {
m.DstPort = addrPort.Port()
if addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName
m.DstIP = addrPort.Addr().Unmap()
return nil
} else {
if addr, ok := addr.(interface{ AddrString() string }); ok { // must be sing's M.Socksaddr
m.Host = addr.AddrString() // actually is M.Socksaddr.Fqdn
return nil
}
}
}
}
return m.SetRemoteAddress(addr.String())
}
func (m *Metadata) SetRemoteAddress(rawAddress string) error {
host, port, err := net.SplitHostPort(rawAddress)
if err != nil {

View File

@ -18,6 +18,9 @@ var (
)
// Path is used to get the configuration path
//
// on Unix systems, `$HOME/.config/clash`.
// on Windows, `%USERPROFILE%/.config/clash`.
var Path = func() *path {
homeDir, err := os.UserHomeDir()
if err != nil {
@ -25,6 +28,13 @@ var Path = func() *path {
}
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
homeDir = P.Join(homeDir, ".config", Name)
if _, err = os.Stat(homeDir); err != nil {
if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok {
homeDir = P.Join(configHome, Name)
}
}
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
}()

View File

@ -4,7 +4,8 @@ import "github.com/Dreamacro/clash/constant"
type Sniffer interface {
SupportNetwork() constant.NetWork
SniffTCP(bytes []byte) (string, error)
// SniffData must not change input bytes
SniffData(bytes []byte) (string, error)
Protocol() string
SupportPort(port uint16) bool
}
@ -12,10 +13,11 @@ type Sniffer interface {
const (
TLS Type = iota
HTTP
QUIC
)
var (
List = []Type{TLS, HTTP}
List = []Type{TLS, HTTP, QUIC}
)
type Type int
@ -26,6 +28,8 @@ func (rt Type) String() string {
return "TLS"
case HTTP:
return "HTTP"
case QUIC:
return "QUIC"
default:
return "Unknown"
}

12
constant/tunnel.go Normal file
View File

@ -0,0 +1,12 @@
package constant
import "net"
type Tunnel interface {
// HandleTCPConn will handle a tcp connection blocking
HandleTCPConn(conn net.Conn, metadata *Metadata)
// HandleUDPPacket will handle a udp packet nonblocking
HandleUDPPacket(packet UDPPacket, metadata *Metadata)
// NatTable return nat table
NatTable() NatTable
}

View File

@ -9,9 +9,9 @@ import (
"strings"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
@ -23,7 +23,7 @@ type client struct {
r *Resolver
port string
host string
iface *atomic.TypedValue[string]
iface atomic.TypedValue[string]
proxyAdapter C.ProxyAdapter
proxyName string
addr string
@ -77,8 +77,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
network = "tcp"
}
options := []dialer.Option{}
if c.iface != nil && c.iface.Load() != "" {
var options []dialer.Option
if c.iface.Load() != "" {
options = append(options, dialer.WithInterface(c.iface.Load()))
}
@ -99,7 +99,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
ch := make(chan result, 1)
go func() {
if strings.HasSuffix(c.Client.Net, "tls") {
conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig))
conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
}
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{

View File

@ -1,3 +1,6 @@
//go:build disabled
// +build disabled
package dns
import (

View File

@ -15,7 +15,7 @@ import (
"sync"
"time"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/ca"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/metacubex/quic-go"
@ -382,7 +382,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error)
// HTTP3 is enabled in the upstream options). If this attempt is successful,
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
tlsConfig := tlsC.GetGlobalTLSConfig(
tlsConfig := ca.GetGlobalTLSConfig(
&tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,

View File

@ -12,7 +12,7 @@ import (
"sync"
"time"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/ca"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/metacubex/quic-go"
@ -330,7 +330,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
return nil, err
}
tlsConfig := tlsC.GetGlobalTLSConfig(
tlsConfig := ca.GetGlobalTLSConfig(
&tls.Config{
ServerName: host,
InsecureSkipVerify: false,

20
dns/local.go Normal file
View File

@ -0,0 +1,20 @@
package dns
import (
"context"
D "github.com/miekg/dns"
)
type LocalServer struct {
handler handler
}
// ServeMsg implement resolver.LocalServer ResolveMsg
func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) {
return handlerWithContext(ctx, s.handler, msg)
}
func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer {
return &LocalServer{handler: NewHandler(resolver, mapper)}
}

View File

@ -4,17 +4,76 @@ import (
"context"
D "github.com/miekg/dns"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/resolver"
)
type LocalServer struct {
handler handler
const SystemDNSPlaceholder = "system"
var systemResolver *Resolver
var isolateHandler handler
var _ dnsClient = (*dhcpClient)(nil)
type dhcpClient struct {
enable bool
}
// ServeMsg implement resolver.LocalServer ResolveMsg
func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) {
return handlerWithContext(ctx, s.handler, msg)
func (d *dhcpClient) Address() string {
return SystemDNSPlaceholder
}
func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer {
return &LocalServer{handler: NewHandler(resolver, mapper)}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return d.ExchangeContext(context.Background(), m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if s := systemResolver; s != nil {
return s.ExchangeContext(ctx, m)
}
return nil, dhcp.ErrNotFound
}
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
if h := isolateHandler; h != nil {
return handlerWithContext(context.Background(), h, msg)
}
return nil, D.ErrTime
}
func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil {
r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true))
}
}
func UpdateSystemDNS(addr []string) {
if len(addr) == 0 {
systemResolver = nil
}
ns := make([]NameServer, 0, len(addr))
for _, d := range addr {
ns = append(ns, NameServer{Addr: d})
}
systemResolver = NewResolver(Config{Main: ns})
}
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
if resolver == nil {
isolateHandler = nil
return
}
isolateHandler = NewHandler(resolver, mapper)
}
func newDHCPClient(ifaceName string) *dhcpClient {
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
}

View File

@ -18,6 +18,7 @@ import (
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
"github.com/samber/lo"
"golang.org/x/sync/singleflight"
)
@ -194,12 +195,17 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
msg := result.(*D.Msg)
if cache {
putMsgToCache(r.lruCache, q.String(), msg)
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool {
return rr.Header().Rrtype != D.TypeOPT
})
putMsgToCache(r.lruCache, q.String(), q, msg)
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
cache = true
return r.ipExchange(ctx, m)
}
@ -331,7 +337,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
res := <-msgCh
if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 {
if !r.shouldIPFallback(ips[0]) {
shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool {
return !r.shouldIPFallback(ip)
})
if shouldNotFallback {
msg, err = res.Msg, res.Error // no need to wait for fallback result
return
}
@ -393,7 +402,7 @@ func (r *Resolver) Invalid() bool {
type NameServer struct {
Net string
Addr string
Interface *atomic.TypedValue[string]
Interface atomic.TypedValue[string]
ProxyAdapter C.ProxyAdapter
ProxyName string
Params map[string]string

View File

@ -49,6 +49,7 @@ func (s *Server) SetHandler(handler handler) {
}
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
UpdateIsolateHandler(resolver, mapper)
if addr == address && resolver != nil {
handler := NewHandler(resolver, mapper)
server.SetHandler(handler)

View File

@ -29,10 +29,16 @@ const (
MaxMsgSize = 65535
)
const serverFailureCacheTTL uint32 = 5
func minimalTTL(records []D.RR) uint32 {
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
return r1.Header().Ttl < r2.Header().Ttl
}).Header().Ttl
})
if rr == nil {
return 0
}
return rr.Header().Ttl
}
func updateTTL(records []D.RR, ttl uint32) {
@ -45,28 +51,25 @@ func updateTTL(records []D.RR, ttl uint32) {
}
}
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, q D.Question, msg *D.Msg) {
// skip dns cache for acme challenge
if len(msg.Question) != 0 {
if q := msg.Question[0]; 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
}
}
var ttl uint32
switch {
case len(msg.Answer) != 0:
ttl = minimalTTL(msg.Answer)
case len(msg.Ns) != 0:
ttl = minimalTTL(msg.Ns)
case len(msg.Extra) != 0:
ttl = minimalTTL(msg.Extra)
default:
log.Debugln("[DNS] response msg empty: %#v", msg)
if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") {
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
return
}
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
var ttl uint32
if msg.Rcode == D.RcodeServerFailure {
// [...] a resolver MAY cache a server failure response.
// If it does so it MUST NOT cache it for longer than five (5) minutes [...]
ttl = serverFailureCacheTTL
} else {
ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...))
}
if ttl == 0 {
return
}
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second))
}
func setMsgTTL(msg *D.Msg, ttl uint32) {
@ -286,7 +289,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
DstPort: uint16(uintPort),
}
if proxyAdapter == nil {
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
}
if !proxyAdapter.SupportUDP() {

View File

@ -8,6 +8,11 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true'*'表示所有地址
authentication: # http,socks入口的验证用户名密码
- "username:password"
skip-auth-prefixes: # 设置跳过验证的IP段
- 127.0.0.1/8
- ::1/128
# find-process-mode has 3 values:always, strict, off
# - always, 开启,强制匹配所有进程
@ -41,7 +46,11 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
# secret: "123456" # `Authorization:Bearer ${secret}`
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
external-ui: /path/to/ui/folder/
external-ui-name: xd
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
# interface-name: en0 # 设置出口网卡
@ -50,8 +59,18 @@ external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{externa
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
global-client-fingerprint: chrome
# TCP keep alive interval
keep-alive-interval: 15
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
experimental:
# Disable quic-go GSO support. This may result in reduced performance on Linux.
# This is not recommended for most users.
# Only users encountering issues with quic-go's internal implementation should enable this,
# and they should disable it as soon as the issue is resolved.
# This field will be removed when quic-go fixes all their issues in GSO.
# This equivalent to the environment variable QUIC_GO_DISABLE_GSO=1.
#quic-go-disable-gso: true
# 类似于 /etc/hosts, 仅支持配置单个 IP
hosts:
@ -123,7 +142,9 @@ sniffer:
# 是否使用嗅探结果作为实际访问,默认 true
# 全局配置,优先级低于 sniffer.sniff 实际配置
override-destination: false
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
sniff: # TLS 和 QUIC 默认如果不配置 ports 默认嗅探 443
QUIC:
# ports: [ 443 ]
TLS:
# ports: [443, 8443]
@ -628,6 +649,25 @@ proxies: # socks5
# fingerprint: xxxx
# fast-open: true # 支持 TCP 快速打开,默认为 false
#hysteria2
- name: "hysteria2"
type: hysteria2
server: server.com
port: 443
# up和down均不写或为0则使用BBR流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps
password: yourpassword
# obfs: salamander # 默认为空如果填写则开启obfs目前仅支持salamander
# obfs-password: yourpassword
# sni: server.com
# skip-cert-verify: false
# fingerprint: xxxx
# alpn:
# - h3
# ca: "./my.ca"
# ca-str: "xyz"
# wireguard
- name: "wg"
type: wireguard
@ -903,6 +943,10 @@ listeners:
- username: 1
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1
# ws-path: "/" # 如果不为空则开启websocket传输层
# 下面两项如果填写则开启tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: tuic-in-1
type: tuic

62
go.mod
View File

@ -5,49 +5,51 @@ go 1.20
require (
github.com/3andne/restls-client-go v0.1.6
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/cilium/ebpf v0.11.0
github.com/cilium/ebpf v0.12.0
github.com/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.10.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.3.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
github.com/jpillora/backoff v1.0.0
github.com/klauspost/cpuid/v2 v2.2.5
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28
github.com/metacubex/sing-shadowsocks v0.2.4
github.com/metacubex/sing-shadowsocks2 v0.1.3
github.com/metacubex/sing-tun v0.1.11
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
github.com/miekg/dns v1.1.55
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
github.com/metacubex/sing-shadowsocks v0.2.5
github.com/metacubex/sing-shadowsocks2 v0.1.4
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170
github.com/miekg/dns v1.1.56
github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0
github.com/puzpuzpuz/xsync/v2 v2.5.1
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c
github.com/sagernet/sing v0.2.14
github.com/sagernet/sing-mux v0.1.3
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/samber/lo v1.38.1
github.com/shirou/gopsutil/v3 v3.23.7
github.com/shirou/gopsutil/v3 v3.23.9
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/zhangyunhao116/fastrand v0.3.0
go.etcd.io/bbolt v1.3.7
go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
golang.org/x/net v0.14.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.11.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.17.0
golang.org/x/sync v0.4.0
golang.org/x/sys v0.13.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.1
@ -63,11 +65,12 @@ require (
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
@ -76,14 +79,14 @@ require (
github.com/klauspost/compress v1.16.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
@ -91,16 +94,17 @@ require (
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
golang.org/x/tools v0.14.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9

139
go.sum
View File

@ -14,8 +14,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/cilium/ebpf v0.12.0 h1:oQEuIQIXgYhe1v7sYUG0P9vtJTYZLLdA6tiQmrOB1mo=
github.com/cilium/ebpf v0.12.0/go.mod h1:u9H29/Iq+8cy70YqI6p5pfADkFl3vdnV2qXDg5JL0Zo=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -33,8 +33,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4Rfsap
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
@ -44,14 +44,19 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
@ -64,13 +69,11 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs=
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@ -92,24 +95,26 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28 h1:ggSo4B1LDH9ZIROoUibxlrUpi7YCMri7HMXn4aNQkiM=
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28/go.mod h1:SthFvvoqgrEUgIxQXRnqdUAAYQECBavkhl7iA0geVd8=
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579 h1:dE1dBB6CTzNdSMFTE5OCHvzHLewiqiA1nhD+7egtvAc=
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0=
github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI=
github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y=
github.com/metacubex/sing-shadowsocks2 v0.1.3/go.mod h1:5Mt93RlmRlIcDmvtapkhQJ8YTRGLFhHciLYopJjs7j8=
github.com/metacubex/sing-tun v0.1.11 h1:B8meDewklvKkeUfjqR2ViuYLam0/m4IgkTi3qcJIOuc=
github.com/metacubex/sing-tun v0.1.11/go.mod h1:vbki176Y5sxXC1DWXucrPh3q5j8cKai1D87y8m8rjQc=
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 h1:AqqZCr9gOeKdO6oIzFh4b2puOUFcw8MdpmGHWRehyX8=
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8/go.mod h1:tyJg7b4s8NrSztl/Y1ajA7X0sJLlIsEJWkgRVocjmgY=
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs=
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c=
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b h1:uZ++sW8yg7Fr/Wvmmrb/V+SfxvRs0iMC+2+u2bRmO8g=
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b/go.mod h1:4pe6cY+nAMFU/Uxn1rfnxNIowsaJGDQ3uyy4VuiPkP4=
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9 h1:F0+IuW0tZ96QHEmrebXAdYnz7ab7Gz4l5yYC4g6Cg8k=
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966 h1:wbOsbU3kfD5LRuJIntJwEPmgGSQukof8CgLNypi8az8=
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966/go.mod h1:GU7g2AZesXItk4CspDP8Dc7eGtlA2GVDihyCwsUXRSo=
github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700 h1:JToLa8cxHrd6tOUHWCg9YM+o/4MXmjgagG909itmnyE=
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700/go.mod h1:atkIOs6Y5NeUzstK5SBvnrFo4z1JLuORhEfQECEVUpI=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
@ -132,17 +137,19 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@ -157,8 +164,8 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@ -181,10 +188,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@ -192,7 +199,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
@ -203,26 +209,25 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -230,35 +235,27 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

View File

@ -2,16 +2,22 @@ package executor
import (
"fmt"
"net"
"net/netip"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/ntp"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
G "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/iface"
@ -19,7 +25,6 @@ import (
"github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/resolver"
SNI "github.com/Dreamacro/clash/component/sniffer"
CTLS "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant"
@ -79,9 +84,9 @@ func ApplyConfig(cfg *config.Config, force bool) {
tunnel.OnSuspend()
CTLS.ResetCertificate()
ca.ResetCertificate()
for _, c := range cfg.TLS.CustomTrustCert {
if err := CTLS.AddCertificate(c); err != nil {
if err := ca.AddCertificate(c); err != nil {
log.Warnln("%s\nadd error: %s", c, err.Error())
}
}
@ -92,6 +97,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts)
updateGeneral(cfg.General)
updateNTP(cfg.NTP)
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force)
updateIPTables(cfg)
@ -112,7 +118,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
}
func initInnerTcp() {
inner.New(tunnel.TCPIn())
inner.New(tunnel.Tunnel)
}
func GetGeneral() *config.General {
@ -129,14 +135,16 @@ func GetGeneral() *config.General {
RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort,
Tun: listener.LastTunConf,
Tun: listener.GetTunConf(),
TuicServer: listener.GetTuicConf(),
ShadowSocksConfig: ports.ShadowSocksConfig,
VmessConfig: ports.VmessConfig,
Authentication: authenticator,
SkipAuthPrefixes: inbound.SkipAuthPrefixes(),
AllowLan: listener.AllowLan(),
BindAddress: listener.BindAddress(),
},
Controller: config.Controller{},
Mode: tunnel.Mode(),
LogLevel: log.Level(),
IPv6: !resolver.DisableIPv6,
@ -150,32 +158,46 @@ func GetGeneral() *config.General {
}
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
if !force {
return
}
allowLan := general.AllowLan
listener.SetAllowLan(allowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
bindAddress := general.BindAddress
listener.SetBindAddress(bindAddress)
listener.ReCreateHTTP(general.Port, tcpIn)
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
}
func updateExperimental(c *config.Config) {
if c.Experimental.QUICGoDisableGSO {
_ = os.Setenv("QUIC_GO_DISABLE_GSO", strconv.FormatBool(true))
}
if c.Experimental.QUICGoDisableECN {
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
}
}
func updateNTP(c *config.NTP) {
if c.Enable {
ntp.ReCreateNTPService(
net.JoinHostPort(c.Server, strconv.Itoa(c.Port)),
time.Duration(c.Interval),
c.DialerProxy,
c.WriteToSystem,
)
}
}
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
@ -318,7 +340,7 @@ func updateTun(general *config.General) {
if general == nil {
return
}
listener.ReCreateTun(LC.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn())
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
}
@ -346,7 +368,7 @@ func updateSniffer(sniffer *config.Sniffer) {
}
func updateTunnels(tunnels []LC.Tunnel) {
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
listener.PatchTunnel(tunnels, tunnel.Tunnel)
}
func updateGeneral(general *config.General) {
@ -479,7 +501,7 @@ func updateIPTables(cfg *config.Config) {
}
func Shutdown() {
listener.Cleanup(false)
listener.Cleanup()
tproxy.CleanupTProxyIPTables()
resolver.StoreFakePoolState()

View File

@ -2,9 +2,11 @@ package route
import (
"net/http"
"net/netip"
"path/filepath"
"sync"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config"
@ -47,6 +49,7 @@ type configSchema struct {
TcptunConfig *string `json:"tcptun-config"`
UdptunConfig *string `json:"udptun-config"`
AllowLan *bool `json:"allow-lan"`
SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"`
BindAddress *string `json:"bind-address"`
Mode *tunnel.TunnelMode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
@ -66,21 +69,21 @@ type tunSchema struct {
//RedirectToTun []string `yaml:"-" json:"-"`
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address *[]LC.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress *[]LC.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress *[]LC.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
}
type tuicServerSchema struct {
@ -231,6 +234,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
P.SetAllowLan(*general.AllowLan)
}
if general.SkipAuthPrefixes != nil {
inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes)
}
if general.BindAddress != nil {
P.SetBindAddress(*general.BindAddress)
}
@ -249,19 +256,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
ports := P.GetPorts()
tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn, natTable)
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn, natTable)
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tcpIn, udpIn)
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn)
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tcpIn, udpIn)
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tcpIn, udpIn)
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tunnel.Tunnel)
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tunnel.Tunnel)
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tunnel.Tunnel)
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tunnel.Tunnel)
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tunnel.Tunnel)
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tunnel.Tunnel)
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tunnel.Tunnel)
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tunnel.Tunnel)
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tunnel.Tunnel)
if general.Mode != nil {
tunnel.SetMode(*general.Mode)

View File

@ -11,7 +11,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
func connectionRouter() http.Handler {
@ -23,13 +24,13 @@ func connectionRouter() http.Handler {
}
func getConnections(w http.ResponseWriter, r *http.Request) {
if !websocket.IsWebSocketUpgrade(r) {
if !(r.Header.Get("Upgrade") == "websocket") {
snapshot := statistic.DefaultManager.Snapshot()
render.JSON(w, r, snapshot)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
conn, _, _, err := ws.UpgradeHTTP(r, w)
if err != nil {
return
}
@ -55,7 +56,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
return err
}
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
return wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpText, buf.Bytes())
}
if err := sendSnapshot(); err != nil {

View File

@ -4,22 +4,35 @@ import (
"context"
"net/http"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/samber/lo"
)
func proxyProviderRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getProviders)
r.Route("/{name}", func(r chi.Router) {
r.Route("/{providerName}", func(r chi.Router) {
r.Use(parseProviderName, findProviderByName)
r.Get("/", getProvider)
r.Put("/", updateProvider)
r.Get("/healthcheck", healthCheckProvider)
r.Mount("/", proxyProviderProxyRouter())
})
return r
}
func proxyProviderProxyRouter() http.Handler {
r := chi.NewRouter()
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProviderProxyByName)
r.Get("/", getProxy)
r.Get("/healthcheck", getProxyDelay)
})
return r
}
@ -54,7 +67,7 @@ func healthCheckProvider(w http.ResponseWriter, r *http.Request) {
func parseProviderName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := getEscapeParam(r, "name")
name := getEscapeParam(r, "providerName")
ctx := context.WithValue(r.Context(), CtxKeyProviderName, name)
next.ServeHTTP(w, r.WithContext(ctx))
})
@ -76,6 +89,27 @@ func findProviderByName(next http.Handler) http.Handler {
})
}
func findProviderProxyByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
name = r.Context().Value(CtxKeyProxyName).(string)
pd = r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)
)
proxy, exist := lo.Find(pd.Proxies(), func(proxy C.Proxy) bool {
return proxy.Name() == name
})
if !exist {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
return
}
ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func ruleProviderRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getRuleProviders)

View File

@ -46,7 +46,7 @@ func parseProxyName(next http.Handler) http.Handler {
func findProxyByName(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxies := tunnel.Proxies()
proxies := tunnel.ProxiesWithProviders()
proxy, exist := proxies[name]
if !exist {
render.Status(r, http.StatusNotFound)
@ -60,7 +60,7 @@ func findProxyByName(next http.Handler) http.Handler {
}
func getProxies(w http.ResponseWriter, r *http.Request) {
proxies := tunnel.Proxies()
proxies := tunnel.ProxiesWithProviders()
render.JSON(w, r, render.M{
"proxies": proxies,
})

View File

@ -5,6 +5,7 @@ import (
"crypto/subtle"
"crypto/tls"
"encoding/json"
"net"
"net/http"
"runtime/debug"
"strings"
@ -21,7 +22,8 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
var (
@ -29,12 +31,6 @@ var (
serverAddr = ""
uiPath = ""
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
)
type Traffic struct {
@ -112,7 +108,7 @@ func Start(addr string, tlsAddr string, secret string,
if len(tlsAddr) > 0 {
go func() {
c, err := CN.ParseCert(certificat, privateKey)
c, err := CN.ParseCert(certificat, privateKey, C.Path)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
@ -166,7 +162,7 @@ func authentication(next http.Handler) http.Handler {
}
// Browser websocket not support custom header
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
token := r.URL.Query().Get("token")
if !safeEuqal(token, serverSecret) {
render.Status(r, http.StatusUnauthorized)
@ -197,10 +193,10 @@ func hello(w http.ResponseWriter, r *http.Request) {
}
func traffic(w http.ResponseWriter, r *http.Request) {
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, err = upgrader.Upgrade(w, r, nil)
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
if err != nil {
return
}
@ -230,7 +226,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
}
if err != nil {
@ -240,10 +236,10 @@ func traffic(w http.ResponseWriter, r *http.Request) {
}
func memory(w http.ResponseWriter, r *http.Request) {
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, err = upgrader.Upgrade(w, r, nil)
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
if err != nil {
return
}
@ -280,7 +276,7 @@ func memory(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
}
if err != nil {
@ -293,6 +289,16 @@ type Log struct {
Type string `json:"type"`
Payload string `json:"payload"`
}
type LogStructuredField struct {
Key string `json:"key"`
Value string `json:"value"`
}
type LogStructured struct {
Time string `json:"time"`
Level string `json:"level"`
Message string `json:"message"`
Fields []LogStructuredField `json:"fields"`
}
func getLogs(w http.ResponseWriter, r *http.Request) {
levelText := r.URL.Query().Get("level")
@ -300,6 +306,12 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
levelText = "info"
}
formatText := r.URL.Query().Get("format")
isStructured := false
if formatText == "structured" {
isStructured = true
}
level, ok := log.LogLevelMapping[levelText]
if !ok {
render.Status(r, http.StatusBadRequest)
@ -307,10 +319,10 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
return
}
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, err = upgrader.Upgrade(w, r, nil)
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
if err != nil {
return
}
@ -342,11 +354,26 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
}
buf.Reset()
if err := json.NewEncoder(buf).Encode(Log{
Type: logM.Type(),
Payload: logM.Payload,
}); err != nil {
break
if !isStructured {
if err := json.NewEncoder(buf).Encode(Log{
Type: logM.Type(),
Payload: logM.Payload,
}); err != nil {
break
}
} else {
newLevel := logM.Type()
if newLevel == "warning" {
newLevel = "warn"
}
if err := json.NewEncoder(buf).Encode(LogStructured{
Time: time.Now().Format(time.TimeOnly),
Level: newLevel,
Message: logM.Payload,
Fields: []LogStructuredField{},
}); err != nil {
break
}
}
var err error
@ -354,7 +381,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
}
if err != nil {

View File

@ -1,10 +1,12 @@
package route
import (
"errors"
"fmt"
"net/http"
"os"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/hub/updater"
"github.com/Dreamacro/clash/log"
@ -14,11 +16,12 @@ import (
func upgradeRouter() http.Handler {
r := chi.NewRouter()
r.Post("/", upgrade)
r.Post("/", upgradeCore)
r.Post("/ui", updateUI)
return r
}
func upgrade(w http.ResponseWriter, r *http.Request) {
func upgradeCore(w http.ResponseWriter, r *http.Request) {
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/home/controlupdate.go#L108
log.Infoln("start update")
execPath, err := os.Executable()
@ -43,3 +46,24 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
go restartExecutable(execPath)
}
func updateUI(w http.ResponseWriter, r *http.Request) {
err := config.UpdateUI()
if err != nil {
if errors.Is(err, config.ErrIncompleteConf) {
log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
} else {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
}
return
}
render.JSON(w, r, render.M{"status": "ok"})
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}

View File

@ -32,7 +32,7 @@ var (
workDir string
// mu protects all fields below.
mu sync.RWMutex
mu sync.Mutex
currentExeName string // 当前可执行文件
updateDir string // 更新目录

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