Compare commits
91 Commits
android-op
...
v1.15.1
Author | SHA1 | Date | |
---|---|---|---|
65071ea7d1 | |||
fa94403629 | |||
1beb2919e7 | |||
75c5d0482e | |||
55bcabdf46 | |||
abf80601e1 | |||
26f97b45d6 | |||
29315ce8e5 | |||
65f84d21ea | |||
e3ac58bc51 | |||
c66438d794 | |||
411e587460 | |||
6cc9c68458 | |||
d1c858d7ff | |||
3eef1ee064 | |||
514d374b8c | |||
a2334430c1 | |||
c8a3d6edd9 | |||
bda2ca3c13 | |||
f4b734c74c | |||
c2cdf43239 | |||
b939c81d3e | |||
0e92496eeb | |||
ea482598e0 | |||
16f3567ddc | |||
73f8da091e | |||
6bdaadc581 | |||
73a2cf593e | |||
665bfcab2d | |||
8be860472a | |||
1ec74f13f7 | |||
564b834e00 | |||
da04e00767 | |||
e0faffbfbd | |||
a0c7641ad5 | |||
1f592c43de | |||
4d7350923c | |||
76a7945994 | |||
a2bbd1cc8d | |||
4ec66d299a | |||
4e46cbfbde | |||
1a44dcee55 | |||
6c7d1657a5 | |||
38e210a851 | |||
359ee70daa | |||
8d1251f128 | |||
fb6a032872 | |||
47ad8e08be | |||
e1af4ddda3 | |||
58e05c42c9 | |||
880cc90e10 | |||
a4334e1d52 | |||
3ba94842cc | |||
a266589faf | |||
d9319ec09a | |||
070f8f8949 | |||
bf3c6a044c | |||
d6d2d90502 | |||
a1d0f4c6ee | |||
d569d8186d | |||
9b7aab1fc7 | |||
3c717097cb | |||
8293b7fdae | |||
0ba415866e | |||
53b41ca166 | |||
8a75f78e63 | |||
d9692c6366 | |||
f4b0062dfc | |||
b9ffc82e53 | |||
78aaea6a45 | |||
3645fbf161 | |||
a1d0f22132 | |||
fa73b0f4bf | |||
3b76a8b839 | |||
667f42dcdc | |||
dfbe09860f | |||
9e20f9c26a | |||
f968d0cb82 | |||
2ad84f4379 | |||
c7aa16426f | |||
5987f8e3b5 | |||
3a8eb72de2 | |||
33abbdfd24 | |||
0703d6cbff | |||
10d2d14938 | |||
691cf1d8d6 | |||
d1decb8e58 | |||
7d04904109 | |||
a5acd3aa97 | |||
eea9a12560 | |||
0a4570b55c |
32
.github/genReleaseNote.sh
vendored
Executable file
32
.github/genReleaseNote.sh
vendored
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/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
|
51
.github/workflows/android-branch-auto-sync.yml
vendored
51
.github/workflows/android-branch-auto-sync.yml
vendored
@ -1,51 +0,0 @@
|
|||||||
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
|
|
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@ -94,11 +94,6 @@ jobs:
|
|||||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
shell: bash
|
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
|
- name: Set variables
|
||||||
if: ${{github.ref_name=='Meta'}}
|
if: ${{github.ref_name=='Meta'}}
|
||||||
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
@ -147,7 +142,7 @@ jobs:
|
|||||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
|
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: r26
|
ndk-version: r25b
|
||||||
add-to-path: false
|
add-to-path: false
|
||||||
local-cache: true
|
local-cache: true
|
||||||
|
|
||||||
@ -209,7 +204,7 @@ jobs:
|
|||||||
|
|
||||||
Upload-Prerelease:
|
Upload-Prerelease:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }}
|
if: ${{ github.ref_type=='branch' }}
|
||||||
needs: [Build]
|
needs: [Build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -267,6 +262,23 @@ jobs:
|
|||||||
needs: [Build]
|
needs: [Build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
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
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: artifact
|
name: artifact
|
||||||
@ -283,6 +295,7 @@ jobs:
|
|||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ github.ref_name }}
|
||||||
files: bin/*
|
files: bin/*
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
body_path: release.md
|
||||||
|
|
||||||
Docker:
|
Docker:
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
28
.github/workflows/cmfa-update-deps-trigger.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: CMFA auto update-dependencies trigger
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
pull_request_target:
|
|
||||||
branches:
|
|
||||||
- Alpha
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-dependencies:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: tibdex/github-app-token@v1
|
|
||||||
id: generate-token
|
|
||||||
with:
|
|
||||||
app_id: ${{ secrets.MAINTAINER_APPID }}
|
|
||||||
private_key: ${{ secrets.MAINTAINER_APP_PRIVATE_KEY }}
|
|
||||||
|
|
||||||
- name: Trigger update-dependencies
|
|
||||||
run: |
|
|
||||||
curl -X POST https://api.github.com/repos/MetaCubeX/ClashMetaForAndroid/dispatches \
|
|
||||||
-H "Accept: application/vnd.github.everest-preview+json" \
|
|
||||||
-H "Authorization: token ${{ steps.generate-token.outputs.token }}" \
|
|
||||||
-d '{"event_type": "core-updated"}'
|
|
||||||
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
|
||||||
|
|
15
.github/workflows/delete.yml
vendored
Normal file
15
.github/workflows/delete.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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
|
320
README.md
320
README.md
@ -21,52 +21,261 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Local HTTP/HTTPS/SOCKS server with authentication support
|
- Local HTTP/HTTPS/SOCKS server with authentication support
|
||||||
- VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support
|
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
|
||||||
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
||||||
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
|
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
|
||||||
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
|
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
|
||||||
based off latency
|
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
|
||||||
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config
|
|
||||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||||
- Comprehensive HTTP RESTful API controller
|
- Comprehensive HTTP RESTful API controller
|
||||||
|
|
||||||
## Dashboard
|
## Wiki
|
||||||
|
Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki).
|
||||||
|
|
||||||
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).
|
## Build
|
||||||
|
|
||||||
## Configration example
|
You should install [golang](https://go.dev) first.
|
||||||
|
|
||||||
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
|
Then get the source code of Clash.Meta:
|
||||||
|
|
||||||
## Docs
|
|
||||||
|
|
||||||
Documentation can be found in [Clash.Meta Docs](https://clash-meta.wiki).
|
|
||||||
|
|
||||||
## For development
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
[Go 1.20 or newer](https://go.dev/dl/)
|
|
||||||
|
|
||||||
Build Clash.Meta:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/MetaCubeX/Clash.Meta.git
|
git clone https://github.com/MetaCubeX/Clash.Meta.git
|
||||||
cd Clash.Meta && go mod download
|
cd Clash.Meta && go mod download
|
||||||
go build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Set go proxy if a connection to GitHub is not possible:
|
If you can't visit github,you should set proxy first:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go env -w GOPROXY=https://goproxy.io,direct
|
go env -w GOPROXY=https://goproxy.io,direct
|
||||||
```
|
```
|
||||||
|
|
||||||
Build with gvisor tun stack:
|
Now you can build it:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need gvisor for tun stack, build with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build -tags with_gvisor
|
go build -tags with_gvisor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- ## Advanced usage of this fork -->
|
||||||
|
|
||||||
|
<!-- ### DNS configuration
|
||||||
|
|
||||||
|
Support `geosite` with `fallback-filter`.
|
||||||
|
|
||||||
|
Restore `Redir remote resolution`.
|
||||||
|
|
||||||
|
Support resolve ip with a `Proxy Tunnel`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxy-groups:
|
||||||
|
- name: DNS
|
||||||
|
type: url-test
|
||||||
|
use:
|
||||||
|
- HK
|
||||||
|
url: http://cp.cloudflare.com
|
||||||
|
interval: 180
|
||||||
|
lazy: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dns:
|
||||||
|
enable: true
|
||||||
|
use-hosts: true
|
||||||
|
ipv6: false
|
||||||
|
enhanced-mode: redir-host
|
||||||
|
fake-ip-range: 198.18.0.1/16
|
||||||
|
listen: 127.0.0.1:6868
|
||||||
|
default-nameserver:
|
||||||
|
- 119.29.29.29
|
||||||
|
- 114.114.114.114
|
||||||
|
nameserver:
|
||||||
|
- https://doh.pub/dns-query
|
||||||
|
- tls://223.5.5.5:853
|
||||||
|
fallback:
|
||||||
|
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
|
||||||
|
- "tls://8.8.4.4:853#DNS"
|
||||||
|
fallback-filter:
|
||||||
|
geoip: false
|
||||||
|
geosite:
|
||||||
|
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
|
||||||
|
domain:
|
||||||
|
- +.example.com
|
||||||
|
ipcidr:
|
||||||
|
- 0.0.0.0/32
|
||||||
|
```
|
||||||
|
|
||||||
|
### TUN configuration
|
||||||
|
|
||||||
|
Supports macOS, Linux and Windows.
|
||||||
|
|
||||||
|
Built-in [Wintun](https://www.wintun.net) driver.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Enable the TUN listener
|
||||||
|
tun:
|
||||||
|
enable: true
|
||||||
|
stack: system # system/gvisor
|
||||||
|
dns-hijack:
|
||||||
|
- 0.0.0.0:53 # additional dns server listen on TUN
|
||||||
|
auto-route: true # auto set global route
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rules configuration
|
||||||
|
|
||||||
|
- Support rule `GEOSITE`.
|
||||||
|
- Support rule-providers `RULE-SET`.
|
||||||
|
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
|
||||||
|
- Support `network` condition for all rules.
|
||||||
|
- Support source IPCIDR condition for all rules, just append to the end.
|
||||||
|
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
# network(tcp/udp) condition for all rules
|
||||||
|
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
|
||||||
|
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
|
||||||
|
|
||||||
|
# multiport condition for rules SRC-PORT and DST-PORT
|
||||||
|
- DST-PORT,123/136/137-139,DIRECT,udp
|
||||||
|
|
||||||
|
# rule GEOSITE
|
||||||
|
- GEOSITE,category-ads-all,REJECT
|
||||||
|
- GEOSITE,icloud@cn,DIRECT
|
||||||
|
- GEOSITE,apple@cn,DIRECT
|
||||||
|
- GEOSITE,apple-cn,DIRECT
|
||||||
|
- GEOSITE,microsoft@cn,DIRECT
|
||||||
|
- GEOSITE,facebook,PROXY
|
||||||
|
- GEOSITE,youtube,PROXY
|
||||||
|
- GEOSITE,geolocation-cn,DIRECT
|
||||||
|
- GEOSITE,geolocation-!cn,PROXY
|
||||||
|
|
||||||
|
# source IPCIDR condition for all rules in gateway proxy
|
||||||
|
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
|
||||||
|
|
||||||
|
- GEOIP,telegram,PROXY,no-resolve
|
||||||
|
- GEOIP,private,DIRECT,no-resolve
|
||||||
|
- GEOIP,cn,DIRECT
|
||||||
|
|
||||||
|
- MATCH,PROXY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proxies configuration
|
||||||
|
|
||||||
|
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
|
||||||
|
|
||||||
|
Support `Policy Group Filter`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxy-groups:
|
||||||
|
- name: 🚀 HK Group
|
||||||
|
type: select
|
||||||
|
use:
|
||||||
|
- ALL
|
||||||
|
filter: "HK"
|
||||||
|
|
||||||
|
- name: 🚀 US Group
|
||||||
|
type: select
|
||||||
|
use:
|
||||||
|
- ALL
|
||||||
|
filter: "US"
|
||||||
|
|
||||||
|
proxy-providers:
|
||||||
|
ALL:
|
||||||
|
type: http
|
||||||
|
url: "xxxxx"
|
||||||
|
interval: 3600
|
||||||
|
path: "xxxxx"
|
||||||
|
health-check:
|
||||||
|
enable: true
|
||||||
|
interval: 600
|
||||||
|
url: http://www.gstatic.com/generate_204
|
||||||
|
```
|
||||||
|
|
||||||
|
Support outbound transport protocol `VLESS`.
|
||||||
|
|
||||||
|
The XTLS support (TCP/UDP) transport by the XRAY-CORE.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxies:
|
||||||
|
- name: "vless"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
servername: example.com # AKA SNI
|
||||||
|
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||||
|
# skip-cert-verify: true
|
||||||
|
|
||||||
|
- name: "vless-ws"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
tls: true
|
||||||
|
udp: true
|
||||||
|
network: ws
|
||||||
|
servername: example.com # priority over wss host
|
||||||
|
# skip-cert-verify: true
|
||||||
|
ws-opts:
|
||||||
|
path: /path
|
||||||
|
headers: { Host: example.com, Edge: "12a00c4.fm.huawei.com:82897" }
|
||||||
|
|
||||||
|
- name: "vless-grpc"
|
||||||
|
type: vless
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
uuid: uuid
|
||||||
|
tls: true
|
||||||
|
udp: true
|
||||||
|
network: grpc
|
||||||
|
servername: example.com # priority over wss host
|
||||||
|
# skip-cert-verify: true
|
||||||
|
grpc-opts:
|
||||||
|
grpc-service-name: grpcname
|
||||||
|
```
|
||||||
|
|
||||||
|
Support outbound transport protocol `Wireguard`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxies:
|
||||||
|
- name: "wg"
|
||||||
|
type: wireguard
|
||||||
|
server: 162.159.192.1
|
||||||
|
port: 2480
|
||||||
|
ip: 172.16.0.2
|
||||||
|
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
|
||||||
|
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||||
|
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||||
|
udp: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Support outbound transport protocol `Tuic`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxies:
|
||||||
|
- name: "tuic"
|
||||||
|
server: www.example.com
|
||||||
|
port: 10443
|
||||||
|
type: tuic
|
||||||
|
token: TOKEN
|
||||||
|
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||||
|
# heartbeat-interval: 10000
|
||||||
|
# alpn: [h3]
|
||||||
|
# disable-sni: true
|
||||||
|
reduce-rtt: true
|
||||||
|
# request-timeout: 8000
|
||||||
|
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||||
|
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||||
|
# max-udp-relay-packet-size: 1500
|
||||||
|
# fast-open: true
|
||||||
|
# skip-cert-verify: true
|
||||||
|
``` -->
|
||||||
|
|
||||||
### IPTABLES configuration
|
### IPTABLES configuration
|
||||||
|
|
||||||
Work on Linux OS which supported `iptables`
|
Work on Linux OS which supported `iptables`
|
||||||
@ -80,10 +289,71 @@ iptables:
|
|||||||
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
|
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debugging
|
### General installation guide for Linux
|
||||||
|
|
||||||
|
- Create user given name `clash-meta`
|
||||||
|
|
||||||
|
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
|
||||||
|
|
||||||
|
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
|
||||||
|
|
||||||
|
- Create folder `/etc/Clash-Meta/` as working directory
|
||||||
|
|
||||||
|
Run Meta Kernel by user `clash-meta` as a daemon.
|
||||||
|
|
||||||
|
Create the systemd configuration file at `/etc/systemd/system/Clash-Meta.service`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Clash-Meta Daemon, Another Clash Kernel.
|
||||||
|
After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=clash-meta
|
||||||
|
Group=clash-meta
|
||||||
|
LimitNPROC=500
|
||||||
|
LimitNOFILE=1000000
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||||
|
Restart=always
|
||||||
|
ExecStartPre=/usr/bin/sleep 1s
|
||||||
|
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Launch clashd on system startup with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ systemctl enable Clash-Meta
|
||||||
|
```
|
||||||
|
|
||||||
|
Launch clashd immediately with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ systemctl start Clash-Meta
|
||||||
|
```
|
||||||
|
|
||||||
|
### Display Process name
|
||||||
|
|
||||||
|
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
|
||||||
|
|
||||||
|
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
|
||||||
|
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
If you want to build an application that uses clash as a library, check out the
|
||||||
|
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
|
||||||
|
|
||||||
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
|
|
||||||
API.
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var UnifiedDelay = atomic.NewBool(false)
|
var UnifiedDelay = atomic.NewBool(false)
|
||||||
@ -30,15 +28,15 @@ const (
|
|||||||
|
|
||||||
type extraProxyState struct {
|
type extraProxyState struct {
|
||||||
history *queue.Queue[C.DelayHistory]
|
history *queue.Queue[C.DelayHistory]
|
||||||
alive atomic.Bool
|
alive *atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
C.ProxyAdapter
|
C.ProxyAdapter
|
||||||
history *queue.Queue[C.DelayHistory]
|
history *queue.Queue[C.DelayHistory]
|
||||||
alive atomic.Bool
|
alive *atomic.Bool
|
||||||
url string
|
url string
|
||||||
extra *xsync.MapOf[string, *extraProxyState]
|
extra map[string]*extraProxyState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alive implements C.Proxy
|
// Alive implements C.Proxy
|
||||||
@ -48,9 +46,11 @@ func (p *Proxy) Alive() bool {
|
|||||||
|
|
||||||
// AliveForTestUrl implements C.Proxy
|
// AliveForTestUrl implements C.Proxy
|
||||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||||
if state, ok := p.extra.Load(url); ok {
|
if p.extra != nil {
|
||||||
|
if state, ok := p.extra[url]; ok {
|
||||||
return state.alive.Load()
|
return state.alive.Load()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return p.alive.Load()
|
return p.alive.Load()
|
||||||
}
|
}
|
||||||
@ -88,17 +88,17 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
|||||||
for _, item := range queueM {
|
for _, item := range queueM {
|
||||||
histories = append(histories, item)
|
histories = append(histories, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return histories
|
return histories
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelayHistoryForTestUrl implements C.Proxy
|
// DelayHistoryForTestUrl implements C.Proxy
|
||||||
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
||||||
var queueM []C.DelayHistory
|
var queueM []C.DelayHistory
|
||||||
|
if p.extra != nil {
|
||||||
if state, ok := p.extra.Load(url); ok {
|
if state, ok := p.extra[url]; ok {
|
||||||
queueM = state.history.Copy()
|
queueM = state.history.Copy()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if queueM == nil {
|
if queueM == nil {
|
||||||
queueM = p.history.Copy()
|
queueM = p.history.Copy()
|
||||||
@ -112,25 +112,19 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
|
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
|
||||||
extraHistory := map[string][]C.DelayHistory{}
|
extra := map[string][]C.DelayHistory{}
|
||||||
|
if p.extra != nil && len(p.extra) != 0 {
|
||||||
p.extra.Range(func(k string, v *extraProxyState) bool {
|
for testUrl, option := range p.extra {
|
||||||
|
|
||||||
testUrl := k
|
|
||||||
state := v
|
|
||||||
|
|
||||||
histories := []C.DelayHistory{}
|
histories := []C.DelayHistory{}
|
||||||
queueM := state.history.Copy()
|
queueM := option.history.Copy()
|
||||||
|
|
||||||
for _, item := range queueM {
|
for _, item := range queueM {
|
||||||
histories = append(histories, item)
|
histories = append(histories, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
extraHistory[testUrl] = histories
|
extra[testUrl] = histories
|
||||||
|
}
|
||||||
return true
|
}
|
||||||
})
|
return extra
|
||||||
return extraHistory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||||
@ -155,10 +149,12 @@ func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
|
|||||||
alive := p.alive.Load()
|
alive := p.alive.Load()
|
||||||
history := p.history.Last()
|
history := p.history.Last()
|
||||||
|
|
||||||
if state, ok := p.extra.Load(url); ok {
|
if p.extra != nil {
|
||||||
|
if state, ok := p.extra[url]; ok {
|
||||||
alive = state.alive.Load()
|
alive = state.alive.Load()
|
||||||
history = state.history.Last()
|
history = state.history.Last()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !alive {
|
if !alive {
|
||||||
return max
|
return max
|
||||||
@ -217,18 +213,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
|||||||
if alive {
|
if alive {
|
||||||
record.Delay = t
|
record.Delay = t
|
||||||
}
|
}
|
||||||
p.history.Put(record)
|
|
||||||
if p.history.Len() > defaultHistoriesNum {
|
if p.extra == nil {
|
||||||
p.history.Pop()
|
p.extra = map[string]*extraProxyState{}
|
||||||
}
|
}
|
||||||
|
|
||||||
state, ok := p.extra.Load(url)
|
state, ok := p.extra[url]
|
||||||
if !ok {
|
if !ok {
|
||||||
state = &extraProxyState{
|
state = &extraProxyState{
|
||||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||||
alive: atomic.NewBool(true),
|
alive: atomic.NewBool(true),
|
||||||
}
|
}
|
||||||
p.extra.Store(url, state)
|
p.extra[url] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
state.alive.Store(alive)
|
state.alive.Store(alive)
|
||||||
@ -311,12 +307,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||||
return &Proxy{
|
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
|
||||||
ProxyAdapter: adapter,
|
|
||||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
|
||||||
alive: atomic.NewBool(true),
|
|
||||||
url: "",
|
|
||||||
extra: xsync.NewMapOf[*extraProxyState]()}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||||
@ -359,14 +350,14 @@ func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url strin
|
|||||||
return C.OriginalHistory
|
return C.OriginalHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
|
if p.extra == nil {
|
||||||
return C.ExtraHistory
|
store = C.ExtraHistory
|
||||||
|
} else {
|
||||||
|
if _, ok := p.extra[url]; ok {
|
||||||
|
store = C.ExtraHistory
|
||||||
|
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||||
|
store = C.ExtraHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := p.extra.Load(url)
|
|
||||||
if ok {
|
|
||||||
return C.ExtraHistory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Addition func(metadata *C.Metadata)
|
type Addition func(metadata *C.Metadata)
|
||||||
|
|
||||||
func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
|
func (a Addition) Apply(metadata *C.Metadata) {
|
||||||
for _, addition := range additions {
|
a(metadata)
|
||||||
addition(metadata)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithInName(name string) Addition {
|
func WithInName(name string) Addition {
|
||||||
@ -37,29 +33,3 @@ func WithSpecialProxy(specialProxy string) Addition {
|
|||||||
metadata.SpecialProxy = specialProxy
|
metadata.SpecialProxy = specialProxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithDstAddr(addr net.Addr) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
_ = metadata.SetRemoteAddr(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSrcAddr(addr net.Addr) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
m := C.Metadata{}
|
|
||||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
|
||||||
metadata.SrcIP = m.DstIP
|
|
||||||
metadata.SrcPort = m.DstPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithInAddr(addr net.Addr) Addition {
|
|
||||||
return func(metadata *C.Metadata) {
|
|
||||||
m := C.Metadata{}
|
|
||||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
|
||||||
metadata.InIP = m.DstIP
|
|
||||||
metadata.InPort = m.DstPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
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,17 +4,25 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTP receive normal http request and return HTTPContext
|
// NewHTTP receive normal http request and return HTTPContext
|
||||||
func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Addition) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.HTTP
|
metadata.Type = C.HTTP
|
||||||
metadata.RawSrcAddr = srcConn.RemoteAddr()
|
for _, addition := range additions {
|
||||||
metadata.RawDstAddr = srcConn.LocalAddr()
|
addition.Apply(metadata)
|
||||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
}
|
||||||
ApplyAdditions(metadata, additions...)
|
if ip, port, err := parseAddr(source); err == nil {
|
||||||
return conn, metadata
|
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)
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,23 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHTTPS receive CONNECT request and return ConnContext
|
// NewHTTPS receive CONNECT request and return ConnContext
|
||||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *context.ConnContext {
|
||||||
metadata := parseHTTPAddr(request)
|
metadata := parseHTTPAddr(request)
|
||||||
metadata.Type = C.HTTPS
|
metadata.Type = C.HTTPS
|
||||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
for _, addition := range additions {
|
||||||
ApplyAdditions(metadata, additions...)
|
addition.Apply(metadata)
|
||||||
return conn, metadata
|
}
|
||||||
|
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
||||||
|
metadata.SrcIP = ip
|
||||||
|
metadata.SrcPort = port
|
||||||
|
}
|
||||||
|
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
||||||
|
metadata.InIP = ip
|
||||||
|
metadata.InPort = port
|
||||||
|
}
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,38 @@ import (
|
|||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
||||||
|
type PacketAdapter struct {
|
||||||
|
C.UDPPacket
|
||||||
|
metadata *C.Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns destination metadata
|
||||||
|
func (s *PacketAdapter) Metadata() *C.Metadata {
|
||||||
|
return s.metadata
|
||||||
|
}
|
||||||
|
|
||||||
// NewPacket is PacketAdapter generator
|
// NewPacket is PacketAdapter generator
|
||||||
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) {
|
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) C.PacketAdapter {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.UDP
|
metadata.NetWork = C.UDP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
metadata.RawSrcAddr = packet.LocalAddr()
|
for _, addition := range additions {
|
||||||
metadata.RawDstAddr = metadata.UDPAddr()
|
addition.Apply(metadata)
|
||||||
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
|
}
|
||||||
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil {
|
||||||
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
|
metadata.SrcIP = ip
|
||||||
|
metadata.SrcPort = port
|
||||||
|
}
|
||||||
|
if p, ok := packet.(C.UDPPacketInAddr); ok {
|
||||||
|
if ip, port, err := parseAddr(p.InAddr()); err == nil {
|
||||||
|
metadata.InIP = ip
|
||||||
|
metadata.InPort = port
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplyAdditions(metadata, additions...)
|
|
||||||
|
|
||||||
return packet, metadata
|
return &PacketAdapter{
|
||||||
|
packet,
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,51 @@ package inbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSocket receive TCP inbound and return ConnContext
|
// NewSocket receive TCP inbound and return ConnContext
|
||||||
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) {
|
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) *context.ConnContext {
|
||||||
metadata := parseSocksAddr(target)
|
metadata := parseSocksAddr(target)
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = source
|
metadata.Type = source
|
||||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
for _, addition := range additions {
|
||||||
ApplyAdditions(metadata, additions...)
|
addition.Apply(metadata)
|
||||||
return conn, metadata
|
}
|
||||||
|
|
||||||
|
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
|
||||||
|
metadata.SrcIP = ip
|
||||||
|
metadata.SrcPort = port
|
||||||
|
}
|
||||||
|
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
|
||||||
|
metadata.InIP = ip
|
||||||
|
metadata.InPort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInner(conn net.Conn, address string) *context.ConnContext {
|
||||||
|
metadata := &C.Metadata{}
|
||||||
|
metadata.NetWork = C.TCP
|
||||||
|
metadata.Type = C.INNER
|
||||||
|
metadata.DNSMode = C.DNSNormal
|
||||||
|
metadata.Process = C.ClashName
|
||||||
|
if h, port, err := net.SplitHostPort(address); err == nil {
|
||||||
|
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||||
|
metadata.DstPort = uint16(port)
|
||||||
|
}
|
||||||
|
if ip, err := netip.ParseAddr(h); err == nil {
|
||||||
|
metadata.DstIP = ip
|
||||||
|
} else {
|
||||||
|
metadata.Host = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.NewConnContext(conn, metadata)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -61,3 +62,29 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
|||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
|
||||||
|
// Filter when net.Addr interface is nil
|
||||||
|
if addr == nil {
|
||||||
|
return netip.Addr{}, 0, errors.New("nil addr")
|
||||||
|
}
|
||||||
|
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
||||||
|
ip, port, err := parseAddr(rawAddr.RawAddr())
|
||||||
|
if err == nil {
|
||||||
|
return ip, port, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrStr := addr.String()
|
||||||
|
host, port, err := net.SplitHostPort(addrStr)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var uint16Port uint16
|
||||||
|
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||||
|
uint16Port = uint16(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := netip.ParseAddr(host)
|
||||||
|
return ip, uint16Port, err
|
||||||
|
}
|
||||||
|
@ -3,9 +3,6 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -15,11 +12,6 @@ type Direct struct {
|
|||||||
*Base
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectOption struct {
|
|
||||||
BasicOption
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
||||||
@ -27,7 +19,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return NewConn(c, d), nil
|
return NewConn(c, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,28 +33,13 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
}
|
}
|
||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newPacketConn(pc, d), nil
|
return newPacketConn(pc, d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirectWithOption(option DirectOption) *Direct {
|
|
||||||
return &Direct{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
tp: C.Direct,
|
|
||||||
udp: true,
|
|
||||||
tfo: option.TFO,
|
|
||||||
mpTcp: option.MPTCP,
|
|
||||||
iface: option.Interface,
|
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDirect() *Direct {
|
func NewDirect() *Direct {
|
||||||
return &Direct{
|
return &Direct{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -7,16 +7,14 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,7 +74,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
@ -157,15 +155,21 @@ func NewHttp(option HttpOption) (*Http, error) {
|
|||||||
if option.SNI != "" {
|
if option.SNI != "" {
|
||||||
sni = option.SNI
|
sni = option.SNI
|
||||||
}
|
}
|
||||||
var err error
|
if len(option.Fingerprint) == 0 {
|
||||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ServerName: sni,
|
ServerName: sni,
|
||||||
}, option.Fingerprint)
|
})
|
||||||
if err != nil {
|
} else {
|
||||||
|
var err error
|
||||||
|
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||||
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
|
ServerName: sni,
|
||||||
|
}, option.Fingerprint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Http{
|
return &Http{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -2,11 +2,16 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,9 +19,9 @@ import (
|
|||||||
"github.com/metacubex/quic-go/congestion"
|
"github.com/metacubex/quic-go/congestion"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
||||||
@ -38,6 +43,8 @@ const (
|
|||||||
DefaultHopInterval = 10
|
DefaultHopInterval = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
||||||
|
|
||||||
type Hysteria struct {
|
type Hysteria struct {
|
||||||
*Base
|
*Base
|
||||||
|
|
||||||
@ -46,7 +53,7 @@ type Hysteria struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
|
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -113,12 +120,12 @@ type HysteriaOption struct {
|
|||||||
|
|
||||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||||
var up, down uint64
|
var up, down uint64
|
||||||
up = StringToBps(c.Up)
|
up = stringToBps(c.Up)
|
||||||
if up == 0 {
|
if up == 0 {
|
||||||
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
||||||
}
|
}
|
||||||
|
|
||||||
down = StringToBps(c.Down)
|
down = stringToBps(c.Down)
|
||||||
if down == 0 {
|
if down == 0 {
|
||||||
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
|
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
|
||||||
}
|
}
|
||||||
@ -146,11 +153,38 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tls.VersionTLS13,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
var err error
|
var err error
|
||||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
if len(option.CustomCA) > 0 {
|
||||||
|
bs, err = os.ReadFile(option.CustomCA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
|
||||||
|
}
|
||||||
|
} else if option.CustomCAString != "" {
|
||||||
|
bs = []byte(option.CustomCAString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bs) > 0 {
|
||||||
|
block, _ := pem.Decode(bs)
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("CA cert is not PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
fpBytes := sha256.Sum256(block.Bytes)
|
||||||
|
if len(option.Fingerprint) == 0 {
|
||||||
|
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(option.Fingerprint) != 0 {
|
||||||
|
var err error
|
||||||
|
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
if len(option.ALPN) > 0 {
|
if len(option.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = option.ALPN
|
tlsConfig.NextProtos = option.ALPN
|
||||||
@ -234,6 +268,42 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringToBps(s string) uint64 {
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// when have not unit, use Mbps
|
||||||
|
if v, err := strconv.Atoi(s); err == nil {
|
||||||
|
return stringToBps(fmt.Sprintf("%d Mbps", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
m := rateStringRegexp.FindStringSubmatch(s)
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var n uint64
|
||||||
|
switch m[2] {
|
||||||
|
case "K":
|
||||||
|
n = 1 << 10
|
||||||
|
case "M":
|
||||||
|
n = 1 << 20
|
||||||
|
case "G":
|
||||||
|
n = 1 << 30
|
||||||
|
case "T":
|
||||||
|
n = 1 << 40
|
||||||
|
default:
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
v, _ := strconv.ParseUint(m[1], 10, 64)
|
||||||
|
n = v * n
|
||||||
|
if m[3] == "b" {
|
||||||
|
// Bits, need to convert to bytes
|
||||||
|
n = n >> 3
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
type hyPacketConn struct {
|
type hyPacketConn struct {
|
||||||
core.UDPConn
|
core.UDPConn
|
||||||
}
|
}
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
tuicCommon "github.com/Dreamacro/clash/transport/tuic/common"
|
|
||||||
|
|
||||||
"github.com/metacubex/sing-quic/hysteria2"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hysteria2 struct {
|
|
||||||
*Base
|
|
||||||
|
|
||||||
option *Hysteria2Option
|
|
||||||
client *hysteria2.Client
|
|
||||||
dialer proxydialer.SingDialer
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hysteria2Option struct {
|
|
||||||
BasicOption
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
Server string `proxy:"server"`
|
|
||||||
Port int `proxy:"port"`
|
|
||||||
Up string `proxy:"up,omitempty"`
|
|
||||||
Down string `proxy:"down,omitempty"`
|
|
||||||
Password string `proxy:"password,omitempty"`
|
|
||||||
Obfs string `proxy:"obfs,omitempty"`
|
|
||||||
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
|
||||||
SNI string `proxy:"sni,omitempty"`
|
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
CustomCA string `proxy:"ca,omitempty"`
|
|
||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
|
||||||
CWND int `proxy:"cwnd,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
||||||
options := h.Base.DialOptions(opts...)
|
|
||||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
|
||||||
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewConn(CN.NewRefConn(c, h), h), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
|
||||||
options := h.Base.DialOptions(opts...)
|
|
||||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
|
||||||
pc, err := h.client.ListenPacket(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if pc == nil {
|
|
||||||
return nil, errors.New("packetConn is nil")
|
|
||||||
}
|
|
||||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeHysteria2(h *Hysteria2) {
|
|
||||||
if h.client != nil {
|
|
||||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
|
||||||
var salamanderPassword string
|
|
||||||
if len(option.Obfs) > 0 {
|
|
||||||
if option.ObfsPassword == "" {
|
|
||||||
return nil, errors.New("missing obfs password")
|
|
||||||
}
|
|
||||||
switch option.Obfs {
|
|
||||||
case hysteria2.ObfsTypeSalamander:
|
|
||||||
salamanderPassword = option.ObfsPassword
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName := option.Server
|
|
||||||
if option.SNI != "" {
|
|
||||||
serverName = option.SNI
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: serverName,
|
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
|
||||||
MinVersion: tls.VersionTLS13,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(option.ALPN) > 0 {
|
|
||||||
tlsConfig.NextProtos = option.ALPN
|
|
||||||
}
|
|
||||||
|
|
||||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
|
||||||
|
|
||||||
clientOptions := hysteria2.ClientOptions{
|
|
||||||
Context: context.TODO(),
|
|
||||||
Dialer: singDialer,
|
|
||||||
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
|
||||||
SendBPS: StringToBps(option.Up),
|
|
||||||
ReceiveBPS: StringToBps(option.Down),
|
|
||||||
SalamanderPassword: salamanderPassword,
|
|
||||||
Password: option.Password,
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
UDPDisabled: false,
|
|
||||||
CWND: option.CWND,
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := hysteria2.NewClient(clientOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound := &Hysteria2{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
addr: addr,
|
|
||||||
tp: C.Hysteria2,
|
|
||||||
udp: true,
|
|
||||||
iface: option.Interface,
|
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
|
||||||
},
|
|
||||||
option: &option,
|
|
||||||
client: client,
|
|
||||||
dialer: singDialer,
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(outbound, closeHysteria2)
|
|
||||||
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
@ -15,10 +15,6 @@ type Reject struct {
|
|||||||
*Base
|
*Base
|
||||||
}
|
}
|
||||||
|
|
||||||
type RejectOption struct {
|
|
||||||
Name string `proxy:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
return NewConn(nopConn{}, r), nil
|
return NewConn(nopConn{}, r), nil
|
||||||
@ -29,16 +25,6 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
return newPacketConn(nopPacketConn{}, r), nil
|
return newPacketConn(nopPacketConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRejectWithOption(option RejectOption) *Reject {
|
|
||||||
return &Reject{
|
|
||||||
Base: &Base{
|
|
||||||
name: option.Name,
|
|
||||||
tp: C.Direct,
|
|
||||||
udp: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReject() *Reject {
|
func NewReject() *Reject {
|
||||||
return &Reject{
|
return &Reject{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||||
|
|
||||||
restlsC "github.com/3andne/restls-client-go"
|
restlsC "github.com/3andne/restls-client-go"
|
||||||
shadowsocks "github.com/metacubex/sing-shadowsocks2"
|
"github.com/metacubex/sing-shadowsocks2"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
@ -123,9 +123,9 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if useEarly {
|
if useEarly {
|
||||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil
|
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
||||||
} else {
|
} else {
|
||||||
return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
@ -294,6 +294,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
||||||
|
restlsConfig.SessionTicketsDisabled = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
|
@ -3,6 +3,7 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
CN "github.com/Dreamacro/clash/common/net"
|
||||||
@ -14,13 +15,14 @@ import (
|
|||||||
mux "github.com/sagernet/sing-mux"
|
mux "github.com/sagernet/sing-mux"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SingMux struct {
|
type SingMux struct {
|
||||||
C.ProxyAdapter
|
C.ProxyAdapter
|
||||||
base ProxyBase
|
base ProxyBase
|
||||||
client *mux.Client
|
client *mux.Client
|
||||||
dialer proxydialer.SingDialer
|
dialer *muxSingDialer
|
||||||
onlyTcp bool
|
onlyTcp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +41,28 @@ type ProxyBase interface {
|
|||||||
DialOptions(opts ...dialer.Option) []dialer.Option
|
DialOptions(opts ...dialer.Option) []dialer.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type muxSingDialer struct {
|
||||||
|
dialer dialer.Dialer
|
||||||
|
proxy C.ProxyAdapter
|
||||||
|
statistic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.Dialer = (*muxSingDialer)(nil)
|
||||||
|
|
||||||
|
func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
||||||
|
return cDialer.DialContext(ctx, network, destination.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
||||||
|
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
options := s.base.DialOptions(opts...)
|
options := s.base.DialOptions(opts...)
|
||||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
s.dialer.dialer = dialer.NewDialer(options...)
|
||||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -54,7 +74,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
}
|
}
|
||||||
options := s.base.DialOptions(opts...)
|
options := s.base.DialOptions(opts...)
|
||||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
s.dialer.dialer = dialer.NewDialer(options...)
|
||||||
|
|
||||||
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
@ -94,7 +114,7 @@ func closeSingMux(s *SingMux) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
|
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
|
||||||
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
|
singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
|
||||||
client, err := mux.NewClient(mux.Options{
|
client, err := mux.NewClient(mux.Options{
|
||||||
Dialer: singDialer,
|
Dialer: singDialer,
|
||||||
Protocol: option.Protocol,
|
Protocol: option.Protocol,
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
@ -94,7 +93,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
@ -122,7 +121,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
|
|
||||||
err = snell.WriteUDPHeader(c, s.version)
|
err = snell.WriteUDPHeader(c, s.version)
|
||||||
@ -208,7 +207,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
@ -82,7 +80,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
@ -128,7 +126,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
var user *socks5.User
|
var user *socks5.User
|
||||||
if ss.user != "" {
|
if ss.user != "" {
|
||||||
user = &socks5.User{
|
user = &socks5.User{
|
||||||
@ -137,8 +135,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
||||||
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("client hanshake error: %w", err)
|
err = fmt.Errorf("client hanshake error: %w", err)
|
||||||
return
|
return
|
||||||
@ -158,7 +155,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
bindUDPAddr.IP = serverAddr.IP
|
bindUDPAddr.IP = serverAddr.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
|
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -182,12 +179,15 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
|||||||
ServerName: option.Server,
|
ServerName: option.Server,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(option.Fingerprint) == 0 {
|
||||||
|
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
} else {
|
||||||
var err error
|
var err error
|
||||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -8,8 +8,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -133,7 +131,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
@ -186,7 +184,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
|||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c, err = t.plainStream(ctx, c)
|
c, err = t.plainStream(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
@ -270,7 +268,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,11 +279,14 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
ServerName: tOption.ServerName,
|
ServerName: tOption.ServerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(option.Fingerprint) == 0 {
|
||||||
|
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
} else {
|
||||||
var err error
|
var err error
|
||||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
||||||
|
|
||||||
|
@ -2,25 +2,25 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
"github.com/Dreamacro/clash/transport/tuic"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/common/uot"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tuic struct {
|
type Tuic struct {
|
||||||
@ -59,9 +59,6 @@ type TuicOption struct {
|
|||||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
|
|
||||||
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
|
||||||
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -85,32 +82,6 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
|
|||||||
|
|
||||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
if t.option.UDPOverStream {
|
|
||||||
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
|
|
||||||
uotMetadata := *metadata
|
|
||||||
uotMetadata.Host = uotDestination.Fqdn
|
|
||||||
uotMetadata.DstPort = uotDestination.Port
|
|
||||||
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
|
|
||||||
if !metadata.Resolved() {
|
|
||||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("can't resolve ip")
|
|
||||||
}
|
|
||||||
metadata.DstIP = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
|
||||||
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
|
|
||||||
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
|
|
||||||
} else {
|
|
||||||
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -158,11 +129,38 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
tlsConfig.ServerName = option.SNI
|
tlsConfig.ServerName = option.SNI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
var err error
|
var err error
|
||||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
if len(option.CustomCA) > 0 {
|
||||||
|
bs, err = os.ReadFile(option.CustomCA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
|
||||||
|
}
|
||||||
|
} else if option.CustomCAString != "" {
|
||||||
|
bs = []byte(option.CustomCAString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bs) > 0 {
|
||||||
|
block, _ := pem.Decode(bs)
|
||||||
|
if block == nil {
|
||||||
|
return nil, fmt.Errorf("CA cert is not PEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
fpBytes := sha256.Sum256(block.Bytes)
|
||||||
|
if len(option.Fingerprint) == 0 {
|
||||||
|
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(option.Fingerprint) != 0 {
|
||||||
|
var err error
|
||||||
|
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||||
tlsConfig.NextProtos = option.ALPN
|
tlsConfig.NextProtos = option.ALPN
|
||||||
@ -241,14 +239,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
switch option.UDPOverStreamVersion {
|
|
||||||
case uot.Version, uot.LegacyVersion:
|
|
||||||
case 0:
|
|
||||||
option.UDPOverStreamVersion = uot.LegacyVersion
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &Tuic{
|
t := &Tuic{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
@ -292,10 +282,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
|
|
||||||
t.client = tuic.NewPoolClientV4(clientOption)
|
t.client = tuic.NewPoolClientV4(clientOption)
|
||||||
} else {
|
} else {
|
||||||
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
|
|
||||||
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
|
|
||||||
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
|
||||||
}
|
|
||||||
clientOption := &tuic.ClientOptionV5{
|
clientOption := &tuic.ClientOptionV5{
|
||||||
TlsConfig: tlsConfig,
|
TlsConfig: tlsConfig,
|
||||||
QuicConfig: quicConfig,
|
QuicConfig: quicConfig,
|
||||||
@ -304,7 +290,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
UdpRelayMode: udpRelayMode,
|
UdpRelayMode: udpRelayMode,
|
||||||
CongestionController: option.CongestionController,
|
CongestionController: option.CongestionController,
|
||||||
ReduceRtt: option.ReduceRtt,
|
ReduceRtt: option.ReduceRtt,
|
||||||
MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
|
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||||
MaxOpenStreams: clientMaxOpenStreams,
|
MaxOpenStreams: clientMaxOpenStreams,
|
||||||
CWND: option.CWND,
|
CWND: option.CWND,
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -21,6 +19,13 @@ var (
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func tcpKeepAlive(c net.Conn) {
|
||||||
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
|
_ = tcp.SetKeepAlive(true)
|
||||||
|
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getClientSessionCache() tls.ClientSessionCache {
|
func getClientSessionCache() tls.ClientSessionCache {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||||
@ -123,41 +128,3 @@ func safeConnClose(c net.Conn, err error) {
|
|||||||
_ = c.Close()
|
_ = c.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
|
||||||
|
|
||||||
func StringToBps(s string) uint64 {
|
|
||||||
if s == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// when have not unit, use Mbps
|
|
||||||
if v, err := strconv.Atoi(s); err == nil {
|
|
||||||
return StringToBps(fmt.Sprintf("%d Mbps", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
m := rateStringRegexp.FindStringSubmatch(s)
|
|
||||||
if m == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
var n uint64
|
|
||||||
switch m[2] {
|
|
||||||
case "K":
|
|
||||||
n = 1 << 10
|
|
||||||
case "M":
|
|
||||||
n = 1 << 20
|
|
||||||
case "G":
|
|
||||||
n = 1 << 30
|
|
||||||
case "T":
|
|
||||||
n = 1 << 40
|
|
||||||
default:
|
|
||||||
n = 1
|
|
||||||
}
|
|
||||||
v, _ := strconv.ParseUint(m[1], 10, 64)
|
|
||||||
n = v * n
|
|
||||||
if m[3] == "b" {
|
|
||||||
// Bits, need to convert to bytes
|
|
||||||
n = n >> 3
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/convert"
|
"github.com/Dreamacro/clash/common/convert"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
@ -58,7 +57,6 @@ type VlessOption struct {
|
|||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
Flow string `proxy:"flow,omitempty"`
|
Flow string `proxy:"flow,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
@ -111,10 +109,14 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
if len(v.option.Fingerprint) == 0 {
|
||||||
|
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
} else {
|
||||||
|
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
@ -209,7 +211,6 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
|||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isH2 {
|
if isH2 {
|
||||||
@ -260,7 +261,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
@ -325,7 +326,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
@ -575,7 +576,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,7 +590,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
ServerName: v.option.ServerName,
|
ServerName: v.option.ServerName,
|
||||||
})
|
})
|
||||||
|
@ -13,13 +13,11 @@ import (
|
|||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/ntp"
|
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
@ -54,7 +52,6 @@ type VmessOption struct {
|
|||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
@ -128,10 +125,13 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: []string{"http/1.1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
if len(v.option.Fingerprint) == 0 {
|
||||||
if err != nil {
|
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
} else {
|
||||||
|
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||||
@ -149,7 +149,6 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -206,7 +205,6 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
Reality: v.realityConfig,
|
||||||
NextProtos: v.option.ALPN,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -260,10 +258,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
|||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
if N.NeedHandshake(c) {
|
||||||
conn = v.client.DialEarlyConn(c,
|
conn = v.client.DialEarlyConn(c,
|
||||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
} else {
|
} else {
|
||||||
conn, err = v.client.DialConn(c,
|
conn, err = v.client.DialConn(c,
|
||||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -284,7 +282,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -306,7 +304,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
@ -367,7 +365,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
@ -415,7 +413,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
if option.AuthenticatedLength {
|
if option.AuthenticatedLength {
|
||||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
options = append(options, vmess.ClientWithAuthenticatedLength())
|
||||||
}
|
}
|
||||||
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
|
|
||||||
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -467,7 +464,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
N.TCPKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +478,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
ServerName: v.option.ServerName,
|
ServerName: v.option.ServerName,
|
||||||
})
|
})
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/wireguard-go/device"
|
"github.com/sagernet/wireguard-go/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ type WireGuard struct {
|
|||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
dialer proxydialer.SingDialer
|
dialer *wgSingDialer
|
||||||
startOnce sync.Once
|
startOnce sync.Once
|
||||||
startErr error
|
startErr error
|
||||||
resolver *dns.Resolver
|
resolver *dns.Resolver
|
||||||
@ -69,6 +70,37 @@ type WireGuardPeerOption struct {
|
|||||||
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
|
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type wgSingDialer struct {
|
||||||
|
dialer dialer.Dialer
|
||||||
|
proxyName string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.Dialer = (*wgSingDialer)(nil)
|
||||||
|
|
||||||
|
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
var cDialer C.Dialer = d.dialer
|
||||||
|
if len(d.proxyName) > 0 {
|
||||||
|
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cDialer = pd
|
||||||
|
}
|
||||||
|
return cDialer.DialContext(ctx, network, destination.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
var cDialer C.Dialer = d.dialer
|
||||||
|
if len(d.proxyName) > 0 {
|
||||||
|
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cDialer = pd
|
||||||
|
}
|
||||||
|
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||||
|
}
|
||||||
|
|
||||||
type wgSingErrorHandler struct {
|
type wgSingErrorHandler struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
@ -136,7 +168,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()),
|
dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||||
|
|
||||||
@ -270,7 +302,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
Verbosef: func(format string, args ...interface{}) {
|
||||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||||
},
|
},
|
||||||
@ -323,7 +355,7 @@ func closeWireGuard(w *WireGuard) {
|
|||||||
|
|
||||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
options := w.Base.DialOptions(opts...)
|
options := w.Base.DialOptions(opts...)
|
||||||
w.dialer.SetDialer(dialer.NewDialer(options...))
|
w.dialer.dialer = dialer.NewDialer(options...)
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
w.startOnce.Do(func() {
|
w.startOnce.Do(func() {
|
||||||
w.startErr = w.tunDevice.Start()
|
w.startErr = w.tunDevice.Start()
|
||||||
@ -355,7 +387,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||||||
|
|
||||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
options := w.Base.DialOptions(opts...)
|
options := w.Base.DialOptions(opts...)
|
||||||
w.dialer.SetDialer(dialer.NewDialer(options...))
|
w.dialer.dialer = dialer.NewDialer(options...)
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
w.startOnce.Do(func() {
|
w.startOnce.Do(func() {
|
||||||
w.startErr = w.tunDevice.Start()
|
w.startErr = w.tunDevice.Start()
|
||||||
|
@ -28,7 +28,7 @@ type GroupBase struct {
|
|||||||
failedTestMux sync.Mutex
|
failedTestMux sync.Mutex
|
||||||
failedTimes int
|
failedTimes int
|
||||||
failedTime time.Time
|
failedTime time.Time
|
||||||
failedTesting atomic.Bool
|
failedTesting *atomic.Bool
|
||||||
proxies [][]C.Proxy
|
proxies [][]C.Proxy
|
||||||
versions []atomic.Uint32
|
versions []atomic.Uint32
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,5 +1,17 @@
|
|||||||
package outboundgroup
|
package outboundgroup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcpKeepAlive(c net.Conn) {
|
||||||
|
if tcp, ok := c.(*net.TCPConn); ok {
|
||||||
|
_ = tcp.SetKeepAlive(true)
|
||||||
|
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type SelectAble interface {
|
type SelectAble interface {
|
||||||
Set(string) error
|
Set(string) error
|
||||||
ForceSet(name string)
|
ForceSet(name string)
|
||||||
|
@ -92,13 +92,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewHysteria(*hyOption)
|
proxy, err = outbound.NewHysteria(*hyOption)
|
||||||
case "hysteria2":
|
|
||||||
hyOption := &outbound.Hysteria2Option{}
|
|
||||||
err = decoder.Decode(mapping, hyOption)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proxy, err = outbound.NewHysteria2(*hyOption)
|
|
||||||
case "wireguard":
|
case "wireguard":
|
||||||
wgOption := &outbound.WireGuardOption{}
|
wgOption := &outbound.WireGuardOption{}
|
||||||
err = decoder.Decode(mapping, wgOption)
|
err = decoder.Decode(mapping, wgOption)
|
||||||
@ -113,20 +106,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewTuic(*tuicOption)
|
proxy, err = outbound.NewTuic(*tuicOption)
|
||||||
case "direct":
|
|
||||||
directOption := &outbound.DirectOption{}
|
|
||||||
err = decoder.Decode(mapping, directOption)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proxy = outbound.NewDirectWithOption(*directOption)
|
|
||||||
case "reject":
|
|
||||||
rejectOption := &outbound.RejectOption{}
|
|
||||||
err = decoder.Decode(mapping, rejectOption)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
proxy = outbound.NewRejectWithOption(*rejectOption)
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultURLTestTimeout = time.Second * 5
|
defaultURLTestTimeout = time.Second * 5
|
||||||
defaultURLTestURL = "https://www.gstatic.com/generate_204"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthCheckOption struct {
|
type HealthCheckOption struct {
|
||||||
@ -35,12 +34,12 @@ type HealthCheck struct {
|
|||||||
url string
|
url string
|
||||||
extra map[string]*extraOption
|
extra map[string]*extraOption
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
started atomic.Bool
|
started *atomic.Bool
|
||||||
proxies []C.Proxy
|
proxies []C.Proxy
|
||||||
interval time.Duration
|
interval uint
|
||||||
lazy bool
|
lazy bool
|
||||||
expectedStatus utils.IntRanges[uint16]
|
expectedStatus utils.IntRanges[uint16]
|
||||||
lastTouch atomic.TypedValue[time.Time]
|
lastTouch *atomic.Int64
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
singleDo *singledo.Single[struct{}]
|
singleDo *singledo.Single[struct{}]
|
||||||
}
|
}
|
||||||
@ -51,14 +50,13 @@ func (hc *HealthCheck) process() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(hc.interval)
|
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||||
hc.start()
|
hc.start()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
lastTouch := hc.lastTouch.Load()
|
now := time.Now().Unix()
|
||||||
since := time.Since(lastTouch)
|
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||||
if !hc.lazy || since < hc.interval {
|
|
||||||
hc.check()
|
hc.check()
|
||||||
} else {
|
} else {
|
||||||
log.Debugln("Skip once health check because we are lazy")
|
log.Debugln("Skip once health check because we are lazy")
|
||||||
@ -87,7 +85,7 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
|
|||||||
|
|
||||||
// if the provider has not set up health checks, then modify it to be the same as the group's interval
|
// if the provider has not set up health checks, then modify it to be the same as the group's interval
|
||||||
if hc.interval == 0 {
|
if hc.interval == 0 {
|
||||||
hc.interval = time.Duration(interval) * time.Second
|
hc.interval = interval
|
||||||
}
|
}
|
||||||
|
|
||||||
if hc.extra == nil {
|
if hc.extra == nil {
|
||||||
@ -137,7 +135,7 @@ func (hc *HealthCheck) auto() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) touch() {
|
func (hc *HealthCheck) touch() {
|
||||||
hc.lastTouch.Store(time.Now())
|
hc.lastTouch.Store(time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) start() {
|
func (hc *HealthCheck) start() {
|
||||||
@ -149,11 +147,6 @@ func (hc *HealthCheck) stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *HealthCheck) check() {
|
func (hc *HealthCheck) check() {
|
||||||
|
|
||||||
if len(hc.proxies) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||||
id := utils.NewUUIDV4().String()
|
id := utils.NewUUIDV4().String()
|
||||||
log.Debugln("Start New Health Checking {%s}", id)
|
log.Debugln("Start New Health Checking {%s}", id)
|
||||||
@ -229,16 +222,17 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
|
|||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
interval = 0
|
interval = 0
|
||||||
expectedStatus = nil
|
expectedStatus = nil
|
||||||
url = defaultURLTestURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HealthCheck{
|
return &HealthCheck{
|
||||||
proxies: proxies,
|
proxies: proxies,
|
||||||
url: url,
|
url: url,
|
||||||
extra: map[string]*extraOption{},
|
extra: map[string]*extraOption{},
|
||||||
interval: time.Duration(interval) * time.Second,
|
started: atomic.NewBool(false),
|
||||||
|
interval: interval,
|
||||||
lazy: lazy,
|
lazy: lazy,
|
||||||
expectedStatus: expectedStatus,
|
expectedStatus: expectedStatus,
|
||||||
|
lastTouch: atomic.NewInt64(0),
|
||||||
done: make(chan struct{}, 1),
|
done: make(chan struct{}, 1),
|
||||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
case "http":
|
case "http":
|
||||||
if schema.Path != "" {
|
if schema.Path != "" {
|
||||||
path := C.Path.Resolve(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)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||||
} else {
|
} else {
|
||||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
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,6 +1,7 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -12,24 +13,45 @@ type SubscriptionInfo struct {
|
|||||||
Expire int64
|
Expire int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
|
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
|
||||||
userinfo = strings.ToLower(userinfo)
|
si = &SubscriptionInfo{}
|
||||||
userinfo = strings.ReplaceAll(userinfo, " ", "")
|
str = strings.ToLower(str)
|
||||||
si = new(SubscriptionInfo)
|
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
|
||||||
for _, field := range strings.Split(userinfo, ";") {
|
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
|
||||||
switch name, value, _ := strings.Cut(field, "="); name {
|
|
||||||
case "upload":
|
match, err := reTraffic.FindStringMatch(str)
|
||||||
si.Upload, err = strconv.ParseInt(value, 10, 64)
|
if err != nil || match == nil {
|
||||||
case "download":
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
group := match.Groups()
|
||||||
|
si.Upload, err = str2uint64(group[1].String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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())
|
||||||
|
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,9 +11,10 @@ type Bool struct {
|
|||||||
atomic.Bool
|
atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBool(val bool) (i Bool) {
|
func NewBool(val bool) *Bool {
|
||||||
|
i := &Bool{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Bool) MarshalJSON() ([]byte, error) {
|
func (i *Bool) MarshalJSON() ([]byte, error) {
|
||||||
@ -38,11 +39,12 @@ type Pointer[T any] struct {
|
|||||||
atomic.Pointer[T]
|
atomic.Pointer[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPointer[T any](v *T) (p Pointer[T]) {
|
func NewPointer[T any](v *T) *Pointer[T] {
|
||||||
|
var p Pointer[T]
|
||||||
if v != nil {
|
if v != nil {
|
||||||
p.Store(v)
|
p.Store(v)
|
||||||
}
|
}
|
||||||
return
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
|
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
|
||||||
@ -66,9 +68,10 @@ type Int32 struct {
|
|||||||
atomic.Int32
|
atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInt32(val int32) (i Int32) {
|
func NewInt32(val int32) *Int32 {
|
||||||
|
i := &Int32{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Int32) MarshalJSON() ([]byte, error) {
|
func (i *Int32) MarshalJSON() ([]byte, error) {
|
||||||
@ -93,9 +96,10 @@ type Int64 struct {
|
|||||||
atomic.Int64
|
atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInt64(val int64) (i Int64) {
|
func NewInt64(val int64) *Int64 {
|
||||||
|
i := &Int64{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Int64) MarshalJSON() ([]byte, error) {
|
func (i *Int64) MarshalJSON() ([]byte, error) {
|
||||||
@ -120,9 +124,10 @@ type Uint32 struct {
|
|||||||
atomic.Uint32
|
atomic.Uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUint32(val uint32) (i Uint32) {
|
func NewUint32(val uint32) *Uint32 {
|
||||||
|
i := &Uint32{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Uint32) MarshalJSON() ([]byte, error) {
|
func (i *Uint32) MarshalJSON() ([]byte, error) {
|
||||||
@ -147,9 +152,10 @@ type Uint64 struct {
|
|||||||
atomic.Uint64
|
atomic.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUint64(val uint64) (i Uint64) {
|
func NewUint64(val uint64) *Uint64 {
|
||||||
|
i := &Uint64{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Uint64) MarshalJSON() ([]byte, error) {
|
func (i *Uint64) MarshalJSON() ([]byte, error) {
|
||||||
@ -174,9 +180,10 @@ type Uintptr struct {
|
|||||||
atomic.Uintptr
|
atomic.Uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUintptr(val uintptr) (i Uintptr) {
|
func NewUintptr(val uintptr) *Uintptr {
|
||||||
|
i := &Uintptr{}
|
||||||
i.Store(val)
|
i.Store(val)
|
||||||
return
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Uintptr) MarshalJSON() ([]byte, error) {
|
func (i *Uintptr) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -51,7 +51,8 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
func NewTypedValue[T any](t T) *TypedValue[T] {
|
||||||
|
v := &TypedValue[T]{}
|
||||||
v.Store(t)
|
v.Store(t)
|
||||||
return
|
return v
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,17 @@ const BufferSize = buf.BufferSize
|
|||||||
type Buffer = buf.Buffer
|
type Buffer = buf.Buffer
|
||||||
|
|
||||||
var New = buf.New
|
var New = buf.New
|
||||||
var NewPacket = buf.NewPacket
|
|
||||||
var NewSize = buf.NewSize
|
var NewSize = buf.NewSize
|
||||||
var With = buf.With
|
var With = buf.With
|
||||||
var As = buf.As
|
var As = buf.As
|
||||||
|
|
||||||
|
var KeepAlive = common.KeepAlive
|
||||||
|
|
||||||
|
//go:norace
|
||||||
|
func Dup[T any](obj T) T {
|
||||||
|
return common.Dup(obj)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Must = common.Must
|
Must = common.Must
|
||||||
Error = common.Error
|
Error = common.Error
|
||||||
|
11
common/cache/lrucache.go
vendored
11
common/cache/lrucache.go
vendored
@ -7,8 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/generics/list"
|
"github.com/Dreamacro/clash/common/generics/list"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option is part of Functional Options Pattern
|
// Option is part of Functional Options Pattern
|
||||||
@ -89,7 +87,7 @@ func (c *LruCache[K, V]) Get(key K) (V, bool) {
|
|||||||
|
|
||||||
el := c.get(key)
|
el := c.get(key)
|
||||||
if el == nil {
|
if el == nil {
|
||||||
return lo.Empty[V](), false
|
return getZero[V](), false
|
||||||
}
|
}
|
||||||
value := el.value
|
value := el.value
|
||||||
|
|
||||||
@ -121,7 +119,7 @@ func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
|
|||||||
|
|
||||||
el := c.get(key)
|
el := c.get(key)
|
||||||
if el == nil {
|
if el == nil {
|
||||||
return lo.Empty[V](), time.Time{}, false
|
return getZero[V](), time.Time{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return el.value, time.Unix(el.expires, 0), true
|
return el.value, time.Unix(el.expires, 0), true
|
||||||
@ -261,3 +259,8 @@ type entry[K comparable, V any] struct {
|
|||||||
value V
|
value V
|
||||||
expires int64
|
expires int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getZero[T any]() T {
|
||||||
|
var result T
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
|
|||||||
|
|
||||||
func TestExecCmd(t *testing.T) {
|
func TestExecCmd(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
_, err := ExecCmd("cmd -c 'dir'")
|
_, err := ExecCmd("dir")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
hysteria["port"] = urlHysteria.Port()
|
hysteria["port"] = urlHysteria.Port()
|
||||||
hysteria["sni"] = query.Get("peer")
|
hysteria["sni"] = query.Get("peer")
|
||||||
hysteria["obfs"] = query.Get("obfs")
|
hysteria["obfs"] = query.Get("obfs")
|
||||||
if alpn := query.Get("alpn"); alpn != "" {
|
hysteria["alpn"] = []string{query.Get("alpn")}
|
||||||
hysteria["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
hysteria["auth_str"] = query.Get("auth")
|
hysteria["auth_str"] = query.Get("auth")
|
||||||
hysteria["protocol"] = query.Get("protocol")
|
hysteria["protocol"] = query.Get("protocol")
|
||||||
up := query.Get("up")
|
up := query.Get("up")
|
||||||
@ -68,79 +66,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
||||||
|
|
||||||
proxies = append(proxies, hysteria)
|
proxies = append(proxies, hysteria)
|
||||||
case "hysteria2":
|
|
||||||
urlHysteria2, err := url.Parse(line)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
query := urlHysteria2.Query()
|
|
||||||
name := uniqueName(names, urlHysteria2.Fragment)
|
|
||||||
hysteria2 := make(map[string]any, 20)
|
|
||||||
|
|
||||||
hysteria2["name"] = name
|
|
||||||
hysteria2["type"] = scheme
|
|
||||||
hysteria2["server"] = urlHysteria2.Hostname()
|
|
||||||
if port := urlHysteria2.Port(); port != "" {
|
|
||||||
hysteria2["port"] = port
|
|
||||||
} else {
|
|
||||||
hysteria2["port"] = "443"
|
|
||||||
}
|
|
||||||
hysteria2["obfs"] = query.Get("obfs")
|
|
||||||
hysteria2["obfs-password"] = query.Get("obfs-password")
|
|
||||||
hysteria2["sni"] = query.Get("sni")
|
|
||||||
hysteria2["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
|
||||||
if alpn := query.Get("alpn"); alpn != "" {
|
|
||||||
hysteria2["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
if auth := urlHysteria2.User.String(); auth != "" {
|
|
||||||
hysteria2["password"] = auth
|
|
||||||
}
|
|
||||||
hysteria2["fingerprint"] = query.Get("pinSHA256")
|
|
||||||
hysteria2["down"] = query.Get("down")
|
|
||||||
hysteria2["up"] = query.Get("up")
|
|
||||||
|
|
||||||
proxies = append(proxies, hysteria2)
|
|
||||||
case "tuic":
|
|
||||||
// A temporary unofficial TUIC share link standard
|
|
||||||
// Modified from https://github.com/daeuniverse/dae/discussions/182
|
|
||||||
// Changes:
|
|
||||||
// 1. Support TUICv4, just replace uuid:password with token
|
|
||||||
// 2. Remove `allow_insecure` field
|
|
||||||
urlTUIC, err := url.Parse(line)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
query := urlTUIC.Query()
|
|
||||||
|
|
||||||
tuic := make(map[string]any, 20)
|
|
||||||
tuic["name"] = uniqueName(names, urlTUIC.Fragment)
|
|
||||||
tuic["type"] = scheme
|
|
||||||
tuic["server"] = urlTUIC.Hostname()
|
|
||||||
tuic["port"] = urlTUIC.Port()
|
|
||||||
tuic["udp"] = true
|
|
||||||
password, v5 := urlTUIC.User.Password()
|
|
||||||
if v5 {
|
|
||||||
tuic["uuid"] = urlTUIC.User.Username()
|
|
||||||
tuic["password"] = password
|
|
||||||
} else {
|
|
||||||
tuic["token"] = urlTUIC.User.Username()
|
|
||||||
}
|
|
||||||
if cc := query.Get("congestion_control"); cc != "" {
|
|
||||||
tuic["congestion-controller"] = cc
|
|
||||||
}
|
|
||||||
if alpn := query.Get("alpn"); alpn != "" {
|
|
||||||
tuic["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
if sni := query.Get("sni"); sni != "" {
|
|
||||||
tuic["sni"] = sni
|
|
||||||
}
|
|
||||||
if query.Get("disable_sni") == "1" {
|
|
||||||
tuic["disable-sni"] = true
|
|
||||||
}
|
|
||||||
if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" {
|
|
||||||
tuic["udp-relay-mode"] = udpRelayMode
|
|
||||||
}
|
|
||||||
|
|
||||||
case "trojan":
|
case "trojan":
|
||||||
urlTrojan, err := url.Parse(line)
|
urlTrojan, err := url.Parse(line)
|
||||||
@ -161,12 +86,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
trojan["udp"] = true
|
trojan["udp"] = true
|
||||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||||
|
|
||||||
if sni := query.Get("sni"); sni != "" {
|
sni := query.Get("sni")
|
||||||
|
if sni != "" {
|
||||||
trojan["sni"] = sni
|
trojan["sni"] = sni
|
||||||
}
|
}
|
||||||
if alpn := query.Get("alpn"); alpn != "" {
|
|
||||||
trojan["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
network := strings.ToLower(query.Get("type"))
|
network := strings.ToLower(query.Get("type"))
|
||||||
if network != "" {
|
if network != "" {
|
||||||
@ -294,9 +217,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
if strings.HasSuffix(tls, "tls") {
|
if strings.HasSuffix(tls, "tls") {
|
||||||
vmess["tls"] = true
|
vmess["tls"] = true
|
||||||
}
|
}
|
||||||
if alpn, ok := values["alpn"].(string); ok {
|
|
||||||
vmess["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
@ -412,7 +332,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
proxies = append(proxies, ss)
|
proxies = append(proxies, ss)
|
||||||
|
|
||||||
case "ssr":
|
case "ssr":
|
||||||
dcBuf, err := encRaw.DecodeString(body)
|
dcBuf, err := encRaw.DecodeString(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://v2.hysteria.network/zh/docs/developers/URI-Scheme/
|
|
||||||
func TestConvertsV2Ray_normal(t *testing.T) {
|
|
||||||
hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test"
|
|
||||||
|
|
||||||
expected := []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"name": "hy2test",
|
|
||||||
"type": "hysteria2",
|
|
||||||
"server": "example.com",
|
|
||||||
"port": "8443",
|
|
||||||
"sni": "real.example.com",
|
|
||||||
"obfs": "salamander",
|
|
||||||
"obfs-password": "gawrgura",
|
|
||||||
"alpn": []string{"h3", "h4"},
|
|
||||||
"password": "letmein",
|
|
||||||
"up": "114",
|
|
||||||
"down": "514",
|
|
||||||
"skip-cert-verify": true,
|
|
||||||
"fingerprint": "deadbeef",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
proxies, err := ConvertsV2Ray([]byte(hy2test))
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, expected, proxies)
|
|
||||||
}
|
|
@ -24,6 +24,8 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
|||||||
proxy["port"] = url.Port()
|
proxy["port"] = url.Port()
|
||||||
proxy["uuid"] = url.User.Username()
|
proxy["uuid"] = url.User.Username()
|
||||||
proxy["udp"] = true
|
proxy["udp"] = true
|
||||||
|
proxy["skip-cert-verify"] = false
|
||||||
|
proxy["tls"] = false
|
||||||
tls := strings.ToLower(query.Get("security"))
|
tls := strings.ToLower(query.Get("security"))
|
||||||
if strings.HasSuffix(tls, "tls") || tls == "reality" {
|
if strings.HasSuffix(tls, "tls") || tls == "reality" {
|
||||||
proxy["tls"] = true
|
proxy["tls"] = true
|
||||||
@ -32,9 +34,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
|||||||
} else {
|
} else {
|
||||||
proxy["client-fingerprint"] = fingerprint
|
proxy["client-fingerprint"] = fingerprint
|
||||||
}
|
}
|
||||||
if alpn := query.Get("alpn"); alpn != "" {
|
|
||||||
proxy["alpn"] = strings.Split(alpn, ",")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if sni := query.Get("sni"); sni != "" {
|
if sni := query.Get("sni"); sni != "" {
|
||||||
proxy["servername"] = sni
|
proxy["servername"] = sni
|
||||||
|
@ -4,11 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var KeepAliveInterval = 15 * time.Second
|
|
||||||
|
|
||||||
func SplitNetworkType(s string) (string, string, error) {
|
func SplitNetworkType(s string) (string, string, error) {
|
||||||
var (
|
var (
|
||||||
shecme string
|
shecme string
|
||||||
@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
|||||||
host, port, err = net.SplitHostPort(temp)
|
host, port, err = net.SplitHostPort(temp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TCPKeepAlive(c net.Conn) {
|
|
||||||
if tcp, ok := c.(*net.TCPConn); ok {
|
|
||||||
_ = tcp.SetKeepAlive(true)
|
|
||||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,11 +10,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Path interface {
|
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
||||||
Resolve(path string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
|
||||||
if certificate == "" && privateKey == "" {
|
if certificate == "" && privateKey == "" {
|
||||||
return newRandomTLSKeyPair()
|
return newRandomTLSKeyPair()
|
||||||
}
|
}
|
||||||
@ -23,8 +19,6 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro
|
|||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate = path.Resolve(certificate)
|
|
||||||
privateKey = path.Resolve(privateKey)
|
|
||||||
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
|
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
|
||||||
if loadErr != nil {
|
if loadErr != nil {
|
||||||
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
|
|||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return input, nil
|
return input, nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return lo.Empty[T](), ctx.Err()
|
return getZero[T](), ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +35,11 @@ func TestPicker_Timeout(t *testing.T) {
|
|||||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||||
|
|
||||||
number := picker.Wait()
|
number := picker.Wait()
|
||||||
assert.Equal(t, number, lo.Empty[int]())
|
assert.Equal(t, number, getZero[int]())
|
||||||
assert.NotNil(t, picker.Error())
|
assert.NotNil(t, picker.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getZero[T any]() T {
|
||||||
|
var result T
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -2,8 +2,6 @@ package queue
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Queue is a simple concurrent safe queue
|
// Queue is a simple concurrent safe queue
|
||||||
@ -26,7 +24,7 @@ func (q *Queue[T]) Put(items ...T) {
|
|||||||
// Pop returns the head of items.
|
// Pop returns the head of items.
|
||||||
func (q *Queue[T]) Pop() T {
|
func (q *Queue[T]) Pop() T {
|
||||||
if len(q.items) == 0 {
|
if len(q.items) == 0 {
|
||||||
return lo.Empty[T]()
|
return GetZero[T]()
|
||||||
}
|
}
|
||||||
|
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
@ -39,7 +37,7 @@ func (q *Queue[T]) Pop() T {
|
|||||||
// Last returns the last of item.
|
// Last returns the last of item.
|
||||||
func (q *Queue[T]) Last() T {
|
func (q *Queue[T]) Last() T {
|
||||||
if len(q.items) == 0 {
|
if len(q.items) == 0 {
|
||||||
return lo.Empty[T]()
|
return GetZero[T]()
|
||||||
}
|
}
|
||||||
|
|
||||||
q.lock.RLock()
|
q.lock.RLock()
|
||||||
@ -71,3 +69,8 @@ func New[T any](hint int64) *Queue[T] {
|
|||||||
items: make([]T, 0, hint),
|
items: make([]T, 0, hint),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetZero[T any]() T {
|
||||||
|
var result T
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import "github.com/samber/lo"
|
|
||||||
|
|
||||||
func EmptyOr[T comparable](v T, def T) T {
|
|
||||||
ret, _ := lo.Coalesce(v, def)
|
|
||||||
return ret
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/puzpuzpuz/xsync/v2"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
@ -15,7 +15,7 @@ type AuthUser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type inMemoryAuthenticator struct {
|
type inMemoryAuthenticator struct {
|
||||||
storage *xsync.MapOf[string, string]
|
storage *sync.Map
|
||||||
usernames []string
|
usernames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
au := &inMemoryAuthenticator{storage: &sync.Map{}}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
au.storage.Store(user.User, user.Pass)
|
au.storage.Store(user.User, user.Pass)
|
||||||
}
|
}
|
||||||
usernames := make([]string, 0, len(users))
|
usernames := make([]string, 0, len(users))
|
||||||
au.storage.Range(func(key string, value string) bool {
|
au.storage.Range(func(key, value any) bool {
|
||||||
usernames = append(usernames, key)
|
usernames = append(usernames, key.(string))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
au.usernames = usernames
|
au.usernames = usernames
|
||||||
|
@ -70,9 +70,6 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||||
if DefaultSocketHook != nil {
|
|
||||||
return listenPacketHooked(ctx, network, address)
|
|
||||||
}
|
|
||||||
cfg := applyOptions(options...)
|
cfg := applyOptions(options...)
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
lc := &net.ListenConfig{}
|
||||||
@ -113,9 +110,6 @@ func GetTcpConcurrent() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
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)
|
address := net.JoinHostPort(destination.String(), port)
|
||||||
|
|
||||||
netDialer := opt.netDialer
|
netDialer := opt.netDialer
|
||||||
@ -168,22 +162,14 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
|
|||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
||||||
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
|
||||||
return nil, ErrorNoIpAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
preferIPVersion := opt.prefer
|
preferIPVersion := opt.prefer
|
||||||
|
|
||||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
fallbackTicker := time.NewTicker(fallbackTimeout)
|
||||||
defer fallbackTicker.Stop()
|
defer fallbackTicker.Stop()
|
||||||
|
|
||||||
results := make(chan dialResult)
|
results := make(chan dialResult)
|
||||||
returned := make(chan struct{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
racer := func(ips []netip.Addr, isPrimary bool) {
|
||||||
defer wg.Done()
|
|
||||||
result := dialResult{isPrimary: isPrimary}
|
result := dialResult{isPrimary: isPrimary}
|
||||||
defer func() {
|
defer func() {
|
||||||
select {
|
select {
|
||||||
@ -196,36 +182,18 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
|||||||
}()
|
}()
|
||||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ipv4s) != 0 {
|
|
||||||
wg.Add(1)
|
|
||||||
go racer(ipv4s, preferIPVersion != 6)
|
go racer(ipv4s, preferIPVersion != 6)
|
||||||
}
|
|
||||||
|
|
||||||
if len(ipv6s) != 0 {
|
|
||||||
wg.Add(1)
|
|
||||||
go racer(ipv6s, preferIPVersion != 4)
|
go racer(ipv6s, preferIPVersion != 4)
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(results)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var fallback dialResult
|
var fallback dialResult
|
||||||
var errs []error
|
var errs []error
|
||||||
|
for i := 0; i < 2; {
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
select {
|
||||||
case <-fallbackTicker.C:
|
case <-fallbackTicker.C:
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
return fallback.Conn, nil
|
return fallback.Conn, nil
|
||||||
}
|
}
|
||||||
case res, ok := <-results:
|
case res := <-results:
|
||||||
if !ok {
|
i++
|
||||||
break loop
|
|
||||||
}
|
|
||||||
if res.error == nil {
|
if res.error == nil {
|
||||||
if res.isPrimary {
|
if res.isPrimary {
|
||||||
return res.Conn, nil
|
return res.Conn, nil
|
||||||
@ -240,7 +208,6 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
if fallback.error == nil && fallback.Conn != nil {
|
||||||
return fallback.Conn, nil
|
return fallback.Conn, nil
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -2,22 +2,22 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
URL "net/url"
|
URL "net/url"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
"github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/listener/inner"
|
"github.com/Dreamacro/clash/listener/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UA = "clash.meta"
|
||||||
|
)
|
||||||
|
|
||||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||||
UA := C.UA
|
|
||||||
method = strings.ToUpper(method)
|
method = strings.ToUpper(method)
|
||||||
urlRes, err := URL.Parse(url)
|
urlRes, err := URL.Parse(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,7 +48,6 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
|||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
// from http.DefaultTransport
|
// from http.DefaultTransport
|
||||||
DisableKeepAlives: runtime.GOOS == "android",
|
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
IdleConnTimeout: 30 * time.Second,
|
IdleConnTimeout: 30 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
@ -61,7 +60,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
|||||||
return d.DialContext(ctx, network, address)
|
return d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
TLSClientConfig: tls.GetDefaultTLSConfig(),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := http.Client{Transport: transport}
|
client := http.Client{Transport: transport}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -5,28 +5,23 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Table struct {
|
type Table struct {
|
||||||
mapping *xsync.MapOf[string, *Entry]
|
mapping sync.Map
|
||||||
lockMap *xsync.MapOf[string, *sync.Cond]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
PacketConn C.PacketConn
|
PacketConn C.PacketConn
|
||||||
WriteBackProxy C.WriteBackProxy
|
WriteBackProxy C.WriteBackProxy
|
||||||
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
|
LocalUDPConnMap sync.Map
|
||||||
LocalLockMap *xsync.MapOf[string, *sync.Cond]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||||
t.mapping.Store(key, &Entry{
|
t.mapping.Store(key, &Entry{
|
||||||
PacketConn: e,
|
PacketConn: e,
|
||||||
WriteBackProxy: w,
|
WriteBackProxy: w,
|
||||||
LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](),
|
LocalUDPConnMap: sync.Map{},
|
||||||
LocalLockMap: xsync.NewMapOf[*sync.Cond](),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,19 +34,15 @@ func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||||
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
|
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||||
return item, loaded
|
return item.(*sync.Cond), loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) Delete(key string) {
|
func (t *Table) Delete(key string) {
|
||||||
t.mapping.Delete(key)
|
t.mapping.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) DeleteLock(lockKey string) {
|
func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||||
t.lockMap.Delete(lockKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
|
||||||
entry, exist := t.getEntry(lAddr)
|
entry, exist := t.getEntry(lAddr)
|
||||||
if !exist {
|
if !exist {
|
||||||
return nil
|
return nil
|
||||||
@ -60,10 +51,10 @@ func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
|||||||
if !exist {
|
if !exist {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return item
|
return item.(*net.UDPConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||||
entry, exist := t.getEntry(lAddr)
|
entry, exist := t.getEntry(lAddr)
|
||||||
if !exist {
|
if !exist {
|
||||||
return false
|
return false
|
||||||
@ -72,7 +63,7 @@ func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) {
|
func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
|
||||||
entry, exist := t.getEntry(lAddr)
|
entry, exist := t.getEntry(lAddr)
|
||||||
if !exist {
|
if !exist {
|
||||||
return
|
return
|
||||||
@ -85,11 +76,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock)
|
item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||||
return item, loaded
|
return item.(*sync.Cond), loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) DeleteForLocalConn(lAddr, key string) {
|
func (t *Table) DeleteLocalConnMap(lAddr, key string) {
|
||||||
entry, loaded := t.getEntry(lAddr)
|
entry, loaded := t.getEntry(lAddr)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return
|
return
|
||||||
@ -97,26 +88,17 @@ func (t *Table) DeleteForLocalConn(lAddr, key string) {
|
|||||||
entry.LocalUDPConnMap.Delete(key)
|
entry.LocalUDPConnMap.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
|
|
||||||
entry, loaded := t.getEntry(lAddr)
|
|
||||||
if !loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entry.LocalLockMap.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Table) getEntry(key string) (*Entry, bool) {
|
func (t *Table) getEntry(key string) (*Entry, bool) {
|
||||||
return t.mapping.Load(key)
|
item, ok := t.mapping.Load(key)
|
||||||
}
|
// This should not happen usually since this function called after PacketConn created
|
||||||
|
if !ok {
|
||||||
func makeLock() *sync.Cond {
|
return nil, false
|
||||||
return sync.NewCond(&sync.Mutex{})
|
}
|
||||||
|
entry, ok := item.(*Entry)
|
||||||
|
return entry, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return *Cache
|
// New return *Cache
|
||||||
func New() *Table {
|
func New() *Table {
|
||||||
return &Table{
|
return &Table{}
|
||||||
mapping: xsync.NewMapOf[*Entry](),
|
|
||||||
lockMap: xsync.NewMapOf[*sync.Cond](),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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,7 +70,10 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||||
currentMeta := &C.Metadata{Type: C.INNER, DstIP: rAddrPort.Addr(), DstPort: rAddrPort.Port()}
|
currentMeta := &C.Metadata{Type: C.INNER}
|
||||||
|
if err := currentMeta.SetRemoteAddress(rAddrPort.String()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return p.listenPacket(ctx, currentMeta)
|
return p.listenPacket(ctx, currentMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package proxydialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SingDialer interface {
|
|
||||||
N.Dialer
|
|
||||||
SetDialer(dialer C.Dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
type singDialer proxyDialer
|
|
||||||
|
|
||||||
var _ N.Dialer = (*singDialer)(nil)
|
|
||||||
|
|
||||||
func (d *singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
return (*proxyDialer)(d).DialContext(ctx, network, destination.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
return (*proxyDialer)(d).ListenPacket(ctx, "udp", "", destination.AddrPort())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *singDialer) SetDialer(dialer C.Dialer) {
|
|
||||||
(*proxyDialer)(d).dialer = dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSingDialer(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) SingDialer {
|
|
||||||
return (*singDialer)(&proxyDialer{
|
|
||||||
proxy: proxy,
|
|
||||||
dialer: dialer,
|
|
||||||
statistic: statistic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type byNameSingDialer struct {
|
|
||||||
dialer C.Dialer
|
|
||||||
proxyName string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ N.Dialer = (*byNameSingDialer)(nil)
|
|
||||||
|
|
||||||
func (d *byNameSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
var cDialer C.Dialer = d.dialer
|
|
||||||
if len(d.proxyName) > 0 {
|
|
||||||
pd, err := NewByName(d.proxyName, d.dialer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cDialer = pd
|
|
||||||
}
|
|
||||||
return cDialer.DialContext(ctx, network, destination.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *byNameSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
var cDialer C.Dialer = d.dialer
|
|
||||||
if len(d.proxyName) > 0 {
|
|
||||||
pd, err := NewByName(d.proxyName, d.dialer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cDialer = pd
|
|
||||||
}
|
|
||||||
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *byNameSingDialer) SetDialer(dialer C.Dialer) {
|
|
||||||
d.dialer = dialer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewByNameSingDialer(proxyName string, dialer C.Dialer) SingDialer {
|
|
||||||
return &byNameSingDialer{
|
|
||||||
dialer: dialer,
|
|
||||||
proxyName: proxyName,
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
_ "unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
@ -21,14 +20,12 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
|
|
||||||
//
|
|
||||||
//go:linkname lookupStaticHost net.lookupStaticHost
|
|
||||||
func lookupStaticHost(host string) ([]string, string)
|
|
||||||
|
|
||||||
// Return the search result and whether to match the parameter `isDomain`
|
// Return the search result and whether to match the parameter `isDomain`
|
||||||
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
||||||
if value := h.DomainTrie.Search(domain); value != nil {
|
value := h.DomainTrie.Search(domain)
|
||||||
|
if value == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
hostValue := value.Data()
|
hostValue := value.Data()
|
||||||
for {
|
for {
|
||||||
if isDomain && hostValue.IsDomain {
|
if isDomain && hostValue.IsDomain {
|
||||||
@ -44,16 +41,7 @@ func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
|||||||
if isDomain == hostValue.IsDomain {
|
if isDomain == hostValue.IsDomain {
|
||||||
return &hostValue, true
|
return &hostValue, true
|
||||||
}
|
}
|
||||||
|
|
||||||
return &hostValue, false
|
return &hostValue, false
|
||||||
}
|
|
||||||
if !isDomain {
|
|
||||||
addr, _ := lookupStaticHost(domain)
|
|
||||||
if hostValue, err := NewHostValue(addr); err == nil {
|
|
||||||
return &hostValue, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostValue struct {
|
type HostValue struct {
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
//go:build !go1.22
|
|
||||||
|
|
||||||
// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
|
|
||||||
|
|
||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname testHookHostsPath net.testHookHostsPath
|
|
||||||
var testHookHostsPath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if dir, err := windows.GetSystemDirectory(); err == nil {
|
|
||||||
testHookHostsPath = dir + "/Drivers/etc/hosts"
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,12 +9,6 @@ import (
|
|||||||
|
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
minInterval = time.Minute * 5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +22,8 @@ type Fetcher[V any] struct {
|
|||||||
resourceType string
|
resourceType string
|
||||||
name string
|
name string
|
||||||
vehicle types.Vehicle
|
vehicle types.Vehicle
|
||||||
UpdatedAt time.Time
|
UpdatedAt *time.Time
|
||||||
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
hash [16]byte
|
hash [16]byte
|
||||||
parser Parser[V]
|
parser Parser[V]
|
||||||
@ -59,7 +54,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||||
buf, err = os.ReadFile(f.vehicle.Path())
|
buf, err = os.ReadFile(f.vehicle.Path())
|
||||||
modTime := stat.ModTime()
|
modTime := stat.ModTime()
|
||||||
f.UpdatedAt = modTime
|
f.UpdatedAt = &modTime
|
||||||
isLocal = true
|
isLocal = true
|
||||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
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())
|
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
||||||
@ -67,11 +62,10 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buf, err = f.vehicle.Read()
|
buf, err = f.vehicle.Read()
|
||||||
f.UpdatedAt = time.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lo.Empty[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
var contents V
|
var contents V
|
||||||
@ -91,18 +85,18 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isLocal {
|
if !isLocal {
|
||||||
return lo.Empty[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse local file error, fallback to remote
|
// parse local file error, fallback to remote
|
||||||
buf, err = f.vehicle.Read()
|
buf, err = f.vehicle.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lo.Empty[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err = f.parser(buf)
|
contents, err = f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lo.Empty[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocal = false
|
isLocal = false
|
||||||
@ -110,14 +104,14 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
|
|
||||||
if f.vehicle.Type() != types.File && !isLocal {
|
if f.vehicle.Type() != types.File && !isLocal {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return lo.Empty[V](), err
|
return getZero[V](), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.hash = md5.Sum(buf)
|
f.hash = md5.Sum(buf)
|
||||||
|
|
||||||
// pull contents automatically
|
// pull contents automatically
|
||||||
if f.interval > 0 {
|
if f.ticker != nil {
|
||||||
go f.pullLoop()
|
go f.pullLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,53 +121,45 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
|||||||
func (f *Fetcher[V]) Update() (V, bool, error) {
|
func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||||
buf, err := f.vehicle.Read()
|
buf, err := f.vehicle.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lo.Empty[V](), false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
hash := md5.Sum(buf)
|
hash := md5.Sum(buf)
|
||||||
if bytes.Equal(f.hash[:], hash[:]) {
|
if bytes.Equal(f.hash[:], hash[:]) {
|
||||||
f.UpdatedAt = now
|
f.UpdatedAt = &now
|
||||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||||
return lo.Empty[V](), true, nil
|
return getZero[V](), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := f.parser(buf)
|
contents, err := f.parser(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return lo.Empty[V](), false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.vehicle.Type() != types.File {
|
if f.vehicle.Type() != types.File {
|
||||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||||
return lo.Empty[V](), false, err
|
return getZero[V](), false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.UpdatedAt = now
|
f.UpdatedAt = &now
|
||||||
f.hash = hash
|
f.hash = hash
|
||||||
|
|
||||||
return contents, false, nil
|
return contents, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) Destroy() error {
|
func (f *Fetcher[V]) Destroy() error {
|
||||||
if f.interval > 0 {
|
if f.ticker != nil {
|
||||||
f.done <- struct{}{}
|
f.done <- struct{}{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fetcher[V]) pullLoop() {
|
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 {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-f.ticker.C:
|
||||||
timer.Reset(f.interval)
|
|
||||||
elm, same, err := f.Update()
|
elm, same, err := f.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||||
@ -190,6 +176,7 @@ func (f *Fetcher[V]) pullLoop() {
|
|||||||
f.OnUpdate(elm)
|
f.OnUpdate(elm)
|
||||||
}
|
}
|
||||||
case <-f.done:
|
case <-f.done:
|
||||||
|
f.ticker.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,13 +195,23 @@ 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] {
|
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]{
|
return &Fetcher[V]{
|
||||||
name: name,
|
name: name,
|
||||||
|
ticker: ticker,
|
||||||
vehicle: vehicle,
|
vehicle: vehicle,
|
||||||
parser: parser,
|
parser: parser,
|
||||||
done: make(chan struct{}, 8),
|
done: make(chan struct{}, 1),
|
||||||
OnUpdate: onUpdate,
|
OnUpdate: onUpdate,
|
||||||
interval: interval,
|
interval: interval,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getZero[V any]() V {
|
||||||
|
var result V
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -23,8 +23,8 @@ func (*BaseSniffer) Protocol() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
// SniffData implements sniffer.Sniffer
|
// SniffTCP implements sniffer.Sniffer
|
||||||
func (*BaseSniffer) SniffData(bytes []byte) (string, error) {
|
func (*BaseSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||||
return "", errors.New("TODO")
|
return "", errors.New("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,40 +35,8 @@ type SnifferDispatcher struct {
|
|||||||
parsePureIp bool
|
parsePureIp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SnifferDispatcher) shouldOverride(metadata *C.Metadata) bool {
|
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
|
||||||
return (metadata.Host == "" && sd.parsePureIp) ||
|
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
|
||||||
sd.forceDomain.Has(metadata.Host) ||
|
|
||||||
(metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sd *SnifferDispatcher) UDPSniff(packet C.PacketAdapter) bool {
|
|
||||||
metadata := packet.Metadata()
|
|
||||||
|
|
||||||
if sd.shouldOverride(packet.Metadata()) {
|
|
||||||
for sniffer, config := range sd.sniffers {
|
|
||||||
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet {
|
|
||||||
inWhitelist := sniffer.SupportPort(metadata.DstPort)
|
|
||||||
overrideDest := config.OverrideDest
|
|
||||||
|
|
||||||
if inWhitelist {
|
|
||||||
host, err := sniffer.SniffData(packet.Data())
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sd.replaceDomain(metadata, host, overrideDest)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TCPSniff returns true if the connection is sniffed to have a domain
|
|
||||||
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool {
|
|
||||||
if sd.shouldOverride(metadata) {
|
|
||||||
inWhitelist := false
|
inWhitelist := false
|
||||||
overrideDest := false
|
overrideDest := false
|
||||||
for sniffer, config := range sd.sniffers {
|
for sniffer, config := range sd.sniffers {
|
||||||
@ -82,7 +50,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !inWhitelist {
|
if !inWhitelist {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sd.rwMux.RLock()
|
sd.rwMux.RLock()
|
||||||
@ -90,18 +58,18 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
|||||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||||
defer sd.rwMux.RUnlock()
|
defer sd.rwMux.RUnlock()
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
sd.rwMux.RUnlock()
|
sd.rwMux.RUnlock()
|
||||||
|
|
||||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
||||||
sd.cacheSniffFailed(metadata)
|
sd.cacheSniffFailed(metadata)
|
||||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||||
return false
|
return
|
||||||
} else {
|
} else {
|
||||||
if sd.skipSNI.Has(host) {
|
if sd.skipSNI.Has(host) {
|
||||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sd.rwMux.RLock()
|
sd.rwMux.RLock()
|
||||||
@ -109,24 +77,20 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
|||||||
sd.rwMux.RUnlock()
|
sd.rwMux.RUnlock()
|
||||||
|
|
||||||
sd.replaceDomain(metadata, host, overrideDest)
|
sd.replaceDomain(metadata, host, overrideDest)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||||
// show log early, since the following code may mutate `metadata.Host`
|
|
||||||
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
|
||||||
metadata.NetWork,
|
|
||||||
metadata.SourceDetail(),
|
|
||||||
metadata.RemoteAddress(),
|
|
||||||
metadata.Host, host)
|
|
||||||
metadata.SniffHost = host
|
metadata.SniffHost = host
|
||||||
if overrideDest {
|
if overrideDest {
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
}
|
}
|
||||||
metadata.DNSMode = C.DNSNormal
|
metadata.DNSMode = C.DNSNormal
|
||||||
|
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||||
|
metadata.SourceDetail(),
|
||||||
|
metadata.RemoteAddress(),
|
||||||
|
metadata.Host, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SnifferDispatcher) Enable() bool {
|
func (sd *SnifferDispatcher) Enable() bool {
|
||||||
@ -157,7 +121,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := s.SniffData(bytes)
|
host, err := s.SniffTCP(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||||
continue
|
continue
|
||||||
@ -226,8 +190,6 @@ func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer
|
|||||||
return NewTLSSniffer(snifferConfig)
|
return NewTLSSniffer(snifferConfig)
|
||||||
case sniffer.HTTP:
|
case sniffer.HTTP:
|
||||||
return NewHTTPSniffer(snifferConfig)
|
return NewHTTPSniffer(snifferConfig)
|
||||||
case sniffer.QUIC:
|
|
||||||
return NewQuicSniffer(snifferConfig)
|
|
||||||
default:
|
default:
|
||||||
return nil, ErrorUnsupportedSniffer
|
return nil, ErrorUnsupportedSniffer
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func (http *HTTPSniffer) SupportNetwork() C.NetWork {
|
|||||||
return C.TCP
|
return C.TCP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (http *HTTPSniffer) SniffData(bytes []byte) (string, error) {
|
func (http *HTTPSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||||
domain, err := SniffHTTP(bytes)
|
domain, err := SniffHTTP(bytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return *domain, nil
|
return *domain, nil
|
||||||
|
@ -1,287 +1,3 @@
|
|||||||
package sniffer
|
package sniffer
|
||||||
|
|
||||||
import (
|
//TODO
|
||||||
"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,40 +1,9 @@
|
|||||||
package sniffer
|
package sniffer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQuicHeaders(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
input string
|
|
||||||
domain string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b",
|
|
||||||
domain: "www.google.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738",
|
|
||||||
domain: "cloudflare-dns.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
q, err := NewQuicSniffer(SnifferConfig{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
for _, test := range cases {
|
|
||||||
pkt, err := hex.DecodeString(test.input)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
oriPkt := bytes.Clone(pkt)
|
|
||||||
domain, err := q.SniffData(pkt)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, test.domain, domain)
|
|
||||||
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSHeaders(t *testing.T) {
|
func TestTLSHeaders(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
input []byte
|
input []byte
|
||||||
@ -173,7 +142,6 @@ func TestTLSHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range cases {
|
for _, test := range cases {
|
||||||
input := bytes.Clone(test.input)
|
|
||||||
domain, err := SniffTLS(test.input)
|
domain, err := SniffTLS(test.input)
|
||||||
if test.err {
|
if test.err {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -187,6 +155,5 @@ func TestTLSHeaders(t *testing.T) {
|
|||||||
t.Error("expect domain ", test.domain, " but got ", domain)
|
t.Error("expect domain ", test.domain, " but got ", domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.Equal(t, input, test.input)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ func (tls *TLSSniffer) SupportNetwork() C.NetWork {
|
|||||||
return C.TCP
|
return C.TCP
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tls *TLSSniffer) SniffData(bytes []byte) (string, error) {
|
func (tls *TLSSniffer) SniffTCP(bytes []byte) (string, error) {
|
||||||
domain, err := SniffTLS(bytes)
|
domain, err := SniffTLS(bytes)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return *domain, nil
|
return *domain, nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package ca
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -8,13 +8,12 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var trustCerts []*x509.Certificate
|
var trustCerts []*x509.Certificate
|
||||||
var globalCertPool *x509.CertPool
|
var certPool *x509.CertPool
|
||||||
var mutex sync.RWMutex
|
var mutex sync.RWMutex
|
||||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||||
|
|
||||||
@ -34,12 +33,12 @@ func AddCertificate(certificate string) error {
|
|||||||
|
|
||||||
func initializeCertPool() {
|
func initializeCertPool() {
|
||||||
var err error
|
var err error
|
||||||
globalCertPool, err = x509.SystemCertPool()
|
certPool, err = x509.SystemCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
globalCertPool = x509.NewCertPool()
|
certPool = x509.NewCertPool()
|
||||||
}
|
}
|
||||||
for _, cert := range trustCerts {
|
for _, cert := range trustCerts {
|
||||||
globalCertPool.AddCert(cert)
|
certPool.AddCert(cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,15 +53,15 @@ func getCertPool() *x509.CertPool {
|
|||||||
if len(trustCerts) == 0 {
|
if len(trustCerts) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if globalCertPool == nil {
|
if certPool == nil {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
if globalCertPool != nil {
|
if certPool != nil {
|
||||||
return globalCertPool
|
return certPool
|
||||||
}
|
}
|
||||||
initializeCertPool()
|
initializeCertPool()
|
||||||
}
|
}
|
||||||
return globalCertPool
|
return certPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
@ -95,49 +94,29 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) {
|
|||||||
return (*[32]byte)(fpByte), nil
|
return (*[32]byte)(fpByte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTLSConfig specified fingerprint, customCA and customCAString
|
func GetDefaultTLSConfig() *tls.Config {
|
||||||
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) {
|
return GetGlobalTLSConfig(nil)
|
||||||
if tlsConfig == nil {
|
|
||||||
tlsConfig = &tls.Config{}
|
|
||||||
}
|
|
||||||
var certificate []byte
|
|
||||||
var err error
|
|
||||||
if len(customCA) > 0 {
|
|
||||||
certificate, err = os.ReadFile(customCA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("load ca error: %w", err)
|
|
||||||
}
|
|
||||||
} else if customCAString != "" {
|
|
||||||
certificate = []byte(customCAString)
|
|
||||||
}
|
|
||||||
if len(certificate) > 0 {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
if !certPool.AppendCertsFromPEM(certificate) {
|
|
||||||
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
|
|
||||||
}
|
|
||||||
tlsConfig.RootCAs = certPool
|
|
||||||
} else {
|
|
||||||
tlsConfig.RootCAs = getCertPool()
|
|
||||||
}
|
|
||||||
if len(fingerprint) > 0 {
|
|
||||||
var fingerprintBytes *[32]byte
|
|
||||||
fingerprintBytes, err = convertFingerprint(fingerprint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
|
||||||
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSpecifiedFingerprintTLSConfig specified fingerprint
|
// GetSpecifiedFingerprintTLSConfig specified fingerprint
|
||||||
func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) {
|
func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) {
|
||||||
return GetTLSConfig(tlsConfig, fingerprint, "", "")
|
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
||||||
|
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||||
tlsConfig, _ = GetTLSConfig(tlsConfig, "", "", "")
|
certPool := getCertPool()
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return &tls.Config{
|
||||||
|
RootCAs: certPool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = certPool
|
||||||
return tlsConfig
|
return tlsConfig
|
||||||
}
|
}
|
@ -22,7 +22,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/ntp"
|
|
||||||
|
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
"github.com/zhangyunhao116/fastrand"
|
||||||
@ -71,7 +70,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
|||||||
rawSessionID[i] = 0
|
rawSessionID[i] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||||||
|
|
||||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||||
hello.SessionId[0] = 1
|
hello.SessionId[0] = 1
|
||||||
|
@ -99,9 +99,10 @@ func copyConfig(c *tls.Config) *utls.Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
|
// WebsocketHandshake basically calls UConn.Handshake inside it but 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
|
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
|
||||||
func (c *UConn) BuildWebsocketHandshakeState() error {
|
func (c *UConn) WebsocketHandshake() error {
|
||||||
// Build the handshake state. This will apply every variable of the TLS of the
|
// Build the handshake state. This will apply every variable of the TLS of the
|
||||||
// fingerprint in the UConn
|
// fingerprint in the UConn
|
||||||
if err := c.BuildHandshakeState(); err != nil {
|
if err := c.BuildHandshakeState(); err != nil {
|
||||||
@ -119,11 +120,11 @@ func (c *UConn) BuildWebsocketHandshakeState() error {
|
|||||||
if !hasALPNExtension { // Append extension if doesn't exists
|
if !hasALPNExtension { // Append extension if doesn't exists
|
||||||
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
|
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
|
||||||
}
|
}
|
||||||
// Rebuild the client hello
|
// Rebuild the client hello and do the handshake
|
||||||
if err := c.BuildHandshakeState(); err != nil {
|
if err := c.BuildHandshakeState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return c.Handshake()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetGlobalUtlsClient(Client string) {
|
func SetGlobalUtlsClient(Client string) {
|
||||||
|
191
config/config.go
191
config/config.go
@ -8,7 +8,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -17,7 +16,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/adapter/provider"
|
"github.com/Dreamacro/clash/adapter/provider"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
@ -59,8 +57,8 @@ type General struct {
|
|||||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||||
Sniffing bool `json:"sniffing"`
|
Sniffing bool `json:"sniffing"`
|
||||||
|
EBpf EBpf `json:"-"`
|
||||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||||
GlobalUA string `json:"global-ua"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound config
|
// Inbound config
|
||||||
@ -75,7 +73,6 @@ type Inbound struct {
|
|||||||
ShadowSocksConfig string `json:"ss-config"`
|
ShadowSocksConfig string `json:"ss-config"`
|
||||||
VmessConfig string `json:"vmess-config"`
|
VmessConfig string `json:"vmess-config"`
|
||||||
Authentication []string `json:"authentication"`
|
Authentication []string `json:"authentication"`
|
||||||
SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"`
|
|
||||||
AllowLan bool `json:"allow-lan"`
|
AllowLan bool `json:"allow-lan"`
|
||||||
BindAddress string `json:"bind-address"`
|
BindAddress string `json:"bind-address"`
|
||||||
InboundTfo bool `json:"inbound-tfo"`
|
InboundTfo bool `json:"inbound-tfo"`
|
||||||
@ -90,16 +87,6 @@ type Controller struct {
|
|||||||
Secret string `json:"-"`
|
Secret string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NTP config
|
|
||||||
type NTP struct {
|
|
||||||
Enable bool `yaml:"enable"`
|
|
||||||
Server string `yaml:"server"`
|
|
||||||
Port int `yaml:"port"`
|
|
||||||
Interval int `yaml:"interval"`
|
|
||||||
DialerProxy string `yaml:"dialer-proxy"`
|
|
||||||
WriteToSystem bool `yaml:"write-to-system"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNS config
|
// DNS config
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
@ -158,15 +145,12 @@ type Sniffer struct {
|
|||||||
// Experimental config
|
// Experimental config
|
||||||
type Experimental struct {
|
type Experimental struct {
|
||||||
Fingerprints []string `yaml:"fingerprints"`
|
Fingerprints []string `yaml:"fingerprints"`
|
||||||
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
|
|
||||||
QUICGoDisableECN bool `yaml:"quic-go-disable-ecn"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is clash config manager
|
// Config is clash config manager
|
||||||
type Config struct {
|
type Config struct {
|
||||||
General *General
|
General *General
|
||||||
IPTables *IPTables
|
IPTables *IPTables
|
||||||
NTP *NTP
|
|
||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
@ -183,44 +167,30 @@ type Config struct {
|
|||||||
TLS *TLS
|
TLS *TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawNTP struct {
|
|
||||||
Enable bool `yaml:"enable"`
|
|
||||||
Server string `yaml:"server"`
|
|
||||||
ServerPort int `yaml:"server-port"`
|
|
||||||
Interval int `yaml:"interval"`
|
|
||||||
DialerProxy string `yaml:"dialer-proxy"`
|
|
||||||
WriteToSystem bool `yaml:"write-to-system"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
||||||
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
UseHosts bool `yaml:"use-hosts"`
|
||||||
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback" json:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
FallbackFilter RawFallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen" json:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter"`
|
||||||
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
DefaultNameserver []string `yaml:"default-nameserver"`
|
||||||
NameServerPolicy map[string]any `yaml:"nameserver-policy" json:"nameserver-policy"`
|
NameServerPolicy map[string]any `yaml:"nameserver-policy"`
|
||||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip" json:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"`
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
Domain []string `yaml:"domain" json:"domain"`
|
Domain []string `yaml:"domain"`
|
||||||
GeoSite []string `yaml:"geosite" json:"geosite"`
|
GeoSite []string `yaml:"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 {
|
type RawTun struct {
|
||||||
@ -233,11 +203,11 @@ type RawTun struct {
|
|||||||
RedirectToTun []string `yaml:"-" json:"-"`
|
RedirectToTun []string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
//Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
//Inet4Address []LC.ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
|
||||||
Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
Inet6Address []LC.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
|
||||||
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
|
||||||
Inet4RouteAddress []netip.Prefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
Inet4RouteAddress []LC.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
|
||||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
Inet6RouteAddress []LC.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
|
||||||
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
|
||||||
@ -266,46 +236,40 @@ type RawTuicServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
Port int `yaml:"port" json:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port" json:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port" json:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
TProxyPort int `yaml:"tproxy-port" json:"tproxy-port"`
|
TProxyPort int `yaml:"tproxy-port"`
|
||||||
MixedPort int `yaml:"mixed-port" json:"mixed-port"`
|
MixedPort int `yaml:"mixed-port"`
|
||||||
ShadowSocksConfig string `yaml:"ss-config"`
|
ShadowSocksConfig string `yaml:"ss-config"`
|
||||||
VmessConfig string `yaml:"vmess-config"`
|
VmessConfig string `yaml:"vmess-config"`
|
||||||
InboundTfo bool `yaml:"inbound-tfo"`
|
InboundTfo bool `yaml:"inbound-tfo"`
|
||||||
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
||||||
Authentication []string `yaml:"authentication" json:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
AllowLan bool `yaml:"allow-lan" json:"allow-lan"`
|
BindAddress string `yaml:"bind-address"`
|
||||||
BindAddress string `yaml:"bind-address" json:"bind-address"`
|
Mode T.TunnelMode `yaml:"mode"`
|
||||||
Mode T.TunnelMode `yaml:"mode" json:"mode"`
|
UnifiedDelay bool `yaml:"unified-delay"`
|
||||||
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
|
|
||||||
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
|
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
Interface string `yaml:"interface-name"`
|
Interface string `yaml:"interface-name"`
|
||||||
RoutingMark int `yaml:"routing-mark"`
|
RoutingMark int `yaml:"routing-mark"`
|
||||||
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
||||||
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
|
GeodataMode bool `yaml:"geodata-mode"`
|
||||||
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
|
GeodataLoader string `yaml:"geodata-loader"`
|
||||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||||
GlobalUA string `yaml:"global-ua"`
|
|
||||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
|
||||||
|
|
||||||
Sniffer RawSniffer `yaml:"sniffer" json:"sniffer"`
|
Sniffer RawSniffer `yaml:"sniffer"`
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||||
Hosts map[string]any `yaml:"hosts" json:"hosts"`
|
Hosts map[string]any `yaml:"hosts"`
|
||||||
NTP RawNTP `yaml:"ntp" json:"ntp"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
DNS RawDNS `yaml:"dns" json:"dns"`
|
|
||||||
Tun RawTun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||||
EBpf EBpf `yaml:"ebpf"`
|
EBpf EBpf `yaml:"ebpf"`
|
||||||
@ -319,8 +283,6 @@ type RawConfig struct {
|
|||||||
SubRules map[string][]string `yaml:"sub-rules"`
|
SubRules map[string][]string `yaml:"sub-rules"`
|
||||||
RawTLS TLS `yaml:"tls"`
|
RawTLS TLS `yaml:"tls"`
|
||||||
Listeners []map[string]any `yaml:"listeners"`
|
Listeners []map[string]any `yaml:"listeners"`
|
||||||
|
|
||||||
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoXUrl struct {
|
type GeoXUrl struct {
|
||||||
@ -386,7 +348,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
ProxyGroup: []map[string]any{},
|
ProxyGroup: []map[string]any{},
|
||||||
TCPConcurrent: false,
|
TCPConcurrent: false,
|
||||||
FindProcessMode: P.FindProcessStrict,
|
FindProcessMode: P.FindProcessStrict,
|
||||||
GlobalUA: "clash.meta",
|
|
||||||
Tun: RawTun{
|
Tun: RawTun{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Device: "",
|
Device: "",
|
||||||
@ -394,7 +355,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||||
AutoRoute: true,
|
AutoRoute: true,
|
||||||
AutoDetectInterface: true,
|
AutoDetectInterface: true,
|
||||||
Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")},
|
Inet6Address: []LC.ListenPrefix{LC.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))},
|
||||||
},
|
},
|
||||||
TuicServer: RawTuicServer{
|
TuicServer: RawTuicServer{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
@ -418,13 +379,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
InboundInterface: "lo",
|
InboundInterface: "lo",
|
||||||
Bypass: []string{},
|
Bypass: []string{},
|
||||||
},
|
},
|
||||||
NTP: RawNTP{
|
|
||||||
Enable: false,
|
|
||||||
WriteToSystem: false,
|
|
||||||
Server: "time.apple.com",
|
|
||||||
ServerPort: 123,
|
|
||||||
Interval: 30,
|
|
||||||
},
|
|
||||||
DNS: RawDNS{
|
DNS: RawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
IPv6: false,
|
IPv6: false,
|
||||||
@ -472,7 +426,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
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",
|
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||||
},
|
},
|
||||||
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||||
@ -488,6 +441,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
config.Experimental = &rawCfg.Experimental
|
config.Experimental = &rawCfg.Experimental
|
||||||
config.Profile = &rawCfg.Profile
|
config.Profile = &rawCfg.Profile
|
||||||
|
config.IPTables = &rawCfg.IPTables
|
||||||
config.TLS = &rawCfg.RawTLS
|
config.TLS = &rawCfg.RawTLS
|
||||||
|
|
||||||
general, err := parseGeneral(rawCfg)
|
general, err := parseGeneral(rawCfg)
|
||||||
@ -539,15 +493,17 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.Hosts = hosts
|
config.Hosts = hosts
|
||||||
|
|
||||||
ntpCfg := paresNTP(rawCfg)
|
|
||||||
config.NTP = ntpCfg
|
|
||||||
|
|
||||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
|
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.DNS = dnsCfg
|
config.DNS = dnsCfg
|
||||||
|
|
||||||
|
err = parseTun(rawCfg.Tun, config.General)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = parseTuicServer(rawCfg.TuicServer, config.General)
|
err = parseTuicServer(rawCfg.TuicServer, config.General)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -577,40 +533,19 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
|
externalUI := cfg.ExternalUI
|
||||||
geodata.SetLoader(cfg.GeodataLoader)
|
geodata.SetLoader(cfg.GeodataLoader)
|
||||||
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
||||||
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
|
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
|
||||||
C.MmdbUrl = cfg.GeoXUrl.Mmdb
|
C.MmdbUrl = cfg.GeoXUrl.Mmdb
|
||||||
C.GeodataMode = cfg.GeodataMode
|
C.GeodataMode = cfg.GeodataMode
|
||||||
C.UA = cfg.GlobalUA
|
|
||||||
if cfg.KeepAliveInterval != 0 {
|
|
||||||
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
ExternalUIPath = cfg.ExternalUI
|
|
||||||
// checkout externalUI exist
|
// checkout externalUI exist
|
||||||
if ExternalUIPath != "" {
|
if externalUI != "" {
|
||||||
ExternalUIPath = C.Path.Resolve(ExternalUIPath)
|
externalUI = C.Path.Resolve(externalUI)
|
||||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
||||||
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
|
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||||
log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath)
|
|
||||||
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ExternalUIPath = defaultUIpath
|
|
||||||
cfg.ExternalUI = defaultUIpath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// checkout UIpath/name exist
|
|
||||||
if cfg.ExternalUIName != "" {
|
|
||||||
ExternalUIName = cfg.ExternalUIName
|
|
||||||
} else {
|
|
||||||
ExternalUIFolder = ExternalUIPath
|
|
||||||
}
|
|
||||||
if cfg.ExternalUIURL != "" {
|
|
||||||
ExternalUIURL = cfg.ExternalUIURL
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
|
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
|
||||||
return &General{
|
return &General{
|
||||||
Inbound: Inbound{
|
Inbound: Inbound{
|
||||||
@ -622,7 +557,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
ShadowSocksConfig: cfg.ShadowSocksConfig,
|
ShadowSocksConfig: cfg.ShadowSocksConfig,
|
||||||
VmessConfig: cfg.VmessConfig,
|
VmessConfig: cfg.VmessConfig,
|
||||||
AllowLan: cfg.AllowLan,
|
AllowLan: cfg.AllowLan,
|
||||||
SkipAuthPrefixes: cfg.SkipAuthPrefixes,
|
|
||||||
BindAddress: cfg.BindAddress,
|
BindAddress: cfg.BindAddress,
|
||||||
InboundTfo: cfg.InboundTfo,
|
InboundTfo: cfg.InboundTfo,
|
||||||
InboundMPTCP: cfg.InboundMPTCP,
|
InboundMPTCP: cfg.InboundMPTCP,
|
||||||
@ -644,8 +578,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
GeodataLoader: cfg.GeodataLoader,
|
GeodataLoader: cfg.GeodataLoader,
|
||||||
TCPConcurrent: cfg.TCPConcurrent,
|
TCPConcurrent: cfg.TCPConcurrent,
|
||||||
FindProcessMode: cfg.FindProcessMode,
|
FindProcessMode: cfg.FindProcessMode,
|
||||||
|
EBpf: cfg.EBpf,
|
||||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||||
GlobalUA: cfg.GlobalUA,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1198,19 +1132,6 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
|||||||
return sites, nil
|
return sites, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func paresNTP(rawCfg *RawConfig) *NTP {
|
|
||||||
cfg := rawCfg.NTP
|
|
||||||
ntpCfg := &NTP{
|
|
||||||
Enable: cfg.Enable,
|
|
||||||
Server: cfg.Server,
|
|
||||||
Port: cfg.ServerPort,
|
|
||||||
Interval: cfg.Interval,
|
|
||||||
DialerProxy: cfg.DialerProxy,
|
|
||||||
WriteToSystem: cfg.WriteToSystem,
|
|
||||||
}
|
|
||||||
return ntpCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
|
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
|
||||||
cfg := rawCfg.DNS
|
cfg := rawCfg.DNS
|
||||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||||
@ -1363,7 +1284,7 @@ func parseTun(rawTun RawTun, general *General) error {
|
|||||||
RedirectToTun: rawTun.RedirectToTun,
|
RedirectToTun: rawTun.RedirectToTun,
|
||||||
|
|
||||||
MTU: rawTun.MTU,
|
MTU: rawTun.MTU,
|
||||||
Inet4Address: []netip.Prefix{tunAddressPrefix},
|
Inet4Address: []LC.ListenPrefix{LC.ListenPrefix(tunAddressPrefix)},
|
||||||
Inet6Address: rawTun.Inet6Address,
|
Inet6Address: rawTun.Inet6Address,
|
||||||
StrictRoute: rawTun.StrictRoute,
|
StrictRoute: rawTun.StrictRoute,
|
||||||
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
Inet4RouteAddress: rawTun.Inet4RouteAddress,
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||||
|
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
@ -66,3 +72,19 @@ func UpdateGeoDatabases() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadForBytes(url string) ([]byte, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||||
|
defer cancel()
|
||||||
|
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveFile(bytes []byte, path string) error {
|
||||||
|
return os.WriteFile(path, bytes, 0o644)
|
||||||
|
}
|
@ -1,145 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ExternalUIURL string
|
|
||||||
ExternalUIPath string
|
|
||||||
ExternalUIFolder string
|
|
||||||
ExternalUIName string
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
|
|
||||||
)
|
|
||||||
var xdMutex sync.Mutex
|
|
||||||
|
|
||||||
func UpdateUI() error {
|
|
||||||
xdMutex.Lock()
|
|
||||||
defer xdMutex.Unlock()
|
|
||||||
|
|
||||||
err := prepare()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := downloadForBytes(ExternalUIURL)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't download file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
saved := path.Join(C.Path.HomeDir(), "download.zip")
|
|
||||||
if saveFile(data, saved) != nil {
|
|
||||||
return fmt.Errorf("can't save zip file: %w", err)
|
|
||||||
}
|
|
||||||
defer os.Remove(saved)
|
|
||||||
|
|
||||||
err = cleanup(ExternalUIFolder)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("cleanup exist file error: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unzipFolder, err := unzip(saved, C.Path.HomeDir())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't extract zip file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Rename(unzipFolder, ExternalUIFolder)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't rename folder: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepare() error {
|
|
||||||
if ExternalUIPath == "" || ExternalUIURL == "" {
|
|
||||||
return ErrIncompleteConf
|
|
||||||
}
|
|
||||||
|
|
||||||
if ExternalUIName != "" {
|
|
||||||
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
|
|
||||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ExternalUIFolder = ExternalUIPath
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unzip(src, dest string) (string, error) {
|
|
||||||
r, err := zip.OpenReader(src)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
var extractedFolder string
|
|
||||||
for _, f := range r.File {
|
|
||||||
fpath := filepath.Join(dest, f.Name)
|
|
||||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
|
||||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
|
||||||
}
|
|
||||||
if f.FileInfo().IsDir() {
|
|
||||||
os.MkdirAll(fpath, os.ModePerm)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
rc, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(outFile, rc)
|
|
||||||
outFile.Close()
|
|
||||||
rc.Close()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if extractedFolder == "" {
|
|
||||||
extractedFolder = filepath.Dir(fpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return extractedFolder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanup(root string) error {
|
|
||||||
if _, err := os.Stat(root); os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
if err := os.RemoveAll(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := os.Remove(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,37 +1,15 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func downloadForBytes(url string) ([]byte, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
|
||||||
defer cancel()
|
|
||||||
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFile(bytes []byte, path string) error {
|
|
||||||
return os.WriteFile(path, bytes, 0o644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimArr(arr []string) (r []string) {
|
func trimArr(arr []string) (r []string) {
|
||||||
for _, e := range arr {
|
for _, e := range arr {
|
||||||
r = append(r, strings.Trim(e, " "))
|
r = append(r, strings.Trim(e, " "))
|
||||||
|
@ -36,7 +36,6 @@ const (
|
|||||||
Vless
|
Vless
|
||||||
Trojan
|
Trojan
|
||||||
Hysteria
|
Hysteria
|
||||||
Hysteria2
|
|
||||||
WireGuard
|
WireGuard
|
||||||
Tuic
|
Tuic
|
||||||
)
|
)
|
||||||
@ -201,8 +200,6 @@ func (at AdapterType) String() string {
|
|||||||
return "Trojan"
|
return "Trojan"
|
||||||
case Hysteria:
|
case Hysteria:
|
||||||
return "Hysteria"
|
return "Hysteria"
|
||||||
case Hysteria2:
|
|
||||||
return "Hysteria2"
|
|
||||||
case WireGuard:
|
case WireGuard:
|
||||||
return "WireGuard"
|
return "WireGuard"
|
||||||
case Tuic:
|
case Tuic:
|
||||||
@ -252,23 +249,6 @@ type PacketAdapter interface {
|
|||||||
Metadata() *Metadata
|
Metadata() *Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
type packetAdapter struct {
|
|
||||||
UDPPacket
|
|
||||||
metadata *Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata returns destination metadata
|
|
||||||
func (s *packetAdapter) Metadata() *Metadata {
|
|
||||||
return s.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
|
|
||||||
return &packetAdapter{
|
|
||||||
packet,
|
|
||||||
metadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriteBack interface {
|
type WriteBack interface {
|
||||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||||
}
|
}
|
||||||
@ -287,17 +267,13 @@ type NatTable interface {
|
|||||||
|
|
||||||
Delete(key string)
|
Delete(key string)
|
||||||
|
|
||||||
DeleteLock(key string)
|
GetLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||||
|
|
||||||
GetForLocalConn(lAddr, rAddr string) *net.UDPConn
|
AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||||
|
|
||||||
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
RangeLocalConn(lAddr string, f func(key, value any) bool)
|
||||||
|
|
||||||
RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool)
|
GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
|
||||||
|
|
||||||
GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool)
|
DeleteLocalConnMap(lAddr, key string)
|
||||||
|
|
||||||
DeleteForLocalConn(lAddr, key string)
|
|
||||||
|
|
||||||
DeleteLockForLocalConn(lAddr, key string)
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package constant
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
GeodataMode bool
|
GeodataMode bool
|
||||||
GeoIpUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
|
GeoIpUrl string
|
||||||
MmdbUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
|
MmdbUrl string
|
||||||
GeoSiteUrl = "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
|
GeoSiteUrl string
|
||||||
)
|
)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
var (
|
|
||||||
UA string
|
|
||||||
)
|
|
@ -16,7 +16,7 @@ type MultiAddrListener interface {
|
|||||||
|
|
||||||
type InboundListener interface {
|
type InboundListener interface {
|
||||||
Name() string
|
Name() string
|
||||||
Listen(tunnel Tunnel) error
|
Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error
|
||||||
Close() error
|
Close() error
|
||||||
Address() string
|
Address() string
|
||||||
RawAddress() string
|
RawAddress() string
|
||||||
|
@ -30,7 +30,6 @@ const (
|
|||||||
TUNNEL
|
TUNNEL
|
||||||
TUN
|
TUN
|
||||||
TUIC
|
TUIC
|
||||||
HYSTERIA2
|
|
||||||
INNER
|
INNER
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,8 +78,6 @@ func (t Type) String() string {
|
|||||||
return "Tun"
|
return "Tun"
|
||||||
case TUIC:
|
case TUIC:
|
||||||
return "Tuic"
|
return "Tuic"
|
||||||
case HYSTERIA2:
|
|
||||||
return "Hysteria2"
|
|
||||||
case INNER:
|
case INNER:
|
||||||
return "Inner"
|
return "Inner"
|
||||||
default:
|
default:
|
||||||
@ -113,8 +110,6 @@ func ParseType(t string) (*Type, error) {
|
|||||||
res = TUN
|
res = TUN
|
||||||
case "TUIC":
|
case "TUIC":
|
||||||
res = TUIC
|
res = TUIC
|
||||||
case "HYSTERIA2":
|
|
||||||
res = HYSTERIA2
|
|
||||||
case "INNER":
|
case "INNER":
|
||||||
res = INNER
|
res = INNER
|
||||||
default:
|
default:
|
||||||
@ -147,9 +142,6 @@ type Metadata struct {
|
|||||||
SpecialProxy string `json:"specialProxy"`
|
SpecialProxy string `json:"specialProxy"`
|
||||||
SpecialRules string `json:"specialRules"`
|
SpecialRules string `json:"specialRules"`
|
||||||
RemoteDst string `json:"remoteDestination"`
|
RemoteDst string `json:"remoteDestination"`
|
||||||
|
|
||||||
RawSrcAddr net.Addr `json:"-"`
|
|
||||||
RawDstAddr net.Addr `json:"-"`
|
|
||||||
// Only domain rule
|
// Only domain rule
|
||||||
SniffHost string `json:"sniffHost"`
|
SniffHost string `json:"sniffHost"`
|
||||||
}
|
}
|
||||||
@ -243,34 +235,6 @@ func (m *Metadata) Valid() bool {
|
|||||||
return m.Host != "" || m.DstIP.IsValid()
|
return m.Host != "" || m.DstIP.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metadata) SetRemoteAddr(addr net.Addr) error {
|
|
||||||
if addr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
|
||||||
if rawAddr := rawAddr.RawAddr(); rawAddr != nil {
|
|
||||||
if err := m.SetRemoteAddr(rawAddr); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok { // *net.TCPAddr, *net.UDPAddr, M.Socksaddr
|
|
||||||
if addrPort := addr.AddrPort(); addrPort.Port() != 0 {
|
|
||||||
m.DstPort = addrPort.Port()
|
|
||||||
if addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName
|
|
||||||
m.DstIP = addrPort.Addr().Unmap()
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
if addr, ok := addr.(interface{ AddrString() string }); ok { // must be sing's M.Socksaddr
|
|
||||||
m.Host = addr.AddrString() // actually is M.Socksaddr.Fqdn
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m.SetRemoteAddress(addr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
||||||
host, port, err := net.SplitHostPort(rawAddress)
|
host, port, err := net.SplitHostPort(rawAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,9 +18,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Path is used to get the configuration path
|
// Path is used to get the configuration path
|
||||||
//
|
|
||||||
// on Unix systems, `$HOME/.config/clash`.
|
|
||||||
// on Windows, `%USERPROFILE%/.config/clash`.
|
|
||||||
var Path = func() *path {
|
var Path = func() *path {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -28,13 +25,6 @@ var Path = func() *path {
|
|||||||
}
|
}
|
||||||
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
|
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
|
||||||
homeDir = P.Join(homeDir, ".config", Name)
|
homeDir = P.Join(homeDir, ".config", Name)
|
||||||
|
|
||||||
if _, err = os.Stat(homeDir); err != nil {
|
|
||||||
if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok {
|
|
||||||
homeDir = P.Join(configHome, Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ import "github.com/Dreamacro/clash/constant"
|
|||||||
|
|
||||||
type Sniffer interface {
|
type Sniffer interface {
|
||||||
SupportNetwork() constant.NetWork
|
SupportNetwork() constant.NetWork
|
||||||
// SniffData must not change input bytes
|
SniffTCP(bytes []byte) (string, error)
|
||||||
SniffData(bytes []byte) (string, error)
|
|
||||||
Protocol() string
|
Protocol() string
|
||||||
SupportPort(port uint16) bool
|
SupportPort(port uint16) bool
|
||||||
}
|
}
|
||||||
@ -13,11 +12,10 @@ type Sniffer interface {
|
|||||||
const (
|
const (
|
||||||
TLS Type = iota
|
TLS Type = iota
|
||||||
HTTP
|
HTTP
|
||||||
QUIC
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
List = []Type{TLS, HTTP, QUIC}
|
List = []Type{TLS, HTTP}
|
||||||
)
|
)
|
||||||
|
|
||||||
type Type int
|
type Type int
|
||||||
@ -28,8 +26,6 @@ func (rt Type) String() string {
|
|||||||
return "TLS"
|
return "TLS"
|
||||||
case HTTP:
|
case HTTP:
|
||||||
return "HTTP"
|
return "HTTP"
|
||||||
case QUIC:
|
|
||||||
return "QUIC"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -9,9 +9,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/atomic"
|
"github.com/Dreamacro/clash/common/atomic"
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
@ -23,7 +23,7 @@ type client struct {
|
|||||||
r *Resolver
|
r *Resolver
|
||||||
port string
|
port string
|
||||||
host string
|
host string
|
||||||
iface atomic.TypedValue[string]
|
iface *atomic.TypedValue[string]
|
||||||
proxyAdapter C.ProxyAdapter
|
proxyAdapter C.ProxyAdapter
|
||||||
proxyName string
|
proxyName string
|
||||||
addr string
|
addr string
|
||||||
@ -77,8 +77,8 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
network = "tcp"
|
network = "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
var options []dialer.Option
|
options := []dialer.Option{}
|
||||||
if c.iface.Load() != "" {
|
if c.iface != nil && c.iface.Load() != "" {
|
||||||
options = append(options, dialer.WithInterface(c.iface.Load()))
|
options = append(options, dialer.WithInterface(c.iface.Load()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
ch := make(chan result, 1)
|
ch := make(chan result, 1)
|
||||||
go func() {
|
go func() {
|
||||||
if strings.HasSuffix(c.Client.Net, "tls") {
|
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||||
conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
|
conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
|
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
//go:build disabled
|
|
||||||
// +build disabled
|
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
@ -382,7 +382,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error)
|
|||||||
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
||||||
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
||||||
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
|
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
|
||||||
tlsConfig := ca.GetGlobalTLSConfig(
|
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||||
&tls.Config{
|
&tls.Config{
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
@ -330,7 +330,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := ca.GetGlobalTLSConfig(
|
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||||
&tls.Config{
|
&tls.Config{
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
|
20
dns/local.go
20
dns/local.go
@ -1,20 +0,0 @@
|
|||||||
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,76 +4,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
|
||||||
"github.com/Dreamacro/clash/component/dhcp"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const SystemDNSPlaceholder = "system"
|
type LocalServer struct {
|
||||||
|
handler handler
|
||||||
var systemResolver *Resolver
|
|
||||||
var isolateHandler handler
|
|
||||||
|
|
||||||
var _ dnsClient = (*dhcpClient)(nil)
|
|
||||||
|
|
||||||
type dhcpClient struct {
|
|
||||||
enable bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dhcpClient) Address() string {
|
// ServeMsg implement resolver.LocalServer ResolveMsg
|
||||||
return SystemDNSPlaceholder
|
func (s *LocalServer) ServeMsg(ctx context.Context, msg *D.Msg) (*D.Msg, error) {
|
||||||
|
return handlerWithContext(ctx, s.handler, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func NewLocalServer(resolver *Resolver, mapper *ResolverEnhancer) *LocalServer {
|
||||||
return d.ExchangeContext(context.Background(), m)
|
return &LocalServer{handler: NewHandler(resolver, mapper)}
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
"github.com/samber/lo"
|
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -195,17 +194,12 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
msg := result.(*D.Msg)
|
msg := result.(*D.Msg)
|
||||||
|
|
||||||
if cache {
|
if cache {
|
||||||
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
|
putMsgToCache(r.lruCache, q.String(), msg)
|
||||||
msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool {
|
|
||||||
return rr.Header().Rrtype != D.TypeOPT
|
|
||||||
})
|
|
||||||
putMsgToCache(r.lruCache, q.String(), q, msg)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
isIPReq := isIPRequest(q)
|
isIPReq := isIPRequest(q)
|
||||||
if isIPReq {
|
if isIPReq {
|
||||||
cache = true
|
|
||||||
return r.ipExchange(ctx, m)
|
return r.ipExchange(ctx, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,10 +331,7 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
|||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||||
shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool {
|
if !r.shouldIPFallback(ips[0]) {
|
||||||
return !r.shouldIPFallback(ip)
|
|
||||||
})
|
|
||||||
if shouldNotFallback {
|
|
||||||
msg, err = res.Msg, res.Error // no need to wait for fallback result
|
msg, err = res.Msg, res.Error // no need to wait for fallback result
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -402,7 +393,7 @@ func (r *Resolver) Invalid() bool {
|
|||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
Net string
|
Net string
|
||||||
Addr string
|
Addr string
|
||||||
Interface atomic.TypedValue[string]
|
Interface *atomic.TypedValue[string]
|
||||||
ProxyAdapter C.ProxyAdapter
|
ProxyAdapter C.ProxyAdapter
|
||||||
ProxyName string
|
ProxyName string
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
|
@ -49,7 +49,6 @@ func (s *Server) SetHandler(handler handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||||
UpdateIsolateHandler(resolver, mapper)
|
|
||||||
if addr == address && resolver != nil {
|
if addr == address && resolver != nil {
|
||||||
handler := NewHandler(resolver, mapper)
|
handler := NewHandler(resolver, mapper)
|
||||||
server.SetHandler(handler)
|
server.SetHandler(handler)
|
||||||
|
39
dns/util.go
39
dns/util.go
@ -29,16 +29,10 @@ const (
|
|||||||
MaxMsgSize = 65535
|
MaxMsgSize = 65535
|
||||||
)
|
)
|
||||||
|
|
||||||
const serverFailureCacheTTL uint32 = 5
|
|
||||||
|
|
||||||
func minimalTTL(records []D.RR) uint32 {
|
func minimalTTL(records []D.RR) uint32 {
|
||||||
rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||||
return r1.Header().Ttl < r2.Header().Ttl
|
return r1.Header().Ttl < r2.Header().Ttl
|
||||||
})
|
}).Header().Ttl
|
||||||
if rr == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return rr.Header().Ttl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTTL(records []D.RR, ttl uint32) {
|
func updateTTL(records []D.RR, ttl uint32) {
|
||||||
@ -51,25 +45,28 @@ func updateTTL(records []D.RR, ttl uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, q D.Question, msg *D.Msg) {
|
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||||
// skip dns cache for acme challenge
|
// skip dns cache for acme challenge
|
||||||
if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") {
|
if len(msg.Question) != 0 {
|
||||||
|
if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") {
|
||||||
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
|
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
var ttl uint32
|
||||||
|
switch {
|
||||||
|
case len(msg.Answer) != 0:
|
||||||
|
ttl = minimalTTL(msg.Answer)
|
||||||
|
case len(msg.Ns) != 0:
|
||||||
|
ttl = minimalTTL(msg.Ns)
|
||||||
|
case len(msg.Extra) != 0:
|
||||||
|
ttl = minimalTTL(msg.Extra)
|
||||||
|
default:
|
||||||
|
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second))
|
|
||||||
|
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||||
@ -289,7 +286,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
|||||||
DstPort: uint16(uintPort),
|
DstPort: uint16(uintPort),
|
||||||
}
|
}
|
||||||
if proxyAdapter == nil {
|
if proxyAdapter == nil {
|
||||||
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !proxyAdapter.SupportUDP() {
|
if !proxyAdapter.SupportUDP() {
|
||||||
|
@ -8,11 +8,6 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
|||||||
|
|
||||||
allow-lan: true # 允许局域网连接
|
allow-lan: true # 允许局域网连接
|
||||||
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||||
authentication: # http,socks入口的验证用户名,密码
|
|
||||||
- "username:password"
|
|
||||||
skip-auth-prefixes: # 设置跳过验证的IP段
|
|
||||||
- 127.0.0.1/8
|
|
||||||
- ::1/128
|
|
||||||
|
|
||||||
# find-process-mode has 3 values:always, strict, off
|
# find-process-mode has 3 values:always, strict, off
|
||||||
# - always, 开启,强制匹配所有进程
|
# - always, 开启,强制匹配所有进程
|
||||||
@ -46,11 +41,7 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
|
|||||||
# secret: "123456" # `Authorization:Bearer ${secret}`
|
# secret: "123456" # `Authorization:Bearer ${secret}`
|
||||||
|
|
||||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||||
|
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||||
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
|
||||||
external-ui: /path/to/ui/folder/
|
|
||||||
external-ui-name: xd
|
|
||||||
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
|
||||||
|
|
||||||
# interface-name: en0 # 设置出口网卡
|
# interface-name: en0 # 设置出口网卡
|
||||||
|
|
||||||
@ -59,18 +50,8 @@ external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-
|
|||||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||||
global-client-fingerprint: chrome
|
global-client-fingerprint: chrome
|
||||||
|
|
||||||
# TCP keep alive interval
|
|
||||||
keep-alive-interval: 15
|
|
||||||
|
|
||||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||||
experimental:
|
experimental:
|
||||||
# Disable quic-go GSO support. This may result in reduced performance on Linux.
|
|
||||||
# This is not recommended for most users.
|
|
||||||
# Only users encountering issues with quic-go's internal implementation should enable this,
|
|
||||||
# and they should disable it as soon as the issue is resolved.
|
|
||||||
# This field will be removed when quic-go fixes all their issues in GSO.
|
|
||||||
# This equivalent to the environment variable QUIC_GO_DISABLE_GSO=1.
|
|
||||||
#quic-go-disable-gso: true
|
|
||||||
|
|
||||||
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
||||||
hosts:
|
hosts:
|
||||||
@ -142,9 +123,7 @@ sniffer:
|
|||||||
# 是否使用嗅探结果作为实际访问,默认 true
|
# 是否使用嗅探结果作为实际访问,默认 true
|
||||||
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
||||||
override-destination: false
|
override-destination: false
|
||||||
sniff: # TLS 和 QUIC 默认如果不配置 ports 默认嗅探 443
|
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||||
QUIC:
|
|
||||||
# ports: [ 443 ]
|
|
||||||
TLS:
|
TLS:
|
||||||
# ports: [443, 8443]
|
# ports: [443, 8443]
|
||||||
|
|
||||||
@ -649,25 +628,6 @@ proxies: # socks5
|
|||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||||
|
|
||||||
#hysteria2
|
|
||||||
- name: "hysteria2"
|
|
||||||
type: hysteria2
|
|
||||||
server: server.com
|
|
||||||
port: 443
|
|
||||||
# up和down均不写或为0则使用BBR流控
|
|
||||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
|
||||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
|
||||||
password: yourpassword
|
|
||||||
# obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander
|
|
||||||
# obfs-password: yourpassword
|
|
||||||
# sni: server.com
|
|
||||||
# skip-cert-verify: false
|
|
||||||
# fingerprint: xxxx
|
|
||||||
# alpn:
|
|
||||||
# - h3
|
|
||||||
# ca: "./my.ca"
|
|
||||||
# ca-str: "xyz"
|
|
||||||
|
|
||||||
# wireguard
|
# wireguard
|
||||||
- name: "wg"
|
- name: "wg"
|
||||||
type: wireguard
|
type: wireguard
|
||||||
@ -721,11 +681,6 @@ proxies: # socks5
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||||
# sni: example.com
|
# sni: example.com
|
||||||
#
|
|
||||||
# meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效
|
|
||||||
# 警告,与原版tuic不兼容!!!
|
|
||||||
# udp-over-stream: false
|
|
||||||
# udp-over-stream-version: 1
|
|
||||||
|
|
||||||
# ShadowsocksR
|
# ShadowsocksR
|
||||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||||
@ -943,10 +898,6 @@ listeners:
|
|||||||
- username: 1
|
- username: 1
|
||||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
alterId: 1
|
alterId: 1
|
||||||
# ws-path: "/" # 如果不为空则开启websocket传输层
|
|
||||||
# 下面两项如果填写则开启tls(需要同时填写)
|
|
||||||
# certificate: ./server.crt
|
|
||||||
# private-key: ./server.key
|
|
||||||
|
|
||||||
- name: tuic-in-1
|
- name: tuic-in-1
|
||||||
type: tuic
|
type: tuic
|
||||||
|
73
go.mod
73
go.mod
@ -3,53 +3,51 @@ module github.com/Dreamacro/clash
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/3andne/restls-client-go v0.1.6
|
github.com/3andne/restls-client-go v0.1.4
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||||
github.com/cilium/ebpf v0.12.0
|
github.com/cilium/ebpf v0.11.0
|
||||||
github.com/coreos/go-iptables v0.7.0
|
github.com/coreos/go-iptables v0.7.0
|
||||||
github.com/dlclark/regexp2 v1.10.0
|
github.com/dlclark/regexp2 v1.10.0
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gobwas/ws v1.3.0
|
|
||||||
github.com/gofrs/uuid/v5 v5.0.0
|
github.com/gofrs/uuid/v5 v5.0.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c
|
||||||
github.com/jpillora/backoff v1.0.0
|
github.com/jpillora/backoff v1.0.0
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5
|
github.com/klauspost/cpuid/v2 v2.2.5
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||||
github.com/mdlayher/netlink v1.7.2
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||||
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b
|
github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86
|
||||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966
|
github.com/metacubex/sing-shadowsocks v0.2.4
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.5
|
github.com/metacubex/sing-shadowsocks2 v0.1.3
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4
|
github.com/metacubex/sing-tun v0.1.11
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700
|
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8
|
||||||
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.55
|
||||||
github.com/miekg/dns v1.1.56
|
github.com/mroth/weightedrand/v2 v2.0.2
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0
|
|
||||||
github.com/openacid/low v0.1.21
|
github.com/openacid/low v0.1.21
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1
|
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||||
github.com/sagernet/sing v0.2.14
|
github.com/sagernet/sing v0.2.9
|
||||||
github.com/sagernet/sing-mux v0.1.3
|
github.com/sagernet/sing-mux v0.1.2
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.4
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||||
github.com/samber/lo v1.38.1
|
github.com/samber/lo v1.38.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.9
|
github.com/shirou/gopsutil/v3 v3.23.7
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/zhangyunhao116/fastrand v0.3.0
|
github.com/zhangyunhao116/fastrand v0.3.0
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
go.uber.org/automaxprocs v1.5.3
|
go.uber.org/automaxprocs v1.5.3
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||||
golang.org/x/net v0.17.0
|
golang.org/x/net v0.14.0
|
||||||
golang.org/x/sync v0.4.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/sys v0.13.0
|
golang.org/x/sys v0.11.0
|
||||||
google.golang.org/protobuf v1.31.0
|
google.golang.org/protobuf v1.31.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
lukechampine.com/blake3 v1.2.1
|
lukechampine.com/blake3 v1.2.1
|
||||||
@ -65,28 +63,26 @@ require (
|
|||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.16.7 // indirect
|
github.com/klauspost/compress v1.15.15 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect
|
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
|
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||||
@ -94,17 +90,16 @@ require (
|
|||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||||
go.uber.org/mock v0.3.0 // indirect
|
golang.org/x/mod v0.11.0 // indirect
|
||||||
golang.org/x/mod v0.13.0 // indirect
|
golang.org/x/text v0.12.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.14.0 // indirect
|
golang.org/x/tools v0.9.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9
|
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7
|
||||||
|
162
go.sum
162
go.sum
@ -1,5 +1,5 @@
|
|||||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
|
||||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
|
||||||
github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
|
github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
|
||||||
github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
|
github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
|
||||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||||
@ -14,8 +14,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
|||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/cilium/ebpf v0.12.0 h1:oQEuIQIXgYhe1v7sYUG0P9vtJTYZLLdA6tiQmrOB1mo=
|
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||||
github.com/cilium/ebpf v0.12.0/go.mod h1:u9H29/Iq+8cy70YqI6p5pfADkFl3vdnV2qXDg5JL0Zo=
|
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -33,10 +33,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4Rfsap
|
|||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
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=
|
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
@ -44,19 +42,14 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
|
|||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
|
|
||||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
|
||||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
@ -69,18 +62,20 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
|||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@ -95,28 +90,26 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
|||||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs=
|
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||||
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c=
|
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||||
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.37.4-0.20230809092428-5acf8eb2de86 h1:qGExcB3lYk51LPEJh5HTdQplbZmuTn+tkcuhuas1LC8=
|
||||||
github.com/metacubex/quic-go v0.39.1-0.20231019030608-fd969d66f16b/go.mod h1:4pe6cY+nAMFU/Uxn1rfnxNIowsaJGDQ3uyy4VuiPkP4=
|
github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86/go.mod h1:HhHoyskMk4kzfLPKcm7EF7pGXF89KRVwjbGrEaN6lIU=
|
||||||
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9 h1:F0+IuW0tZ96QHEmrebXAdYnz7ab7Gz4l5yYC4g6Cg8k=
|
github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7 h1:XY3Y6nPL45XuN/k3rDXJ1TJknLo8rTo1SVuDOmOEf4E=
|
||||||
github.com/metacubex/sing v0.0.0-20231001053806-1230641572b9/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
|
github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966 h1:wbOsbU3kfD5LRuJIntJwEPmgGSQukof8CgLNypi8az8=
|
github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20231008050747-a684db516966/go.mod h1:GU7g2AZesXItk4CspDP8Dc7eGtlA2GVDihyCwsUXRSo=
|
github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
|
github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
|
github.com/metacubex/sing-shadowsocks2 v0.1.3/go.mod h1:5Mt93RlmRlIcDmvtapkhQJ8YTRGLFhHciLYopJjs7j8=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
|
github.com/metacubex/sing-tun v0.1.11 h1:B8meDewklvKkeUfjqR2ViuYLam0/m4IgkTi3qcJIOuc=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
|
github.com/metacubex/sing-tun v0.1.11/go.mod h1:vbki176Y5sxXC1DWXucrPh3q5j8cKai1D87y8m8rjQc=
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700 h1:JToLa8cxHrd6tOUHWCg9YM+o/4MXmjgagG909itmnyE=
|
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 h1:AqqZCr9gOeKdO6oIzFh4b2puOUFcw8MdpmGHWRehyX8=
|
||||||
github.com/metacubex/sing-tun v0.1.15-0.20231022153326-92d6e97f0700/go.mod h1:atkIOs6Y5NeUzstK5SBvnrFo4z1JLuORhEfQECEVUpI=
|
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8/go.mod h1:tyJg7b4s8NrSztl/Y1ajA7X0sJLlIsEJWkgRVocjmgY=
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
|
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
|
||||||
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/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
|
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY=
|
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
github.com/mroth/weightedrand/v2 v2.0.2 h1:A8wJRUBcfguGl6oOQHI8fy5P4ViGRT9hdQdlG/7RiXo=
|
||||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
github.com/mroth/weightedrand/v2 v2.0.2/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||||
@ -137,35 +130,33 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.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 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
|
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
|
||||||
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g=
|
||||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
|
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
|
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@ -188,10 +179,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
@ -199,6 +190,7 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
|
|||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||||
@ -209,25 +201,26 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
|||||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
|
||||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -235,27 +228,36 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||||
|
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
@ -2,22 +2,16 @@ package executor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/ntp"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
"github.com/Dreamacro/clash/component/auth"
|
"github.com/Dreamacro/clash/component/auth"
|
||||||
"github.com/Dreamacro/clash/component/ca"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
G "github.com/Dreamacro/clash/component/geodata"
|
G "github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
@ -25,6 +19,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
SNI "github.com/Dreamacro/clash/component/sniffer"
|
SNI "github.com/Dreamacro/clash/component/sniffer"
|
||||||
|
CTLS "github.com/Dreamacro/clash/component/tls"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -84,9 +79,9 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
|
|
||||||
tunnel.OnSuspend()
|
tunnel.OnSuspend()
|
||||||
|
|
||||||
ca.ResetCertificate()
|
CTLS.ResetCertificate()
|
||||||
for _, c := range cfg.TLS.CustomTrustCert {
|
for _, c := range cfg.TLS.CustomTrustCert {
|
||||||
if err := ca.AddCertificate(c); err != nil {
|
if err := CTLS.AddCertificate(c); err != nil {
|
||||||
log.Warnln("%s\nadd error: %s", c, err.Error())
|
log.Warnln("%s\nadd error: %s", c, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +92,6 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateSniffer(cfg.Sniffer)
|
updateSniffer(cfg.Sniffer)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateGeneral(cfg.General)
|
updateGeneral(cfg.General)
|
||||||
updateNTP(cfg.NTP)
|
|
||||||
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
|
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
|
||||||
updateListeners(cfg.General, cfg.Listeners, force)
|
updateListeners(cfg.General, cfg.Listeners, force)
|
||||||
updateIPTables(cfg)
|
updateIPTables(cfg)
|
||||||
@ -118,7 +112,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initInnerTcp() {
|
func initInnerTcp() {
|
||||||
inner.New(tunnel.Tunnel)
|
inner.New(tunnel.TCPIn())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneral() *config.General {
|
func GetGeneral() *config.General {
|
||||||
@ -140,11 +134,9 @@ func GetGeneral() *config.General {
|
|||||||
ShadowSocksConfig: ports.ShadowSocksConfig,
|
ShadowSocksConfig: ports.ShadowSocksConfig,
|
||||||
VmessConfig: ports.VmessConfig,
|
VmessConfig: ports.VmessConfig,
|
||||||
Authentication: authenticator,
|
Authentication: authenticator,
|
||||||
SkipAuthPrefixes: inbound.SkipAuthPrefixes(),
|
|
||||||
AllowLan: listener.AllowLan(),
|
AllowLan: listener.AllowLan(),
|
||||||
BindAddress: listener.BindAddress(),
|
BindAddress: listener.BindAddress(),
|
||||||
},
|
},
|
||||||
Controller: config.Controller{},
|
|
||||||
Mode: tunnel.Mode(),
|
Mode: tunnel.Mode(),
|
||||||
LogLevel: log.Level(),
|
LogLevel: log.Level(),
|
||||||
IPv6: !resolver.DisableIPv6,
|
IPv6: !resolver.DisableIPv6,
|
||||||
@ -158,46 +150,32 @@ func GetGeneral() *config.General {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
|
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
|
||||||
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
|
tcpIn := tunnel.TCPIn()
|
||||||
|
udpIn := tunnel.UDPIn()
|
||||||
|
natTable := tunnel.NatTable()
|
||||||
|
|
||||||
|
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
|
||||||
if !force {
|
if !force {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
allowLan := general.AllowLan
|
allowLan := general.AllowLan
|
||||||
listener.SetAllowLan(allowLan)
|
listener.SetAllowLan(allowLan)
|
||||||
inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes)
|
|
||||||
|
|
||||||
bindAddress := general.BindAddress
|
bindAddress := general.BindAddress
|
||||||
listener.SetBindAddress(bindAddress)
|
listener.SetBindAddress(bindAddress)
|
||||||
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
|
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||||
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
|
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||||
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
|
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
||||||
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
|
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||||
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
|
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
||||||
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
|
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
|
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
||||||
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
|
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
||||||
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
|
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExperimental(c *config.Config) {
|
func updateExperimental(c *config.Config) {
|
||||||
if c.Experimental.QUICGoDisableGSO {
|
|
||||||
_ = os.Setenv("QUIC_GO_DISABLE_GSO", strconv.FormatBool(true))
|
|
||||||
}
|
|
||||||
if c.Experimental.QUICGoDisableECN {
|
|
||||||
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNTP(c *config.NTP) {
|
|
||||||
if c.Enable {
|
|
||||||
ntp.ReCreateNTPService(
|
|
||||||
net.JoinHostPort(c.Server, strconv.Itoa(c.Port)),
|
|
||||||
time.Duration(c.Interval),
|
|
||||||
c.DialerProxy,
|
|
||||||
c.WriteToSystem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
||||||
@ -340,7 +318,7 @@ func updateTun(general *config.General) {
|
|||||||
if general == nil {
|
if general == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
listener.ReCreateTun(general.Tun, tunnel.Tunnel)
|
listener.ReCreateTun(LC.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn())
|
||||||
listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
|
listener.ReCreateRedirToTun(general.Tun.RedirectToTun)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,7 +346,7 @@ func updateSniffer(sniffer *config.Sniffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTunnels(tunnels []LC.Tunnel) {
|
func updateTunnels(tunnels []LC.Tunnel) {
|
||||||
listener.PatchTunnel(tunnels, tunnel.Tunnel)
|
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General) {
|
||||||
@ -501,7 +479,7 @@ func updateIPTables(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
listener.Cleanup()
|
listener.Cleanup(false)
|
||||||
tproxy.CleanupTProxyIPTables()
|
tproxy.CleanupTProxyIPTables()
|
||||||
resolver.StoreFakePoolState()
|
resolver.StoreFakePoolState()
|
||||||
|
|
||||||
|
@ -2,11 +2,9 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
@ -49,7 +47,6 @@ type configSchema struct {
|
|||||||
TcptunConfig *string `json:"tcptun-config"`
|
TcptunConfig *string `json:"tcptun-config"`
|
||||||
UdptunConfig *string `json:"udptun-config"`
|
UdptunConfig *string `json:"udptun-config"`
|
||||||
AllowLan *bool `json:"allow-lan"`
|
AllowLan *bool `json:"allow-lan"`
|
||||||
SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"`
|
|
||||||
BindAddress *string `json:"bind-address"`
|
BindAddress *string `json:"bind-address"`
|
||||||
Mode *tunnel.TunnelMode `json:"mode"`
|
Mode *tunnel.TunnelMode `json:"mode"`
|
||||||
LogLevel *log.LogLevel `json:"log-level"`
|
LogLevel *log.LogLevel `json:"log-level"`
|
||||||
@ -69,11 +66,11 @@ type tunSchema struct {
|
|||||||
//RedirectToTun []string `yaml:"-" json:"-"`
|
//RedirectToTun []string `yaml:"-" json:"-"`
|
||||||
|
|
||||||
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
|
||||||
//Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
//Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
|
||||||
Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
Inet6Address *[]LC.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
|
||||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
Inet4RouteAddress *[]LC.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
|
||||||
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
Inet6RouteAddress *[]LC.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||||
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
|
||||||
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
@ -234,10 +231,6 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
P.SetAllowLan(*general.AllowLan)
|
P.SetAllowLan(*general.AllowLan)
|
||||||
}
|
}
|
||||||
|
|
||||||
if general.SkipAuthPrefixes != nil {
|
|
||||||
inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if general.BindAddress != nil {
|
if general.BindAddress != nil {
|
||||||
P.SetBindAddress(*general.BindAddress)
|
P.SetBindAddress(*general.BindAddress)
|
||||||
}
|
}
|
||||||
@ -256,15 +249,19 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ports := P.GetPorts()
|
ports := P.GetPorts()
|
||||||
|
|
||||||
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tunnel.Tunnel)
|
tcpIn := tunnel.TCPIn()
|
||||||
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tunnel.Tunnel)
|
udpIn := tunnel.UDPIn()
|
||||||
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tunnel.Tunnel)
|
natTable := tunnel.NatTable()
|
||||||
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tunnel.Tunnel)
|
|
||||||
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tunnel.Tunnel)
|
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
|
||||||
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tunnel.Tunnel)
|
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
|
||||||
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tunnel.Tunnel)
|
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn, natTable)
|
||||||
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tunnel.Tunnel)
|
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn, natTable)
|
||||||
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tunnel.Tunnel)
|
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)
|
||||||
|
|
||||||
if general.Mode != nil {
|
if general.Mode != nil {
|
||||||
tunnel.SetMode(*general.Mode)
|
tunnel.SetMode(*general.Mode)
|
||||||
|
@ -11,8 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/gobwas/ws"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/gobwas/ws/wsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func connectionRouter() http.Handler {
|
func connectionRouter() http.Handler {
|
||||||
@ -24,13 +23,13 @@ func connectionRouter() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getConnections(w http.ResponseWriter, r *http.Request) {
|
func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||||
if !(r.Header.Get("Upgrade") == "websocket") {
|
if !websocket.IsWebSocketUpgrade(r) {
|
||||||
snapshot := statistic.DefaultManager.Snapshot()
|
snapshot := statistic.DefaultManager.Snapshot()
|
||||||
render.JSON(w, r, snapshot)
|
render.JSON(w, r, snapshot)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -56,7 +55,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpText, buf.Bytes())
|
return conn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sendSnapshot(); err != nil {
|
if err := sendSnapshot(); err != nil {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/tunnel"
|
"github.com/Dreamacro/clash/tunnel"
|
||||||
@ -58,11 +57,6 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxy.(*adapter.Proxy).Type() == C.URLTest {
|
|
||||||
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
|
|
||||||
URLTestGroup.ForceSet("")
|
|
||||||
}
|
|
||||||
|
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
url := query.Get("url")
|
url := query.Get("url")
|
||||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||||
@ -83,6 +77,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
dm, err := group.URLTest(ctx, url, expectedStatus)
|
dm, err := group.URLTest(ctx, url, expectedStatus)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Status(r, http.StatusGatewayTimeout)
|
render.Status(r, http.StatusGatewayTimeout)
|
||||||
render.JSON(w, r, newError(err.Error()))
|
render.JSON(w, r, newError(err.Error()))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user