Compare commits

...
This repository has been archived on 2024-09-06. You can view files and clone it, but cannot push or open issues or pull requests.

83 Commits

Author SHA1 Message Date
GitHub Action
807d41f57b Android: patch 2023-11-03 05:59:14 +00:00
wwqgtxx
8c3557e96b chore: support v2ray http upgrade server too 2023-11-03 13:58:53 +08:00
wwqgtxx
228990472d fix: avoid tls panic 2023-11-03 12:04:22 +08:00
wwqgtxx
09e7866a5c fix: gvisor panic 2023-11-03 11:50:25 +08:00
wwqgtxx
665ba7f9f1 chore: do websocket client upgrade directly instead of gobwas/ws 2023-11-03 11:50:25 +08:00
wwqgtxx
ee3038d5e4 chore: add SetupContextForConn for common/net 2023-11-03 11:50:25 +08:00
wwqgtxx
885ee7a820 fix: v2ray http upgrade Hosts header not working 2023-11-03 11:50:25 +08:00
Steve Johnson
ef303b11f2 action: trigger CMFA PR update in every commit 2023-11-02 16:01:35 +08:00
wwqgtxx
a82ce85707 chore: add route exclude support 2023-11-02 11:37:40 +08:00
wwqgtxx
5bfe7ba169 chore: better tls handshake 2023-11-02 11:22:01 +08:00
wwqgtxx
ceac5bfaa4 feat: add v2ray-http-upgrade support 2023-11-02 11:11:35 +08:00
wwqgtxx
b0638cfc49 chore: better bufio.Reader warp 2023-11-02 11:11:35 +08:00
Skyxim
96220aa8ea feat: cancel RULE-SET nested SUB-RULE restrictions 2023-10-31 11:10:38 +00:00
HolgerHuo
8ff476a3a1
fix: remote logic rules cannot be parsed (#837) 2023-10-31 19:07:01 +08:00
Steve Johnson
261b6e8dce
action: small fix to cmfa core-update trigger 2023-10-30 20:00:15 +08:00
xishang0128
2b9141e0e5 chore: geo link replaced with github 2023-10-30 19:46:56 +08:00
xishang0128
55255faa52 chore: modify configuration fields 2023-10-27 17:49:12 +08:00
Steve Johnson
d42e3f74ad action: add question issue guidance 2023-10-26 19:08:42 +08:00
wwqgtxx
81a8a63861 build: more go120 build 2023-10-26 11:39:54 +08:00
wwqgtxx
c3a61e2db5 build: add go120 build for win7/8.1 2023-10-26 11:09:19 +08:00
wwqgtxx
bffe47a974 chore: netip.Prefix should not using pointer 2023-10-26 11:02:53 +08:00
wwqgtxx
4314b37d04 fix: dhcp not working on windows 2023-10-26 10:27:38 +08:00
wwqgtxx
cf93f69f40 chore: cleanup error using of dialer.DefaultInterface 2023-10-26 09:07:49 +08:00
wwqgtxx
55f626424f chore: better dns batchExchange 2023-10-25 20:16:44 +08:00
wwqgtxx
431d52f250 chore: system resolver can autoupdate 2023-10-25 19:21:20 +08:00
wwqgtxx
c1f24d8f0e chore: code cleanup 2023-10-25 18:07:45 +08:00
Steve Johnson
fc5a3cf80c action: ban black issues 2023-10-25 18:06:10 +08:00
wwqgtxx
e1e999180a chore: inMemoryAuthenticator unneed sync map 2023-10-24 21:25:03 +08:00
wwqgtxx
8755618910 fix: reality panic 2023-10-23 23:33:59 +08:00
Steve Johnson
aede97571f Merge branch 'Alpha' of https://github.com/MetaCubeX/Clash.Meta into Alpha 2023-10-23 17:02:08 +08:00
Steve Johnson
01bc84db02 chore: add labels to issue template 2023-10-23 17:02:04 +08:00
wwqgtxx
3564e96a00 chore: share some code 2023-10-23 16:45:22 +08:00
wwqgtxx
f6f8f27668 action: update sync 2023-10-23 15:39:56 +08:00
Steve Johnson
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
Steve Johnson
e987cdaaae chore: add CMFA auto update-dependencies trigger 2023-10-23 15:39:56 +08:00
wwqgtxx
6cd0e58fd0 fix: ssr panic 2023-10-23 15:39:56 +08:00
wwqgtxx
f794c090a5 chore: update sing-tun 2023-10-23 15:39:56 +08:00
xishang0128
0d3197e437 chore: fix sniffer log error 2023-10-20 22:36:29 +08:00
wwqgtxx
150bf7fc65 chore: decrease memory copy in sing listener 2023-10-20 08:39:04 +08:00
Larvan2
51004b14d9 docs: update readme.md 2023-10-20 00:34:10 +08:00
wwqgtxx
ea7e15b447 chore: decrease memory copy in quic sniffer 2023-10-19 23:51:37 +08:00
wwqgtxx
8e637a2ec7 chore: code cleanup 2023-10-19 20:44:49 +08:00
Larvan2
96d886380a
Merge pull request #810 from 5aaee9/Alpha
feat: add quic sniffer
2023-10-19 19:34:45 +08:00
5aaee9
981c69040f docs: update about quic sniffer 2023-10-19 19:09:13 +08:00
5aaee9
de90c276af feat(sniffer): add quic sniffer 2023-10-19 18:30:20 +08:00
wwqgtxx
0129a8579f chore: merge some quic-go fix 2023-10-19 11:08:14 +08:00
wwqgtxx
11ed4a56bd chore: code cleanup 2023-10-17 12:46:41 +08:00
wwqgtxx
d75a0e69a0 chore: Update dependencies 2023-10-16 09:56:41 +08:00
Dreamacro
1faad73381 fix: socks5 udp associate 2023-10-16 09:27:55 +08:00
septs
d2499cd69d feature: add xdg base support (#2913) 2023-10-16 09:23:31 +08:00
septs
98df77439c feature: add environs startup option support (#2909) 2023-10-16 09:22:16 +08:00
Jiahao Lu
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
sduoduo233
9f530525d7 fix: method in vmess http-opts is not used 2023-10-16 09:16:36 +08:00
wwqgtxx
129283066f chore: code cleanup 2023-10-11 22:54:19 +08:00
wwqgtxx
0dc6a726c1 fix: unmap 4in6 ip 2023-10-11 18:17:39 +08:00
wwqgtxx
4636499439 chore: support reject proxy type 2023-10-11 13:01:14 +08:00
wwqgtxx
9a16eb2895 fix: BBR memory leak
from: 7c46e845a6
2023-10-11 11:01:17 +08:00
wwqgtxx
270a080b55 fix: sing listener panic 2023-10-11 10:55:12 +08:00
wwqgtxx
1cf9a55e3e chore: code cleanup 2023-10-10 21:29:12 +08:00
wwqgtxx
6bcd91a801 feat: add skip-auth-prefixes 2023-10-10 21:29:12 +08:00
wwqgtxx
7ed25ddc74 chore: better atomic using 2023-10-10 21:28:46 +08:00
wwqgtxx
ae557c30d3 fix: quic-go min MTU 2023-10-08 13:15:17 +08:00
wwqgtxx
5a1800d642 fix: BBR bandwidth estimation edge case
from 89429598bf
2023-10-08 07:26:28 +08:00
wwqgtxx
d8fe7a52d6 feat: add certificate and private-key to vmess listener 2023-10-08 07:26:28 +08:00
wwqgtxx
791ecfbb32 feat: add ws-path to vmess listener 2023-10-08 07:26:28 +08:00
wwqgtxx
5ff4473083 chore: migrate from gorilla/websocket to gobwas/ws 2023-10-06 17:44:36 +08:00
wwqgtxx
d1e88a30cb fix: gVisor UDP 6to4 check 2023-10-03 16:00:03 +08:00
wwqgtxx
7eae7756f5 chore: update gvisor 2023-10-01 19:15:26 +08:00
wwqgtxx
4e3cd01aad chore: merge some quic-go fix 2023-10-01 13:44:56 +08:00
Larvan2
dbaee284e4 fix: hy2/tuic inbound cert isn't path
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-10-01 12:04:34 +08:00
wwqgtxx
8253bfe2e0 add quic-go-disable-ecn to experimental 2023-10-01 09:10:11 +08:00
wwqgtxx
828b5ad8bb chore: add new bbr implementation 2023-10-01 00:01:32 +08:00
Larvan2
fedad26c13 chore: support relative path for hy2/tuic inbound cert 2023-10-01 00:01:32 +08:00
wwqgtxx
a526bb70ea chore: fix bbr bugs 2023-09-30 13:40:07 +08:00
yaling888
5f6de610e1 Fix: should check all ips need to fallback (#2915) 2023-09-29 13:42:22 +08:00
Larvan2
02397868fc
docs: support reload in service 2023-09-29 13:26:59 +08:00
Kiva
265a6b9b68
chore: reduce string split immediately after string concat (#773) 2023-09-29 08:51:13 +08:00
NyaMisty
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
septs
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
Andrei Shevchuk
c2b06a02bf
feat: add reload signal support (#780)
Backport Clash feature by @septs, see Dreamacro/clash#2908
2023-09-29 08:36:25 +08:00
wwqgtxx
e0458a8fde chore: decrease goroutine used in core tunnel 2023-09-28 18:59:31 +08:00
wwqgtxx
21fb5f75b8 fix: gvisor panic 2023-09-26 09:06:00 +08:00
wwqgtxx
fb99412193 chore: update quic-go to 0.39.0 2023-09-26 08:51:25 +08:00
162 changed files with 5251 additions and 1857 deletions

View File

@ -1,6 +1,7 @@
name: Bug report
description: Create a report to help us improve
title: "[Bug] "
labels: ["bug"]
body:
- type: checkboxes
id: ensure

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Clash.Meta Community Support
url: https://github.com/MetaCubeX/Clash.Meta/discussions
about: Please ask and answer questions about Clash.Meta here.

View File

@ -1,6 +1,7 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature] "
labels: ["enhancement"]
body:
- type: checkboxes
id: ensure

12
.github/rename-go120.sh vendored Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
mv $FILENAME ${FILENAME}-go120
elif [[ $FILENAME =~ ".exe" ]];then
mv $FILENAME ${FILENAME%.*}-go120.exe
else echo "skip $FILENAME"
fi
done

View File

@ -0,0 +1,69 @@
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
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
trigger-CMFA-update:
needs: 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"}'

View File

@ -69,6 +69,12 @@ jobs:
target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9",
}
# only for test
- { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" }
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
- { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" }
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
- { type: "WithCGO", target: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" }
@ -126,18 +132,26 @@ jobs:
shell: bash
- name: Setup Go
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.21"
check-latest: true
- name: Setup Go
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
uses: actions/setup-go@v4
with:
go-version: "1.20"
check-latest: true
- name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
run: |
go test ./...
- name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }}
if: ${{ matrix.job.type!='WithCGO' }}
env:
NAME: Clash.Meta
BINDIR: bin
@ -185,6 +199,17 @@ jobs:
ls -la
cd ..
- name: Rename
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
run: |
cd bin
ls -la
cp ../.github/rename-go120.sh ./
bash ./rename-go120.sh
rm ./rename-go120.sh
ls -la
cd ..
- name: Zip
if: ${{ success() }}
run: |

302
README.md
View File

@ -21,7 +21,7 @@
## 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
@ -32,259 +32,41 @@
## Dashboard
We made an official web dashboard providing first class support for this project, check it out
at [metacubexd](https://github.com/MetaCubeX/metacubexd)
A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd).
## Wiki
## Configration example
Configuration examples can be found
at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be
found [Clash.Meta Wiki](https://clash-meta.wiki).
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
## Build
## Docs
You should install [golang](https://go.dev) first.
Documentation can be found in [Clash.Meta Docs](https://clash-meta.wiki).
Then get the source code of Clash.Meta:
## 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`
@ -298,61 +80,9 @@ iptables:
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
```
### General installation guide for Linux
- Create user given name `clash-meta`
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
- Create folder `/etc/Clash-Meta/` as working directory
Run Meta Kernel by user `clash-meta` as a daemon.
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
```
[Unit]
Description=Clash-Meta Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
User=clash-meta
Group=clash-meta
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install]
WantedBy=multi-user.target
```
Launch clash-meta daemon on system startup with:
```shell
$ systemctl enable Clash-Meta
```
Launch clash-meta daemon immediately with:
```shell
$ systemctl start Clash-Meta
```
## Development
If you want to build an application that uses clash as a library, check out
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Debugging
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
API.
## Credits

View File

@ -30,13 +30,13 @@ 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 *xsync.MapOf[string, *extraProxyState]
}

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

@ -46,7 +46,7 @@ type Hysteria struct {
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...))
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}

View File

@ -55,7 +55,7 @@ type Hysteria2Option struct {
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...))
c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress()))
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}

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

@ -58,14 +58,15 @@ type simpleObfsOption struct {
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
}
type shadowTLSOption struct {
@ -123,9 +124,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))
}
}
@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
}
obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
}
if opts.TLS {

View File

@ -42,7 +42,7 @@ type ProxyBase interface {
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net"
"net/netip"
"strconv"
N "github.com/Dreamacro/clash/common/net"
@ -136,7 +137,8 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
}
}
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return

View File

@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
}
if t.option.SNI != "" {

View File

@ -93,6 +93,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
}

View File

@ -91,6 +91,7 @@ type WSOptions struct {
Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
@ -110,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
}
@ -260,10 +262,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c,
M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else {
conn, err = v.client.DialConn(c,
M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
if err != nil {
@ -284,7 +286,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
}

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

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

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

@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
type TypedValue[T any] struct {
value atomic.Value
_ noCopy
}
func (t *TypedValue[T]) Load() T {
@ -51,8 +52,13 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil
}
func NewTypedValue[T any](t T) *TypedValue[T] {
v := &TypedValue[T]{}
func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t)
return v
return
}
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

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

@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
}
func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn {
if br != nil && br.Buffered() > 0 {
if bc, ok := c.(*BufferedConn); ok && bc.r == br {
return bc
}
return &BufferedConn{br, NewExtendedConn(c), true}
}
return c
}
// Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader {
return c.r

49
common/net/cached.go Normal file
View File

@ -0,0 +1,49 @@
package net
import (
"net"
"github.com/Dreamacro/clash/common/buf"
)
var _ ExtendedConn = (*CachedConn)(nil)
type CachedConn struct {
ExtendedConn
data []byte
}
func NewCachedConn(c net.Conn, data []byte) *CachedConn {
return &CachedConn{NewExtendedConn(c), data}
}
func (c *CachedConn) Read(b []byte) (n int, err error) {
if len(c.data) > 0 {
n = copy(b, c.data)
c.data = c.data[n:]
return
}
return c.ExtendedConn.Read(b)
}
func (c *CachedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
if len(c.data) > 0 {
return buf.As(c.data)
}
return nil
}
func (c *CachedConn) Upstream() any {
return c.ExtendedConn
}
func (c *CachedConn) ReaderReplaceable() bool {
if len(c.data) > 0 {
return false
}
return true
}
func (c *CachedConn) WriterReplaceable() bool {
return true
}

31
common/net/context.go Normal file
View File

@ -0,0 +1,31 @@
package net
import (
"context"
"net"
)
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine.
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
var (
quit = make(chan struct{})
interrupt = make(chan error, 1)
)
go func() {
select {
case <-quit:
interrupt <- nil
case <-ctx.Done():
// Close the connection, discarding the error
_ = conn.Close()
interrupt <- ctx.Err()
}
}()
return func(inputErr *error) {
close(quit)
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
// Return context error to user.
inputErr = &ctxErr
}
}
}

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,9 +1,5 @@
package auth
import (
"github.com/puzpuzpuz/xsync/v2"
)
type Authenticator interface {
Verify(user string, pass string) bool
Users() []string
@ -15,12 +11,12 @@ type AuthUser struct {
}
type inMemoryAuthenticator struct {
storage *xsync.MapOf[string, string]
storage map[string]string
usernames []string
}
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
realPass, ok := au.storage.Load(user)
realPass, ok := au.storage[user]
return ok && realPass == pass
}
@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
if len(users) == 0 {
return nil
}
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
for _, user := range users {
au.storage.Store(user.User, user.Pass)
au := &inMemoryAuthenticator{
storage: make(map[string]string),
usernames: make([]string, 0, len(users)),
}
for _, user := range users {
au.storage[user.User] = user.Pass
au.usernames = append(au.usernames, user.User)
}
usernames := make([]string, 0, len(users))
au.storage.Range(func(key string, value string) bool {
usernames = append(usernames, key)
return true
})
au.usernames = usernames
return au
}

View File

@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
listenAddr = "255.255.255.255:68"
}
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
options := []dialer.Option{
dialer.WithInterface(ifaceName),
dialer.WithAddrReuse(true),
}
// fallback bind on windows, because syscall bind can not receive broadcast
if runtime.GOOS == "windows" {
options = append(options, dialer.WithFallbackBind(true))
}
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
}

View File

@ -3,6 +3,7 @@ package dialer
import (
"net"
"net/netip"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/iface"
@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, err
}
var addr *netip.Prefix
var addr netip.Prefix
switch network {
case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination)
@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, iface.ErrAddrNotFound
}
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() {
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
}
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
}
func fallbackParseNetwork(network string, addr netip.Addr) string {
// fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
}

View File

@ -5,55 +5,16 @@ package dialer
import (
"net"
"net/netip"
"strconv"
"strings"
)
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() {
return nil
}
local := uint64(0)
if dialer.LocalAddr != nil {
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
if err == nil {
local, _ = strconv.ParseUint(port, 10, 16)
}
}
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
if err != nil {
return err
}
dialer.LocalAddr = addr
return nil
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
}
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address)
if err != nil {
port = "0"
}
local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
if err != nil {
return "", err
}
return addr.String(), nil
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
}
func ParseNetwork(network string, addr netip.Addr) string {
// fix bindIfaceToListenConfig() force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
return fallbackParseNetwork(network, addr)
}

View File

@ -70,11 +70,18 @@ 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{}
if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
bind := bindIfaceToListenConfig
if cfg.fallbackBind {
bind = fallbackBindIfaceToListenConfig
}
addr, err := bind(cfg.interfaceName, lc, network, address)
if err != nil {
return nil, err
}
@ -110,6 +117,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
@ -125,7 +135,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
bind := bindIfaceToDialer
if opt.fallbackBind {
bind = fallbackBindIfaceToDialer
}
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err
}
}

View File

@ -20,6 +20,7 @@ type NetDialer interface {
type option struct {
interfaceName string
fallbackBind bool
addrReuse bool
routingMark int
network int
@ -38,6 +39,12 @@ func WithInterface(name string) Option {
}
}
func WithFallbackBind(fallback bool) Option {
return func(opt *option) {
opt.fallbackBind = fallback
}
}
func WithAddrReuse(reuse bool) Option {
return func(opt *option) {
opt.addrReuse = reuse

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

@ -36,7 +36,7 @@ type Pool struct {
cycle bool
mux sync.Mutex
host *trie.DomainTrie[struct{}]
ipnet *netip.Prefix
ipnet netip.Prefix
store store
}
@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
}
// IPNet return raw ipnet
func (p *Pool) IPNet() *netip.Prefix {
func (p *Pool) IPNet() netip.Prefix {
return p.ipnet
}
@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
}
type Options struct {
IPNet *netip.Prefix
IPNet netip.Prefix
Host *trie.DomainTrie[struct{}]
// Size sets the maximum number of entries in memory
@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next()
first = gateway.Next().Next().Next() // default start with 198.18.0.4
last = nnip.UnMasked(*options.IPNet)
last = nnip.UnMasked(options.IPNet)
)
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {

View File

@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
func TestPool_Basic(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.0/28")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
func TestPool_BasicV6(t *testing.T) {
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
func TestPool_Case_Insensitive(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
func TestPool_CycleUsed(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.16/28")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
tree := trie.New[struct{}]()
tree.Insert("example.com", struct{}{})
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
Host: tree,
})
@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
func TestPool_MaxCacheSize(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 2,
})
@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
func TestPool_DoubleMapping(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 2,
})
@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
func TestPool_Clone(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 2,
})
@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
newPool, _ := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 2,
})
newPool.CloneFrom(pool)
@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
func TestPool_Error(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/31")
_, err := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
func TestPool_FlushFileCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28")
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})
assert.Nil(t, err)
@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
func TestPool_FlushMemoryCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28")
pool, _ := New(Options{
IPNet: &ipnet,
IPNet: ipnet,
Size: 10,
})

View File

@ -7,6 +7,7 @@ import (
"net"
"net/http"
URL "net/url"
"runtime"
"strings"
"time"
@ -47,6 +48,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
transport := &http.Transport{
// from http.DefaultTransport
DisableKeepAlives: runtime.GOOS == "android",
MaxIdleConns: 100,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,

View File

@ -13,7 +13,7 @@ import (
type Interface struct {
Index int
Name string
Addrs []*netip.Prefix
Addrs []netip.Prefix
HardwareAddr net.HardwareAddr
}
@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) {
continue
}
ipNets := make([]*netip.Prefix, 0, len(addrs))
ipNets := make([]netip.Prefix, 0, len(addrs))
for _, addr := range addrs {
ipNet := addr.(*net.IPNet)
ip, _ := netip.AddrFromSlice(ipNet.IP)
@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) {
}
pf := netip.PrefixFrom(ip, ones)
ipNets = append(ipNets, &pf)
ipNets = append(ipNets, pf)
}
r[iface.Name] = &Interface{
@ -89,27 +89,27 @@ func FlushCache() {
interfaces.Reset()
}
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
return addr.Addr().Is4()
})
}
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) {
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
return addr.Addr().Is6()
})
}
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) {
var fallback *netip.Prefix
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
var fallback netip.Prefix
for _, addr := range iface.Addrs {
if !accept(addr) {
continue
}
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() {
if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() {
fallback = addr
if !destination.IsValid() {
@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net
}
}
if fallback == nil {
return nil, ErrAddrNotFound
if !fallback.IsValid() {
return netip.Prefix{}, ErrAddrNotFound
}
return fallback, nil

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

@ -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

@ -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,9 +35,40 @@ type SnifferDispatcher struct {
parsePureIp bool
}
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
return (metadata.Host == "" && sd.parsePureIp) ||
sd.forceDomain.Has(metadata.Host) ||
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping)
}
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
metadata := packet.Metadata()
if sd.shouldOverride(packet.Metadata()) {
for sniffer, config := range sd.sniffers {
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet {
inWhitelist := sniffer.SupportPort(metadata.DstPort)
overrideDest := config.OverrideDest
if inWhitelist {
host, err := sniffer.SniffData(packet.Data())
if err != nil {
continue
}
sd.replaceDomain(metadata, host, overrideDest)
return true
}
}
}
}
return false
}
// TCPSniff returns true if the connection is sniffed to have a domain
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
if sd.shouldOverride(metadata) {
inWhitelist := false
overrideDest := false
for sniffer, config := range sd.sniffers {
@ -86,7 +117,8 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
// show log early, since the following code may mutate `metadata.Host`
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
metadata.NetWork,
metadata.SourceDetail(),
metadata.RemoteAddress(),
metadata.Host, host)
@ -125,7 +157,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
continue
}
host, err := s.SniffTCP(bytes)
host, err := s.SniffData(bytes)
if err != nil {
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
continue
@ -194,6 +226,8 @@ func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer
return NewTLSSniffer(snifferConfig)
case sniffer.HTTP:
return NewHTTPSniffer(snifferConfig)
case sniffer.QUIC:
return NewQuicSniffer(snifferConfig)
default:
return nil, ErrorUnsupportedSniffer
}

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

@ -43,7 +43,8 @@ type RealityConfig struct {
func aesgcmPreferred(ciphers []uint16) bool
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
retry := 0
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
verifier := &realityVerifier{
serverName: tlsConfig.ServerName,
}
@ -80,7 +81,15 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
ecdheParams := uConn.HandshakeState.State13.EcdheParams
if ecdheParams == nil {
// WTF???
if retry > 2 {
return nil, errors.New("nil ecdheParams")
}
continue // retry
}
authKey := ecdheParams.SharedKey(realityConfig.PublicKey[:])
if authKey == nil {
return nil, errors.New("nil auth_key")
}

View File

@ -21,7 +21,7 @@ type UClientHelloID struct {
var initRandomFingerprint UClientHelloID
var initUtlsClient string
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn {
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn {
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
Client: fingerprint.Client,
Version: fingerprint.Version,
@ -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

@ -20,7 +20,6 @@ import (
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router"
@ -59,27 +58,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
@ -122,7 +121,7 @@ type DNS struct {
type FallbackFilter struct {
GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"`
IPCIDR []*netip.Prefix `yaml:"ipcidr"`
IPCIDR []netip.Prefix `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
GeoSite []*router.DomainMatcher `yaml:"geosite"`
}
@ -159,6 +158,7 @@ type Sniffer struct {
type Experimental struct {
Fingerprints []string `yaml:"fingerprints"`
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
}
// Config is clash config manager
@ -192,29 +192,34 @@ type RawNTP struct {
}
type RawDNS struct {
Enable bool `yaml:"enable"`
PreferH3 bool `yaml:"prefer-h3"`
IPv6 bool `yaml:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts"`
NameServer []string `yaml:"nameserver"`
Fallback []string `yaml:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
Listen string `yaml:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]any `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
Enable bool `yaml:"enable" json:"enable"`
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
NameServer []string `yaml:"nameserver" json:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
Listen string `yaml:"listen" json:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
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 {
@ -227,21 +232,23 @@ 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"`
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
}
type RawTuicServer struct {
@ -260,22 +267,23 @@ 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"`
@ -285,20 +293,20 @@ type RawConfig struct {
Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"`
Tunnels []LC.Tunnel `yaml:"tunnels"`
GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"`
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"`
NTP RawNTP `yaml:"ntp"`
DNS RawDNS `yaml:"dns"`
Hosts map[string]any `yaml:"hosts" json:"hosts"`
NTP RawNTP `yaml:"ntp" json:"ntp"`
DNS RawDNS `yaml:"dns" json:"dns"`
Tun RawTun `yaml:"tun"`
TuicServer RawTuicServer `yaml:"tuic-server"`
EBpf EBpf `yaml:"ebpf"`
@ -312,6 +320,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 {
@ -385,7 +395,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true,
AutoDetectInterface: true,
Inet6Address: []LC.ListenPrefix{LC.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")},
},
TuicServer: RawTuicServer{
Enable: false,
@ -459,9 +469,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
StoreSelected: true,
},
GeoXUrl: GeoXUrl{
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
},
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
}
@ -479,7 +489,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)
@ -540,11 +549,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
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
@ -619,6 +623,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,
@ -640,7 +645,6 @@ 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
@ -1044,7 +1048,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
Net: dnsNetType,
Addr: addr,
ProxyName: proxyName,
Interface: dialer.DefaultInterface,
Params: params,
PreferH3: preferH3,
},
@ -1146,15 +1149,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
return policy, nil
}
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
var ipNets []*netip.Prefix
func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
var ipNets []netip.Prefix
for idx, ip := range ips {
ipnet, err := netip.ParsePrefix(ip)
if err != nil {
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
}
ipNets = append(ipNets, &ipnet)
ipNets = append(ipNets, ipnet)
}
return ipNets, nil
@ -1222,7 +1225,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{
IPCIDR: []*netip.Prefix{},
IPCIDR: []netip.Prefix{},
GeoSite: []*router.DomainMatcher{},
},
}
@ -1296,7 +1299,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
}
pool, err := fakeip.New(fakeip.Options{
IPNet: &fakeIPRange,
IPNet: fakeIPRange,
Size: 1000,
Host: host,
Persistence: rawCfg.Profile.StoreFakeIP,
@ -1359,22 +1362,24 @@ func parseTun(rawTun RawTun, general *General) error {
AutoDetectInterface: rawTun.AutoDetectInterface,
RedirectToTun: rawTun.RedirectToTun,
MTU: rawTun.MTU,
Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)},
Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute,
Inet4RouteAddress: rawTun.Inet4RouteAddress,
Inet6RouteAddress: rawTun.Inet6RouteAddress,
IncludeUID: rawTun.IncludeUID,
IncludeUIDRange: rawTun.IncludeUIDRange,
ExcludeUID: rawTun.ExcludeUID,
ExcludeUIDRange: rawTun.ExcludeUIDRange,
IncludeAndroidUser: rawTun.IncludeAndroidUser,
IncludePackage: rawTun.IncludePackage,
ExcludePackage: rawTun.ExcludePackage,
EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor,
MTU: rawTun.MTU,
Inet4Address: []netip.Prefix{tunAddressPrefix},
Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute,
Inet4RouteAddress: rawTun.Inet4RouteAddress,
Inet6RouteAddress: rawTun.Inet6RouteAddress,
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
IncludeUID: rawTun.IncludeUID,
IncludeUIDRange: rawTun.IncludeUIDRange,
ExcludeUID: rawTun.ExcludeUID,
ExcludeUIDRange: rawTun.ExcludeUIDRange,
IncludeAndroidUser: rawTun.IncludeAndroidUser,
IncludePackage: rawTun.IncludePackage,
ExcludePackage: rawTun.ExcludePackage,
EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor,
}
return nil

View File

@ -252,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)
}

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"
)

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

@ -147,6 +147,9 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"`
RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"`
// Only domain rule
SniffHost string `json:"sniffHost"`
}
@ -240,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

@ -8,7 +8,6 @@ import (
"net/netip"
"strings"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
@ -23,7 +22,7 @@ type client struct {
r *Resolver
port string
host string
iface *atomic.TypedValue[string]
iface string
proxyAdapter C.ProxyAdapter
proxyName string
addr string
@ -48,10 +47,6 @@ func (c *client) Address() string {
return c.addr
}
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
return c.ExchangeContext(context.Background(), m)
}
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
var (
ip netip.Addr
@ -77,9 +72,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
network = "tcp"
}
options := []dialer.Option{}
if c.iface != nil && c.iface.Load() != "" {
options = append(options, dialer.WithInterface(c.iface.Load()))
var options []dialer.Option
if c.iface != "" {
options = append(options, dialer.WithInterface(c.iface))
}
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))

View File

@ -1,3 +1,6 @@
//go:build disabled
// +build disabled
package dns
import (
@ -8,11 +11,8 @@ import (
"sync"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns"
)
@ -29,7 +29,7 @@ type dhcpClient struct {
ifaceInvalidate time.Time
dnsInvalidate time.Time
ifaceAddr *netip.Prefix
ifaceAddr netip.Prefix
done chan struct{}
clients []dnsClient
err error
@ -46,13 +46,6 @@ func (d *dhcpClient) Address() string {
return strings.Join(addrs, ",")
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
return d.ExchangeContext(ctx, m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
clients, err := d.resolve(ctx)
if err != nil {
@ -86,7 +79,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
for _, item := range dns {
nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"),
Interface: atomic.NewTypedValue(d.ifaceName),
Interface: d.ifaceName,
})
}

View File

@ -157,11 +157,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
return msg, err
}
// Exchange implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
return doh.ExchangeContext(context.Background(), m)
}
// Close implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Close() (err error) {
doh.clientMu.Lock()

View File

@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M
return msg, err
}
// Exchange implements the Upstream interface for *dnsOverQUIC.
func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return doq.ExchangeContext(context.Background(), m)
}
// Close implements the Upstream interface for *dnsOverQUIC.
func (doq *dnsOverQUIC) Close() (err error) {
doq.connMu.Lock()

View File

@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
}
type ipnetFilter struct {
ipnet *netip.Prefix
ipnet netip.Prefix
}
func (inf *ipnetFilter) Match(ip netip.Addr) bool {

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

@ -39,16 +39,12 @@ type rcodeClient struct {
var _ dnsClient = rcodeClient{}
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) {
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
m.Response = true
m.Rcode = r.rcode
return m, nil
}
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
return r.Exchange(m)
}
func (r rcodeClient) Address() string {
return r.addr
}

View File

@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router"
@ -18,11 +17,11 @@ import (
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
"github.com/samber/lo"
"golang.org/x/sync/singleflight"
)
type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string
}
@ -135,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
return false
}
// Exchange a batch of dns request, and it use cache
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return r.ExchangeContext(context.Background(), m)
}
// ExchangeContext a batch of dns request with context.Context, and it use cache
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 {
@ -194,21 +188,25 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
msg := result.(*D.Msg)
if cache {
putMsgToCache(r.lruCache, q.String(), msg)
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool {
return rr.Header().Rrtype != D.TypeOPT
})
putMsgToCache(r.lruCache, q.String(), q, msg)
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
cache=true
cache = true
return r.ipExchange(ctx, m)
}
if matched := r.matchPolicy(m); len(matched) != 0 {
result, cache, err = r.batchExchange(ctx, matched, m)
result, cache, err = batchExchange(ctx, matched, m)
return
}
result, cache, err = r.batchExchange(ctx, r.main, m)
result, cache, err = batchExchange(ctx, r.main, m)
return
}
@ -250,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
return
}
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
defer cancel()
return batchExchange(ctx, clients, m)
}
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil {
return nil
@ -332,7 +323,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
res := <-msgCh
if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 {
if !r.shouldIPFallback(ips[0]) {
shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool {
return !r.shouldIPFallback(ip)
})
if shouldNotFallback {
msg, err = res.Msg, res.Error // no need to wait for fallback result
return
}
@ -377,7 +371,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result, 1)
go func() {
res, _, err := r.batchExchange(ctx, client, msg)
res, _, err := batchExchange(ctx, client, msg)
ch <- &result{Msg: res, Error: err}
}()
return ch
@ -394,7 +388,7 @@ func (r *Resolver) Invalid() bool {
type NameServer struct {
Net string
Addr string
Interface *atomic.TypedValue[string]
Interface string
ProxyAdapter C.ProxyAdapter
ProxyName string
Params map[string]string
@ -404,7 +398,7 @@ type NameServer struct {
type FallbackFilter struct {
GeoIP bool
GeoIPCode string
IPCIDR []*netip.Prefix
IPCIDR []netip.Prefix
Domain []string
GeoSite []*router.DomainMatcher
}

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

@ -1,23 +1,113 @@
package dns
import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
"golang.org/x/exp/slices"
)
func loadSystemResolver() (clients []dnsClient, err error) {
nameservers, err := dnsReadConfig()
const (
SystemDnsFlushTime = 5 * time.Minute
SystemDnsDeleteTimes = 12 // 12*5 = 60min
)
type systemDnsClient struct {
disableTimes uint32
dnsClient
}
type systemClient struct {
mu sync.Mutex
dnsClients map[string]*systemDnsClient
lastFlush time.Time
}
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
c.mu.Lock()
defer c.mu.Unlock()
var err error
if time.Since(c.lastFlush) > SystemDnsFlushTime {
var nameservers []string
if nameservers, err = dnsReadConfig(); err == nil {
log.Debugln("[DNS] system dns update to %s", nameservers)
for _, addr := range nameservers {
if _, ok := c.dnsClients[addr]; !ok {
clients := transform(
[]NameServer{{
Addr: net.JoinHostPort(addr, "53"),
Net: "udp",
}},
nil,
)
if len(clients) > 0 {
c.dnsClients[addr] = &systemDnsClient{
disableTimes: 0,
dnsClient: clients[0],
}
}
}
}
available := 0
for nameserver, sdc := range c.dnsClients {
if slices.Contains(nameservers, nameserver) {
sdc.disableTimes = 0 // enable
available++
} else {
if sdc.disableTimes > SystemDnsDeleteTimes {
delete(c.dnsClients, nameserver) // drop too old dnsClient
} else {
sdc.disableTimes++
}
}
}
if available > 0 {
c.lastFlush = time.Now()
}
}
}
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
for _, sdc := range c.dnsClients {
if sdc.disableTimes == 0 {
dnsClients = append(dnsClients, sdc.dnsClient)
}
}
if len(dnsClients) > 0 {
return dnsClients, nil
}
return nil, err
}
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
dnsClients, err := c.getDnsClients()
if err != nil {
return
}
if len(nameservers) == 0 {
return
}
servers := make([]NameServer, 0, len(nameservers))
for _, addr := range nameservers {
servers = append(servers, NameServer{
Addr: net.JoinHostPort(addr, "53"),
Net: "udp",
})
}
return transform(servers, nil), nil
msg, _, err = batchExchange(ctx, dnsClients, m)
return
}
// Address implements dnsClient
func (c *systemClient) Address() string {
dnsClients, _ := c.getDnsClients()
addrs := make([]string, 0, len(dnsClients))
for _, c := range dnsClients {
addrs = append(addrs, c.Address())
}
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
}
var _ dnsClient = (*systemClient)(nil)
func newSystemClient() *systemClient {
return &systemClient{
dnsClients: map[string]*systemDnsClient{},
}
}

View File

@ -29,14 +29,16 @@ const (
MaxMsgSize = 65535
)
const serverFailureCacheTTL uint32 = 5
func minimalTTL(records []D.RR) uint32 {
minObj := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
return r1.Header().Ttl < r2.Header().Ttl
})
if minObj != nil {
return minObj.Header().Ttl
if rr == nil {
return 0
}
return 0
return rr.Header().Ttl
}
func updateTTL(records []D.RR, ttl uint32) {
@ -49,28 +51,25 @@ func updateTTL(records []D.RR, ttl uint32) {
}
}
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
putMsgToCacheWithExpire(c, key, msg, 0)
}
func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) {
if sec == 0 {
if sec = minimalTTL(msg.Answer); sec == 0 {
if sec = minimalTTL(msg.Ns); sec == 0 {
sec = minimalTTL(msg.Extra)
}
}
if sec == 0 {
return
}
if sec > 120 {
sec = 120 // at least 2 minutes to cache
}
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, q D.Question, msg *D.Msg) {
// skip dns cache for acme challenge
if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") {
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
return
}
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(sec)*time.Second))
var ttl uint32
if msg.Rcode == D.RcodeServerFailure {
// [...] a resolver MAY cache a server failure response.
// If it does so it MUST NOT cache it for longer than five (5) minutes [...]
ttl = serverFailureCacheTTL
} else {
ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...))
}
if ttl == 0 {
return
}
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second))
}
func setMsgTTL(msg *D.Msg, ttl uint32) {
@ -108,16 +107,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret = append(ret, newDHCPClient(s.Addr))
continue
case "system":
clients, err := loadSystemResolver()
if err != nil {
log.Errorln("[DNS:system] load system resolver failed: %s", err.Error())
continue
}
if len(clients) == 0 {
log.Errorln("[DNS:system] no nameserver found in system")
continue
}
ret = append(ret, clients...)
ret = append(ret, newSystemClient())
continue
case "rcode":
ret = append(ret, newRCodeClient(s.Addr))
@ -290,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
DstPort: uint16(uintPort),
}
if proxyAdapter == nil {
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
}
if !proxyAdapter.SupportUDP() {
@ -300,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
var errIPNotFound = errors.New("couldn't find ip")
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
cache = true
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
defer fast.Close()
domain := msgToDomain(m)
var noIpMsg *D.Msg
for _, client := range clients {
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
msg, err = client.Exchange(m)
msg, err = client.ExchangeContext(ctx, m)
return msg, false, err
}
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
@ -321,13 +314,31 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
// so we would ignore RCode errors from RCode clients.
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
}
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
if ips := msgToIP(m); len(m.Question) > 0 {
qType := m.Question[0].Qtype
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address())
switch qType {
case D.TypeAAAA:
if len(ips) == 0 {
noIpMsg = m
return nil, errIPNotFound
}
case D.TypeA:
if len(ips) == 0 {
noIpMsg = m
return nil, errIPNotFound
}
}
}
return m, nil
})
}
msg = fast.Wait()
if msg == nil {
if noIpMsg != nil {
return noIpMsg, false, nil
}
err = errors.New("all DNS requests failed")
if fErr := fast.Error(); fErr != nil {
err = fmt.Errorf("%w, first error: %w", err, fErr)

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, 开启,强制匹配所有进程
@ -137,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]
@ -345,16 +352,17 @@ proxies: # socks5
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
# tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
# v2ray-http-upgrade: false
- name: "ss4-shadow-tls"
type: ss
@ -427,11 +435,12 @@ proxies: # socks5
# servername: example.com # priority over wss host
# network: ws
# ws-opts:
# path: /path
# headers:
# Host: v2ray.com
# max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol
# path: /path
# headers:
# Host: v2ray.com
# max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol
# v2ray-http-upgrade: false
- name: "vmess-h2"
type: vmess
@ -559,6 +568,7 @@ proxies: # socks5
path: "/"
headers:
Host: example.com
# v2ray-http-upgrade: false
# Trojan
- name: "trojan"
@ -599,9 +609,10 @@ proxies: # socks5
# fingerprint: xxxx
udp: true
# ws-opts:
# path: /path
# headers:
# Host: example.com
# path: /path
# headers:
# Host: example.com
# v2ray-http-upgrade: false
- name: "trojan-xtls"
type: trojan
@ -936,6 +947,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

45
go.mod
View File

@ -5,51 +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-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.20230909013832-033f6a2115cf
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81
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.12
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
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.0
github.com/puzpuzpuz/xsync/v2 v2.5.1
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.2.11
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.8
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.13.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/net v0.15.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.12.0
golang.org/x/crypto v0.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
@ -65,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.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
@ -78,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.3 // 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
@ -99,10 +100,12 @@ require (
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
golang.org/x/mod v0.12.0 // indirect
go.uber.org/mock v0.3.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // 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.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9

101
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=
@ -49,10 +49,14 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.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=
@ -65,8 +69,6 @@ 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=
@ -93,24 +95,24 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8=
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8=
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4=
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 h1:6g+ohVa8FQLXz/ATmped/4kWuK0HKvhy1hwzQXyF0EI=
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4=
github.com/metacubex/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.12 h1:Jgmz0k3ddRiJ8zfS4X7j6B/iSy6GnOdDEU0nhqiZcK4=
github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M=
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
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=
@ -135,12 +137,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c=
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/puzpuzpuz/xsync/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.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/qtls-go1-20 v0.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=
@ -162,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.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE=
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ=
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=
@ -197,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=
@ -208,26 +209,27 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/crypto v0.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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
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=
@ -235,36 +237,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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.13.0 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.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.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.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

@ -118,7 +118,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
}
func initInnerTcp() {
inner.New(tunnel.TCPIn())
inner.New(tunnel.Tunnel)
}
func GetGeneral() *config.General {
@ -140,6 +140,7 @@ func GetGeneral() *config.General {
ShadowSocksConfig: ports.ShadowSocksConfig,
VmessConfig: ports.VmessConfig,
Authentication: authenticator,
SkipAuthPrefixes: inbound.SkipAuthPrefixes(),
AllowLan: listener.AllowLan(),
BindAddress: listener.BindAddress(),
},
@ -157,34 +158,34 @@ 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", "1")
_ = os.Setenv("QUIC_GO_DISABLE_GSO", strconv.FormatBool(true))
}
if c.Experimental.QUICGoDisableECN {
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
}
}
@ -339,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)
}
@ -367,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) {

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,23 @@ 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"`
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
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 {
@ -145,6 +150,18 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.Inet6Address != nil {
def.Inet6Address = *p.Inet6Address
}
if p.Inet4RouteAddress != nil {
def.Inet4RouteAddress = *p.Inet4RouteAddress
}
if p.Inet6RouteAddress != nil {
def.Inet6RouteAddress = *p.Inet6RouteAddress
}
if p.Inet4RouteExcludeAddress != nil {
def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress
}
if p.Inet6RouteExcludeAddress != nil {
def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress
}
if p.IncludeUID != nil {
def.IncludeUID = *p.IncludeUID
}
@ -231,6 +248,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 +270,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

@ -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

@ -43,7 +43,7 @@ func (l *Listener) SetLookupFunc(lookupFunc func(netip.AddrPort) (socks5.Addr, e
l.lookupFunc = lookupFunc
}
func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
func (l *Listener) handleRedir(conn net.Conn, tunnel C.Tunnel) {
if l.lookupFunc == nil {
log.Errorln("[Auto Redirect] lookup function is nil")
return
@ -58,10 +58,10 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
N.TCPKeepAlive(conn)
in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...)
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, l.additions...))
}
func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
if len(additions) == 0 {
additions = []inbound.Addition{
inbound.WithInName("DEFAULT-REDIR"),
@ -87,7 +87,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*
}
continue
}
go rl.handleRedir(c, in)
go rl.handleRedir(c, tunnel)
}
}()

View File

@ -1,72 +1,19 @@
package config
import (
"encoding/json"
"net/netip"
C "github.com/Dreamacro/clash/constant"
"gopkg.in/yaml.v3"
)
type ListenPrefix netip.Prefix
func (p ListenPrefix) MarshalJSON() ([]byte, error) {
prefix := netip.Prefix(p)
if !prefix.IsValid() {
return json.Marshal(nil)
}
return json.Marshal(prefix.String())
}
func (p ListenPrefix) MarshalYAML() (interface{}, error) {
prefix := netip.Prefix(p)
if !prefix.IsValid() {
return nil, nil
}
return prefix.String(), nil
}
func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
var value string
err := json.Unmarshal(bytes, &value)
if err != nil {
return err
}
prefix, err := netip.ParsePrefix(value)
if err != nil {
return err
}
*p = ListenPrefix(prefix)
return nil
}
func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error {
var value string
err := node.Decode(&value)
if err != nil {
return err
}
prefix, err := netip.ParsePrefix(value)
if err != nil {
return err
}
*p = ListenPrefix(prefix)
return nil
}
func (p ListenPrefix) Build() netip.Prefix {
return netip.Prefix(p)
}
func StringSliceToListenPrefixSlice(ss []string) ([]ListenPrefix, error) {
lps := make([]ListenPrefix, 0, len(ss))
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
lps := make([]netip.Prefix, 0, len(ss))
for _, s := range ss {
prefix, err := netip.ParsePrefix(s)
if err != nil {
return nil, err
}
lps = append(lps, ListenPrefix(prefix))
lps = append(lps, prefix)
}
return lps, nil
}
@ -80,20 +27,22 @@ type Tun struct {
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress []ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress []ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
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"`
}

View File

@ -11,9 +11,12 @@ type VmessUser struct {
}
type VmessServer struct {
Enable bool
Listen string
Users []VmessUser
Enable bool
Listen string
Users []VmessUser
WsPath string
Certificate string
PrivateKey string
}
func (t VmessServer) String() string {

View File

@ -12,7 +12,7 @@ import (
"github.com/Dreamacro/clash/transport/socks5"
)
func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Addition) *http.Client {
func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client {
return &http.Client{
Transport: &http.Transport{
// from http.DefaultTransport
@ -32,7 +32,7 @@ func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Ad
left, right := net.Pipe()
in <- inbound.NewHTTP(dstAddr, source, right, additions...)
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, srcConn, right, additions...))
return left, nil
},

7
listener/http/patch.go Normal file
View File

@ -0,0 +1,7 @@
package http
import "net"
func (l *Listener) Listener() net.Listener {
return l.listener
}

View File

@ -14,8 +14,8 @@ import (
"github.com/Dreamacro/clash/log"
)
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
client := newClient(c.RemoteAddr(), in, additions...)
func HandleConn(c net.Conn, tunnel C.Tunnel, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
client := newClient(c, tunnel, additions...)
defer client.CloseIdleConnections()
conn := N.NewBufferedConn(c)
@ -48,7 +48,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
break // close connection
}
in <- inbound.NewHTTPS(request, conn, additions...)
tunnel.HandleTCPConn(inbound.NewHTTPS(request, conn, additions...))
return // hijack connection
}
@ -61,7 +61,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
request.RequestURI = ""
if isUpgradeRequest(request) {
handleUpgrade(conn, request, in, additions...)
handleUpgrade(conn, request, tunnel, additions...)
return // hijack connection
}
@ -100,6 +100,9 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response {
authenticator := authStore.Authenticator()
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
authenticator = nil
}
if authenticator != nil {
credential := parseBasicProxyAuthorization(request)
if credential == "" {

View File

@ -30,11 +30,11 @@ func (l *Listener) Close() error {
return l.listener.Close()
}
func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticate(addr, in, true, additions...)
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticate(addr, tunnel, true, additions...)
}
func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
if len(additions) == 0 {
additions = []inbound.Addition{
inbound.WithInName("DEFAULT-HTTP"),
@ -65,7 +65,10 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
}
continue
}
go HandleConn(conn, in, c, additions...)
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
go HandleConn(conn, tunnel, c, additions...)
}
}()

View File

@ -25,7 +25,7 @@ func isUpgradeRequest(req *http.Request) bool {
return false
}
func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext, additions ...inbound.Addition) {
func handleUpgrade(conn net.Conn, request *http.Request, tunnel C.Tunnel, additions ...inbound.Addition) {
defer conn.Close()
removeProxyHeaders(request.Header)
@ -43,7 +43,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext
left, right := net.Pipe()
in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right, additions...)
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, conn, right, additions...))
var bufferedLeft *N.BufferedConn
if request.TLS != nil {

View File

@ -61,7 +61,7 @@ func (b *Base) RawAddress() string {
}
// Listen implements constant.InboundListener
func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error {
func (*Base) Listen(tunnel C.Tunnel) error {
return nil
}

View File

@ -42,9 +42,9 @@ func (h *HTTP) Address() string {
}
// Listen implements constant.InboundListener
func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error {
func (h *HTTP) Listen(tunnel C.Tunnel) error {
var err error
h.l, err = http.New(h.RawAddress(), tcpIn, h.Additions()...)
h.l, err = http.New(h.RawAddress(), tunnel, h.Additions()...)
if err != nil {
return err
}

View File

@ -77,9 +77,9 @@ func (t *Hysteria2) Address() string {
}
// Listen implements constant.InboundListener
func (t *Hysteria2) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error {
func (t *Hysteria2) Listen(tunnel C.Tunnel) error {
var err error
t.l, err = sing_hysteria2.New(t.ts, tcpIn, udpIn, t.Additions()...)
t.l, err = sing_hysteria2.New(t.ts, tunnel, t.Additions()...)
if err != nil {
return err
}

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