Compare commits
83 Commits
Meta
...
android-re
Author | SHA1 | Date | |
---|---|---|---|
![]() |
807d41f57b | ||
![]() |
8c3557e96b | ||
![]() |
228990472d | ||
![]() |
09e7866a5c | ||
![]() |
665ba7f9f1 | ||
![]() |
ee3038d5e4 | ||
![]() |
885ee7a820 | ||
![]() |
ef303b11f2 | ||
![]() |
a82ce85707 | ||
![]() |
5bfe7ba169 | ||
![]() |
ceac5bfaa4 | ||
![]() |
b0638cfc49 | ||
![]() |
96220aa8ea | ||
![]() |
8ff476a3a1 | ||
![]() |
261b6e8dce | ||
![]() |
2b9141e0e5 | ||
![]() |
55255faa52 | ||
![]() |
d42e3f74ad | ||
![]() |
81a8a63861 | ||
![]() |
c3a61e2db5 | ||
![]() |
bffe47a974 | ||
![]() |
4314b37d04 | ||
![]() |
cf93f69f40 | ||
![]() |
55f626424f | ||
![]() |
431d52f250 | ||
![]() |
c1f24d8f0e | ||
![]() |
fc5a3cf80c | ||
![]() |
e1e999180a | ||
![]() |
8755618910 | ||
![]() |
aede97571f | ||
![]() |
01bc84db02 | ||
![]() |
3564e96a00 | ||
![]() |
f6f8f27668 | ||
![]() |
dff54464c6 | ||
![]() |
e987cdaaae | ||
![]() |
6cd0e58fd0 | ||
![]() |
f794c090a5 | ||
![]() |
0d3197e437 | ||
![]() |
150bf7fc65 | ||
![]() |
51004b14d9 | ||
![]() |
ea7e15b447 | ||
![]() |
8e637a2ec7 | ||
![]() |
96d886380a | ||
![]() |
981c69040f | ||
![]() |
de90c276af | ||
![]() |
0129a8579f | ||
![]() |
11ed4a56bd | ||
![]() |
d75a0e69a0 | ||
![]() |
1faad73381 | ||
![]() |
d2499cd69d | ||
![]() |
98df77439c | ||
![]() |
81bbbe4eec | ||
![]() |
9f530525d7 | ||
![]() |
129283066f | ||
![]() |
0dc6a726c1 | ||
![]() |
4636499439 | ||
![]() |
9a16eb2895 | ||
![]() |
270a080b55 | ||
![]() |
1cf9a55e3e | ||
![]() |
6bcd91a801 | ||
![]() |
7ed25ddc74 | ||
![]() |
ae557c30d3 | ||
![]() |
5a1800d642 | ||
![]() |
d8fe7a52d6 | ||
![]() |
791ecfbb32 | ||
![]() |
5ff4473083 | ||
![]() |
d1e88a30cb | ||
![]() |
7eae7756f5 | ||
![]() |
4e3cd01aad | ||
![]() |
dbaee284e4 | ||
![]() |
8253bfe2e0 | ||
![]() |
828b5ad8bb | ||
![]() |
fedad26c13 | ||
![]() |
a526bb70ea | ||
![]() |
5f6de610e1 | ||
![]() |
02397868fc | ||
![]() |
265a6b9b68 | ||
![]() |
10e7c533d7 | ||
![]() |
0ed3c5a5ec | ||
![]() |
c2b06a02bf | ||
![]() |
e0458a8fde | ||
![]() |
21fb5f75b8 | ||
![]() |
fb99412193 |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,6 +1,7 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
@ -79,4 +80,4 @@ Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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.
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,6 +1,7 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
|
32
.github/genReleaseNote.sh
vendored
32
.github/genReleaseNote.sh
vendored
@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
while getopts "v:" opt; do
|
||||
case $opt in
|
||||
v)
|
||||
version_range=$OPTARG
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$version_range" ]; then
|
||||
echo "Please provide the version range using -v option. Example: ./genReleashNote.sh -v v1.14.1...v1.14.2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "## What's Changed" > release.md
|
||||
git log --pretty=format:"* %s by @%an" --grep="^feat" -i $version_range | sort -f | uniq >> release.md
|
||||
echo "" >> release.md
|
||||
|
||||
echo "## BUG & Fix" >> release.md
|
||||
git log --pretty=format:"* %s by @%an" --grep="^fix" -i $version_range | sort -f | uniq >> release.md
|
||||
echo "" >> release.md
|
||||
|
||||
echo "## Maintenance" >> release.md
|
||||
git log --pretty=format:"* %s by @%an" --grep="^chore\|^docs\|^refactor" -i $version_range | sort -f | uniq >> release.md
|
||||
echo "" >> release.md
|
||||
|
||||
echo "**Full Changelog**: https://github.com/MetaCubeX/Clash.Meta/compare/$version_range" >> release.md
|
12
.github/rename-go120.sh
vendored
Normal file
12
.github/rename-go120.sh
vendored
Normal 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
|
69
.github/workflows/android-branch-auto-sync.yml
vendored
Normal file
69
.github/workflows/android-branch-auto-sync.yml
vendored
Normal 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"}'
|
52
.github/workflows/build.yml
vendored
52
.github/workflows/build.yml
vendored
@ -69,6 +69,12 @@ jobs:
|
||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
||||
id: "9",
|
||||
}
|
||||
# only for test
|
||||
- { type: "WithoutCGO-GO120", target: "linux-amd64 linux-amd64-compatible",id: "1" }
|
||||
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
|
||||
- { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" }
|
||||
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
|
||||
- { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" }
|
||||
- { type: "WithCGO", target: "windows/*", id: "1" }
|
||||
- { type: "WithCGO", target: "linux/386", id: "2" }
|
||||
- { type: "WithCGO", target: "linux/amd64", id: "3" }
|
||||
@ -94,6 +100,11 @@ jobs:
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Beta'}}
|
||||
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Meta'}}
|
||||
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
@ -121,18 +132,26 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Setup Go
|
||||
if: ${{ matrix.job.type!='WithoutCGO-GO120' }}
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
check-latest: true
|
||||
|
||||
- name: Setup Go
|
||||
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20"
|
||||
check-latest: true
|
||||
|
||||
- name: Test
|
||||
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
|
||||
if: ${{ matrix.job.id=='1' && matrix.job.type!='WithCGO' }}
|
||||
run: |
|
||||
go test ./...
|
||||
|
||||
- name: Build WithoutCGO
|
||||
if: ${{ matrix.job.type=='WithoutCGO' }}
|
||||
if: ${{ matrix.job.type!='WithCGO' }}
|
||||
env:
|
||||
NAME: Clash.Meta
|
||||
BINDIR: bin
|
||||
@ -180,6 +199,17 @@ jobs:
|
||||
ls -la
|
||||
cd ..
|
||||
|
||||
- name: Rename
|
||||
if: ${{ matrix.job.type=='WithoutCGO-GO120' }}
|
||||
run: |
|
||||
cd bin
|
||||
ls -la
|
||||
cp ../.github/rename-go120.sh ./
|
||||
bash ./rename-go120.sh
|
||||
rm ./rename-go120.sh
|
||||
ls -la
|
||||
cd ..
|
||||
|
||||
- name: Zip
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
@ -262,23 +292,6 @@ jobs:
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
@ -295,7 +308,6 @@ jobs:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: bin/*
|
||||
generate_release_notes: true
|
||||
body_path: release.md
|
||||
|
||||
Docker:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
|
15
.github/workflows/delete.yml
vendored
15
.github/workflows/delete.yml
vendored
@ -1,15 +0,0 @@
|
||||
name: Delete old workflow runs
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * SUN"
|
||||
|
||||
jobs:
|
||||
del_runs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete workflow runs
|
||||
uses: GitRML/delete-workflow-runs@main
|
||||
with:
|
||||
token: ${{ secrets.AUTH_PAT }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: 30
|
302
README.md
302
README.md
@ -21,7 +21,7 @@
|
||||
## Features
|
||||
|
||||
- Local HTTP/HTTPS/SOCKS server with authentication support
|
||||
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
|
||||
- VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support
|
||||
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
||||
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
|
||||
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
|
||||
@ -32,259 +32,41 @@
|
||||
|
||||
## Dashboard
|
||||
|
||||
We made an official web dashboard providing first class support for this project, check it out
|
||||
at [metacubexd](https://github.com/MetaCubeX/metacubexd)
|
||||
A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd).
|
||||
|
||||
## Wiki
|
||||
## Configration example
|
||||
|
||||
Configuration examples can be found
|
||||
at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be
|
||||
found [Clash.Meta Wiki](https://clash-meta.wiki).
|
||||
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
|
||||
|
||||
## Build
|
||||
## Docs
|
||||
|
||||
You should install [golang](https://go.dev) first.
|
||||
Documentation can be found in [Clash.Meta Docs](https://clash-meta.wiki).
|
||||
|
||||
Then get the source code of Clash.Meta:
|
||||
## For development
|
||||
|
||||
Requirements:
|
||||
[Go 1.20 or newer](https://go.dev/dl/)
|
||||
|
||||
Build Clash.Meta:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/MetaCubeX/Clash.Meta.git
|
||||
cd Clash.Meta && go mod download
|
||||
go build
|
||||
```
|
||||
|
||||
If you can't visit GitHub, you should set proxy first:
|
||||
Set go proxy if a connection to GitHub is not possible:
|
||||
|
||||
```shell
|
||||
go env -w GOPROXY=https://goproxy.io,direct
|
||||
```
|
||||
|
||||
Now you can build it:
|
||||
|
||||
```shell
|
||||
go build
|
||||
```
|
||||
|
||||
If you need gvisor for tun stack, build with:
|
||||
Build with gvisor tun stack:
|
||||
|
||||
```shell
|
||||
go build -tags with_gvisor
|
||||
```
|
||||
|
||||
<!-- ## Advanced usage of this fork -->
|
||||
|
||||
<!-- ### DNS configuration
|
||||
|
||||
Support `geosite` with `fallback-filter`.
|
||||
|
||||
Restore `Redir remote resolution`.
|
||||
|
||||
Support resolve ip with a `Proxy Tunnel`.
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: DNS
|
||||
type: url-test
|
||||
use:
|
||||
- HK
|
||||
url: http://cp.cloudflare.com
|
||||
interval: 180
|
||||
lazy: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
dns:
|
||||
enable: true
|
||||
use-hosts: true
|
||||
ipv6: false
|
||||
enhanced-mode: redir-host
|
||||
fake-ip-range: 198.18.0.1/16
|
||||
listen: 127.0.0.1:6868
|
||||
default-nameserver:
|
||||
- 119.29.29.29
|
||||
- 114.114.114.114
|
||||
nameserver:
|
||||
- https://doh.pub/dns-query
|
||||
- tls://223.5.5.5:853
|
||||
fallback:
|
||||
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
|
||||
- "tls://8.8.4.4:853#DNS"
|
||||
fallback-filter:
|
||||
geoip: false
|
||||
geosite:
|
||||
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
|
||||
domain:
|
||||
- +.example.com
|
||||
ipcidr:
|
||||
- 0.0.0.0/32
|
||||
```
|
||||
|
||||
### TUN configuration
|
||||
|
||||
Supports macOS, Linux and Windows.
|
||||
|
||||
Built-in [Wintun](https://www.wintun.net) driver.
|
||||
|
||||
```yaml
|
||||
# Enable the TUN listener
|
||||
tun:
|
||||
enable: true
|
||||
stack: system # system/gvisor
|
||||
dns-hijack:
|
||||
- 0.0.0.0:53 # additional dns server listen on TUN
|
||||
auto-route: true # auto set global route
|
||||
```
|
||||
|
||||
### Rules configuration
|
||||
|
||||
- Support rule `GEOSITE`.
|
||||
- Support rule-providers `RULE-SET`.
|
||||
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
|
||||
- Support `network` condition for all rules.
|
||||
- Support source IPCIDR condition for all rules, just append to the end.
|
||||
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
# network(tcp/udp) condition for all rules
|
||||
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
|
||||
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
|
||||
|
||||
# multiport condition for rules SRC-PORT and DST-PORT
|
||||
- DST-PORT,123/136/137-139,DIRECT,udp
|
||||
|
||||
# rule GEOSITE
|
||||
- GEOSITE,category-ads-all,REJECT
|
||||
- GEOSITE,icloud@cn,DIRECT
|
||||
- GEOSITE,apple@cn,DIRECT
|
||||
- GEOSITE,apple-cn,DIRECT
|
||||
- GEOSITE,microsoft@cn,DIRECT
|
||||
- GEOSITE,facebook,PROXY
|
||||
- GEOSITE,youtube,PROXY
|
||||
- GEOSITE,geolocation-cn,DIRECT
|
||||
- GEOSITE,geolocation-!cn,PROXY
|
||||
|
||||
# source IPCIDR condition for all rules in gateway proxy
|
||||
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
|
||||
|
||||
- GEOIP,telegram,PROXY,no-resolve
|
||||
- GEOIP,private,DIRECT,no-resolve
|
||||
- GEOIP,cn,DIRECT
|
||||
|
||||
- MATCH,PROXY
|
||||
```
|
||||
|
||||
### Proxies configuration
|
||||
|
||||
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
|
||||
|
||||
Support `Policy Group Filter`
|
||||
|
||||
```yaml
|
||||
proxy-groups:
|
||||
- name: 🚀 HK Group
|
||||
type: select
|
||||
use:
|
||||
- ALL
|
||||
filter: "HK"
|
||||
|
||||
- name: 🚀 US Group
|
||||
type: select
|
||||
use:
|
||||
- ALL
|
||||
filter: "US"
|
||||
|
||||
proxy-providers:
|
||||
ALL:
|
||||
type: http
|
||||
url: "xxxxx"
|
||||
interval: 3600
|
||||
path: "xxxxx"
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
url: http://www.gstatic.com/generate_204
|
||||
```
|
||||
|
||||
Support outbound transport protocol `VLESS`.
|
||||
|
||||
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
|
||||
|
||||
```yaml
|
||||
proxies:
|
||||
- name: "vless"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
servername: example.com # AKA SNI
|
||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
tls: true
|
||||
udp: true
|
||||
network: ws
|
||||
servername: example.com # priority over wss host
|
||||
# skip-cert-verify: true
|
||||
ws-opts:
|
||||
path: /path
|
||||
headers: { Host: example.com, Edge: "12a00c4.fm.huawei.com:82897" }
|
||||
|
||||
- name: "vless-grpc"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
tls: true
|
||||
udp: true
|
||||
network: grpc
|
||||
servername: example.com # priority over wss host
|
||||
# skip-cert-verify: true
|
||||
grpc-opts:
|
||||
grpc-service-name: grpcname
|
||||
```
|
||||
|
||||
Support outbound transport protocol `Wireguard`
|
||||
|
||||
```yaml
|
||||
proxies:
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
server: 162.159.192.1
|
||||
port: 2480
|
||||
ip: 172.16.0.2
|
||||
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
|
||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||
udp: true
|
||||
```
|
||||
|
||||
Support outbound transport protocol `Tuic`
|
||||
|
||||
```yaml
|
||||
proxies:
|
||||
- name: "tuic"
|
||||
server: www.example.com
|
||||
port: 10443
|
||||
type: tuic
|
||||
token: TOKEN
|
||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||
# heartbeat-interval: 10000
|
||||
# alpn: [h3]
|
||||
# disable-sni: true
|
||||
reduce-rtt: true
|
||||
# request-timeout: 8000
|
||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||
# max-udp-relay-packet-size: 1500
|
||||
# fast-open: true
|
||||
# skip-cert-verify: true
|
||||
``` -->
|
||||
|
||||
### IPTABLES configuration
|
||||
|
||||
Work on Linux OS which supported `iptables`
|
||||
@ -298,61 +80,9 @@ iptables:
|
||||
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
|
||||
```
|
||||
|
||||
### General installation guide for Linux
|
||||
|
||||
- Create user given name `clash-meta`
|
||||
|
||||
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
|
||||
|
||||
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
|
||||
|
||||
- Create folder `/etc/Clash-Meta/` as working directory
|
||||
|
||||
Run Meta Kernel by user `clash-meta` as a daemon.
|
||||
|
||||
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Clash-Meta Daemon, Another Clash Kernel.
|
||||
After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=clash-meta
|
||||
Group=clash-meta
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 1s
|
||||
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Launch clash-meta daemon on system startup with:
|
||||
|
||||
```shell
|
||||
$ systemctl enable Clash-Meta
|
||||
```
|
||||
|
||||
Launch clash-meta daemon immediately with:
|
||||
|
||||
```shell
|
||||
$ systemctl start Clash-Meta
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
If you want to build an application that uses clash as a library, check out
|
||||
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
||||
## Debugging
|
||||
|
||||
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug
|
||||
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
|
||||
API.
|
||||
|
||||
## Credits
|
||||
|
@ -30,13 +30,13 @@ const (
|
||||
|
||||
type extraProxyState struct {
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
alive atomic.Bool
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
alive atomic.Bool
|
||||
url string
|
||||
extra *xsync.MapOf[string, *extraProxyState]
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Addition func(metadata *C.Metadata)
|
||||
|
||||
func (a Addition) Apply(metadata *C.Metadata) {
|
||||
a(metadata)
|
||||
func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
|
||||
for _, addition := range additions {
|
||||
addition(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func WithInName(name string) Addition {
|
||||
@ -33,3 +37,29 @@ func WithSpecialProxy(specialProxy string) Addition {
|
||||
metadata.SpecialProxy = specialProxy
|
||||
}
|
||||
}
|
||||
|
||||
func WithDstAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
_ = metadata.SetRemoteAddr(addr)
|
||||
}
|
||||
}
|
||||
|
||||
func WithSrcAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
metadata.SrcIP = m.DstIP
|
||||
metadata.SrcPort = m.DstPort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithInAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
metadata.InIP = m.DstIP
|
||||
metadata.InPort = m.DstPort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
adapter/inbound/auth.go
Normal file
45
adapter/inbound/auth.go
Normal 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
|
||||
}
|
@ -4,25 +4,17 @@ import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewHTTP receive normal http request and return HTTPContext
|
||||
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext {
|
||||
func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.HTTP
|
||||
for _, addition := range additions {
|
||||
addition.Apply(metadata)
|
||||
}
|
||||
if ip, port, err := parseAddr(source); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
||||
metadata.InIP = ip
|
||||
metadata.InPort = port
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
metadata.RawSrcAddr = srcConn.RemoteAddr()
|
||||
metadata.RawDstAddr = srcConn.LocalAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
}
|
||||
|
@ -5,23 +5,13 @@ import (
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
)
|
||||
|
||||
// NewHTTPS receive CONNECT request and return ConnContext
|
||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext {
|
||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPS
|
||||
for _, addition := range additions {
|
||||
addition.Apply(metadata)
|
||||
}
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
||||
metadata.InIP = ip
|
||||
metadata.InPort = port
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
}
|
||||
|
@ -5,38 +5,18 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
||||
type PacketAdapter struct {
|
||||
C.UDPPacket
|
||||
metadata *C.Metadata
|
||||
}
|
||||
|
||||
// Metadata returns destination metadata
|
||||
func (s *PacketAdapter) Metadata() *C.Metadata {
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
// NewPacket is PacketAdapter generator
|
||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter {
|
||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.UDP
|
||||
metadata.Type = source
|
||||
for _, addition := range additions {
|
||||
addition.Apply(metadata)
|
||||
}
|
||||
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
metadata.RawSrcAddr = packet.LocalAddr()
|
||||
metadata.RawDstAddr = metadata.UDPAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
|
||||
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
||||
if ip, port, err := parseAddr(p.InAddr()); err == nil {
|
||||
metadata.InIP = ip
|
||||
metadata.InPort = port
|
||||
}
|
||||
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
|
||||
}
|
||||
ApplyAdditions(metadata, additions...)
|
||||
|
||||
return &PacketAdapter{
|
||||
packet,
|
||||
metadata,
|
||||
}
|
||||
return packet, metadata
|
||||
}
|
||||
|
@ -2,51 +2,17 @@ package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewSocket receive TCP inbound and return ConnContext
|
||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext {
|
||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = source
|
||||
for _, addition := range additions {
|
||||
addition.Apply(metadata)
|
||||
}
|
||||
|
||||
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
||||
metadata.InIP = ip
|
||||
metadata.InPort = port
|
||||
}
|
||||
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
||||
|
||||
func NewInner(conn net.Conn, address string) *context.ConnContext {
|
||||
metadata := &C.Metadata{}
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.INNER
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
metadata.Process = C.ClashName
|
||||
if h, port, err := net.SplitHostPort(address); err == nil {
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
metadata.DstPort = uint16(port)
|
||||
}
|
||||
if ip, err := netip.ParseAddr(h); err == nil {
|
||||
metadata.DstIP = ip
|
||||
} else {
|
||||
metadata.Host = h
|
||||
}
|
||||
}
|
||||
|
||||
return context.NewConnContext(conn, metadata)
|
||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@ -62,29 +61,3 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
|
||||
// Filter when net.Addr interface is nil
|
||||
if addr == nil {
|
||||
return netip.Addr{}, 0, errors.New("nil addr")
|
||||
}
|
||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
||||
ip, port, err := parseAddr(rawAddr.RawAddr())
|
||||
if err == nil {
|
||||
return ip, port, err
|
||||
}
|
||||
}
|
||||
addrStr := addr.String()
|
||||
host, port, err := net.SplitHostPort(addrStr)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, err
|
||||
}
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
return ip, uint16Port, err
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ type Hysteria struct {
|
||||
}
|
||||
|
||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...))
|
||||
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ type Hysteria2Option struct {
|
||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := h.Base.DialOptions(opts...)
|
||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ type Reject struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
type RejectOption struct {
|
||||
Name string `proxy:"name"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
return NewConn(nopConn{}, r), nil
|
||||
@ -25,6 +29,16 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
return newPacketConn(nopPacketConn{}, r), nil
|
||||
}
|
||||
|
||||
func NewRejectWithOption(option RejectOption) *Reject {
|
||||
return &Reject{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Direct,
|
||||
udp: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewReject() *Reject {
|
||||
return &Reject{
|
||||
Base: &Base{
|
||||
|
@ -58,14 +58,15 @@ type simpleObfsOption struct {
|
||||
}
|
||||
|
||||
type v2rayObfsOption struct {
|
||||
Mode string `obfs:"mode"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
Mode string `obfs:"mode"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"`
|
||||
}
|
||||
|
||||
type shadowTLSOption struct {
|
||||
@ -123,9 +124,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||
}
|
||||
}
|
||||
if useEarly {
|
||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil
|
||||
} else {
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,10 +260,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
obfsMode = opts.Mode
|
||||
v2rayOption = &v2rayObfs.Option{
|
||||
Host: opts.Host,
|
||||
Path: opts.Path,
|
||||
Headers: opts.Headers,
|
||||
Mux: opts.Mux,
|
||||
Host: opts.Host,
|
||||
Path: opts.Path,
|
||||
Headers: opts.Headers,
|
||||
Mux: opts.Mux,
|
||||
V2rayHttpUpgrade: opts.V2rayHttpUpgrade,
|
||||
}
|
||||
|
||||
if opts.TLS {
|
||||
|
@ -42,7 +42,7 @@ type ProxyBase interface {
|
||||
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := s.base.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
@ -136,7 +137,8 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
||||
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
||||
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("client hanshake error: %w", err)
|
||||
return
|
||||
|
@ -53,9 +53,10 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
|
||||
if t.option.Network == "ws" {
|
||||
host, port, _ := net.SplitHostPort(t.addr)
|
||||
wsOpts := &trojan.WebsocketOption{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: t.option.WSOpts.Path,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: t.option.WSOpts.Path,
|
||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||
}
|
||||
|
||||
if t.option.SNI != "" {
|
||||
|
@ -93,6 +93,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Path: v.option.WSOpts.Path,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ type WSOptions struct {
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
MaxEarlyData int `proxy:"max-early-data,omitempty"`
|
||||
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
|
||||
V2rayHttpUpgrade bool `proxy:"v2ray-http-upgrade,omitempty"`
|
||||
}
|
||||
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
@ -110,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Path: v.option.WSOpts.Path,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
@ -260,10 +262,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
} else {
|
||||
conn, err = v.client.DialConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@ -284,7 +286,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ type GroupBase struct {
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
failedTime time.Time
|
||||
failedTesting *atomic.Bool
|
||||
failedTesting atomic.Bool
|
||||
proxies [][]C.Proxy
|
||||
versions []atomic.Uint32
|
||||
}
|
||||
|
62
adapter/outboundgroup/patch.go
Normal file
62
adapter/outboundgroup/patch.go
Normal file
@ -0,0 +1,62 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type ProxyGroup interface {
|
||||
C.ProxyAdapter
|
||||
|
||||
Providers() []provider.ProxyProvider
|
||||
Proxies() []C.Proxy
|
||||
Now() string
|
||||
}
|
||||
|
||||
func (f *Fallback) Providers() []provider.ProxyProvider {
|
||||
return f.providers
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
|
||||
return lb.providers
|
||||
}
|
||||
|
||||
func (f *Fallback) Proxies() []C.Proxy {
|
||||
return f.GetProxies(false)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Proxies() []C.Proxy {
|
||||
return lb.GetProxies(false)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) Now() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Relay) Providers() []provider.ProxyProvider {
|
||||
return r.providers
|
||||
}
|
||||
|
||||
func (r *Relay) Proxies() []C.Proxy {
|
||||
return r.GetProxies(false)
|
||||
}
|
||||
|
||||
func (r *Relay) Now() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Selector) Providers() []provider.ProxyProvider {
|
||||
return s.providers
|
||||
}
|
||||
|
||||
func (s *Selector) Proxies() []C.Proxy {
|
||||
return s.GetProxies(false)
|
||||
}
|
||||
|
||||
func (u *URLTest) Providers() []provider.ProxyProvider {
|
||||
return u.providers
|
||||
}
|
||||
|
||||
func (u *URLTest) Proxies() []C.Proxy {
|
||||
return u.GetProxies(false)
|
||||
}
|
@ -120,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewDirectWithOption(*directOption)
|
||||
case "reject":
|
||||
rejectOption := &outbound.RejectOption{}
|
||||
err = decoder.Decode(mapping, rejectOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewRejectWithOption(*rejectOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
|
||||
const (
|
||||
defaultURLTestTimeout = time.Second * 5
|
||||
defaultURLTestURL = "https://www.gstatic.com/generate_204"
|
||||
)
|
||||
|
||||
type HealthCheckOption struct {
|
||||
@ -34,12 +35,12 @@ type HealthCheck struct {
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
started *atomic.Bool
|
||||
started atomic.Bool
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
interval time.Duration
|
||||
lazy bool
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
lastTouch *atomic.Int64
|
||||
lastTouch atomic.TypedValue[time.Time]
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
}
|
||||
@ -50,13 +51,14 @@ func (hc *HealthCheck) process() {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||
ticker := time.NewTicker(hc.interval)
|
||||
hc.start()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
lastTouch := hc.lastTouch.Load()
|
||||
since := time.Since(lastTouch)
|
||||
if !hc.lazy || since < hc.interval {
|
||||
hc.check()
|
||||
} else {
|
||||
log.Debugln("Skip once health check because we are lazy")
|
||||
@ -85,7 +87,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
|
||||
|
||||
// if the provider has not set up health checks, then modify it to be the same as the group's interval
|
||||
if hc.interval == 0 {
|
||||
hc.interval = interval
|
||||
hc.interval = time.Duration(interval) * time.Second
|
||||
}
|
||||
|
||||
if hc.extra == nil {
|
||||
@ -135,7 +137,7 @@ func (hc *HealthCheck) auto() bool {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
hc.lastTouch.Store(time.Now())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) start() {
|
||||
@ -147,6 +149,11 @@ func (hc *HealthCheck) stop() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
|
||||
if len(hc.proxies) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := utils.NewUUIDV4().String()
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
@ -222,17 +229,16 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
|
||||
if len(url) == 0 {
|
||||
interval = 0
|
||||
expectedStatus = nil
|
||||
url = defaultURLTestURL
|
||||
}
|
||||
|
||||
return &HealthCheck{
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
extra: map[string]*extraOption{},
|
||||
started: atomic.NewBool(false),
|
||||
interval: interval,
|
||||
interval: time.Duration(interval) * time.Second,
|
||||
lazy: lazy,
|
||||
expectedStatus: expectedStatus,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
}
|
||||
|
@ -68,9 +68,6 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
case "http":
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
|
34
adapter/provider/patch.go
Normal file
34
adapter/provider/patch.go
Normal file
@ -0,0 +1,34 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
suspended bool
|
||||
)
|
||||
|
||||
type UpdatableProvider interface {
|
||||
UpdatedAt() time.Time
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) UpdatedAt() time.Time {
|
||||
return pp.Fetcher.UpdatedAt
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Close() error {
|
||||
pp.healthCheck.close()
|
||||
pp.Fetcher.Destroy()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Close() error {
|
||||
cp.healthCheck.close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Suspend(s bool) {
|
||||
suspended = s
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/dlclark/regexp2"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -13,45 +12,24 @@ type SubscriptionInfo struct {
|
||||
Expire int64
|
||||
}
|
||||
|
||||
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
|
||||
si = &SubscriptionInfo{}
|
||||
str = strings.ToLower(str)
|
||||
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
|
||||
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
|
||||
|
||||
match, err := reTraffic.FindStringMatch(str)
|
||||
if err != nil || match == nil {
|
||||
return nil, err
|
||||
}
|
||||
group := match.Groups()
|
||||
si.Upload, err = str2uint64(group[1].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
si.Download, err = str2uint64(group[2].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
si.Total, err = str2uint64(group[3].String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match, _ = reExpire.FindStringMatch(str)
|
||||
if match != nil {
|
||||
group = match.Groups()
|
||||
si.Expire, err = str2uint64(group[1].String())
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
|
||||
userinfo = strings.ToLower(userinfo)
|
||||
userinfo = strings.ReplaceAll(userinfo, " ", "")
|
||||
si = new(SubscriptionInfo)
|
||||
for _, field := range strings.Split(userinfo, ";") {
|
||||
switch name, value, _ := strings.Cut(field, "="); name {
|
||||
case "upload":
|
||||
si.Upload, err = strconv.ParseInt(value, 10, 64)
|
||||
case "download":
|
||||
si.Download, err = strconv.ParseInt(value, 10, 64)
|
||||
case "total":
|
||||
si.Total, err = strconv.ParseInt(value, 10, 64)
|
||||
case "expire":
|
||||
si.Expire, err = strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func str2uint64(str string) (int64, error) {
|
||||
i, err := strconv.ParseInt(str, 10, 64)
|
||||
return i, err
|
||||
}
|
||||
|
@ -11,10 +11,9 @@ type Bool struct {
|
||||
atomic.Bool
|
||||
}
|
||||
|
||||
func NewBool(val bool) *Bool {
|
||||
i := &Bool{}
|
||||
func NewBool(val bool) (i Bool) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Bool) MarshalJSON() ([]byte, error) {
|
||||
@ -39,12 +38,11 @@ type Pointer[T any] struct {
|
||||
atomic.Pointer[T]
|
||||
}
|
||||
|
||||
func NewPointer[T any](v *T) *Pointer[T] {
|
||||
var p Pointer[T]
|
||||
func NewPointer[T any](v *T) (p Pointer[T]) {
|
||||
if v != nil {
|
||||
p.Store(v)
|
||||
}
|
||||
return &p
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
|
||||
@ -68,10 +66,9 @@ type Int32 struct {
|
||||
atomic.Int32
|
||||
}
|
||||
|
||||
func NewInt32(val int32) *Int32 {
|
||||
i := &Int32{}
|
||||
func NewInt32(val int32) (i Int32) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Int32) MarshalJSON() ([]byte, error) {
|
||||
@ -96,10 +93,9 @@ type Int64 struct {
|
||||
atomic.Int64
|
||||
}
|
||||
|
||||
func NewInt64(val int64) *Int64 {
|
||||
i := &Int64{}
|
||||
func NewInt64(val int64) (i Int64) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Int64) MarshalJSON() ([]byte, error) {
|
||||
@ -124,10 +120,9 @@ type Uint32 struct {
|
||||
atomic.Uint32
|
||||
}
|
||||
|
||||
func NewUint32(val uint32) *Uint32 {
|
||||
i := &Uint32{}
|
||||
func NewUint32(val uint32) (i Uint32) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Uint32) MarshalJSON() ([]byte, error) {
|
||||
@ -152,10 +147,9 @@ type Uint64 struct {
|
||||
atomic.Uint64
|
||||
}
|
||||
|
||||
func NewUint64(val uint64) *Uint64 {
|
||||
i := &Uint64{}
|
||||
func NewUint64(val uint64) (i Uint64) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Uint64) MarshalJSON() ([]byte, error) {
|
||||
@ -180,10 +174,9 @@ type Uintptr struct {
|
||||
atomic.Uintptr
|
||||
}
|
||||
|
||||
func NewUintptr(val uintptr) *Uintptr {
|
||||
i := &Uintptr{}
|
||||
func NewUintptr(val uintptr) (i Uintptr) {
|
||||
i.Store(val)
|
||||
return i
|
||||
return
|
||||
}
|
||||
|
||||
func (i *Uintptr) MarshalJSON() ([]byte, error) {
|
||||
|
@ -12,6 +12,7 @@ func DefaultValue[T any]() T {
|
||||
|
||||
type TypedValue[T any] struct {
|
||||
value atomic.Value
|
||||
_ noCopy
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Load() T {
|
||||
@ -51,8 +52,13 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTypedValue[T any](t T) *TypedValue[T] {
|
||||
v := &TypedValue[T]{}
|
||||
func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
||||
v.Store(t)
|
||||
return v
|
||||
return
|
||||
}
|
||||
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
@ -10,6 +10,7 @@ const BufferSize = buf.BufferSize
|
||||
type Buffer = buf.Buffer
|
||||
|
||||
var New = buf.New
|
||||
var NewPacket = buf.NewPacket
|
||||
var NewSize = buf.NewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
|
@ -22,6 +22,16 @@ func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
||||
}
|
||||
|
||||
func WarpConnWithBioReader(c net.Conn, br *bufio.Reader) net.Conn {
|
||||
if br != nil && br.Buffered() > 0 {
|
||||
if bc, ok := c.(*BufferedConn); ok && bc.r == br {
|
||||
return bc
|
||||
}
|
||||
return &BufferedConn{br, NewExtendedConn(c), true}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
func (c *BufferedConn) Reader() *bufio.Reader {
|
||||
return c.r
|
||||
|
49
common/net/cached.go
Normal file
49
common/net/cached.go
Normal 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
31
common/net/context.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,11 @@ import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
||||
type Path interface {
|
||||
Resolve(path string) string
|
||||
}
|
||||
|
||||
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
||||
if certificate == "" && privateKey == "" {
|
||||
return newRandomTLSKeyPair()
|
||||
}
|
||||
@ -19,6 +23,8 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
certificate = path.Resolve(certificate)
|
||||
privateKey = path.Resolve(privateKey)
|
||||
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
|
||||
if loadErr != nil {
|
||||
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||
|
8
common/util/manipulation.go
Normal file
8
common/util/manipulation.go
Normal 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
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type Authenticator interface {
|
||||
Verify(user string, pass string) bool
|
||||
Users() []string
|
||||
@ -15,12 +11,12 @@ type AuthUser struct {
|
||||
}
|
||||
|
||||
type inMemoryAuthenticator struct {
|
||||
storage *xsync.MapOf[string, string]
|
||||
storage map[string]string
|
||||
usernames []string
|
||||
}
|
||||
|
||||
func (au *inMemoryAuthenticator) Verify(user string, pass string) bool {
|
||||
realPass, ok := au.storage.Load(user)
|
||||
realPass, ok := au.storage[user]
|
||||
return ok && realPass == pass
|
||||
}
|
||||
|
||||
@ -30,17 +26,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
||||
if len(users) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
||||
for _, user := range users {
|
||||
au.storage.Store(user.User, user.Pass)
|
||||
au := &inMemoryAuthenticator{
|
||||
storage: make(map[string]string),
|
||||
usernames: make([]string, 0, len(users)),
|
||||
}
|
||||
for _, user := range users {
|
||||
au.storage[user.User] = user.Pass
|
||||
au.usernames = append(au.usernames, user.User)
|
||||
}
|
||||
usernames := make([]string, 0, len(users))
|
||||
au.storage.Range(func(key string, value string) bool {
|
||||
usernames = append(usernames, key)
|
||||
return true
|
||||
})
|
||||
au.usernames = usernames
|
||||
|
||||
return au
|
||||
}
|
||||
|
@ -14,5 +14,15 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
||||
listenAddr = "255.255.255.255:68"
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
|
||||
options := []dialer.Option{
|
||||
dialer.WithInterface(ifaceName),
|
||||
dialer.WithAddrReuse(true),
|
||||
}
|
||||
|
||||
// fallback bind on windows, because syscall bind can not receive broadcast
|
||||
if runtime.GOOS == "windows" {
|
||||
options = append(options, dialer.WithFallbackBind(true))
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package dialer
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
@ -14,7 +15,7 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
var addr netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
@ -49,3 +50,52 @@ func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
|
||||
local := uint64(0)
|
||||
if dialer.LocalAddr != nil {
|
||||
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||
if err == nil {
|
||||
local, _ = strconv.ParseUint(port, 10, 16)
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.LocalAddr = addr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
port = "0"
|
||||
}
|
||||
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addr.String(), nil
|
||||
}
|
||||
|
||||
func fallbackParseNetwork(network string, addr netip.Addr) string {
|
||||
// fix fallbackBindIfaceToListenConfig() force bind to an ipv4 address
|
||||
if !strings.HasSuffix(network, "4") &&
|
||||
!strings.HasSuffix(network, "6") &&
|
||||
addr.Unmap().Is6() {
|
||||
network += "6"
|
||||
}
|
||||
return network
|
||||
}
|
||||
|
@ -5,55 +5,16 @@ package dialer
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
}
|
||||
|
||||
local := uint64(0)
|
||||
if dialer.LocalAddr != nil {
|
||||
_, port, err := net.SplitHostPort(dialer.LocalAddr.String())
|
||||
if err == nil {
|
||||
local, _ = strconv.ParseUint(port, 10, 16)
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.LocalAddr = addr
|
||||
|
||||
return nil
|
||||
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
port = "0"
|
||||
}
|
||||
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return addr.String(), nil
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
|
||||
}
|
||||
|
||||
func ParseNetwork(network string, addr netip.Addr) string {
|
||||
// fix bindIfaceToListenConfig() force bind to an ipv4 address
|
||||
if !strings.HasSuffix(network, "4") &&
|
||||
!strings.HasSuffix(network, "6") &&
|
||||
addr.Unmap().Is6() {
|
||||
network += "6"
|
||||
}
|
||||
return network
|
||||
return fallbackParseNetwork(network, addr)
|
||||
}
|
||||
|
@ -70,11 +70,18 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
if DefaultSocketHook != nil {
|
||||
return listenPacketHooked(ctx, network, address)
|
||||
}
|
||||
cfg := applyOptions(options...)
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -110,6 +117,9 @@ func GetTcpConcurrent() bool {
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if DefaultSocketHook != nil {
|
||||
return dialContextHooked(ctx, network, destination, port)
|
||||
}
|
||||
address := net.JoinHostPort(destination.String(), port)
|
||||
|
||||
netDialer := opt.netDialer
|
||||
@ -125,7 +135,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
if opt.interfaceName != "" {
|
||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type NetDialer interface {
|
||||
|
||||
type option struct {
|
||||
interfaceName string
|
||||
fallbackBind bool
|
||||
addrReuse bool
|
||||
routingMark int
|
||||
network int
|
||||
@ -38,6 +39,12 @@ func WithInterface(name string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithFallbackBind(fallback bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.fallbackBind = fallback
|
||||
}
|
||||
}
|
||||
|
||||
func WithAddrReuse(reuse bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.addrReuse = reuse
|
||||
|
37
component/dialer/patch.go
Normal file
37
component/dialer/patch.go
Normal file
@ -0,0 +1,37 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t, ok := conn.(*net.TCPConn); ok {
|
||||
t.SetKeepAlive(false)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
lc := &net.ListenConfig{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
@ -36,7 +36,7 @@ type Pool struct {
|
||||
cycle bool
|
||||
mux sync.Mutex
|
||||
host *trie.DomainTrie[struct{}]
|
||||
ipnet *netip.Prefix
|
||||
ipnet netip.Prefix
|
||||
store store
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ func (p *Pool) Broadcast() netip.Addr {
|
||||
}
|
||||
|
||||
// IPNet return raw ipnet
|
||||
func (p *Pool) IPNet() *netip.Prefix {
|
||||
func (p *Pool) IPNet() netip.Prefix {
|
||||
return p.ipnet
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ func (p *Pool) restoreState() {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
IPNet *netip.Prefix
|
||||
IPNet netip.Prefix
|
||||
Host *trie.DomainTrie[struct{}]
|
||||
|
||||
// Size sets the maximum number of entries in memory
|
||||
@ -171,7 +171,7 @@ func New(options Options) (*Pool, error) {
|
||||
hostAddr = options.IPNet.Masked().Addr()
|
||||
gateway = hostAddr.Next()
|
||||
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
||||
last = nnip.UnMasked(*options.IPNet)
|
||||
last = nnip.UnMasked(options.IPNet)
|
||||
)
|
||||
|
||||
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
|
||||
|
@ -51,7 +51,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
|
||||
func TestPool_Basic(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.0/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -79,7 +79,7 @@ func TestPool_Basic(t *testing.T) {
|
||||
func TestPool_BasicV6(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("2001:4860:4860::8888/118")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -107,7 +107,7 @@ func TestPool_BasicV6(t *testing.T) {
|
||||
func TestPool_Case_Insensitive(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/29")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -128,7 +128,7 @@ func TestPool_Case_Insensitive(t *testing.T) {
|
||||
func TestPool_CycleUsed(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.16/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -152,7 +152,7 @@ func TestPool_Skip(t *testing.T) {
|
||||
tree := trie.New[struct{}]()
|
||||
tree.Insert("example.com", struct{}{})
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
Host: tree,
|
||||
})
|
||||
@ -168,7 +168,7 @@ func TestPool_Skip(t *testing.T) {
|
||||
func TestPool_MaxCacheSize(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@ -183,7 +183,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
|
||||
func TestPool_DoubleMapping(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@ -213,7 +213,7 @@ func TestPool_DoubleMapping(t *testing.T) {
|
||||
func TestPool_Clone(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/24")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
|
||||
@ -223,7 +223,7 @@ func TestPool_Clone(t *testing.T) {
|
||||
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
|
||||
newPool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 2,
|
||||
})
|
||||
newPool.CloneFrom(pool)
|
||||
@ -236,7 +236,7 @@ func TestPool_Clone(t *testing.T) {
|
||||
func TestPool_Error(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/31")
|
||||
_, err := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
|
||||
@ -246,7 +246,7 @@ func TestPool_Error(t *testing.T) {
|
||||
func TestPool_FlushFileCache(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||
pools, tempfile, err := createPools(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
@ -278,7 +278,7 @@ func TestPool_FlushFileCache(t *testing.T) {
|
||||
func TestPool_FlushMemoryCache(t *testing.T) {
|
||||
ipnet := netip.MustParsePrefix("192.168.0.1/28")
|
||||
pool, _ := New(Options{
|
||||
IPNet: &ipnet,
|
||||
IPNet: ipnet,
|
||||
Size: 10,
|
||||
})
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
URL "net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -47,6 +48,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
||||
|
||||
transport := &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
DisableKeepAlives: runtime.GOOS == "android",
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
type Interface struct {
|
||||
Index int
|
||||
Name string
|
||||
Addrs []*netip.Prefix
|
||||
Addrs []netip.Prefix
|
||||
HardwareAddr net.HardwareAddr
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipNets := make([]*netip.Prefix, 0, len(addrs))
|
||||
ipNets := make([]netip.Prefix, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
ipNet := addr.(*net.IPNet)
|
||||
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
||||
@ -59,7 +59,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
}
|
||||
|
||||
pf := netip.PrefixFrom(ip, ones)
|
||||
ipNets = append(ipNets, &pf)
|
||||
ipNets = append(ipNets, pf)
|
||||
}
|
||||
|
||||
r[iface.Name] = &Interface{
|
||||
@ -89,27 +89,27 @@ func FlushCache() {
|
||||
interfaces.Reset()
|
||||
}
|
||||
|
||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (*netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||
return addr.Addr().Is4()
|
||||
})
|
||||
}
|
||||
|
||||
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (*netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr *netip.Prefix) bool {
|
||||
func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||
return iface.pickIPAddr(destination, func(addr netip.Prefix) bool {
|
||||
return addr.Addr().Is6()
|
||||
})
|
||||
}
|
||||
|
||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *netip.Prefix) bool) (*netip.Prefix, error) {
|
||||
var fallback *netip.Prefix
|
||||
func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) {
|
||||
var fallback netip.Prefix
|
||||
|
||||
for _, addr := range iface.Addrs {
|
||||
if !accept(addr) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fallback == nil && !addr.Addr().IsLinkLocalUnicast() {
|
||||
if !fallback.IsValid() && !addr.Addr().IsLinkLocalUnicast() {
|
||||
fallback = addr
|
||||
|
||||
if !destination.IsValid() {
|
||||
@ -122,8 +122,8 @@ func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr *net
|
||||
}
|
||||
}
|
||||
|
||||
if fallback == nil {
|
||||
return nil, ErrAddrNotFound
|
||||
if !fallback.IsValid() {
|
||||
return netip.Prefix{}, ErrAddrNotFound
|
||||
}
|
||||
|
||||
return fallback, nil
|
||||
|
16
component/mmdb/patch.go
Normal file
16
component/mmdb/patch.go
Normal file
@ -0,0 +1,16 @@
|
||||
package mmdb
|
||||
|
||||
import "github.com/oschwald/maxminddb-golang"
|
||||
|
||||
func InstallOverride(override *maxminddb.Reader) {
|
||||
newReader := Reader{Reader: override}
|
||||
switch override.Metadata.DatabaseType {
|
||||
case "sing-geoip":
|
||||
reader.databaseType = typeSing
|
||||
case "Meta-geoip0":
|
||||
reader.databaseType = typeMetaV0
|
||||
default:
|
||||
reader.databaseType = typeMaxmind
|
||||
}
|
||||
reader = newReader
|
||||
}
|
14
component/process/patch.go
Normal file
14
component/process/patch.go
Normal file
@ -0,0 +1,14 @@
|
||||
package process
|
||||
|
||||
import "github.com/Dreamacro/clash/constant"
|
||||
|
||||
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
|
||||
|
||||
var DefaultPackageNameResolver PackageNameResolver
|
||||
|
||||
func FindPackageName(metadata *constant.Metadata) (string, error) {
|
||||
if resolver := DefaultPackageNameResolver; resolver != nil {
|
||||
return resolver(metadata)
|
||||
}
|
||||
return "", ErrPlatformNotSupport
|
||||
}
|
@ -70,10 +70,7 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
|
||||
}
|
||||
|
||||
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||
currentMeta := &C.Metadata{Type: C.INNER}
|
||||
if err := currentMeta.SetRemoteAddress(rAddrPort.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentMeta := &C.Metadata{Type: C.INNER, DstIP: rAddrPort.Addr(), DstPort: rAddrPort.Port()}
|
||||
return p.listenPacket(ctx, currentMeta)
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,10 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
minInterval = time.Minute * 5
|
||||
)
|
||||
|
||||
var (
|
||||
fileMode os.FileMode = 0o666
|
||||
dirMode os.FileMode = 0o755
|
||||
@ -24,8 +28,7 @@ type Fetcher[V any] struct {
|
||||
resourceType string
|
||||
name string
|
||||
vehicle types.Vehicle
|
||||
UpdatedAt *time.Time
|
||||
ticker *time.Ticker
|
||||
UpdatedAt time.Time
|
||||
done chan struct{}
|
||||
hash [16]byte
|
||||
parser Parser[V]
|
||||
@ -56,7 +59,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||
buf, err = os.ReadFile(f.vehicle.Path())
|
||||
modTime := stat.ModTime()
|
||||
f.UpdatedAt = &modTime
|
||||
f.UpdatedAt = modTime
|
||||
isLocal = true
|
||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
||||
@ -64,6 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
}
|
||||
} else {
|
||||
buf, err = f.vehicle.Read()
|
||||
f.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -113,7 +117,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
f.hash = md5.Sum(buf)
|
||||
|
||||
// pull contents automatically
|
||||
if f.ticker != nil {
|
||||
if f.interval > 0 {
|
||||
go f.pullLoop()
|
||||
}
|
||||
|
||||
@ -129,7 +133,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
now := time.Now()
|
||||
hash := md5.Sum(buf)
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
f.UpdatedAt = &now
|
||||
f.UpdatedAt = now
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return lo.Empty[V](), true, nil
|
||||
}
|
||||
@ -145,23 +149,31 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
f.UpdatedAt = &now
|
||||
f.UpdatedAt = now
|
||||
f.hash = hash
|
||||
|
||||
return contents, false, nil
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) Destroy() error {
|
||||
if f.ticker != nil {
|
||||
if f.interval > 0 {
|
||||
f.done <- struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) pullLoop() {
|
||||
initialInterval := f.interval - time.Since(f.UpdatedAt)
|
||||
if initialInterval < minInterval {
|
||||
initialInterval = minInterval
|
||||
}
|
||||
|
||||
timer := time.NewTimer(initialInterval)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-f.ticker.C:
|
||||
case <-timer.C:
|
||||
timer.Reset(f.interval)
|
||||
elm, same, err := f.Update()
|
||||
if err != nil {
|
||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||
@ -178,7 +190,6 @@ func (f *Fetcher[V]) pullLoop() {
|
||||
f.OnUpdate(elm)
|
||||
}
|
||||
case <-f.done:
|
||||
f.ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -197,17 +208,12 @@ func safeWrite(path string, buf []byte) error {
|
||||
}
|
||||
|
||||
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
|
||||
var ticker *time.Ticker
|
||||
if interval != 0 {
|
||||
ticker = time.NewTicker(interval)
|
||||
}
|
||||
|
||||
return &Fetcher[V]{
|
||||
name: name,
|
||||
ticker: ticker,
|
||||
vehicle: vehicle,
|
||||
parser: parser,
|
||||
done: make(chan struct{}, 1),
|
||||
done: make(chan struct{}, 8),
|
||||
OnUpdate: onUpdate,
|
||||
interval: interval,
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ func (*BaseSniffer) Protocol() string {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// SniffTCP implements sniffer.Sniffer
|
||||
func (*BaseSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||
// SniffData implements sniffer.Sniffer
|
||||
func (*BaseSniffer) SniffData(bytes []byte) (string, error) {
|
||||
return "", errors.New("TODO")
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,40 @@ type SnifferDispatcher struct {
|
||||
parsePureIp bool
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
|
||||
return (metadata.Host == "" && sd.parsePureIp) ||
|
||||
sd.forceDomain.Has(metadata.Host) ||
|
||||
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping)
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
|
||||
metadata := packet.Metadata()
|
||||
|
||||
if sd.shouldOverride(packet.Metadata()) {
|
||||
for sniffer, config := range sd.sniffers {
|
||||
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet {
|
||||
inWhitelist := sniffer.SupportPort(metadata.DstPort)
|
||||
overrideDest := config.OverrideDest
|
||||
|
||||
if inWhitelist {
|
||||
host, err := sniffer.SniffData(packet.Data())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sd.replaceDomain(metadata, host, overrideDest)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TCPSniff returns true if the connection is sniffed to have a domain
|
||||
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool {
|
||||
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
|
||||
if sd.shouldOverride(metadata) {
|
||||
inWhitelist := false
|
||||
overrideDest := false
|
||||
for sniffer, config := range sd.sniffers {
|
||||
@ -86,7 +117,8 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
|
||||
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
// show log early, since the following code may mutate `metadata.Host`
|
||||
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.NetWork,
|
||||
metadata.SourceDetail(),
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
@ -125,7 +157,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := s.SniffTCP(bytes)
|
||||
host, err := s.SniffData(bytes)
|
||||
if err != nil {
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||
continue
|
||||
@ -194,6 +226,8 @@ func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer
|
||||
return NewTLSSniffer(snifferConfig)
|
||||
case sniffer.HTTP:
|
||||
return NewHTTPSniffer(snifferConfig)
|
||||
case sniffer.QUIC:
|
||||
return NewQuicSniffer(snifferConfig)
|
||||
default:
|
||||
return nil, ErrorUnsupportedSniffer
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func (http *HTTPSniffer) SupportNetwork() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||
func (http *HTTPSniffer) SniffData(bytes []byte) (string, error) {
|
||||
domain, err := SniffHTTP(bytes)
|
||||
if err == nil {
|
||||
return *domain, nil
|
||||
|
@ -1,3 +1,287 @@
|
||||
package sniffer
|
||||
|
||||
//TODO
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/metacubex/quic-go/quicvarint"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
// Modified from https://github.com/v2fly/v2ray-core/blob/master/common/protocol/quic/sniff.go
|
||||
|
||||
const (
|
||||
versionDraft29 uint32 = 0xff00001d
|
||||
version1 uint32 = 0x1
|
||||
)
|
||||
|
||||
var (
|
||||
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||
errNotQuic = errors.New("not QUIC")
|
||||
errNotQuicInitial = errors.New("not QUIC initial packet")
|
||||
)
|
||||
|
||||
type QuicSniffer struct {
|
||||
*BaseSniffer
|
||||
}
|
||||
|
||||
func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) {
|
||||
ports := snifferConfig.Ports
|
||||
if len(ports) == 0 {
|
||||
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)}
|
||||
}
|
||||
return &QuicSniffer{
|
||||
BaseSniffer: NewBaseSniffer(ports, C.UDP),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) Protocol() string {
|
||||
return "quic"
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) SupportNetwork() C.NetWork {
|
||||
return C.UDP
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
buffer := buf.As(b)
|
||||
typeByte, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
isLongHeader := typeByte&0x80 > 0
|
||||
if !isLongHeader || typeByte&0x40 == 0 {
|
||||
return "", errNotQuicInitial
|
||||
}
|
||||
|
||||
vb, err := buffer.ReadBytes(4)
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
versionNumber := binary.BigEndian.Uint32(vb)
|
||||
|
||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||
return "", errNotQuic
|
||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
if (typeByte&0x30)>>4 != 0x0 {
|
||||
return "", errNotQuicInitial
|
||||
}
|
||||
|
||||
var destConnID []byte
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return "", errNotQuic
|
||||
} else if destConnID, err = buffer.ReadBytes(int(l)); err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return "", errNotQuic
|
||||
} else if _, err := buffer.ReadBytes(int(l)); err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
tokenLen, err := quicvarint.Read(buffer)
|
||||
if err != nil || tokenLen > uint64(len(b)) {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
if _, err = buffer.ReadBytes(int(tokenLen)); err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
packetLen, err := quicvarint.Read(buffer)
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
}
|
||||
|
||||
hdrLen := len(b) - buffer.Len()
|
||||
|
||||
var salt []byte
|
||||
if versionNumber == version1 {
|
||||
salt = quicSalt
|
||||
} else {
|
||||
salt = quicSaltOld
|
||||
}
|
||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||
hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16)
|
||||
block, err := aes.NewCipher(hpKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cache := buf.NewPacket()
|
||||
defer cache.Release()
|
||||
|
||||
mask := cache.Extend(block.BlockSize())
|
||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
|
||||
firstByte := b[0]
|
||||
// Encrypt/decrypt first byte.
|
||||
if isLongHeader {
|
||||
// Long header: 4 bits masked
|
||||
// High 4 bits are not protected.
|
||||
firstByte ^= mask[0] & 0x0f
|
||||
} else {
|
||||
// Short header: 5 bits masked
|
||||
// High 3 bits are not protected.
|
||||
firstByte ^= mask[0] & 0x1f
|
||||
}
|
||||
packetNumberLength := int(firstByte&0x3 + 1) // max = 4 (64-bit sequence number)
|
||||
extHdrLen := hdrLen + packetNumberLength
|
||||
|
||||
// copy to avoid modify origin data
|
||||
extHdr := cache.Extend(extHdrLen)
|
||||
copy(extHdr, b)
|
||||
extHdr[0] = firstByte
|
||||
|
||||
packetNumber := extHdr[hdrLen:extHdrLen]
|
||||
// Encrypt/decrypt packet number.
|
||||
for i := range packetNumber {
|
||||
packetNumber[i] ^= mask[1+i]
|
||||
}
|
||||
|
||||
if packetNumber[0] != 0 && packetNumber[0] != 1 {
|
||||
return "", errNotQuicInitial
|
||||
}
|
||||
|
||||
data := b[extHdrLen : int(packetLen)+hdrLen]
|
||||
|
||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
aead, err := cipher.NewGCM(aesCipher)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// We only decrypt once, so we do not need to XOR it back.
|
||||
// https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496
|
||||
for i, b := range packetNumber {
|
||||
iv[len(iv)-len(packetNumber)+i] ^= b
|
||||
}
|
||||
dst := cache.Extend(len(data))
|
||||
decrypted, err := aead.Open(dst[:0], iv, data, extHdr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buffer = buf.As(decrypted)
|
||||
|
||||
cryptoLen := uint(0)
|
||||
cryptoData := cache.Extend(buffer.Len())
|
||||
for i := 0; !buffer.IsEmpty(); i++ {
|
||||
frameType := byte(0x0) // Default to PADDING frame
|
||||
for frameType == 0x0 && !buffer.IsEmpty() {
|
||||
frameType, _ = buffer.ReadByte()
|
||||
}
|
||||
switch frameType {
|
||||
case 0x00: // PADDING frame
|
||||
case 0x01: // PING frame
|
||||
case 0x02, 0x03: // ACK frame
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
if frameType == 0x03 {
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
case 0x06: // CRYPTO frame, we will use this frame
|
||||
offset, err := quicvarint.Read(buffer) // Field: Offset
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := quicvarint.Read(buffer) // Field: Length
|
||||
if err != nil || length > uint64(buffer.Len()) {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if cryptoLen < uint(offset+length) {
|
||||
cryptoLen = uint(offset + length)
|
||||
}
|
||||
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase
|
||||
return "", io.ErrUnexpectedEOF
|
||||
}
|
||||
default:
|
||||
// Only above frame types are permitted in initial packet.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||
return "", errNotQuicInitial
|
||||
}
|
||||
}
|
||||
|
||||
domain, err := ReadClientHello(cryptoData[:cryptoLen])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return *domain, nil
|
||||
}
|
||||
|
||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||
binary.BigEndian.PutUint16(b, uint16(length))
|
||||
b[2] = uint8(6 + len(label))
|
||||
b = append(b, []byte("tls13 ")...)
|
||||
b = append(b, []byte(label)...)
|
||||
b = b[:3+6+len(label)+1]
|
||||
b[3+6+len(label)] = uint8(len(context))
|
||||
b = append(b, context...)
|
||||
|
||||
out := make([]byte, length)
|
||||
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||
if err != nil || n != length {
|
||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
@ -1,9 +1,40 @@
|
||||
package sniffer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQuicHeaders(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
domain string
|
||||
}{
|
||||
{
|
||||
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b",
|
||||
domain: "www.google.com",
|
||||
},
|
||||
{
|
||||
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738",
|
||||
domain: "cloudflare-dns.com",
|
||||
},
|
||||
}
|
||||
q, err := NewQuicSniffer(SnifferConfig{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, test := range cases {
|
||||
pkt, err := hex.DecodeString(test.input)
|
||||
assert.NoError(t, err)
|
||||
oriPkt := bytes.Clone(pkt)
|
||||
domain, err := q.SniffData(pkt)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.domain, domain)
|
||||
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSHeaders(t *testing.T) {
|
||||
cases := []struct {
|
||||
input []byte
|
||||
@ -142,6 +173,7 @@ func TestTLSHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
input := bytes.Clone(test.input)
|
||||
domain, err := SniffTLS(test.input)
|
||||
if test.err {
|
||||
if err == nil {
|
||||
@ -155,5 +187,6 @@ func TestTLSHeaders(t *testing.T) {
|
||||
t.Error("expect domain ", test.domain, " but got ", domain)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, input, test.input)
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func (tls *TLSSniffer) SupportNetwork() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||
func (tls *TLSSniffer) SniffData(bytes []byte) (string, error) {
|
||||
domain, err := SniffTLS(bytes)
|
||||
if err == nil {
|
||||
return *domain, nil
|
||||
|
@ -43,7 +43,8 @@ type RealityConfig struct {
|
||||
func aesgcmPreferred(ciphers []uint16) bool
|
||||
|
||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
||||
retry := 0
|
||||
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
|
||||
verifier := &realityVerifier{
|
||||
serverName: tlsConfig.ServerName,
|
||||
}
|
||||
@ -80,7 +81,15 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
|
||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||
|
||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
|
||||
ecdheParams := uConn.HandshakeState.State13.EcdheParams
|
||||
if ecdheParams == nil {
|
||||
// WTF???
|
||||
if retry > 2 {
|
||||
return nil, errors.New("nil ecdheParams")
|
||||
}
|
||||
continue // retry
|
||||
}
|
||||
authKey := ecdheParams.SharedKey(realityConfig.PublicKey[:])
|
||||
if authKey == nil {
|
||||
return nil, errors.New("nil auth_key")
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type UClientHelloID struct {
|
||||
var initRandomFingerprint UClientHelloID
|
||||
var initUtlsClient string
|
||||
|
||||
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn {
|
||||
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn {
|
||||
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
|
||||
Client: fingerprint.Client,
|
||||
Version: fingerprint.Version,
|
||||
@ -99,10 +99,9 @@ func copyConfig(c *tls.Config) *utls.Config {
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
|
||||
// http/1.1 in its ALPN.
|
||||
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
|
||||
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
|
||||
func (c *UConn) WebsocketHandshake() error {
|
||||
func (c *UConn) BuildWebsocketHandshakeState() error {
|
||||
// Build the handshake state. This will apply every variable of the TLS of the
|
||||
// fingerprint in the UConn
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
@ -120,11 +119,11 @@ func (c *UConn) WebsocketHandshake() error {
|
||||
if !hasALPNExtension { // Append extension if doesn't exists
|
||||
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
|
||||
}
|
||||
// Rebuild the client hello and do the handshake
|
||||
// Rebuild the client hello
|
||||
if err := c.BuildHandshakeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Handshake()
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetGlobalUtlsClient(Client string) {
|
||||
|
211
config/config.go
211
config/config.go
@ -20,7 +20,6 @@ import (
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@ -59,27 +58,27 @@ type General struct {
|
||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||
Sniffing bool `json:"sniffing"`
|
||||
EBpf EBpf `json:"-"`
|
||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||
GlobalUA string `json:"global-ua"`
|
||||
}
|
||||
|
||||
// Inbound config
|
||||
type Inbound struct {
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Tun LC.Tun `json:"tun"`
|
||||
TuicServer LC.TuicServer `json:"tuic-server"`
|
||||
ShadowSocksConfig string `json:"ss-config"`
|
||||
VmessConfig string `json:"vmess-config"`
|
||||
Authentication []string `json:"authentication"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
InboundTfo bool `json:"inbound-tfo"`
|
||||
InboundMPTCP bool `json:"inbound-mptcp"`
|
||||
Port int `json:"port"`
|
||||
SocksPort int `json:"socks-port"`
|
||||
RedirPort int `json:"redir-port"`
|
||||
TProxyPort int `json:"tproxy-port"`
|
||||
MixedPort int `json:"mixed-port"`
|
||||
Tun LC.Tun `json:"tun"`
|
||||
TuicServer LC.TuicServer `json:"tuic-server"`
|
||||
ShadowSocksConfig string `json:"ss-config"`
|
||||
VmessConfig string `json:"vmess-config"`
|
||||
Authentication []string `json:"authentication"`
|
||||
SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"`
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
InboundTfo bool `json:"inbound-tfo"`
|
||||
InboundMPTCP bool `json:"inbound-mptcp"`
|
||||
}
|
||||
|
||||
// Controller config
|
||||
@ -122,7 +121,7 @@ type DNS struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []*netip.Prefix `yaml:"ipcidr"`
|
||||
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
||||
}
|
||||
@ -159,6 +158,7 @@ type Sniffer struct {
|
||||
type Experimental struct {
|
||||
Fingerprints []string `yaml:"fingerprints"`
|
||||
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
|
||||
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
|
||||
}
|
||||
|
||||
// Config is clash config manager
|
||||
@ -192,29 +192,34 @@ type RawNTP struct {
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||
UseHosts bool `yaml:"use-hosts"`
|
||||
NameServer []string `yaml:"nameserver"`
|
||||
Fallback []string `yaml:"fallback"`
|
||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||
Listen string `yaml:"listen"`
|
||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||
FakeIPRange string `yaml:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||
NameServerPolicy map[string]any `yaml:"nameserver-policy"`
|
||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
||||
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
||||
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
||||
Fallback []string `yaml:"fallback" json:"fallback"`
|
||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
|
||||
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
|
||||
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
||||
NameServerPolicy map[string]any `yaml:"nameserver-policy" json:"nameserver-policy"`
|
||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
||||
}
|
||||
|
||||
type RawFallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []string `yaml:"geosite"`
|
||||
GeoIP bool `yaml:"geoip" json:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"`
|
||||
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"`
|
||||
Domain []string `yaml:"domain" json:"domain"`
|
||||
GeoSite []string `yaml:"geosite" json:"geosite"`
|
||||
}
|
||||
|
||||
type RawClashForAndroid struct {
|
||||
AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"`
|
||||
UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"`
|
||||
}
|
||||
|
||||
type RawTun struct {
|
||||
@ -227,21 +232,23 @@ type RawTun struct {
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
//Inet4Address []LC.ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []LC.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []LC.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []LC.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"`
|
||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
@ -260,22 +267,23 @@ type RawTuicServer struct {
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
TProxyPort int `yaml:"tproxy-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
Port int `yaml:"port" json:"port"`
|
||||
SocksPort int `yaml:"socks-port" json:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port" json:"redir-port"`
|
||||
TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"`
|
||||
MixedPort int `yaml:"mixed-port" json:"mixed-port"`
|
||||
ShadowSocksConfig string `yaml:"ss-config"`
|
||||
VmessConfig string `yaml:"vmess-config"`
|
||||
InboundTfo bool `yaml:"inbound-tfo"`
|
||||
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address"`
|
||||
Mode T.TunnelMode `yaml:"mode"`
|
||||
UnifiedDelay bool `yaml:"unified-delay"`
|
||||
LogLevel log.LogLevel `yaml:"log-level"`
|
||||
IPv6 bool `yaml:"ipv6"`
|
||||
Authentication []string `yaml:"authentication" json:"authentication"`
|
||||
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
|
||||
AllowLan bool `yaml:"allow-lan" json:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address" json:"bind-address"`
|
||||
Mode T.TunnelMode `yaml:"mode" json:"mode"`
|
||||
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"`
|
||||
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
|
||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||
ExternalController string `yaml:"external-controller"`
|
||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||
ExternalUI string `yaml:"external-ui"`
|
||||
@ -285,20 +293,20 @@ type RawConfig struct {
|
||||
Interface string `yaml:"interface-name"`
|
||||
RoutingMark int `yaml:"routing-mark"`
|
||||
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
||||
GeodataMode bool `yaml:"geodata-mode"`
|
||||
GeodataLoader string `yaml:"geodata-loader"`
|
||||
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
|
||||
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
|
||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||
GlobalUA string `yaml:"global-ua"`
|
||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
||||
|
||||
Sniffer RawSniffer `yaml:"sniffer"`
|
||||
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"`
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||
Hosts map[string]any `yaml:"hosts"`
|
||||
NTP RawNTP `yaml:"ntp"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Hosts map[string]any `yaml:"hosts" json:"hosts"`
|
||||
NTP RawNTP `yaml:"ntp" json:"ntp"`
|
||||
DNS RawDNS `yaml:"dns" json:"dns"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||
EBpf EBpf `yaml:"ebpf"`
|
||||
@ -312,6 +320,8 @@ type RawConfig struct {
|
||||
SubRules map[string][]string `yaml:"sub-rules"`
|
||||
RawTLS TLS `yaml:"tls"`
|
||||
Listeners []map[string]any `yaml:"listeners"`
|
||||
|
||||
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
|
||||
}
|
||||
|
||||
type GeoXUrl struct {
|
||||
@ -385,7 +395,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||
AutoRoute: true,
|
||||
AutoDetectInterface: true,
|
||||
Inet6Address: []LC.ListenPrefix{LC.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
|
||||
Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")},
|
||||
},
|
||||
TuicServer: RawTuicServer{
|
||||
Enable: false,
|
||||
@ -459,9 +469,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: GeoXUrl{
|
||||
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
|
||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
|
||||
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
|
||||
},
|
||||
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
||||
}
|
||||
@ -479,7 +489,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
startTime := time.Now()
|
||||
config.Experimental = &rawCfg.Experimental
|
||||
config.Profile = &rawCfg.Profile
|
||||
config.IPTables = &rawCfg.IPTables
|
||||
config.TLS = &rawCfg.RawTLS
|
||||
|
||||
general, err := parseGeneral(rawCfg)
|
||||
@ -540,11 +549,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.DNS = dnsCfg
|
||||
|
||||
err = parseTun(rawCfg.Tun, config.General)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseTuicServer(rawCfg.TuicServer, config.General)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -619,6 +623,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
ShadowSocksConfig: cfg.ShadowSocksConfig,
|
||||
VmessConfig: cfg.VmessConfig,
|
||||
AllowLan: cfg.AllowLan,
|
||||
SkipAuthPrefixes: cfg.SkipAuthPrefixes,
|
||||
BindAddress: cfg.BindAddress,
|
||||
InboundTfo: cfg.InboundTfo,
|
||||
InboundMPTCP: cfg.InboundMPTCP,
|
||||
@ -640,7 +645,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
GeodataLoader: cfg.GeodataLoader,
|
||||
TCPConcurrent: cfg.TCPConcurrent,
|
||||
FindProcessMode: cfg.FindProcessMode,
|
||||
EBpf: cfg.EBpf,
|
||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||
GlobalUA: cfg.GlobalUA,
|
||||
}, nil
|
||||
@ -1044,7 +1048,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyName: proxyName,
|
||||
Interface: dialer.DefaultInterface,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
},
|
||||
@ -1146,15 +1149,15 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
||||
var ipNets []*netip.Prefix
|
||||
func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
|
||||
var ipNets []netip.Prefix
|
||||
|
||||
for idx, ip := range ips {
|
||||
ipnet, err := netip.ParsePrefix(ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
ipNets = append(ipNets, &ipnet)
|
||||
ipNets = append(ipNets, ipnet)
|
||||
}
|
||||
|
||||
return ipNets, nil
|
||||
@ -1222,7 +1225,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
IPv6: cfg.IPv6,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
IPCIDR: []*netip.Prefix{},
|
||||
IPCIDR: []netip.Prefix{},
|
||||
GeoSite: []*router.DomainMatcher{},
|
||||
},
|
||||
}
|
||||
@ -1296,7 +1299,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
}
|
||||
|
||||
pool, err := fakeip.New(fakeip.Options{
|
||||
IPNet: &fakeIPRange,
|
||||
IPNet: fakeIPRange,
|
||||
Size: 1000,
|
||||
Host: host,
|
||||
Persistence: rawCfg.Profile.StoreFakeIP,
|
||||
@ -1359,22 +1362,24 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
AutoDetectInterface: rawTun.AutoDetectInterface,
|
||||
RedirectToTun: rawTun.RedirectToTun,
|
||||
|
||||
MTU: rawTun.MTU,
|
||||
Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
MTU: rawTun.MTU,
|
||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
||||
Inet6Address: rawTun.Inet6Address,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||
IncludeUID: rawTun.IncludeUID,
|
||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
FileDescriptor: rawTun.FileDescriptor,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -252,6 +252,23 @@ type PacketAdapter interface {
|
||||
Metadata() *Metadata
|
||||
}
|
||||
|
||||
type packetAdapter struct {
|
||||
UDPPacket
|
||||
metadata *Metadata
|
||||
}
|
||||
|
||||
// Metadata returns destination metadata
|
||||
func (s *packetAdapter) Metadata() *Metadata {
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
|
||||
return &packetAdapter{
|
||||
packet,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
type WriteBack interface {
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package constant
|
||||
|
||||
var (
|
||||
GeodataMode bool
|
||||
GeoIpUrl string
|
||||
MmdbUrl string
|
||||
GeoSiteUrl string
|
||||
GeoIpUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
|
||||
MmdbUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
|
||||
GeoSiteUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ type MultiAddrListener interface {
|
||||
|
||||
type InboundListener interface {
|
||||
Name() string
|
||||
Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error
|
||||
Listen(tunnel Tunnel) error
|
||||
Close() error
|
||||
Address() string
|
||||
RawAddress() string
|
||||
|
@ -147,6 +147,9 @@ type Metadata struct {
|
||||
SpecialProxy string `json:"specialProxy"`
|
||||
SpecialRules string `json:"specialRules"`
|
||||
RemoteDst string `json:"remoteDestination"`
|
||||
|
||||
RawSrcAddr net.Addr `json:"-"`
|
||||
RawDstAddr net.Addr `json:"-"`
|
||||
// Only domain rule
|
||||
SniffHost string `json:"sniffHost"`
|
||||
}
|
||||
@ -240,6 +243,34 @@ func (m *Metadata) Valid() bool {
|
||||
return m.Host != "" || m.DstIP.IsValid()
|
||||
}
|
||||
|
||||
func (m *Metadata) SetRemoteAddr(addr net.Addr) error {
|
||||
if addr == nil {
|
||||
return nil
|
||||
}
|
||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
||||
if rawAddr := rawAddr.RawAddr(); rawAddr != nil {
|
||||
if err := m.SetRemoteAddr(rawAddr); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok { // *net.TCPAddr, *net.UDPAddr, M.Socksaddr
|
||||
if addrPort := addr.AddrPort(); addrPort.Port() != 0 {
|
||||
m.DstPort = addrPort.Port()
|
||||
if addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName
|
||||
m.DstIP = addrPort.Addr().Unmap()
|
||||
return nil
|
||||
} else {
|
||||
if addr, ok := addr.(interface{ AddrString() string }); ok { // must be sing's M.Socksaddr
|
||||
m.Host = addr.AddrString() // actually is M.Socksaddr.Fqdn
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.SetRemoteAddress(addr.String())
|
||||
}
|
||||
|
||||
func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
||||
host, port, err := net.SplitHostPort(rawAddress)
|
||||
if err != nil {
|
||||
|
@ -18,6 +18,9 @@ var (
|
||||
)
|
||||
|
||||
// Path is used to get the configuration path
|
||||
//
|
||||
// on Unix systems, `$HOME/.config/clash`.
|
||||
// on Windows, `%USERPROFILE%/.config/clash`.
|
||||
var Path = func() *path {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@ -25,6 +28,13 @@ var Path = func() *path {
|
||||
}
|
||||
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
|
||||
homeDir = P.Join(homeDir, ".config", Name)
|
||||
|
||||
if _, err = os.Stat(homeDir); err != nil {
|
||||
if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok {
|
||||
homeDir = P.Join(configHome, Name)
|
||||
}
|
||||
}
|
||||
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
||||
}()
|
||||
|
||||
|
@ -4,7 +4,8 @@ import "github.com/Dreamacro/clash/constant"
|
||||
|
||||
type Sniffer interface {
|
||||
SupportNetwork() constant.NetWork
|
||||
SniffTCP(bytes []byte) (string, error)
|
||||
// SniffData must not change input bytes
|
||||
SniffData(bytes []byte) (string, error)
|
||||
Protocol() string
|
||||
SupportPort(port uint16) bool
|
||||
}
|
||||
@ -12,10 +13,11 @@ type Sniffer interface {
|
||||
const (
|
||||
TLS Type = iota
|
||||
HTTP
|
||||
QUIC
|
||||
)
|
||||
|
||||
var (
|
||||
List = []Type{TLS, HTTP}
|
||||
List = []Type{TLS, HTTP, QUIC}
|
||||
)
|
||||
|
||||
type Type int
|
||||
@ -26,6 +28,8 @@ func (rt Type) String() string {
|
||||
return "TLS"
|
||||
case HTTP:
|
||||
return "HTTP"
|
||||
case QUIC:
|
||||
return "QUIC"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
12
constant/tunnel.go
Normal file
12
constant/tunnel.go
Normal 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
|
||||
}
|
@ -8,7 +8,6 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@ -23,7 +22,7 @@ type client struct {
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
iface *atomic.TypedValue[string]
|
||||
iface string
|
||||
proxyAdapter C.ProxyAdapter
|
||||
proxyName string
|
||||
addr string
|
||||
@ -48,10 +47,6 @@ func (c *client) Address() string {
|
||||
return c.addr
|
||||
}
|
||||
|
||||
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
return c.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
var (
|
||||
ip netip.Addr
|
||||
@ -77,9 +72,9 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
network = "tcp"
|
||||
}
|
||||
|
||||
options := []dialer.Option{}
|
||||
if c.iface != nil && c.iface.Load() != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface.Load()))
|
||||
var options []dialer.Option
|
||||
if c.iface != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface))
|
||||
}
|
||||
|
||||
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
|
||||
|
17
dns/dhcp.go
17
dns/dhcp.go
@ -1,3 +1,6 @@
|
||||
//go:build disabled
|
||||
// +build disabled
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
@ -8,11 +11,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/component/dhcp"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
@ -29,7 +29,7 @@ type dhcpClient struct {
|
||||
ifaceInvalidate time.Time
|
||||
dnsInvalidate time.Time
|
||||
|
||||
ifaceAddr *netip.Prefix
|
||||
ifaceAddr netip.Prefix
|
||||
done chan struct{}
|
||||
clients []dnsClient
|
||||
err error
|
||||
@ -46,13 +46,6 @@ func (d *dhcpClient) Address() string {
|
||||
return strings.Join(addrs, ",")
|
||||
}
|
||||
|
||||
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
return d.ExchangeContext(ctx, m)
|
||||
}
|
||||
|
||||
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
clients, err := d.resolve(ctx)
|
||||
if err != nil {
|
||||
@ -86,7 +79,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
||||
for _, item := range dns {
|
||||
nameserver = append(nameserver, NameServer{
|
||||
Addr: net.JoinHostPort(item.String(), "53"),
|
||||
Interface: atomic.NewTypedValue(d.ifaceName),
|
||||
Interface: d.ifaceName,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -157,11 +157,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// Exchange implements the Upstream interface for *dnsOverHTTPS.
|
||||
func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
return doh.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
// Close implements the Upstream interface for *dnsOverHTTPS.
|
||||
func (doh *dnsOverHTTPS) Close() (err error) {
|
||||
doh.clientMu.Lock()
|
||||
|
@ -134,11 +134,6 @@ func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// Exchange implements the Upstream interface for *dnsOverQUIC.
|
||||
func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return doq.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
// Close implements the Upstream interface for *dnsOverQUIC.
|
||||
func (doq *dnsOverQUIC) Close() (err error) {
|
||||
doq.connMu.Lock()
|
||||
|
@ -45,7 +45,7 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
||||
}
|
||||
|
||||
type ipnetFilter struct {
|
||||
ipnet *netip.Prefix
|
||||
ipnet netip.Prefix
|
||||
}
|
||||
|
||||
func (inf *ipnetFilter) Match(ip netip.Addr) bool {
|
||||
|
20
dns/local.go
Normal file
20
dns/local.go
Normal file
@ -0,0 +1,20 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type LocalServer struct {
|
||||
handler handler
|
||||
}
|
||||
|
||||
// ServeMsg implement resolver.LocalServer ResolveMsg
|
||||
func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) {
|
||||
return handlerWithContext(ctx, s.handler, msg)
|
||||
}
|
||||
|
||||
func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer {
|
||||
return &LocalServer{handler: NewHandler(resolver, mapper)}
|
||||
}
|
73
dns/patch.go
73
dns/patch.go
@ -4,17 +4,76 @@ import (
|
||||
"context"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/dhcp"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
)
|
||||
|
||||
type LocalServer struct {
|
||||
handler handler
|
||||
const SystemDNSPlaceholder = "system"
|
||||
|
||||
var systemResolver *Resolver
|
||||
var isolateHandler handler
|
||||
|
||||
var _ dnsClient = (*dhcpClient)(nil)
|
||||
|
||||
type dhcpClient struct {
|
||||
enable bool
|
||||
}
|
||||
|
||||
// ServeMsg implement resolver.LocalServer ResolveMsg
|
||||
func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) {
|
||||
return handlerWithContext(ctx, s.handler, msg)
|
||||
func (d *dhcpClient) Address() string {
|
||||
return SystemDNSPlaceholder
|
||||
}
|
||||
|
||||
func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer {
|
||||
return &LocalServer{handler: NewHandler(resolver, mapper)}
|
||||
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return d.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
if s := systemResolver; s != nil {
|
||||
return s.ExchangeContext(ctx, m)
|
||||
}
|
||||
|
||||
return nil, dhcp.ErrNotFound
|
||||
}
|
||||
|
||||
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
|
||||
if h := isolateHandler; h != nil {
|
||||
return handlerWithContext(context.Background(), h, msg)
|
||||
}
|
||||
|
||||
return nil, D.ErrTime
|
||||
}
|
||||
|
||||
func FlushCacheWithDefaultResolver() {
|
||||
if r := resolver.DefaultResolver; r != nil {
|
||||
r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true))
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateSystemDNS(addr []string) {
|
||||
if len(addr) == 0 {
|
||||
systemResolver = nil
|
||||
}
|
||||
|
||||
ns := make([]NameServer, 0, len(addr))
|
||||
for _, d := range addr {
|
||||
ns = append(ns, NameServer{Addr: d})
|
||||
}
|
||||
|
||||
systemResolver = NewResolver(Config{Main: ns})
|
||||
}
|
||||
|
||||
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
if resolver == nil {
|
||||
isolateHandler = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
isolateHandler = NewHandler(resolver, mapper)
|
||||
}
|
||||
|
||||
func newDHCPClient(ifaceName string) *dhcpClient {
|
||||
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
|
||||
}
|
||||
|
@ -39,16 +39,12 @@ type rcodeClient struct {
|
||||
|
||||
var _ dnsClient = rcodeClient{}
|
||||
|
||||
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
m.Response = true
|
||||
m.Rcode = r.rcode
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
return r.Exchange(m)
|
||||
}
|
||||
|
||||
func (r rcodeClient) Address() string {
|
||||
return r.addr
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@ -18,11 +17,11 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
Address() string
|
||||
}
|
||||
@ -135,11 +134,6 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Exchange a batch of dns request, and it use cache
|
||||
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
return r.ExchangeContext(context.Background(), m)
|
||||
}
|
||||
|
||||
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
||||
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
if len(m.Question) == 0 {
|
||||
@ -194,21 +188,25 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
msg := result.(*D.Msg)
|
||||
|
||||
if cache {
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
|
||||
msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool {
|
||||
return rr.Header().Rrtype != D.TypeOPT
|
||||
})
|
||||
putMsgToCache(r.lruCache, q.String(), q, msg)
|
||||
}
|
||||
}()
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
if isIPReq {
|
||||
cache=true
|
||||
cache = true
|
||||
return r.ipExchange(ctx, m)
|
||||
}
|
||||
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
result, cache, err = r.batchExchange(ctx, matched, m)
|
||||
result, cache, err = batchExchange(ctx, matched, m)
|
||||
return
|
||||
}
|
||||
result, cache, err = r.batchExchange(ctx, r.main, m)
|
||||
result, cache, err = batchExchange(ctx, r.main, m)
|
||||
return
|
||||
}
|
||||
|
||||
@ -250,13 +248,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
return batchExchange(ctx, clients, m)
|
||||
}
|
||||
|
||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||
if r.policy == nil {
|
||||
return nil
|
||||
@ -332,7 +323,10 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||
if !r.shouldIPFallback(ips[0]) {
|
||||
shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool {
|
||||
return !r.shouldIPFallback(ip)
|
||||
})
|
||||
if shouldNotFallback {
|
||||
msg, err = res.Msg, res.Error // no need to wait for fallback result
|
||||
return
|
||||
}
|
||||
@ -377,7 +371,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
|
||||
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
res, _, err := r.batchExchange(ctx, client, msg)
|
||||
res, _, err := batchExchange(ctx, client, msg)
|
||||
ch <- &result{Msg: res, Error: err}
|
||||
}()
|
||||
return ch
|
||||
@ -394,7 +388,7 @@ func (r *Resolver) Invalid() bool {
|
||||
type NameServer struct {
|
||||
Net string
|
||||
Addr string
|
||||
Interface *atomic.TypedValue[string]
|
||||
Interface string
|
||||
ProxyAdapter C.ProxyAdapter
|
||||
ProxyName string
|
||||
Params map[string]string
|
||||
@ -404,7 +398,7 @@ type NameServer struct {
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool
|
||||
GeoIPCode string
|
||||
IPCIDR []*netip.Prefix
|
||||
IPCIDR []netip.Prefix
|
||||
Domain []string
|
||||
GeoSite []*router.DomainMatcher
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ func (s *Server) SetHandler(handler handler) {
|
||||
}
|
||||
|
||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
UpdateIsolateHandler(resolver, mapper)
|
||||
if addr == address && resolver != nil {
|
||||
handler := NewHandler(resolver, mapper)
|
||||
server.SetHandler(handler)
|
||||
|
116
dns/system.go
116
dns/system.go
@ -1,23 +1,113 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func loadSystemResolver() (clients []dnsClient, err error) {
|
||||
nameservers, err := dnsReadConfig()
|
||||
const (
|
||||
SystemDnsFlushTime = 5 * time.Minute
|
||||
SystemDnsDeleteTimes = 12 // 12*5 = 60min
|
||||
)
|
||||
|
||||
type systemDnsClient struct {
|
||||
disableTimes uint32
|
||||
dnsClient
|
||||
}
|
||||
|
||||
type systemClient struct {
|
||||
mu sync.Mutex
|
||||
dnsClients map[string]*systemDnsClient
|
||||
lastFlush time.Time
|
||||
}
|
||||
|
||||
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
var err error
|
||||
if time.Since(c.lastFlush) > SystemDnsFlushTime {
|
||||
var nameservers []string
|
||||
if nameservers, err = dnsReadConfig(); err == nil {
|
||||
log.Debugln("[DNS] system dns update to %s", nameservers)
|
||||
for _, addr := range nameservers {
|
||||
if _, ok := c.dnsClients[addr]; !ok {
|
||||
clients := transform(
|
||||
[]NameServer{{
|
||||
Addr: net.JoinHostPort(addr, "53"),
|
||||
Net: "udp",
|
||||
}},
|
||||
nil,
|
||||
)
|
||||
if len(clients) > 0 {
|
||||
c.dnsClients[addr] = &systemDnsClient{
|
||||
disableTimes: 0,
|
||||
dnsClient: clients[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
available := 0
|
||||
for nameserver, sdc := range c.dnsClients {
|
||||
if slices.Contains(nameservers, nameserver) {
|
||||
sdc.disableTimes = 0 // enable
|
||||
available++
|
||||
} else {
|
||||
if sdc.disableTimes > SystemDnsDeleteTimes {
|
||||
delete(c.dnsClients, nameserver) // drop too old dnsClient
|
||||
} else {
|
||||
sdc.disableTimes++
|
||||
}
|
||||
}
|
||||
}
|
||||
if available > 0 {
|
||||
c.lastFlush = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
|
||||
for _, sdc := range c.dnsClients {
|
||||
if sdc.disableTimes == 0 {
|
||||
dnsClients = append(dnsClients, sdc.dnsClient)
|
||||
}
|
||||
}
|
||||
if len(dnsClients) > 0 {
|
||||
return dnsClients, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
dnsClients, err := c.getDnsClients()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(nameservers) == 0 {
|
||||
return
|
||||
}
|
||||
servers := make([]NameServer, 0, len(nameservers))
|
||||
for _, addr := range nameservers {
|
||||
servers = append(servers, NameServer{
|
||||
Addr: net.JoinHostPort(addr, "53"),
|
||||
Net: "udp",
|
||||
})
|
||||
}
|
||||
return transform(servers, nil), nil
|
||||
msg, _, err = batchExchange(ctx, dnsClients, m)
|
||||
return
|
||||
}
|
||||
|
||||
// Address implements dnsClient
|
||||
func (c *systemClient) Address() string {
|
||||
dnsClients, _ := c.getDnsClients()
|
||||
addrs := make([]string, 0, len(dnsClients))
|
||||
for _, c := range dnsClients {
|
||||
addrs = append(addrs, c.Address())
|
||||
}
|
||||
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
|
||||
}
|
||||
|
||||
var _ dnsClient = (*systemClient)(nil)
|
||||
|
||||
func newSystemClient() *systemClient {
|
||||
return &systemClient{
|
||||
dnsClients: map[string]*systemDnsClient{},
|
||||
}
|
||||
}
|
||||
|
85
dns/util.go
85
dns/util.go
@ -29,14 +29,16 @@ const (
|
||||
MaxMsgSize = 65535
|
||||
)
|
||||
|
||||
const serverFailureCacheTTL uint32 = 5
|
||||
|
||||
func minimalTTL(records []D.RR) uint32 {
|
||||
minObj := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return r1.Header().Ttl < r2.Header().Ttl
|
||||
})
|
||||
if minObj != nil {
|
||||
return minObj.Header().Ttl
|
||||
if rr == nil {
|
||||
return 0
|
||||
}
|
||||
return 0
|
||||
return rr.Header().Ttl
|
||||
}
|
||||
|
||||
func updateTTL(records []D.RR, ttl uint32) {
|
||||
@ -49,28 +51,25 @@ func updateTTL(records []D.RR, ttl uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
putMsgToCacheWithExpire(c, key, msg, 0)
|
||||
}
|
||||
|
||||
func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) {
|
||||
if sec == 0 {
|
||||
if sec = minimalTTL(msg.Answer); sec == 0 {
|
||||
if sec = minimalTTL(msg.Ns); sec == 0 {
|
||||
sec = minimalTTL(msg.Extra)
|
||||
}
|
||||
}
|
||||
if sec == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if sec > 120 {
|
||||
sec = 120 // at least 2 minutes to cache
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, q D.Question, msg *D.Msg) {
|
||||
// skip dns cache for acme challenge
|
||||
if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") {
|
||||
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(sec)*time.Second))
|
||||
var ttl uint32
|
||||
if msg.Rcode == D.RcodeServerFailure {
|
||||
// [...] a resolver MAY cache a server failure response.
|
||||
// If it does so it MUST NOT cache it for longer than five (5) minutes [...]
|
||||
ttl = serverFailureCacheTTL
|
||||
} else {
|
||||
ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...))
|
||||
}
|
||||
if ttl == 0 {
|
||||
return
|
||||
}
|
||||
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second))
|
||||
}
|
||||
|
||||
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
@ -108,16 +107,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret = append(ret, newDHCPClient(s.Addr))
|
||||
continue
|
||||
case "system":
|
||||
clients, err := loadSystemResolver()
|
||||
if err != nil {
|
||||
log.Errorln("[DNS:system] load system resolver failed: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
if len(clients) == 0 {
|
||||
log.Errorln("[DNS:system] no nameserver found in system")
|
||||
continue
|
||||
}
|
||||
ret = append(ret, clients...)
|
||||
ret = append(ret, newSystemClient())
|
||||
continue
|
||||
case "rcode":
|
||||
ret = append(ret, newRCodeClient(s.Addr))
|
||||
@ -290,7 +280,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
}
|
||||
|
||||
if !proxyAdapter.SupportUDP() {
|
||||
@ -300,14 +290,17 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
}
|
||||
|
||||
var errIPNotFound = errors.New("couldn't find ip")
|
||||
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
cache = true
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
defer fast.Close()
|
||||
domain := msgToDomain(m)
|
||||
var noIpMsg *D.Msg
|
||||
for _, client := range clients {
|
||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||
msg, err = client.Exchange(m)
|
||||
msg, err = client.ExchangeContext(ctx, m)
|
||||
return msg, false, err
|
||||
}
|
||||
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
||||
@ -321,13 +314,31 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
// so we would ignore RCode errors from RCode clients.
|
||||
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
||||
}
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
|
||||
if ips := msgToIP(m); len(m.Question) > 0 {
|
||||
qType := m.Question[0].Qtype
|
||||
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, D.Type(qType), client.Address())
|
||||
switch qType {
|
||||
case D.TypeAAAA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, errIPNotFound
|
||||
}
|
||||
case D.TypeA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, errIPNotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
})
|
||||
}
|
||||
|
||||
msg = fast.Wait()
|
||||
if msg == nil {
|
||||
if noIpMsg != nil {
|
||||
return noIpMsg, false, nil
|
||||
}
|
||||
err = errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||
|
@ -8,6 +8,11 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
||||
|
||||
allow-lan: true # 允许局域网连接
|
||||
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||
authentication: # http,socks入口的验证用户名,密码
|
||||
- "username:password"
|
||||
skip-auth-prefixes: # 设置跳过验证的IP段
|
||||
- 127.0.0.1/8
|
||||
- ::1/128
|
||||
|
||||
# find-process-mode has 3 values:always, strict, off
|
||||
# - always, 开启,强制匹配所有进程
|
||||
@ -137,7 +142,9 @@ sniffer:
|
||||
# 是否使用嗅探结果作为实际访问,默认 true
|
||||
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
||||
override-destination: false
|
||||
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||
sniff: # TLS 和 QUIC 默认如果不配置 ports 默认嗅探 443
|
||||
QUIC:
|
||||
# ports: [ 443 ]
|
||||
TLS:
|
||||
# ports: [443, 8443]
|
||||
|
||||
@ -345,16 +352,17 @@ proxies: # socks5
|
||||
plugin: v2ray-plugin
|
||||
plugin-opts:
|
||||
mode: websocket # no QUIC now
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
@ -427,11 +435,12 @@ proxies: # socks5
|
||||
# servername: example.com # priority over wss host
|
||||
# network: ws
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
@ -559,6 +568,7 @@ proxies: # socks5
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
@ -599,9 +609,10 @@ proxies: # socks5
|
||||
# fingerprint: xxxx
|
||||
udp: true
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
# v2ray-http-upgrade: false
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
@ -936,6 +947,10 @@ listeners:
|
||||
- username: 1
|
||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
alterId: 1
|
||||
# ws-path: "/" # 如果不为空则开启websocket传输层
|
||||
# 下面两项如果填写则开启tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
||||
- name: tuic-in-1
|
||||
type: tuic
|
||||
|
45
go.mod
45
go.mod
@ -5,51 +5,51 @@ go 1.20
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/cilium/ebpf v0.12.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.3.0
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81
|
||||
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b
|
||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4
|
||||
github.com/metacubex/sing-tun v0.1.12
|
||||
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.11
|
||||
github.com/sagernet/sing v0.2.14
|
||||
github.com/sagernet/sing-mux v0.1.3
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.8
|
||||
github.com/shirou/gopsutil/v3 v3.23.9
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.12.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sync v0.4.0
|
||||
golang.org/x/sys v0.13.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
@ -65,11 +65,12 @@ require (
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
@ -78,14 +79,14 @@ require (
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
@ -99,10 +100,12 @@ require (
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9
|
||||
|
101
go.sum
101
go.sum
@ -14,8 +14,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/cilium/ebpf v0.12.0 h1:oQEuIQIXgYhe1v7sYUG0P9vtJTYZLLdA6tiQmrOB1mo=
|
||||
github.com/cilium/ebpf v0.12.0/go.mod h1:u9H29/Iq+8cy70YqI6p5pfADkFl3vdnV2qXDg5JL0Zo=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -33,8 +33,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4Rfsap
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
@ -49,10 +49,14 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
|
||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
@ -65,8 +69,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@ -93,24 +95,24 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8=
|
||||
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4=
|
||||
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 h1:6g+ohVa8FQLXz/ATmped/4kWuK0HKvhy1hwzQXyF0EI=
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4=
|
||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs=
|
||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c=
|
||||
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b h1:uZ++sW8yg7Fr/Wvmmrb/V+SfxvRs0iMC+2+u2bRmO8g=
|
||||
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b/go.mod h1:4pe6cY+nAMFU/Uxn1rfnxNIowsaJGDQ3uyy4VuiPkP4=
|
||||
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9 h1:F0+IuW0tZ96QHEmrebXAdYnz7ab7Gz4l5yYC4g6Cg8k=
|
||||
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966 h1:wbOsbU3kfD5LRuJIntJwEPmgGSQukof8CgLNypi8az8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966/go.mod h1:GU7g2AZesXItk4CspDP8Dc7eGtlA2GVDihyCwsUXRSo=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
|
||||
github.com/metacubex/sing-tun v0.1.12 h1:Jgmz0k3ddRiJ8zfS4X7j6B/iSy6GnOdDEU0nhqiZcK4=
|
||||
github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M=
|
||||
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
|
||||
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
@ -135,12 +137,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
@ -162,8 +164,8 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@ -197,7 +199,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
@ -208,26 +209,27 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -235,36 +237,27 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
@ -118,7 +118,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
}
|
||||
|
||||
func initInnerTcp() {
|
||||
inner.New(tunnel.TCPIn())
|
||||
inner.New(tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
@ -140,6 +140,7 @@ func GetGeneral() *config.General {
|
||||
ShadowSocksConfig: ports.ShadowSocksConfig,
|
||||
VmessConfig: ports.VmessConfig,
|
||||
Authentication: authenticator,
|
||||
SkipAuthPrefixes: inbound.SkipAuthPrefixes(),
|
||||
AllowLan: listener.AllowLan(),
|
||||
BindAddress: listener.BindAddress(),
|
||||
},
|
||||
@ -157,34 +158,34 @@ func GetGeneral() *config.General {
|
||||
}
|
||||
|
||||
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
natTable := tunnel.NatTable()
|
||||
|
||||
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
|
||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
||||
if !force {
|
||||
return
|
||||
}
|
||||
|
||||
allowLan := general.AllowLan
|
||||
listener.SetAllowLan(allowLan)
|
||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
||||
|
||||
bindAddress := general.BindAddress
|
||||
listener.SetBindAddress(bindAddress)
|
||||
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
||||
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
||||
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
||||
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
||||
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
||||
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
||||
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
|
||||
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Config) {
|
||||
if c.Experimental.QUICGoDisableGSO {
|
||||
_ = os.Setenv("QUIC_GO_DISABLE_GSO", "1")
|
||||
_ = os.Setenv("QUIC_GO_DISABLE_GSO", strconv.FormatBool(true))
|
||||
}
|
||||
if c.Experimental.QUICGoDisableECN {
|
||||
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +340,7 @@ func updateTun(general *config.General) {
|
||||
if general == nil {
|
||||
return
|
||||
}
|
||||
listener.ReCreateTun(LC.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn())
|
||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
||||
listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
|
||||
}
|
||||
|
||||
@ -367,7 +368,7 @@ func updateSniffer(sniffer *config.Sniffer) {
|
||||
}
|
||||
|
||||
func updateTunnels(tunnels []LC.Tunnel) {
|
||||
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
listener.PatchTunnel(tunnels, tunnel.Tunnel)
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General) {
|
||||
|
@ -2,9 +2,11 @@ package route
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
@ -47,6 +49,7 @@ type configSchema struct {
|
||||
TcptunConfig *string `json:"tcptun-config"`
|
||||
UdptunConfig *string `json:"udptun-config"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
@ -66,21 +69,23 @@ type tunSchema struct {
|
||||
//RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
//Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address *[]LC.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
Inet4RouteAddress *[]LC.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress *[]LC.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
@ -145,6 +150,18 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.Inet6Address != nil {
|
||||
def.Inet6Address = *p.Inet6Address
|
||||
}
|
||||
if p.Inet4RouteAddress != nil {
|
||||
def.Inet4RouteAddress = *p.Inet4RouteAddress
|
||||
}
|
||||
if p.Inet6RouteAddress != nil {
|
||||
def.Inet6RouteAddress = *p.Inet6RouteAddress
|
||||
}
|
||||
if p.Inet4RouteExcludeAddress != nil {
|
||||
def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress
|
||||
}
|
||||
if p.Inet6RouteExcludeAddress != nil {
|
||||
def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress
|
||||
}
|
||||
if p.IncludeUID != nil {
|
||||
def.IncludeUID = *p.IncludeUID
|
||||
}
|
||||
@ -231,6 +248,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.SetAllowLan(*general.AllowLan)
|
||||
}
|
||||
|
||||
if general.SkipAuthPrefixes != nil {
|
||||
inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes)
|
||||
}
|
||||
|
||||
if general.BindAddress != nil {
|
||||
P.SetBindAddress(*general.BindAddress)
|
||||
}
|
||||
@ -249,19 +270,15 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ports := P.GetPorts()
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
natTable := tunnel.NatTable()
|
||||
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn, natTable)
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn, natTable)
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
|
||||
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tcpIn, udpIn)
|
||||
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn)
|
||||
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tcpIn, udpIn)
|
||||
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tcpIn, udpIn)
|
||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tunnel.Tunnel)
|
||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tunnel.Tunnel)
|
||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tunnel.Tunnel)
|
||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tunnel.Tunnel)
|
||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tunnel.Tunnel)
|
||||
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tunnel.Tunnel)
|
||||
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tunnel.Tunnel)
|
||||
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tunnel.Tunnel)
|
||||
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tunnel.Tunnel)
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
|
@ -11,7 +11,8 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
)
|
||||
|
||||
func connectionRouter() http.Handler {
|
||||
@ -23,13 +24,13 @@ func connectionRouter() http.Handler {
|
||||
}
|
||||
|
||||
func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
if !websocket.IsWebSocketUpgrade(r) {
|
||||
if !(r.Header.Get("Upgrade") == "websocket") {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
render.JSON(w, r, snapshot)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -55,7 +56,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
return wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpText, buf.Bytes())
|
||||
}
|
||||
|
||||
if err := sendSnapshot(); err != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
@ -21,7 +22,8 @@ import (
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -29,12 +31,6 @@ var (
|
||||
serverAddr = ""
|
||||
|
||||
uiPath = ""
|
||||
|
||||
upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type Traffic struct {
|
||||
@ -112,7 +108,7 @@ func Start(addr string, tlsAddr string, secret string,
|
||||
|
||||
if len(tlsAddr) > 0 {
|
||||
go func() {
|
||||
c, err := CN.ParseCert(certificat, privateKey)
|
||||
c, err := CN.ParseCert(certificat, privateKey, C.Path)
|
||||
if err != nil {
|
||||
log.Errorln("External controller tls listen error: %s", err)
|
||||
return
|
||||
@ -166,7 +162,7 @@ func authentication(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// Browser websocket not support custom header
|
||||
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
|
||||
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
|
||||
token := r.URL.Query().Get("token")
|
||||
if !safeEuqal(token, serverSecret) {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
@ -197,10 +193,10 @@ func hello(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var wsConn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -230,7 +226,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -240,10 +236,10 @@ func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func memory(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var wsConn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -280,7 +276,7 @@ func memory(w http.ResponseWriter, r *http.Request) {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -293,6 +289,16 @@ type Log struct {
|
||||
Type string `json:"type"`
|
||||
Payload string `json:"payload"`
|
||||
}
|
||||
type LogStructuredField struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
type LogStructured struct {
|
||||
Time string `json:"time"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
Fields []LogStructuredField `json:"fields"`
|
||||
}
|
||||
|
||||
func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
levelText := r.URL.Query().Get("level")
|
||||
@ -300,6 +306,12 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
levelText = "info"
|
||||
}
|
||||
|
||||
formatText := r.URL.Query().Get("format")
|
||||
isStructured := false
|
||||
if formatText == "structured" {
|
||||
isStructured = true
|
||||
}
|
||||
|
||||
level, ok := log.LogLevelMapping[levelText]
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
@ -307,10 +319,10 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var wsConn net.Conn
|
||||
if r.Header.Get("Upgrade") == "websocket" {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
wsConn, _, _, err = ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -342,11 +354,26 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
if err := json.NewEncoder(buf).Encode(Log{
|
||||
Type: logM.Type(),
|
||||
Payload: logM.Payload,
|
||||
}); err != nil {
|
||||
break
|
||||
if !isStructured {
|
||||
if err := json.NewEncoder(buf).Encode(Log{
|
||||
Type: logM.Type(),
|
||||
Payload: logM.Payload,
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
newLevel := logM.Type()
|
||||
if newLevel == "warning" {
|
||||
newLevel = "warn"
|
||||
}
|
||||
if err := json.NewEncoder(buf).Encode(LogStructured{
|
||||
Time: time.Now().Format(time.TimeOnly),
|
||||
Level: newLevel,
|
||||
Message: logM.Payload,
|
||||
Fields: []LogStructuredField{},
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -354,7 +381,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -43,7 +43,7 @@ func (l *Listener) SetLookupFunc(lookupFunc func(netip.AddrPort) (socks5.Addr, e
|
||||
l.lookupFunc = lookupFunc
|
||||
}
|
||||
|
||||
func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||
func (l *Listener) handleRedir(conn net.Conn, tunnel C.Tunnel) {
|
||||
if l.lookupFunc == nil {
|
||||
log.Errorln("[Auto Redirect] lookup function is nil")
|
||||
return
|
||||
@ -58,10 +58,10 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...)
|
||||
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, l.additions...))
|
||||
}
|
||||
|
||||
func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
|
||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-REDIR"),
|
||||
@ -87,7 +87,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*
|
||||
}
|
||||
continue
|
||||
}
|
||||
go rl.handleRedir(c, in)
|
||||
go rl.handleRedir(c, tunnel)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -1,72 +1,19 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ListenPrefix netip.Prefix
|
||||
|
||||
func (p ListenPrefix) MarshalJSON() ([]byte, error) {
|
||||
prefix := netip.Prefix(p)
|
||||
if !prefix.IsValid() {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
return json.Marshal(prefix.String())
|
||||
}
|
||||
|
||||
func (p ListenPrefix) MarshalYAML() (interface{}, error) {
|
||||
prefix := netip.Prefix(p)
|
||||
if !prefix.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
return prefix.String(), nil
|
||||
}
|
||||
|
||||
func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error {
|
||||
var value string
|
||||
err := json.Unmarshal(bytes, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p = ListenPrefix(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error {
|
||||
var value string
|
||||
err := node.Decode(&value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p = ListenPrefix(prefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p ListenPrefix) Build() netip.Prefix {
|
||||
return netip.Prefix(p)
|
||||
}
|
||||
|
||||
func StringSliceToListenPrefixSlice(ss []string) ([]ListenPrefix, error) {
|
||||
lps := make([]ListenPrefix, 0, len(ss))
|
||||
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
|
||||
lps := make([]netip.Prefix, 0, len(ss))
|
||||
for _, s := range ss {
|
||||
prefix, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lps = append(lps, ListenPrefix(prefix))
|
||||
lps = append(lps, prefix)
|
||||
}
|
||||
return lps, nil
|
||||
}
|
||||
@ -80,20 +27,22 @@ type Tun struct {
|
||||
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
|
||||
RedirectToTun []string `yaml:"-" json:"-"`
|
||||
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
Inet4RouteAddress []ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress []ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||
Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
}
|
||||
|
@ -11,9 +11,12 @@ type VmessUser struct {
|
||||
}
|
||||
|
||||
type VmessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VmessUser
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VmessUser
|
||||
WsPath string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
func (t VmessServer) String() string {
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Addition) *http.Client {
|
||||
func newClient(srcConn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// from http.DefaultTransport
|
||||
@ -32,7 +32,7 @@ func newClient(source net.Addr, in chan<- C.ConnContext, additions ...inbound.Ad
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewHTTP(dstAddr, source, right, additions...)
|
||||
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, srcConn, right, additions...))
|
||||
|
||||
return left, nil
|
||||
},
|
||||
|
7
listener/http/patch.go
Normal file
7
listener/http/patch.go
Normal file
@ -0,0 +1,7 @@
|
||||
package http
|
||||
|
||||
import "net"
|
||||
|
||||
func (l *Listener) Listener() net.Listener {
|
||||
return l.listener
|
||||
}
|
@ -14,8 +14,8 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
|
||||
client := newClient(c.RemoteAddr(), in, additions...)
|
||||
func HandleConn(c net.Conn, tunnel C.Tunnel, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
|
||||
client := newClient(c, tunnel, additions...)
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
conn := N.NewBufferedConn(c)
|
||||
@ -48,7 +48,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
break // close connection
|
||||
}
|
||||
|
||||
in <- inbound.NewHTTPS(request, conn, additions...)
|
||||
tunnel.HandleTCPConn(inbound.NewHTTPS(request, conn, additions...))
|
||||
|
||||
return // hijack connection
|
||||
}
|
||||
@ -61,7 +61,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
request.RequestURI = ""
|
||||
|
||||
if isUpgradeRequest(request) {
|
||||
handleUpgrade(conn, request, in, additions...)
|
||||
handleUpgrade(conn, request, tunnel, additions...)
|
||||
|
||||
return // hijack connection
|
||||
}
|
||||
@ -100,6 +100,9 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
|
||||
func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response {
|
||||
authenticator := authStore.Authenticator()
|
||||
if inbound.SkipAuthRemoteAddress(request.RemoteAddr) {
|
||||
authenticator = nil
|
||||
}
|
||||
if authenticator != nil {
|
||||
credential := parseBasicProxyAuthorization(request)
|
||||
if credential == "" {
|
||||
|
@ -30,11 +30,11 @@ func (l *Listener) Close() error {
|
||||
return l.listener.Close()
|
||||
}
|
||||
|
||||
func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
|
||||
return NewWithAuthenticate(addr, in, true, additions...)
|
||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||
return NewWithAuthenticate(addr, tunnel, true, additions...)
|
||||
}
|
||||
|
||||
func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
|
||||
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-HTTP"),
|
||||
@ -65,7 +65,10 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool
|
||||
}
|
||||
continue
|
||||
}
|
||||
go HandleConn(conn, in, c, additions...)
|
||||
if t, ok := conn.(*net.TCPConn); ok {
|
||||
t.SetKeepAlive(false)
|
||||
}
|
||||
go HandleConn(conn, tunnel, c, additions...)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -25,7 +25,7 @@ func isUpgradeRequest(req *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext, additions ...inbound.Addition) {
|
||||
func handleUpgrade(conn net.Conn, request *http.Request, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||
defer conn.Close()
|
||||
|
||||
removeProxyHeaders(request.Header)
|
||||
@ -43,7 +43,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right, additions...)
|
||||
go tunnel.HandleTCPConn(inbound.NewHTTP(dstAddr, conn, right, additions...))
|
||||
|
||||
var bufferedLeft *N.BufferedConn
|
||||
if request.TLS != nil {
|
||||
|
@ -61,7 +61,7 @@ func (b *Base) RawAddress() string {
|
||||
}
|
||||
|
||||
// Listen implements constant.InboundListener
|
||||
func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error {
|
||||
func (*Base) Listen(tunnel C.Tunnel) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user