Compare commits

...

82 Commits

Author SHA1 Message Date
8c3557e96b chore: support v2ray http upgrade server too 2023-11-03 13:58:53 +08:00
228990472d fix: avoid tls panic 2023-11-03 12:04:22 +08:00
09e7866a5c fix: gvisor panic 2023-11-03 11:50:25 +08:00
665ba7f9f1 chore: do websocket client upgrade directly instead of gobwas/ws 2023-11-03 11:50:25 +08:00
ee3038d5e4 chore: add SetupContextForConn for common/net 2023-11-03 11:50:25 +08:00
885ee7a820 fix: v2ray http upgrade Hosts header not working 2023-11-03 11:50:25 +08:00
ef303b11f2 action: trigger CMFA PR update in every commit 2023-11-02 16:01:35 +08:00
a82ce85707 chore: add route exclude support 2023-11-02 11:37:40 +08:00
5bfe7ba169 chore: better tls handshake 2023-11-02 11:22:01 +08:00
ceac5bfaa4 feat: add v2ray-http-upgrade support 2023-11-02 11:11:35 +08:00
b0638cfc49 chore: better bufio.Reader warp 2023-11-02 11:11:35 +08:00
96220aa8ea feat: cancel RULE-SET nested SUB-RULE restrictions 2023-10-31 11:10:38 +00:00
8ff476a3a1 fix: remote logic rules cannot be parsed (#837) 2023-10-31 19:07:01 +08:00
261b6e8dce action: small fix to cmfa core-update trigger 2023-10-30 20:00:15 +08:00
2b9141e0e5 chore: geo link replaced with github 2023-10-30 19:46:56 +08:00
55255faa52 chore: modify configuration fields 2023-10-27 17:49:12 +08:00
d42e3f74ad action: add question issue guidance 2023-10-26 19:08:42 +08:00
81a8a63861 build: more go120 build 2023-10-26 11:39:54 +08:00
c3a61e2db5 build: add go120 build for win7/8.1 2023-10-26 11:09:19 +08:00
bffe47a974 chore: netip.Prefix should not using pointer 2023-10-26 11:02:53 +08:00
4314b37d04 fix: dhcp not working on windows 2023-10-26 10:27:38 +08:00
cf93f69f40 chore: cleanup error using of dialer.DefaultInterface 2023-10-26 09:07:49 +08:00
55f626424f chore: better dns batchExchange 2023-10-25 20:16:44 +08:00
431d52f250 chore: system resolver can autoupdate 2023-10-25 19:21:20 +08:00
c1f24d8f0e chore: code cleanup 2023-10-25 18:07:45 +08:00
fc5a3cf80c action: ban black issues 2023-10-25 18:06:10 +08:00
e1e999180a chore: inMemoryAuthenticator unneed sync map 2023-10-24 21:25:03 +08:00
8755618910 fix: reality panic 2023-10-23 23:33:59 +08:00
aede97571f Merge branch 'Alpha' of https://github.com/MetaCubeX/Clash.Meta into Alpha 2023-10-23 17:02:08 +08:00
01bc84db02 chore: add labels to issue template 2023-10-23 17:02:04 +08:00
3564e96a00 chore: share some code 2023-10-23 16:45:22 +08:00
f6f8f27668 action: update sync 2023-10-23 15:39:56 +08:00
dff54464c6 Add auto sync Alpha rebase android-open -> android-real (#817)
* chore: add android branch auto sync

* chore: fix

* chore: fix missing

* chore: fix actions

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

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

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

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

Signed-off-by: Misty <gyc990326@gmail.com>
2023-09-29 08:50:50 +08:00
0ed3c5a5ec chore: improve subscription userinfo parsing (#781)
do not use regex parsing for `Subscription-UserInfo` header field
2023-09-29 08:42:57 +08:00
c2b06a02bf feat: add reload signal support (#780)
Backport Clash feature by @septs, see Dreamacro/clash#2908
2023-09-29 08:36:25 +08:00
e0458a8fde chore: decrease goroutine used in core tunnel 2023-09-28 18:59:31 +08:00
21fb5f75b8 fix: gvisor panic 2023-09-26 09:06:00 +08:00
fb99412193 chore: update quic-go to 0.39.0 2023-09-26 08:51:25 +08:00
148 changed files with 4855 additions and 1770 deletions

View File

@ -1,6 +1,7 @@
name: Bug report name: Bug report
description: Create a report to help us improve description: Create a report to help us improve
title: "[Bug] " title: "[Bug] "
labels: ["bug"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure 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 name: Feature request
description: Suggest an idea for this project description: Suggest an idea for this project
title: "[Feature] " title: "[Feature] "
labels: ["enhancement"]
body: body:
- type: checkboxes - type: checkboxes
id: ensure 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", target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9", 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: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" } - { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" } - { type: "WithCGO", target: "linux/amd64", id: "3" }
@ -126,18 +132,26 @@ jobs:
shell: bash shell: bash
- name: Setup Go - name: Setup Go
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: "1.21" go-version: "1.21"
check-latest: true 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 - name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
run: | run: |
go test ./... go test ./...
- name: Build WithoutCGO - name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }} if: ${{ matrix.job.type!='WithCGO' }}
env: env:
NAME: Clash.Meta NAME: Clash.Meta
BINDIR: bin BINDIR: bin
@ -185,6 +199,17 @@ jobs:
ls -la ls -la
cd .. 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 - name: Zip
if: ${{ success() }} if: ${{ success() }}
run: | run: |

302
README.md
View File

@ -21,7 +21,7 @@
## Features ## Features
- Local HTTP/HTTPS/SOCKS server with authentication support - 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. - 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 - 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 - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
@ -32,259 +32,41 @@
## Dashboard ## Dashboard
We made an official web dashboard providing first class support for this project, check it out 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).
at [metacubexd](https://github.com/MetaCubeX/metacubexd)
## Wiki ## Configration example
Configuration examples can be found Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
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).
## 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 ```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download 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 ```shell
go env -w GOPROXY=https://goproxy.io,direct go env -w GOPROXY=https://goproxy.io,direct
``` ```
Now you can build it: Build with gvisor tun stack:
```shell
go build
```
If you need gvisor for tun stack, build with:
```shell ```shell
go build -tags with_gvisor 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 ### IPTABLES configuration
Work on Linux OS which supported `iptables` Work on Linux OS which supported `iptables`
@ -298,61 +80,9 @@ iptables:
inbound-interface: eth0 # detect the inbound interface, default is 'lo' 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 ## 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. API.
## Credits ## Credits

View File

@ -30,13 +30,13 @@ const (
type extraProxyState struct { type extraProxyState struct {
history *queue.Queue[C.DelayHistory] history *queue.Queue[C.DelayHistory]
alive *atomic.Bool alive atomic.Bool
} }
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue[C.DelayHistory] history *queue.Queue[C.DelayHistory]
alive *atomic.Bool alive atomic.Bool
url string url string
extra *xsync.MapOf[string, *extraProxyState] extra *xsync.MapOf[string, *extraProxyState]
} }

View File

@ -1,13 +1,17 @@
package inbound package inbound
import ( import (
"net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Addition func(metadata *C.Metadata) type Addition func(metadata *C.Metadata)
func (a Addition) Apply(metadata *C.Metadata) { func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
a(metadata) for _, addition := range additions {
addition(metadata)
}
} }
func WithInName(name string) Addition { func WithInName(name string) Addition {
@ -33,3 +37,29 @@ func WithSpecialProxy(specialProxy string) Addition {
metadata.SpecialProxy = specialProxy 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,15 @@ import (
"net" "net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
// NewHTTP receive normal http request and return HTTPContext // 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 := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = C.HTTP metadata.Type = C.HTTP
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
addition.Apply(metadata) ApplyAdditions(metadata, additions...)
} return conn, 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)
} }

View File

@ -5,23 +5,13 @@ import (
"net/http" "net/http"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
) )
// NewHTTPS receive CONNECT request and return ConnContext // 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 := parseHTTPAddr(request)
metadata.Type = C.HTTPS metadata.Type = C.HTTPS
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
addition.Apply(metadata) ApplyAdditions(metadata, additions...)
} return conn, 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)
} }

View File

@ -5,38 +5,16 @@ import (
"github.com/Dreamacro/clash/transport/socks5" "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 // 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 := parseSocksAddr(target)
metadata.NetWork = C.UDP metadata.NetWork = C.UDP
metadata.Type = source metadata.Type = source
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
addition.Apply(metadata)
}
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if p, ok := packet.(C.UDPPacketInAddr); ok { if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr()); err == nil { ApplyAdditions(metadata, WithInAddr(p.InAddr()))
metadata.InIP = ip
metadata.InPort = port
}
} }
ApplyAdditions(metadata, additions...)
return &PacketAdapter{ return packet, metadata
packet,
metadata,
}
} }

View File

@ -2,51 +2,17 @@ package inbound
import ( import (
"net" "net"
"net/netip"
"strconv"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
// NewSocket receive TCP inbound and return ConnContext // 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 := parseSocksAddr(target)
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = source metadata.Type = source
for _, addition := range additions { ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
addition.Apply(metadata) ApplyAdditions(metadata, additions...)
} return conn, 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)
} }

View File

@ -1,7 +1,6 @@
package inbound package inbound
import ( import (
"errors"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
@ -62,29 +61,3 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return 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) { 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 { if err != nil {
return nil, err 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) { func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...) options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...)) 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 { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

@ -58,14 +58,15 @@ type simpleObfsOption struct {
} }
type v2rayObfsOption struct { type v2rayObfsOption struct {
Mode string `obfs:"mode"` Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
} }
type shadowTLSOption struct { type shadowTLSOption struct {
@ -123,9 +124,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
} }
} }
if useEarly { 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 { } 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 obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{ v2rayOption = &v2rayObfs.Option{
Host: opts.Host, Host: opts.Host,
Path: opts.Path, Path: opts.Path,
Headers: opts.Headers, Headers: opts.Headers,
Mux: opts.Mux, Mux: opts.Mux,
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
} }
if opts.TLS { 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) { func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...) options := s.base.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...)) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/netip"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net" 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 { if err != nil {
err = fmt.Errorf("client hanshake error: %w", err) err = fmt.Errorf("client hanshake error: %w", err)
return 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" { if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr) host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{ wsOpts := &trojan.WebsocketOption{
Host: host, Host: host,
Port: port, Port: port,
Path: t.option.WSOpts.Path, Path: t.option.WSOpts.Path,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
} }
if t.option.SNI != "" { 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, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }

View File

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

View File

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

View File

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

View File

@ -34,12 +34,12 @@ type HealthCheck struct {
url string url string
extra map[string]*extraOption extra map[string]*extraOption
mu sync.Mutex mu sync.Mutex
started *atomic.Bool started atomic.Bool
proxies []C.Proxy proxies []C.Proxy
interval uint interval time.Duration
lazy bool lazy bool
expectedStatus utils.IntRanges[uint16] expectedStatus utils.IntRanges[uint16]
lastTouch *atomic.Int64 lastTouch atomic.TypedValue[time.Time]
done chan struct{} done chan struct{}
singleDo *singledo.Single[struct{}] singleDo *singledo.Single[struct{}]
} }
@ -50,13 +50,14 @@ func (hc *HealthCheck) process() {
return return
} }
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(hc.interval)
hc.start() hc.start()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
now := time.Now().Unix() lastTouch := hc.lastTouch.Load()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { since := time.Since(lastTouch)
if !hc.lazy || since < hc.interval {
hc.check() hc.check()
} else { } else {
log.Debugln("Skip once health check because we are lazy") log.Debugln("Skip once health check because we are lazy")
@ -85,7 +86,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
// if the provider has not set up health checks, then modify it to be the same as the group's interval // if the provider has not set up health checks, then modify it to be the same as the group's interval
if hc.interval == 0 { if hc.interval == 0 {
hc.interval = interval hc.interval = time.Duration(interval) * time.Second
} }
if hc.extra == nil { if hc.extra == nil {
@ -135,7 +136,7 @@ func (hc *HealthCheck) auto() bool {
} }
func (hc *HealthCheck) touch() { func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix()) hc.lastTouch.Store(time.Now())
} }
func (hc *HealthCheck) start() { func (hc *HealthCheck) start() {
@ -228,11 +229,9 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
proxies: proxies, proxies: proxies,
url: url, url: url,
extra: map[string]*extraOption{}, extra: map[string]*extraOption{},
started: atomic.NewBool(false), interval: time.Duration(interval) * time.Second,
interval: interval,
lazy: lazy, lazy: lazy,
expectedStatus: expectedStatus, expectedStatus: expectedStatus,
lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second), singleDo: singledo.NewSingle[struct{}](time.Second),
} }

View File

@ -1,7 +1,6 @@
package provider package provider
import ( import (
"github.com/dlclark/regexp2"
"strconv" "strconv"
"strings" "strings"
) )
@ -13,45 +12,24 @@ type SubscriptionInfo struct {
Expire int64 Expire int64
} }
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) { func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
si = &SubscriptionInfo{} userinfo = strings.ToLower(userinfo)
str = strings.ToLower(str) userinfo = strings.ReplaceAll(userinfo, " ", "")
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0) si = new(SubscriptionInfo)
reExpire := regexp2.MustCompile("expire=(\\d+)", 0) for _, field := range strings.Split(userinfo, ";") {
switch name, value, _ := strings.Cut(field, "="); name {
match, err := reTraffic.FindStringMatch(str) case "upload":
if err != nil || match == nil { si.Upload, err = strconv.ParseInt(value, 10, 64)
return nil, err case "download":
} si.Download, err = strconv.ParseInt(value, 10, 64)
group := match.Groups() case "total":
si.Upload, err = str2uint64(group[1].String()) si.Total, err = strconv.ParseInt(value, 10, 64)
if err != nil { case "expire":
return nil, err si.Expire, err = strconv.ParseInt(value, 10, 64)
} }
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())
if err != nil { if err != nil {
return nil, err return
} }
} }
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 atomic.Bool
} }
func NewBool(val bool) *Bool { func NewBool(val bool) (i Bool) {
i := &Bool{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Bool) MarshalJSON() ([]byte, error) { func (i *Bool) MarshalJSON() ([]byte, error) {
@ -39,12 +38,11 @@ type Pointer[T any] struct {
atomic.Pointer[T] atomic.Pointer[T]
} }
func NewPointer[T any](v *T) *Pointer[T] { func NewPointer[T any](v *T) (p Pointer[T]) {
var p Pointer[T]
if v != nil { if v != nil {
p.Store(v) p.Store(v)
} }
return &p return
} }
func (p *Pointer[T]) MarshalJSON() ([]byte, error) { func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
@ -68,10 +66,9 @@ type Int32 struct {
atomic.Int32 atomic.Int32
} }
func NewInt32(val int32) *Int32 { func NewInt32(val int32) (i Int32) {
i := &Int32{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Int32) MarshalJSON() ([]byte, error) { func (i *Int32) MarshalJSON() ([]byte, error) {
@ -96,10 +93,9 @@ type Int64 struct {
atomic.Int64 atomic.Int64
} }
func NewInt64(val int64) *Int64 { func NewInt64(val int64) (i Int64) {
i := &Int64{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Int64) MarshalJSON() ([]byte, error) { func (i *Int64) MarshalJSON() ([]byte, error) {
@ -124,10 +120,9 @@ type Uint32 struct {
atomic.Uint32 atomic.Uint32
} }
func NewUint32(val uint32) *Uint32 { func NewUint32(val uint32) (i Uint32) {
i := &Uint32{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uint32) MarshalJSON() ([]byte, error) { func (i *Uint32) MarshalJSON() ([]byte, error) {
@ -152,10 +147,9 @@ type Uint64 struct {
atomic.Uint64 atomic.Uint64
} }
func NewUint64(val uint64) *Uint64 { func NewUint64(val uint64) (i Uint64) {
i := &Uint64{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uint64) MarshalJSON() ([]byte, error) { func (i *Uint64) MarshalJSON() ([]byte, error) {
@ -180,10 +174,9 @@ type Uintptr struct {
atomic.Uintptr atomic.Uintptr
} }
func NewUintptr(val uintptr) *Uintptr { func NewUintptr(val uintptr) (i Uintptr) {
i := &Uintptr{}
i.Store(val) i.Store(val)
return i return
} }
func (i *Uintptr) MarshalJSON() ([]byte, error) { func (i *Uintptr) MarshalJSON() ([]byte, error) {

View File

@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
type TypedValue[T any] struct { type TypedValue[T any] struct {
value atomic.Value value atomic.Value
_ noCopy
} }
func (t *TypedValue[T]) Load() T { func (t *TypedValue[T]) Load() T {
@ -51,8 +52,13 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func NewTypedValue[T any](t T) *TypedValue[T] { func NewTypedValue[T any](t T) (v TypedValue[T]) {
v := &TypedValue[T]{}
v.Store(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 type Buffer = buf.Buffer
var New = buf.New var New = buf.New
var NewPacket = buf.NewPacket
var NewSize = buf.NewSize var NewSize = buf.NewSize
var With = buf.With var With = buf.With
var As = buf.As var As = buf.As

View File

@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false} 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. // Reader returns the internal bufio.Reader.
func (c *BufferedConn) Reader() *bufio.Reader { func (c *BufferedConn) Reader() *bufio.Reader {
return c.r 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" "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 == "" { if certificate == "" && privateKey == "" {
return newRandomTLSKeyPair() return newRandomTLSKeyPair()
} }
@ -19,6 +23,8 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
return cert, nil return cert, nil
} }
certificate = path.Resolve(certificate)
privateKey = path.Resolve(privateKey)
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
if loadErr != nil { if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) 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 package auth
import (
"github.com/puzpuzpuz/xsync/v2"
)
type Authenticator interface { type Authenticator interface {
Verify(user string, pass string) bool Verify(user string, pass string) bool
Users() []string Users() []string
@ -15,12 +11,12 @@ type AuthUser struct {
} }
type inMemoryAuthenticator struct { type inMemoryAuthenticator struct {
storage *xsync.MapOf[string, string] storage map[string]string
usernames []string usernames []string
} }
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
realPass, ok := au.storage.Load(user) realPass, ok := au.storage[user]
return ok && realPass == pass return ok && realPass == pass
} }
@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
if len(users) == 0 { if len(users) == 0 {
return nil return nil
} }
au := &inMemoryAuthenticator{
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()} storage: make(map[string]string),
for _, user := range users { usernames: make([]string, 0, len(users)),
au.storage.Store(user.User, user.Pass) }
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 return au
} }

View File

@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
listenAddr = "255.255.255.255:68" 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 ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, err return nil, err
} }
var addr *netip.Prefix var addr netip.Prefix
switch network { switch network {
case "udp4", "tcp4": case "udp4", "tcp4":
addr, err = ifaceObj.PickIPv4Addr(destination) addr, err = ifaceObj.PickIPv4Addr(destination)
@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
return nil, iface.ErrAddrNotFound 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 ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
"strings"
) )
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() { return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
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 bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
_, port, err := net.SplitHostPort(address) return fallbackBindIfaceToListenConfig(ifaceName, lc, network, 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 ParseNetwork(network string, addr netip.Addr) string { func ParseNetwork(network string, addr netip.Addr) string {
// fix bindIfaceToListenConfig() force bind to an ipv4 address return fallbackParseNetwork(network, addr)
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
} }

View File

@ -74,7 +74,11 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.interfaceName != "" { 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 { if err != nil {
return nil, err return nil, err
} }
@ -125,7 +129,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" { 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 return nil, err
} }
} }

View File

@ -20,6 +20,7 @@ type NetDialer interface {
type option struct { type option struct {
interfaceName string interfaceName string
fallbackBind bool
addrReuse bool addrReuse bool
routingMark int routingMark int
network 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 { func WithAddrReuse(reuse bool) Option {
return func(opt *option) { return func(opt *option) {
opt.addrReuse = reuse opt.addrReuse = reuse

View File

@ -36,7 +36,7 @@ type Pool struct {
cycle bool cycle bool
mux sync.Mutex mux sync.Mutex
host *trie.DomainTrie[struct{}] host *trie.DomainTrie[struct{}]
ipnet *netip.Prefix ipnet netip.Prefix
store store store store
} }
@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
} }
// IPNet return raw ipnet // IPNet return raw ipnet
func (p *Pool) IPNet() *netip.Prefix { func (p *Pool) IPNet() netip.Prefix {
return p.ipnet return p.ipnet
} }
@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
} }
type Options struct { type Options struct {
IPNet *netip.Prefix IPNet netip.Prefix
Host *trie.DomainTrie[struct{}] Host *trie.DomainTrie[struct{}]
// Size sets the maximum number of entries in memory // Size sets the maximum number of entries in memory
@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
hostAddr = options.IPNet.Masked().Addr() hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next() gateway = hostAddr.Next()
first = gateway.Next().Next().Next() // default start with 198.18.0.4 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) { 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) { func TestPool_Basic(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.0/28") ipnet := netip.MustParsePrefix("192.168.0.0/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
func TestPool_BasicV6(t *testing.T) { func TestPool_BasicV6(t *testing.T) {
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118") ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
func TestPool_Case_Insensitive(t *testing.T) { func TestPool_Case_Insensitive(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29") ipnet := netip.MustParsePrefix("192.168.0.1/29")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
func TestPool_CycleUsed(t *testing.T) { func TestPool_CycleUsed(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.16/28") ipnet := netip.MustParsePrefix("192.168.0.16/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
tree := trie.New[struct{}]() tree := trie.New[struct{}]()
tree.Insert("example.com", struct{}{}) tree.Insert("example.com", struct{}{})
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
Host: tree, Host: tree,
}) })
@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
func TestPool_MaxCacheSize(t *testing.T) { func TestPool_MaxCacheSize(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
func TestPool_DoubleMapping(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
func TestPool_Clone(t *testing.T) { func TestPool_Clone(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/24") ipnet := netip.MustParsePrefix("192.168.0.1/24")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
newPool, _ := New(Options{ newPool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 2, Size: 2,
}) })
newPool.CloneFrom(pool) newPool.CloneFrom(pool)
@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
func TestPool_Error(t *testing.T) { func TestPool_Error(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/31") ipnet := netip.MustParsePrefix("192.168.0.1/31")
_, err := New(Options{ _, err := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
func TestPool_FlushFileCache(t *testing.T) { func TestPool_FlushFileCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28") ipnet := netip.MustParsePrefix("192.168.0.1/28")
pools, tempfile, err := createPools(Options{ pools, tempfile, err := createPools(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
func TestPool_FlushMemoryCache(t *testing.T) { func TestPool_FlushMemoryCache(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/28") ipnet := netip.MustParsePrefix("192.168.0.1/28")
pool, _ := New(Options{ pool, _ := New(Options{
IPNet: &ipnet, IPNet: ipnet,
Size: 10, Size: 10,
}) })

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,287 @@
package sniffer 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 package sniffer
import ( import (
"bytes"
"encoding/hex"
"github.com/stretchr/testify/assert"
"testing" "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) { func TestTLSHeaders(t *testing.T) {
cases := []struct { cases := []struct {
input []byte input []byte
@ -142,6 +173,7 @@ func TestTLSHeaders(t *testing.T) {
} }
for _, test := range cases { for _, test := range cases {
input := bytes.Clone(test.input)
domain, err := SniffTLS(test.input) domain, err := SniffTLS(test.input)
if test.err { if test.err {
if err == nil { if err == nil {
@ -155,5 +187,6 @@ func TestTLSHeaders(t *testing.T) {
t.Error("expect domain ", test.domain, " but got ", domain) 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 return C.TCP
} }
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) { func (tls *TLSSniffer) SniffData(bytes []byte) (string, error) {
domain, err := SniffTLS(bytes) domain, err := SniffTLS(bytes)
if err == nil { if err == nil {
return *domain, nil return *domain, nil

View File

@ -43,7 +43,8 @@ type RealityConfig struct {
func aesgcmPreferred(ciphers []uint16) bool func aesgcmPreferred(ciphers []uint16) bool
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { 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{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, 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]) //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 { if authKey == nil {
return nil, errors.New("nil auth_key") return nil, errors.New("nil auth_key")
} }

View File

@ -21,7 +21,7 @@ type UClientHelloID struct {
var initRandomFingerprint UClientHelloID var initRandomFingerprint UClientHelloID
var initUtlsClient string 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{ utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
Client: fingerprint.Client, Client: fingerprint.Client,
Version: fingerprint.Version, 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 // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
// http/1.1 in its ALPN.
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go // 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 // Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn // fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil { if err := c.BuildHandshakeState(); err != nil {
@ -120,11 +119,11 @@ func (c *UConn) WebsocketHandshake() error {
if !hasALPNExtension { // Append extension if doesn't exists if !hasALPNExtension { // Append extension if doesn't exists
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}) 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 { if err := c.BuildHandshakeState(); err != nil {
return err return err
} }
return c.Handshake() return nil
} }
func SetGlobalUtlsClient(Client string) { func SetGlobalUtlsClient(Client string) {

View File

@ -20,7 +20,6 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@ -66,20 +65,21 @@ type General struct {
// Inbound config // Inbound config
type Inbound struct { type Inbound struct {
Port int `json:"port"` Port int `json:"port"`
SocksPort int `json:"socks-port"` SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"` RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"` TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"` MixedPort int `json:"mixed-port"`
Tun LC.Tun `json:"tun"` Tun LC.Tun `json:"tun"`
TuicServer LC.TuicServer `json:"tuic-server"` TuicServer LC.TuicServer `json:"tuic-server"`
ShadowSocksConfig string `json:"ss-config"` ShadowSocksConfig string `json:"ss-config"`
VmessConfig string `json:"vmess-config"` VmessConfig string `json:"vmess-config"`
Authentication []string `json:"authentication"` Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"` SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"`
BindAddress string `json:"bind-address"` AllowLan bool `json:"allow-lan"`
InboundTfo bool `json:"inbound-tfo"` BindAddress string `json:"bind-address"`
InboundMPTCP bool `json:"inbound-mptcp"` InboundTfo bool `json:"inbound-tfo"`
InboundMPTCP bool `json:"inbound-mptcp"`
} }
// Controller config // Controller config
@ -122,7 +122,7 @@ type DNS struct {
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []*netip.Prefix `yaml:"ipcidr"` IPCIDR []netip.Prefix `yaml:"ipcidr"`
Domain []string `yaml:"domain"` Domain []string `yaml:"domain"`
GeoSite []*router.DomainMatcher `yaml:"geosite"` GeoSite []*router.DomainMatcher `yaml:"geosite"`
} }
@ -159,6 +159,7 @@ type Sniffer struct {
type Experimental struct { type Experimental struct {
Fingerprints []string `yaml:"fingerprints"` Fingerprints []string `yaml:"fingerprints"`
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"` QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
} }
// Config is clash config manager // Config is clash config manager
@ -227,21 +228,23 @@ type RawTun struct {
RedirectToTun []string `yaml:"-" json:"-"` RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address []LC.ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []LC.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
Inet4RouteAddress []LC.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []LC.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` 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 { type RawTuicServer struct {
@ -270,6 +273,7 @@ type RawConfig struct {
InboundTfo bool `yaml:"inbound-tfo"` InboundTfo bool `yaml:"inbound-tfo"`
InboundMPTCP bool `yaml:"inbound-mptcp"` InboundMPTCP bool `yaml:"inbound-mptcp"`
Authentication []string `yaml:"authentication"` Authentication []string `yaml:"authentication"`
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"` BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode"` Mode T.TunnelMode `yaml:"mode"`
@ -385,7 +389,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true, AutoRoute: true,
AutoDetectInterface: 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{ TuicServer: RawTuicServer{
Enable: false, Enable: false,
@ -459,9 +463,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
StoreSelected: true, StoreSelected: true,
}, },
GeoXUrl: GeoXUrl{ GeoXUrl: GeoXUrl{
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb", Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.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", ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
} }
@ -619,6 +623,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
ShadowSocksConfig: cfg.ShadowSocksConfig, ShadowSocksConfig: cfg.ShadowSocksConfig,
VmessConfig: cfg.VmessConfig, VmessConfig: cfg.VmessConfig,
AllowLan: cfg.AllowLan, AllowLan: cfg.AllowLan,
SkipAuthPrefixes: cfg.SkipAuthPrefixes,
BindAddress: cfg.BindAddress, BindAddress: cfg.BindAddress,
InboundTfo: cfg.InboundTfo, InboundTfo: cfg.InboundTfo,
InboundMPTCP: cfg.InboundMPTCP, InboundMPTCP: cfg.InboundMPTCP,
@ -1044,7 +1049,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyName: proxyName, ProxyName: proxyName,
Interface: dialer.DefaultInterface,
Params: params, Params: params,
PreferH3: preferH3, PreferH3: preferH3,
}, },
@ -1146,15 +1150,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
return policy, nil return policy, nil
} }
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) { func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
var ipNets []*netip.Prefix var ipNets []netip.Prefix
for idx, ip := range ips { for idx, ip := range ips {
ipnet, err := netip.ParsePrefix(ip) ipnet, err := netip.ParsePrefix(ip)
if err != nil { if err != nil {
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
} }
ipNets = append(ipNets, &ipnet) ipNets = append(ipNets, ipnet)
} }
return ipNets, nil return ipNets, nil
@ -1222,7 +1226,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{ FallbackFilter: FallbackFilter{
IPCIDR: []*netip.Prefix{}, IPCIDR: []netip.Prefix{},
GeoSite: []*router.DomainMatcher{}, GeoSite: []*router.DomainMatcher{},
}, },
} }
@ -1296,7 +1300,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
} }
pool, err := fakeip.New(fakeip.Options{ pool, err := fakeip.New(fakeip.Options{
IPNet: &fakeIPRange, IPNet: fakeIPRange,
Size: 1000, Size: 1000,
Host: host, Host: host,
Persistence: rawCfg.Profile.StoreFakeIP, Persistence: rawCfg.Profile.StoreFakeIP,
@ -1359,22 +1363,24 @@ func parseTun(rawTun RawTun, general *General) error {
AutoDetectInterface: rawTun.AutoDetectInterface, AutoDetectInterface: rawTun.AutoDetectInterface,
RedirectToTun: rawTun.RedirectToTun, RedirectToTun: rawTun.RedirectToTun,
MTU: rawTun.MTU, MTU: rawTun.MTU,
Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)}, Inet4Address: []netip.Prefix{tunAddressPrefix},
Inet6Address: rawTun.Inet6Address, Inet6Address: rawTun.Inet6Address,
StrictRoute: rawTun.StrictRoute, StrictRoute: rawTun.StrictRoute,
Inet4RouteAddress: rawTun.Inet4RouteAddress, Inet4RouteAddress: rawTun.Inet4RouteAddress,
Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet6RouteAddress: rawTun.Inet6RouteAddress,
IncludeUID: rawTun.IncludeUID, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
IncludeUIDRange: rawTun.IncludeUIDRange, Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
ExcludeUID: rawTun.ExcludeUID, IncludeUID: rawTun.IncludeUID,
ExcludeUIDRange: rawTun.ExcludeUIDRange, IncludeUIDRange: rawTun.IncludeUIDRange,
IncludeAndroidUser: rawTun.IncludeAndroidUser, ExcludeUID: rawTun.ExcludeUID,
IncludePackage: rawTun.IncludePackage, ExcludeUIDRange: rawTun.ExcludeUIDRange,
ExcludePackage: rawTun.ExcludePackage, IncludeAndroidUser: rawTun.IncludeAndroidUser,
EndpointIndependentNat: rawTun.EndpointIndependentNat, IncludePackage: rawTun.IncludePackage,
UDPTimeout: rawTun.UDPTimeout, ExcludePackage: rawTun.ExcludePackage,
FileDescriptor: rawTun.FileDescriptor, EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor,
} }
return nil return nil

View File

@ -252,6 +252,23 @@ type PacketAdapter interface {
Metadata() *Metadata 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 { type WriteBack interface {
WriteBack(b []byte, addr net.Addr) (n int, err error) WriteBack(b []byte, addr net.Addr) (n int, err error)
} }

View File

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

View File

@ -240,6 +240,34 @@ func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP.IsValid() 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 { func (m *Metadata) SetRemoteAddress(rawAddress string) error {
host, port, err := net.SplitHostPort(rawAddress) host, port, err := net.SplitHostPort(rawAddress)
if err != nil { if err != nil {

View File

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

View File

@ -4,7 +4,8 @@ import "github.com/Dreamacro/clash/constant"
type Sniffer interface { type Sniffer interface {
SupportNetwork() constant.NetWork SupportNetwork() constant.NetWork
SniffTCP(bytes []byte) (string, error) // SniffData must not change input bytes
SniffData(bytes []byte) (string, error)
Protocol() string Protocol() string
SupportPort(port uint16) bool SupportPort(port uint16) bool
} }
@ -12,10 +13,11 @@ type Sniffer interface {
const ( const (
TLS Type = iota TLS Type = iota
HTTP HTTP
QUIC
) )
var ( var (
List = []Type{TLS, HTTP} List = []Type{TLS, HTTP, QUIC}
) )
type Type int type Type int
@ -26,6 +28,8 @@ func (rt Type) String() string {
return "TLS" return "TLS"
case HTTP: case HTTP:
return "HTTP" return "HTTP"
case QUIC:
return "QUIC"
default: default:
return "Unknown" 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" "net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -23,7 +22,7 @@ type client struct {
r *Resolver r *Resolver
port string port string
host string host string
iface *atomic.TypedValue[string] iface string
proxyAdapter C.ProxyAdapter proxyAdapter C.ProxyAdapter
proxyName string proxyName string
addr string addr string
@ -48,10 +47,6 @@ func (c *client) Address() string {
return c.addr 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) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
var ( var (
ip netip.Addr ip netip.Addr
@ -77,9 +72,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
network = "tcp" network = "tcp"
} }
options := []dialer.Option{} var options []dialer.Option
if c.iface != nil && c.iface.Load() != "" { if c.iface != "" {
options = append(options, dialer.WithInterface(c.iface.Load())) 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)) conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))

View File

@ -8,11 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/dhcp" "github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -29,7 +26,7 @@ type dhcpClient struct {
ifaceInvalidate time.Time ifaceInvalidate time.Time
dnsInvalidate time.Time dnsInvalidate time.Time
ifaceAddr *netip.Prefix ifaceAddr netip.Prefix
done chan struct{} done chan struct{}
clients []dnsClient clients []dnsClient
err error err error
@ -46,13 +43,6 @@ func (d *dhcpClient) Address() string {
return strings.Join(addrs, ",") 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) { func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
clients, err := d.resolve(ctx) clients, err := d.resolve(ctx)
if err != nil { if err != nil {
@ -86,7 +76,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
for _, item := range dns { for _, item := range dns {
nameserver = append(nameserver, NameServer{ nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"), 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 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. // Close implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Close() (err error) { func (doh *dnsOverHTTPS) Close() (err error) {
doh.clientMu.Lock() 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 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. // Close implements the Upstream interface for *dnsOverQUIC.
func (doq *dnsOverQUIC) Close() (err error) { func (doq *dnsOverQUIC) Close() (err error) {
doq.connMu.Lock() doq.connMu.Lock()

View File

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

View File

@ -39,16 +39,12 @@ type rcodeClient struct {
var _ dnsClient = rcodeClient{} 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.Response = true
m.Rcode = r.rcode m.Rcode = r.rcode
return m, nil 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 { func (r rcodeClient) Address() string {
return r.addr return r.addr
} }

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
@ -18,11 +17,11 @@ import (
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/samber/lo"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string Address() string
} }
@ -135,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
return false 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 // 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) { func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 { 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) msg := result.(*D.Msg)
if cache { 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) isIPReq := isIPRequest(q)
if isIPReq { if isIPReq {
cache=true cache = true
return r.ipExchange(ctx, m) return r.ipExchange(ctx, m)
} }
if matched := r.matchPolicy(m); len(matched) != 0 { if matched := r.matchPolicy(m); len(matched) != 0 {
result, cache, err = r.batchExchange(ctx, matched, m) result, cache, err = batchExchange(ctx, matched, m)
return return
} }
result, cache, err = r.batchExchange(ctx, r.main, m) result, cache, err = batchExchange(ctx, r.main, m)
return return
} }
@ -250,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
return 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 { func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil { if r.policy == nil {
return nil return nil
@ -332,7 +323,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
res := <-msgCh res := <-msgCh
if res.Error == nil { if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 { 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 msg, err = res.Msg, res.Error // no need to wait for fallback result
return 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 { func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
ch := make(chan *result, 1) ch := make(chan *result, 1)
go func() { go func() {
res, _, err := r.batchExchange(ctx, client, msg) res, _, err := batchExchange(ctx, client, msg)
ch <- &result{Msg: res, Error: err} ch <- &result{Msg: res, Error: err}
}() }()
return ch return ch
@ -394,7 +388,7 @@ func (r *Resolver) Invalid() bool {
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface *atomic.TypedValue[string] Interface string
ProxyAdapter C.ProxyAdapter ProxyAdapter C.ProxyAdapter
ProxyName string ProxyName string
Params map[string]string Params map[string]string
@ -404,7 +398,7 @@ type NameServer struct {
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool GeoIP bool
GeoIPCode string GeoIPCode string
IPCIDR []*netip.Prefix IPCIDR []netip.Prefix
Domain []string Domain []string
GeoSite []*router.DomainMatcher GeoSite []*router.DomainMatcher
} }

View File

@ -1,23 +1,113 @@
package dns package dns
import ( import (
"context"
"fmt"
"net" "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) { const (
nameservers, err := dnsReadConfig() 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 { if err != nil {
return return
} }
if len(nameservers) == 0 { msg, _, err = batchExchange(ctx, dnsClients, m)
return return
} }
servers := make([]NameServer, 0, len(nameservers))
for _, addr := range nameservers { // Address implements dnsClient
servers = append(servers, NameServer{ func (c *systemClient) Address() string {
Addr: net.JoinHostPort(addr, "53"), dnsClients, _ := c.getDnsClients()
Net: "udp", addrs := make([]string, 0, len(dnsClients))
}) for _, c := range dnsClients {
} addrs = append(addrs, c.Address())
return transform(servers, nil), nil }
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 MaxMsgSize = 65535
) )
const serverFailureCacheTTL uint32 = 5
func minimalTTL(records []D.RR) uint32 { 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 return r1.Header().Ttl < r2.Header().Ttl
}) })
if minObj != nil { if rr == nil {
return minObj.Header().Ttl return 0
} }
return 0 return rr.Header().Ttl
} }
func updateTTL(records []D.RR, ttl uint32) { 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) { func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, q D.Question, msg *D.Msg) {
putMsgToCacheWithExpire(c, key, msg, 0) // 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)
func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) { return
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
}
} }
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) { func setMsgTTL(msg *D.Msg, ttl uint32) {
@ -108,16 +107,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
continue continue
case "system": case "system":
clients, err := loadSystemResolver() ret = append(ret, newSystemClient())
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...)
continue continue
case "rcode": case "rcode":
ret = append(ret, newRCodeClient(s.Addr)) ret = append(ret, newRCodeClient(s.Addr))
@ -290,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
DstPort: uint16(uintPort), DstPort: uint16(uintPort),
} }
if proxyAdapter == nil { 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() { if !proxyAdapter.SupportUDP() {
@ -300,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...) 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) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
cache = true cache = true
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
defer fast.Close() defer fast.Close()
domain := msgToDomain(m) domain := msgToDomain(m)
var noIpMsg *D.Msg
for _, client := range clients { for _, client := range clients {
if _, isRCodeClient := client.(rcodeClient); isRCodeClient { if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
msg, err = client.Exchange(m) msg, err = client.ExchangeContext(ctx, m)
return msg, false, err 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 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. // so we would ignore RCode errors from RCode clients.
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode]) 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 return m, nil
}) })
} }
msg = fast.Wait() msg = fast.Wait()
if msg == nil { if msg == nil {
if noIpMsg != nil {
return noIpMsg, false, nil
}
err = errors.New("all DNS requests failed") err = errors.New("all DNS requests failed")
if fErr := fast.Error(); fErr != nil { if fErr := fast.Error(); fErr != nil {
err = fmt.Errorf("%w, first error: %w", err, fErr) err = fmt.Errorf("%w, first error: %w", err, fErr)

View File

@ -8,6 +8,11 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
allow-lan: true # 允许局域网连接 allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定 IP 地址,仅作用于 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 # find-process-mode has 3 values:always, strict, off
# - always, 开启,强制匹配所有进程 # - always, 开启,强制匹配所有进程
@ -137,7 +142,9 @@ sniffer:
# 是否使用嗅探结果作为实际访问,默认 true # 是否使用嗅探结果作为实际访问,默认 true
# 全局配置,优先级低于 sniffer.sniff 实际配置 # 全局配置,优先级低于 sniffer.sniff 实际配置
override-destination: false override-destination: false
sniff: # TLS 默认如果不配置 ports 默认嗅探 443 sniff: # TLS 和 QUIC 默认如果不配置 ports 默认嗅探 443
QUIC:
# ports: [ 443 ]
TLS: TLS:
# ports: [443, 8443] # ports: [443, 8443]
@ -345,16 +352,17 @@ proxies: # socks5
plugin: v2ray-plugin plugin: v2ray-plugin
plugin-opts: plugin-opts:
mode: websocket # no QUIC now mode: websocket # no QUIC now
# tls: true # wss # tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果 # 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx # fingerprint: xxxx
# skip-cert-verify: true # skip-cert-verify: true
# host: bing.com # host: bing.com
# path: "/" # path: "/"
# mux: true # mux: true
# headers: # headers:
# custom: value # custom: value
# v2ray-http-upgrade: false
- name: "ss4-shadow-tls" - name: "ss4-shadow-tls"
type: ss type: ss
@ -427,11 +435,12 @@ proxies: # socks5
# servername: example.com # priority over wss host # servername: example.com # priority over wss host
# network: ws # network: ws
# ws-opts: # ws-opts:
# path: /path # path: /path
# headers: # headers:
# Host: v2ray.com # Host: v2ray.com
# max-early-data: 2048 # max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol # early-data-header-name: Sec-WebSocket-Protocol
# v2ray-http-upgrade: false
- name: "vmess-h2" - name: "vmess-h2"
type: vmess type: vmess
@ -559,6 +568,7 @@ proxies: # socks5
path: "/" path: "/"
headers: headers:
Host: example.com Host: example.com
# v2ray-http-upgrade: false
# Trojan # Trojan
- name: "trojan" - name: "trojan"
@ -599,9 +609,10 @@ proxies: # socks5
# fingerprint: xxxx # fingerprint: xxxx
udp: true udp: true
# ws-opts: # ws-opts:
# path: /path # path: /path
# headers: # headers:
# Host: example.com # Host: example.com
# v2ray-http-upgrade: false
- name: "trojan-xtls" - name: "trojan-xtls"
type: trojan type: trojan
@ -936,6 +947,10 @@ listeners:
- username: 1 - username: 1
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68 uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1 alterId: 1
# ws-path: "/" # 如果不为空则开启websocket传输层
# 下面两项如果填写则开启tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: tuic-in-1 - name: tuic-in-1
type: tuic type: tuic

45
go.mod
View File

@ -5,51 +5,51 @@ go 1.20
require ( require (
github.com/3andne/restls-client-go v0.1.6 github.com/3andne/restls-client-go v0.1.6
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da 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/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.10.0 github.com/dlclark/regexp2 v1.10.0
github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3 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/gofrs/uuid/v5 v5.0.0
github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/klauspost/cpuid/v2 v2.2.5 github.com/klauspost/cpuid/v2 v2.2.5
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.2
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
github.com/metacubex/sing-shadowsocks v0.2.5 github.com/metacubex/sing-shadowsocks v0.2.5
github.com/metacubex/sing-shadowsocks2 v0.1.4 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-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/miekg/dns v1.1.56
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 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/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-mux v0.1.3
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
github.com/samber/lo v1.38.1 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/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/zhangyunhao116/fastrand v0.3.0 github.com/zhangyunhao116/fastrand v0.3.0
go.etcd.io/bbolt v1.3.7 go.etcd.io/bbolt v1.3.7
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
golang.org/x/crypto v0.13.0 golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.15.0 golang.org/x/net v0.17.0
golang.org/x/sync v0.3.0 golang.org/x/sync v0.4.0
golang.org/x/sys v0.12.0 golang.org/x/sys v0.13.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.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/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // 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/gaukas/godicttls v0.0.4 // indirect
github.com/go-ole/go-ole v1.3.0 // 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/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/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // 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/klauspost/compress v1.16.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.4.1 // 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/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // 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/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/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // 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/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // 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/text v0.13.0 // indirect
golang.org/x/time v0.3.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/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/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/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.12.0 h1:oQEuIQIXgYhe1v7sYUG0P9vtJTYZLLdA6tiQmrOB1mo=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= 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 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.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 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= 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/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= 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-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 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= 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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 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 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/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 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/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/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 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= 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-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs=
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg= github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c=
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8= 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.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8= github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b/go.mod h1:4pe6cY+nAMFU/Uxn1rfnxNIowsaJGDQ3uyy4VuiPkP4=
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4= github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9 h1:F0+IuW0tZ96QHEmrebXAdYnz7ab7Gz4l5yYC4g6Cg8k=
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg= github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9/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-20231008050747-a684db516966 h1:wbOsbU3kfD5LRuJIntJwEPmgGSQukof8CgLNypi8az8=
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4= 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 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo= 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 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k= 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.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M= 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 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM= 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-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
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/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= 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/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 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 h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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/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.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= 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 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= 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.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
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/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 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 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= 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/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 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= 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.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= 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 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 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-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 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 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 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig= github.com/zhangyunhao116/fastrand v0.3.0 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.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 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 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/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.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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.3.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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/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-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-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-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-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-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-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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.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/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.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 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 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 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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.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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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.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 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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() { func initInnerTcp() {
inner.New(tunnel.TCPIn()) inner.New(tunnel.Tunnel)
} }
func GetGeneral() *config.General { func GetGeneral() *config.General {
@ -140,6 +140,7 @@ func GetGeneral() *config.General {
ShadowSocksConfig: ports.ShadowSocksConfig, ShadowSocksConfig: ports.ShadowSocksConfig,
VmessConfig: ports.VmessConfig, VmessConfig: ports.VmessConfig,
Authentication: authenticator, Authentication: authenticator,
SkipAuthPrefixes: inbound.SkipAuthPrefixes(),
AllowLan: listener.AllowLan(), AllowLan: listener.AllowLan(),
BindAddress: listener.BindAddress(), BindAddress: listener.BindAddress(),
}, },
@ -157,34 +158,34 @@ func GetGeneral() *config.General {
} }
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) { func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
tcpIn := tunnel.TCPIn() listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
if !force { if !force {
return return
} }
allowLan := general.AllowLan allowLan := general.AllowLan
listener.SetAllowLan(allowLan) listener.SetAllowLan(allowLan)
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
bindAddress := general.BindAddress bindAddress := general.BindAddress
listener.SetBindAddress(bindAddress) listener.SetBindAddress(bindAddress)
listener.ReCreateHTTP(general.Port, tcpIn) listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn) listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable) listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn) listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable) listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn) listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn) listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn) listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
} }
func updateExperimental(c *config.Config) { func updateExperimental(c *config.Config) {
if c.Experimental.QUICGoDisableGSO { 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 { if general == nil {
return return
} }
listener.ReCreateTun(LC.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn()) listener.ReCreateTun(general.Tun, tunnel.Tunnel)
listener.ReCreateRedirToTun(general.Tun.RedirectToTun) listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
} }
@ -367,7 +368,7 @@ func updateSniffer(sniffer *config.Sniffer) {
} }
func updateTunnels(tunnels []LC.Tunnel) { func updateTunnels(tunnels []LC.Tunnel) {
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn()) listener.PatchTunnel(tunnels, tunnel.Tunnel)
} }
func updateGeneral(general *config.General) { func updateGeneral(general *config.General) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,11 +30,11 @@ func (l *Listener) Close() error {
return l.listener.Close() return l.listener.Close()
} }
func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticate(addr, in, true, additions...) 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 { if len(additions) == 0 {
additions = []inbound.Addition{ additions = []inbound.Addition{
inbound.WithInName("DEFAULT-HTTP"), inbound.WithInName("DEFAULT-HTTP"),
@ -65,7 +65,7 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
} }
continue continue
} }
go HandleConn(conn, in, c, additions...) go HandleConn(conn, tunnel, c, additions...)
} }
}() }()

View File

@ -25,7 +25,7 @@ func isUpgradeRequest(req *http.Request) bool {
return false 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() defer conn.Close()
removeProxyHeaders(request.Header) removeProxyHeaders(request.Header)
@ -43,7 +43,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext
left, right := net.Pipe() 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 var bufferedLeft *N.BufferedConn
if request.TLS != nil { if request.TLS != nil {

View File

@ -61,7 +61,7 @@ func (b *Base) RawAddress() string {
} }
// Listen implements constant.InboundListener // 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 return nil
} }

View File

@ -42,9 +42,9 @@ func (h *HTTP) Address() string {
} }
// Listen implements constant.InboundListener // 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 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 { if err != nil {
return err return err
} }

View File

@ -77,9 +77,9 @@ func (t *Hysteria2) Address() string {
} }
// Listen implements constant.InboundListener // 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 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 { if err != nil {
return err return err
} }

View File

@ -50,14 +50,14 @@ func (m *Mixed) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (m *Mixed) Listen(tunnel C.Tunnel) error {
var err error var err error
m.l, err = mixed.New(m.RawAddress(), tcpIn, m.Additions()...) m.l, err = mixed.New(m.RawAddress(), tunnel, m.Additions()...)
if err != nil { if err != nil {
return err return err
} }
if m.udp { if m.udp {
m.lUDP, err = socks.NewUDP(m.RawAddress(), udpIn, m.Additions()...) m.lUDP, err = socks.NewUDP(m.RawAddress(), tunnel, m.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

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

View File

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

View File

@ -68,13 +68,13 @@ func (s *Socks) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (s *Socks) Listen(tunnel C.Tunnel) error {
var err error var err error
if s.stl, err = socks.New(s.RawAddress(), tcpIn, s.Additions()...); err != nil { if s.stl, err = socks.New(s.RawAddress(), tunnel, s.Additions()...); err != nil {
return err return err
} }
if s.udp { if s.udp {
if s.sul, err = socks.NewUDP(s.RawAddress(), udpIn, s.Additions()...); err != nil { if s.sul, err = socks.NewUDP(s.RawAddress(), tunnel, s.Additions()...); err != nil {
return err return err
} }
} }

View File

@ -49,14 +49,14 @@ func (t *TProxy) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (t *TProxy) Listen(tunnel C.Tunnel) error {
var err error var err error
t.lTCP, err = tproxy.New(t.RawAddress(), tcpIn, t.Additions()...) t.lTCP, err = tproxy.New(t.RawAddress(), tunnel, t.Additions()...)
if err != nil { if err != nil {
return err return err
} }
if t.udp { if t.udp {
t.lUDP, err = tproxy.NewUDP(t.RawAddress(), udpIn, natTable, t.Additions()...) t.lUDP, err = tproxy.NewUDP(t.RawAddress(), tunnel, t.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

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

View File

@ -18,22 +18,24 @@ type TunOption struct {
AutoRoute bool `inbound:"auto-route,omitempty"` AutoRoute bool `inbound:"auto-route,omitempty"`
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
MTU uint32 `inbound:"mtu,omitempty"` MTU uint32 `inbound:"mtu,omitempty"`
Inet4Address []string `inbound:"inet4_address,omitempty"` Inet4Address []string `inbound:"inet4_address,omitempty"`
Inet6Address []string `inbound:"inet6_address,omitempty"` Inet6Address []string `inbound:"inet6_address,omitempty"`
StrictRoute bool `inbound:"strict_route,omitempty"` StrictRoute bool `inbound:"strict_route,omitempty"`
Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"`
Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"`
IncludeUID []uint32 `inbound:"include_uid,omitempty"` Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"`
IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"`
ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` IncludeUID []uint32 `inbound:"include_uid,omitempty"`
ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` IncludeUIDRange []string `inbound:"include_uid_range,omitempty"`
IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"`
IncludePackage []string `inbound:"include_package,omitempty"` ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"`
ExcludePackage []string `inbound:"exclude_package,omitempty"` IncludeAndroidUser []int `inbound:"include_android_user,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` IncludePackage []string `inbound:"include_package,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"` ExcludePackage []string `inbound:"exclude_package,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"` EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `inbound:"udp_timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
} }
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
@ -56,19 +58,27 @@ func NewTun(options *TunOption) (*Tun, error) {
if !exist { if !exist {
return nil, errors.New("invalid tun stack") return nil, errors.New("invalid tun stack")
} }
inet4Address, err := LC.StringSliceToListenPrefixSlice(options.Inet4Address) inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
inet6Address, err := LC.StringSliceToListenPrefixSlice(options.Inet6Address) inet6Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet6Address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
inet4RouteAddress, err := LC.StringSliceToListenPrefixSlice(options.Inet4RouteAddress) inet4RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
inet6RouteAddress, err := LC.StringSliceToListenPrefixSlice(options.Inet6RouteAddress) inet6RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteAddress)
if err != nil {
return nil, err
}
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
if err != nil {
return nil, err
}
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,28 +86,30 @@ func NewTun(options *TunOption) (*Tun, error) {
Base: base, Base: base,
config: options, config: options,
tun: LC.Tun{ tun: LC.Tun{
Enable: true, Enable: true,
Device: options.Device, Device: options.Device,
Stack: stack, Stack: stack,
DNSHijack: options.DNSHijack, DNSHijack: options.DNSHijack,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
AutoDetectInterface: options.AutoDetectInterface, AutoDetectInterface: options.AutoDetectInterface,
MTU: options.MTU, MTU: options.MTU,
Inet4Address: inet4Address, Inet4Address: inet4Address,
Inet6Address: inet6Address, Inet6Address: inet6Address,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,
IncludeUID: options.IncludeUID, Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
IncludeUIDRange: options.IncludeUIDRange, Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
ExcludeUID: options.ExcludeUID, IncludeUID: options.IncludeUID,
ExcludeUIDRange: options.ExcludeUIDRange, IncludeUIDRange: options.IncludeUIDRange,
IncludeAndroidUser: options.IncludeAndroidUser, ExcludeUID: options.ExcludeUID,
IncludePackage: options.IncludePackage, ExcludeUIDRange: options.ExcludeUIDRange,
ExcludePackage: options.ExcludePackage, IncludeAndroidUser: options.IncludeAndroidUser,
EndpointIndependentNat: options.EndpointIndependentNat, IncludePackage: options.IncludePackage,
UDPTimeout: options.UDPTimeout, ExcludePackage: options.ExcludePackage,
FileDescriptor: options.FileDescriptor, EndpointIndependentNat: options.EndpointIndependentNat,
UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor,
}, },
}, nil }, nil
} }
@ -113,9 +125,9 @@ func (t *Tun) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (t *Tun) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (t *Tun) Listen(tunnel C.Tunnel) error {
var err error var err error
t.l, err = sing_tun.New(t.tun, tcpIn, udpIn, t.Additions()...) t.l, err = sing_tun.New(t.tun, tunnel, t.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/tunnel" LT "github.com/Dreamacro/clash/listener/tunnel"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
@ -21,8 +21,8 @@ func (o TunnelOption) Equal(config C.InboundConfig) bool {
type Tunnel struct { type Tunnel struct {
*Base *Base
config *TunnelOption config *TunnelOption
ttl *tunnel.Listener ttl *LT.Listener
tul *tunnel.PacketConn tul *LT.PacketConn
} }
func NewTunnel(options *TunnelOption) (*Tunnel, error) { func NewTunnel(options *TunnelOption) (*Tunnel, error) {
@ -74,16 +74,16 @@ func (t *Tunnel) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (t *Tunnel) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (t *Tunnel) Listen(tunnel C.Tunnel) error {
var err error var err error
for _, network := range t.config.Network { for _, network := range t.config.Network {
switch network { switch network {
case "tcp": case "tcp":
if t.ttl, err = tunnel.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tcpIn, t.Additions()...); err != nil { if t.ttl, err = LT.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err return err
} }
case "udp": case "udp":
if t.tul, err = tunnel.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, udpIn, t.Additions()...); err != nil { if t.tul, err = LT.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err return err
} }
default: default:

View File

@ -9,7 +9,10 @@ import (
type VmessOption struct { type VmessOption struct {
BaseOption BaseOption
Users []VmessUser `inbound:"users"` Users []VmessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
} }
type VmessUser struct { type VmessUser struct {
@ -46,9 +49,12 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
Base: base, Base: base,
config: options, config: options,
vs: LC.VmessServer{ vs: LC.VmessServer{
Enable: true, Enable: true,
Listen: base.RawAddress(), Listen: base.RawAddress(),
Users: users, Users: users,
WsPath: options.WsPath,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
}, },
}, nil }, nil
} }
@ -69,7 +75,7 @@ func (v *Vmess) Address() string {
} }
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { func (v *Vmess) Listen(tunnel C.Tunnel) error {
var err error var err error
users := make([]LC.VmessUser, len(v.config.Users)) users := make([]LC.VmessUser, len(v.config.Users))
for i, v := range v.config.Users { for i, v := range v.config.Users {
@ -79,7 +85,7 @@ func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter,
AlterID: v.AlterID, AlterID: v.AlterID,
} }
} }
v.l, err = sing_vmess.New(v.vs, tcpIn, udpIn, v.Additions()...) v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -3,24 +3,41 @@ package inner
import ( import (
"errors" "errors"
"net" "net"
"net/netip"
"strconv"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
var tcpIn chan<- C.ConnContext var tunnel C.Tunnel
func New(in chan<- C.ConnContext) { func New(t C.Tunnel) {
tcpIn = in tunnel = t
} }
func HandleTcp(address string) (conn net.Conn, err error) { func HandleTcp(address string) (conn net.Conn, err error) {
if tcpIn == nil { if tunnel == nil {
return nil, errors.New("tcp uninitialized") return nil, errors.New("tcp uninitialized")
} }
// executor Parsed // executor Parsed
conn1, conn2 := net.Pipe() conn1, conn2 := net.Pipe()
context := inbound.NewInner(conn2, address)
tcpIn <- context metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal
metadata.Process = C.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
}
}
go tunnel.HandleTCPConn(conn2, metadata)
return conn1, nil return conn1, nil
} }

View File

@ -23,7 +23,7 @@ import (
"github.com/Dreamacro/clash/listener/socks" "github.com/Dreamacro/clash/listener/socks"
"github.com/Dreamacro/clash/listener/tproxy" "github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/listener/tuic" "github.com/Dreamacro/clash/listener/tuic"
"github.com/Dreamacro/clash/listener/tunnel" LT "github.com/Dreamacro/clash/listener/tunnel"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/samber/lo" "github.com/samber/lo"
@ -42,8 +42,8 @@ var (
tproxyUDPListener *tproxy.UDPListener tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.Listener mixedListener *mixed.Listener
mixedUDPLister *socks.UDPListener mixedUDPLister *socks.UDPListener
tunnelTCPListeners = map[string]*tunnel.Listener{} tunnelTCPListeners = map[string]*LT.Listener{}
tunnelUDPListeners = map[string]*tunnel.PacketConn{} tunnelUDPListeners = map[string]*LT.PacketConn{}
inboundListeners = map[string]C.InboundListener{} inboundListeners = map[string]C.InboundListener{}
tunLister *sing_tun.Listener tunLister *sing_tun.Listener
shadowSocksListener C.MultiAddrListener shadowSocksListener C.MultiAddrListener
@ -112,7 +112,7 @@ func SetBindAddress(host string) {
bindAddress = host bindAddress = host
} }
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { func ReCreateHTTP(port int, tunnel C.Tunnel) {
httpMux.Lock() httpMux.Lock()
defer httpMux.Unlock() defer httpMux.Unlock()
@ -137,7 +137,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
return return
} }
httpListener, err = http.New(addr, tcpIn) httpListener, err = http.New(addr, tunnel)
if err != nil { if err != nil {
log.Errorln("Start HTTP server error: %s", err.Error()) log.Errorln("Start HTTP server error: %s", err.Error())
return return
@ -146,7 +146,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
log.Infoln("HTTP proxy listening at: %s", httpListener.Address()) log.Infoln("HTTP proxy listening at: %s", httpListener.Address())
} }
func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateSocks(port int, tunnel C.Tunnel) {
socksMux.Lock() socksMux.Lock()
defer socksMux.Unlock() defer socksMux.Unlock()
@ -188,12 +188,12 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
return return
} }
tcpListener, err := socks.New(addr, tcpIn) tcpListener, err := socks.New(addr, tunnel)
if err != nil { if err != nil {
return return
} }
udpListener, err := socks.NewUDP(addr, udpIn) udpListener, err := socks.NewUDP(addr, tunnel)
if err != nil { if err != nil {
tcpListener.Close() tcpListener.Close()
return return
@ -205,7 +205,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
log.Infoln("SOCKS proxy listening at: %s", socksListener.Address()) log.Infoln("SOCKS proxy listening at: %s", socksListener.Address())
} }
func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) { func ReCreateRedir(port int, tunnel C.Tunnel) {
redirMux.Lock() redirMux.Lock()
defer redirMux.Unlock() defer redirMux.Unlock()
@ -238,12 +238,12 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
return return
} }
redirListener, err = redir.New(addr, tcpIn) redirListener, err = redir.New(addr, tunnel)
if err != nil { if err != nil {
return return
} }
redirUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) redirUDPListener, err = tproxy.NewUDP(addr, tunnel)
if err != nil { if err != nil {
log.Warnln("Failed to start Redir UDP Listener: %s", err) log.Warnln("Failed to start Redir UDP Listener: %s", err)
} }
@ -251,7 +251,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
log.Infoln("Redirect proxy listening at: %s", redirListener.Address()) log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
} }
func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateShadowSocks(shadowSocksConfig string, tunnel C.Tunnel) {
ssMux.Lock() ssMux.Lock()
defer ssMux.Unlock() defer ssMux.Unlock()
@ -292,7 +292,7 @@ func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, u
return return
} }
listener, err := sing_shadowsocks.New(ssConfig, tcpIn, udpIn) listener, err := sing_shadowsocks.New(ssConfig, tunnel)
if err != nil { if err != nil {
return return
} }
@ -305,7 +305,7 @@ func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, u
return return
} }
func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateVmess(vmessConfig string, tunnel C.Tunnel) {
vmessMux.Lock() vmessMux.Lock()
defer vmessMux.Unlock() defer vmessMux.Unlock()
@ -344,7 +344,7 @@ func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<-
return return
} }
listener, err := sing_vmess.New(vsConfig, tcpIn, udpIn) listener, err := sing_vmess.New(vsConfig, tunnel)
if err != nil { if err != nil {
return return
} }
@ -357,7 +357,7 @@ func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<-
return return
} }
func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateTuic(config LC.TuicServer, tunnel C.Tunnel) {
tuicMux.Lock() tuicMux.Lock()
defer func() { defer func() {
LastTuicConf = config LastTuicConf = config
@ -389,7 +389,7 @@ func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<-
return return
} }
listener, err := tuic.New(config, tcpIn, udpIn) listener, err := tuic.New(config, tunnel)
if err != nil { if err != nil {
return return
} }
@ -402,7 +402,7 @@ func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<-
return return
} }
func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) { func ReCreateTProxy(port int, tunnel C.Tunnel) {
tproxyMux.Lock() tproxyMux.Lock()
defer tproxyMux.Unlock() defer tproxyMux.Unlock()
@ -435,12 +435,12 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketA
return return
} }
tproxyListener, err = tproxy.New(addr, tcpIn) tproxyListener, err = tproxy.New(addr, tunnel)
if err != nil { if err != nil {
return return
} }
tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) tproxyUDPListener, err = tproxy.NewUDP(addr, tunnel)
if err != nil { if err != nil {
log.Warnln("Failed to start TProxy UDP Listener: %s", err) log.Warnln("Failed to start TProxy UDP Listener: %s", err)
} }
@ -448,7 +448,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketA
log.Infoln("TProxy server listening at: %s", tproxyListener.Address()) log.Infoln("TProxy server listening at: %s", tproxyListener.Address())
} }
func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateMixed(port int, tunnel C.Tunnel) {
mixedMux.Lock() mixedMux.Lock()
defer mixedMux.Unlock() defer mixedMux.Unlock()
@ -489,12 +489,12 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
return return
} }
mixedListener, err = mixed.New(addr, tcpIn) mixedListener, err = mixed.New(addr, tunnel)
if err != nil { if err != nil {
return return
} }
mixedUDPLister, err = socks.NewUDP(addr, udpIn) mixedUDPLister, err = socks.NewUDP(addr, tunnel)
if err != nil { if err != nil {
mixedListener.Close() mixedListener.Close()
return return
@ -503,7 +503,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address()) log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
} }
func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func ReCreateTun(tunConf LC.Tun, tunnel C.Tunnel) {
tunMux.Lock() tunMux.Lock()
defer func() { defer func() {
LastTunConf = tunConf LastTunConf = tunConf
@ -531,7 +531,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack
return return
} }
lister, err := sing_tun.New(tunConf, tcpIn, udpIn) lister, err := sing_tun.New(tunConf, tunnel)
if err != nil { if err != nil {
return return
} }
@ -573,7 +573,7 @@ func ReCreateRedirToTun(ifaceNames []string) {
log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs()) log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs())
} }
func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- C.PacketAdapter) { func ReCreateAutoRedir(ifaceNames []string, tunnel C.Tunnel) {
autoRedirMux.Lock() autoRedirMux.Lock()
defer autoRedirMux.Unlock() defer autoRedirMux.Unlock()
@ -614,7 +614,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<-
addr := genAddr("*", C.TcpAutoRedirPort, true) addr := genAddr("*", C.TcpAutoRedirPort, true)
autoRedirListener, err = autoredir.New(addr, tcpIn) autoRedirListener, err = autoredir.New(addr, tunnel)
if err != nil { if err != nil {
return return
} }
@ -629,7 +629,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<-
log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs()) log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs())
} }
func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { func PatchTunnel(tunnels []LC.Tunnel, tunnel C.Tunnel) {
tunnelMux.Lock() tunnelMux.Lock()
defer tunnelMux.Unlock() defer tunnelMux.Unlock()
@ -699,7 +699,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C
for _, elm := range needCreate { for _, elm := range needCreate {
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy) key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
if elm.network == "tcp" { if elm.network == "tcp" {
l, err := tunnel.New(elm.addr, elm.target, elm.proxy, tcpIn) l, err := LT.New(elm.addr, elm.target, elm.proxy, tunnel)
if err != nil { if err != nil {
log.Errorln("Start tunnel %s error: %s", elm.target, err.Error()) log.Errorln("Start tunnel %s error: %s", elm.target, err.Error())
continue continue
@ -707,7 +707,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C
tunnelTCPListeners[key] = l tunnelTCPListeners[key] = l
log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address()) log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address())
} else { } else {
l, err := tunnel.NewUDP(elm.addr, elm.target, elm.proxy, udpIn) l, err := LT.NewUDP(elm.addr, elm.target, elm.proxy, tunnel)
if err != nil { if err != nil {
log.Errorln("Start tunnel %s error: %s", elm.target, err.Error()) log.Errorln("Start tunnel %s error: %s", elm.target, err.Error())
continue continue
@ -718,7 +718,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C
} }
} }
func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable, dropOld bool) { func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tunnel C.Tunnel, dropOld bool) {
inboundMux.Lock() inboundMux.Lock()
defer inboundMux.Unlock() defer inboundMux.Unlock()
@ -730,7 +730,7 @@ func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn ch
continue continue
} }
} }
if err := newListener.Listen(tcpIn, udpIn, natTable); err != nil { if err := newListener.Listen(tunnel); err != nil {
log.Errorln("Listener %s listen err: %s", name, err.Error()) log.Errorln("Listener %s listen err: %s", name, err.Error())
continue continue
} }
@ -834,19 +834,27 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
}) })
sort.Slice(tunConf.Inet4Address, func(i, j int) bool { sort.Slice(tunConf.Inet4Address, func(i, j int) bool {
return tunConf.Inet4Address[i].Build().String() < tunConf.Inet4Address[j].Build().String() return tunConf.Inet4Address[i].String() < tunConf.Inet4Address[j].String()
}) })
sort.Slice(tunConf.Inet6Address, func(i, j int) bool { sort.Slice(tunConf.Inet6Address, func(i, j int) bool {
return tunConf.Inet6Address[i].Build().String() < tunConf.Inet6Address[j].Build().String() return tunConf.Inet6Address[i].String() < tunConf.Inet6Address[j].String()
}) })
sort.Slice(tunConf.Inet4RouteAddress, func(i, j int) bool { sort.Slice(tunConf.Inet4RouteAddress, func(i, j int) bool {
return tunConf.Inet4RouteAddress[i].Build().String() < tunConf.Inet4RouteAddress[j].Build().String() return tunConf.Inet4RouteAddress[i].String() < tunConf.Inet4RouteAddress[j].String()
}) })
sort.Slice(tunConf.Inet6RouteAddress, func(i, j int) bool { sort.Slice(tunConf.Inet6RouteAddress, func(i, j int) bool {
return tunConf.Inet6RouteAddress[i].Build().String() < tunConf.Inet6RouteAddress[j].Build().String() return tunConf.Inet6RouteAddress[i].String() < tunConf.Inet6RouteAddress[j].String()
})
sort.Slice(tunConf.Inet4RouteExcludeAddress, func(i, j int) bool {
return tunConf.Inet4RouteExcludeAddress[i].String() < tunConf.Inet4RouteExcludeAddress[j].String()
})
sort.Slice(tunConf.Inet6RouteExcludeAddress, func(i, j int) bool {
return tunConf.Inet6RouteExcludeAddress[i].String() < tunConf.Inet6RouteExcludeAddress[j].String()
}) })
sort.Slice(tunConf.IncludeUID, func(i, j int) bool { sort.Slice(tunConf.IncludeUID, func(i, j int) bool {
@ -882,6 +890,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) || !slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) || !slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
!slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) || !slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) ||
!slices.Equal(tunConf.Inet4RouteExcludeAddress, LastTunConf.Inet4RouteExcludeAddress) ||
!slices.Equal(tunConf.Inet6RouteExcludeAddress, LastTunConf.Inet6RouteExcludeAddress) ||
!slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) || !slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) ||
!slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) || !slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) || !slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||

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