Compare commits
91 Commits
Author | SHA1 | Date | |
---|---|---|---|
53f9e1ee71 | |||
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 |
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
确保你使用的是**本仓库**最新的的 clash 或 clash Alpha 版本
|
||||
Ensure you are using the latest version of Clash or Clash Premium from **this repository**.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
如果你可以自己 debug 并解决的话,提交 PR 吧
|
||||
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
|
||||
"
|
||||
required: false
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
I have searched on the [issue tracker](……/) for a related issue.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经使用 Alpha 分支版本测试过,问题依旧存在
|
||||
I have tested using the dev branch, and the issue still exists.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法自行解决问题
|
||||
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
|
||||
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash.
|
||||
"
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Clash version
|
||||
description: "use `clash -v`"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: What OS are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
- OpenBSD/FreeBSD
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: yaml
|
||||
label: "Clash config"
|
||||
description: "
|
||||
在下方附上 Clash core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等)
|
||||
Paste the Clash core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port)
|
||||
"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
render: shell
|
||||
label: Clash log
|
||||
description: "
|
||||
在下方附上 Clash Core 的日志,log level 使用 DEBUG
|
||||
Paste the Clash core log below with the log level set to `DEBUG`.
|
||||
"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
validations:
|
||||
required: true
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: ensure
|
||||
attributes:
|
||||
label: Verify steps
|
||||
description: "
|
||||
在提交之前,请确认
|
||||
Please verify that you've followed these steps
|
||||
"
|
||||
options:
|
||||
- label: "
|
||||
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
|
||||
I have searched on the [issue tracker](……/) for a related feature request.
|
||||
"
|
||||
required: true
|
||||
- label: "
|
||||
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法找到这个功能
|
||||
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
|
||||
"
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: "
|
||||
此项非必须,但是如果你有想法的话欢迎提出。
|
||||
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
|
||||
"
|
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
|
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@ -94,11 +94,6 @@ jobs:
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Beta'}}
|
||||
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Meta'}}
|
||||
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
@ -267,6 +262,23 @@ jobs:
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
@ -283,8 +295,10 @@ jobs:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: bin/*
|
||||
generate_release_notes: true
|
||||
body_path: release.md
|
||||
|
||||
Docker:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
permissions: write-all
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
|
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
|
5
Makefile
5
Makefile
@ -31,6 +31,8 @@ PLATFORM_LIST = \
|
||||
linux-mips-hardfloat \
|
||||
linux-mipsle-softfloat \
|
||||
linux-mipsle-hardfloat \
|
||||
linux-riscv64 \
|
||||
linux-loong64 \
|
||||
android-arm64 \
|
||||
freebsd-386 \
|
||||
freebsd-amd64 \
|
||||
@ -103,6 +105,9 @@ linux-mips64le:
|
||||
|
||||
linux-riscv64:
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-loong64:
|
||||
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
android-arm64:
|
||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
@ -3,6 +3,7 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -12,16 +13,29 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
|
||||
const (
|
||||
defaultHistoriesNum = 10
|
||||
)
|
||||
|
||||
type extraProxyState struct {
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
url string
|
||||
extra map[string]*extraProxyState
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
@ -29,6 +43,17 @@ func (p *Proxy) Alive() bool {
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// AliveForTestUrl implements C.Proxy
|
||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
return state.alive.Load()
|
||||
}
|
||||
}
|
||||
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// Dial implements C.Proxy
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
@ -65,6 +90,42 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
return histories
|
||||
}
|
||||
|
||||
// DelayHistoryForTestUrl implements C.Proxy
|
||||
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
||||
var queueM []C.DelayHistory
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
queueM = state.history.Copy()
|
||||
}
|
||||
}
|
||||
|
||||
if queueM == nil {
|
||||
queueM = p.history.Copy()
|
||||
}
|
||||
|
||||
histories := []C.DelayHistory{}
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
return histories
|
||||
}
|
||||
|
||||
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
|
||||
extra := map[string][]C.DelayHistory{}
|
||||
if p.extra != nil && len(p.extra) != 0 {
|
||||
for testUrl, option := range p.extra {
|
||||
histories := []C.DelayHistory{}
|
||||
queueM := option.history.Copy()
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
|
||||
extra[testUrl] = histories
|
||||
}
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) LastDelay() (delay uint16) {
|
||||
@ -80,6 +141,30 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// LastDelayForTestUrl implements C.Proxy
|
||||
func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
|
||||
var max uint16 = 0xffff
|
||||
|
||||
alive := p.alive.Load()
|
||||
history := p.history.Last()
|
||||
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
alive = state.alive.Load()
|
||||
history = state.history.Last()
|
||||
}
|
||||
}
|
||||
|
||||
if !alive {
|
||||
return max
|
||||
}
|
||||
|
||||
if history.Delay == 0 {
|
||||
return max
|
||||
}
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||
@ -90,6 +175,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping := map[string]any{}
|
||||
_ = json.Unmarshal(inner, &mapping)
|
||||
mapping["history"] = p.DelayHistory()
|
||||
mapping["extra"] = p.ExtraDelayHistory()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
mapping["xudp"] = p.SupportXUDP()
|
||||
@ -99,16 +185,53 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// URLTest get the delay for the specified URL
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) {
|
||||
defer func() {
|
||||
p.alive.Store(err == nil)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if err == nil {
|
||||
record.Delay = t
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > 10 {
|
||||
p.history.Pop()
|
||||
alive := err == nil
|
||||
store = p.determineFinalStoreType(store, url)
|
||||
|
||||
switch store {
|
||||
case C.OriginalHistory:
|
||||
p.alive.Store(alive)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if alive {
|
||||
record.Delay = t
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > defaultHistoriesNum {
|
||||
p.history.Pop()
|
||||
}
|
||||
|
||||
// test URL configured by the proxy provider
|
||||
if len(p.url) == 0 {
|
||||
p.url = url
|
||||
}
|
||||
case C.ExtraHistory:
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if alive {
|
||||
record.Delay = t
|
||||
}
|
||||
|
||||
if p.extra == nil {
|
||||
p.extra = map[string]*extraProxyState{}
|
||||
}
|
||||
|
||||
state, ok := p.extra[url]
|
||||
if !ok {
|
||||
state = &extraProxyState{
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
}
|
||||
p.extra[url] = state
|
||||
}
|
||||
|
||||
state.alive.Store(alive)
|
||||
state.history.Put(record)
|
||||
if state.history.Len() > defaultHistoriesNum {
|
||||
state.history.Pop()
|
||||
}
|
||||
default:
|
||||
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -172,12 +295,17 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if expectedStatus != nil && !expectedStatus.Check(uint16(resp.StatusCode)) {
|
||||
// maybe another value should be returned for differentiation
|
||||
err = errors.New("response status is inconsistent with the expected status")
|
||||
}
|
||||
|
||||
t = uint16(time.Since(start) / time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
|
||||
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
@ -206,3 +334,24 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
|
||||
if store != C.DropHistory {
|
||||
return store
|
||||
}
|
||||
|
||||
if len(p.url) == 0 || url == p.url {
|
||||
return C.OriginalHistory
|
||||
}
|
||||
|
||||
if p.extra == nil {
|
||||
store = C.ExtraHistory
|
||||
} else {
|
||||
if _, ok := p.extra[url]; ok {
|
||||
store = C.ExtraHistory
|
||||
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
store = C.ExtraHistory
|
||||
}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
@ -78,8 +78,11 @@ type nopPacketConn struct{}
|
||||
|
||||
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||
func (npc nopPacketConn) Close() error { return nil }
|
||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
|
||||
return nil, nil, nil, io.EOF
|
||||
}
|
||||
func (npc nopPacketConn) Close() error { return nil }
|
||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
@ -97,7 +97,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
|
||||
func (s *SingMux) SupportUDP() bool {
|
||||
if s.onlyTcp {
|
||||
return s.ProxyAdapter.SupportUOT()
|
||||
return s.ProxyAdapter.SupportUDP()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/tuic"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type Tuic struct {
|
||||
@ -33,7 +34,9 @@ type TuicOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Token string `proxy:"token"`
|
||||
Token string `proxy:"token,omitempty"`
|
||||
UUID string `proxy:"uuid,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
Ip string `proxy:"ip,omitempty"`
|
||||
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
@ -46,6 +49,7 @@ type TuicOption struct {
|
||||
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
@ -90,11 +94,7 @@ func (t *Tuic) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
|
||||
return t.dialWithDialer(ctx, dialer.NewDialer(opts...))
|
||||
}
|
||||
|
||||
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
|
||||
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
if len(t.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
@ -106,10 +106,14 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.Pack
|
||||
return nil, nil, err
|
||||
}
|
||||
addr = udpAddr
|
||||
var pc net.PacketConn
|
||||
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
transport = &quic.Transport{Conn: pc}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
return
|
||||
}
|
||||
|
||||
@ -172,8 +176,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
option.HeartbeatInterval = 10000
|
||||
}
|
||||
|
||||
udpRelayMode := tuic.QUIC
|
||||
if option.UdpRelayMode != "quic" {
|
||||
option.UdpRelayMode = "native"
|
||||
udpRelayMode = tuic.NATIVE
|
||||
}
|
||||
|
||||
if option.MaxUdpRelayPacketSize == 0 {
|
||||
@ -184,14 +189,23 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
option.MaxOpenStreams = 100
|
||||
}
|
||||
|
||||
if option.CWND == 0 {
|
||||
option.CWND = 32
|
||||
}
|
||||
|
||||
packetOverHead := tuic.PacketOverHeadV4
|
||||
if len(option.Token) == 0 {
|
||||
packetOverHead = tuic.PacketOverHeadV5
|
||||
}
|
||||
|
||||
if option.MaxDatagramFrameSize == 0 {
|
||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
|
||||
}
|
||||
|
||||
if option.MaxDatagramFrameSize > 1400 {
|
||||
option.MaxDatagramFrameSize = 1400
|
||||
}
|
||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
|
||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
|
||||
|
||||
// ensure server's incoming stream can handle correctly, increase to 1.1x
|
||||
quicMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||
@ -220,12 +234,10 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if len(option.Ip) > 0 {
|
||||
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
|
||||
}
|
||||
host := option.Server
|
||||
if option.DisableSni {
|
||||
host = ""
|
||||
tlsConfig.ServerName = ""
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
tkn := tuic.GenTKN(option.Token)
|
||||
|
||||
t := &Tuic{
|
||||
Base: &Base{
|
||||
@ -251,21 +263,40 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if clientMaxOpenStreams < 1 {
|
||||
clientMaxOpenStreams = 1
|
||||
}
|
||||
clientOption := &tuic.ClientOption{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Host: host,
|
||||
Token: tkn,
|
||||
UdpRelayMode: option.UdpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
FastOpen: option.FastOpen,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClient(clientOption)
|
||||
if len(option.Token) > 0 {
|
||||
tkn := tuic.GenTKN(option.Token)
|
||||
clientOption := &tuic.ClientOptionV4{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Token: tkn,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
FastOpen: option.FastOpen,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV4(clientOption)
|
||||
} else {
|
||||
clientOption := &tuic.ClientOptionV5{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||
Password: option.Password,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV5(clientOption)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@ -25,8 +26,8 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
vmessSing "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
vmessSing "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
@ -373,8 +374,14 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
|
||||
}
|
||||
|
||||
if v.option.XUDP {
|
||||
var globalID [8]byte
|
||||
if metadata.SourceValid() {
|
||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||
}
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
vmessSing.NewXUDPConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
), v), nil
|
||||
} else if v.option.PacketAddr {
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@ -20,8 +21,8 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
@ -223,30 +224,44 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
||||
if metadata.NetWork == C.UDP {
|
||||
if v.option.XUDP {
|
||||
var globalID [8]byte
|
||||
if metadata.SourceValid() {
|
||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||
}
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn = v.client.DialEarlyXUDPPacketConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
} else {
|
||||
conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn, err = v.client.DialXUDPPacketConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
}
|
||||
} else if v.option.PacketAddr {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
} else {
|
||||
conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
conn, err = v.client.DialPacketConn(c,
|
||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
}
|
||||
conn = packetaddr.NewBindConn(conn)
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
} else {
|
||||
conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn, err = v.client.DialPacketConn(c,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
conn = v.client.DialEarlyConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
conn, err = v.client.DialConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -16,9 +17,10 @@ import (
|
||||
|
||||
type Fallback struct {
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
testUrl string
|
||||
selected string
|
||||
disableUDP bool
|
||||
testUrl string
|
||||
selected string
|
||||
expectedStatus string
|
||||
}
|
||||
|
||||
func (f *Fallback) Now() string {
|
||||
@ -82,9 +84,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": f.Type().String(),
|
||||
"now": f.Now(),
|
||||
"all": all,
|
||||
"type": f.Type().String(),
|
||||
"now": f.Now(),
|
||||
"all": all,
|
||||
"testUrl": f.testUrl,
|
||||
"expected": f.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -98,12 +102,14 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.GetProxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if len(f.selected) == 0 {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(f.testUrl) {
|
||||
return proxy
|
||||
}
|
||||
} else {
|
||||
if proxy.Name() == f.selected {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(f.testUrl) {
|
||||
return proxy
|
||||
} else {
|
||||
f.selected = ""
|
||||
@ -129,10 +135,12 @@ func (f *Fallback) Set(name string) error {
|
||||
}
|
||||
|
||||
f.selected = name
|
||||
if !p.Alive() {
|
||||
// if !p.Alive() {
|
||||
if !p.AliveForTestUrl(f.testUrl) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
|
||||
defer cancel()
|
||||
_, _ = p.URLTest(ctx, f.testUrl)
|
||||
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
|
||||
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -156,7 +164,8 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
@ -192,7 +193,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
|
||||
func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
|
||||
var wg sync.WaitGroup
|
||||
var lock sync.Mutex
|
||||
mp := map[string]uint16{}
|
||||
@ -201,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
||||
proxy := proxy
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
delay, err := proxy.URLTest(ctx, url)
|
||||
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory)
|
||||
if err == nil {
|
||||
lock.Lock()
|
||||
mp[proxy.Name()] = delay
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -25,8 +25,10 @@ type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Pr
|
||||
|
||||
type LoadBalance struct {
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
strategyFn strategyFn
|
||||
disableUDP bool
|
||||
strategyFn strategyFn
|
||||
testUrl string
|
||||
expectedStatus string
|
||||
}
|
||||
|
||||
var errStrategy = errors.New("unsupported strategy")
|
||||
@ -129,7 +131,7 @@ func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
|
||||
}
|
||||
|
||||
func strategyRoundRobin() strategyFn {
|
||||
func strategyRoundRobin(url string) strategyFn {
|
||||
idx := 0
|
||||
idxMutex := sync.Mutex{}
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
@ -148,7 +150,8 @@ func strategyRoundRobin() strategyFn {
|
||||
for ; i < length; i++ {
|
||||
id := (idx + i) % length
|
||||
proxy := proxies[id]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
i++
|
||||
return proxy
|
||||
}
|
||||
@ -158,22 +161,24 @@ func strategyRoundRobin() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
func strategyConsistentHashing() strategyFn {
|
||||
func strategyConsistentHashing(url string) strategyFn {
|
||||
maxRetry := 5
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||
key := utils.MapHash(getKey(metadata))
|
||||
buckets := int32(len(proxies))
|
||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||
idx := jumpHash(key, buckets)
|
||||
proxy := proxies[idx]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
// when availability is poor, traverse the entire list to get the available nodes
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
@ -182,14 +187,14 @@ func strategyConsistentHashing() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
func strategyStickySessions() strategyFn {
|
||||
func strategyStickySessions(url string) strategyFn {
|
||||
ttl := time.Minute * 10
|
||||
maxRetry := 5
|
||||
lruCache := cache.New[uint64, int](
|
||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||
cache.WithSize[uint64, int](1000))
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||
key := utils.MapHash(getKeyWithSrcAndDst(metadata))
|
||||
length := len(proxies)
|
||||
idx, has := lruCache.Get(key)
|
||||
if !has {
|
||||
@ -199,7 +204,8 @@ func strategyStickySessions() strategyFn {
|
||||
nowIdx := idx
|
||||
for i := 1; i < maxRetry; i++ {
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
if nowIdx != idx {
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, nowIdx)
|
||||
@ -230,8 +236,10 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": lb.Type().String(),
|
||||
"all": all,
|
||||
"type": lb.Type().String(),
|
||||
"all": all,
|
||||
"testUrl": lb.testUrl,
|
||||
"expectedStatus": lb.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -239,11 +247,11 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
||||
var strategyFn strategyFn
|
||||
switch strategy {
|
||||
case "consistent-hashing":
|
||||
strategyFn = strategyConsistentHashing()
|
||||
strategyFn = strategyConsistentHashing(option.URL)
|
||||
case "round-robin":
|
||||
strategyFn = strategyRoundRobin()
|
||||
strategyFn = strategyRoundRobin(option.URL)
|
||||
case "sticky-sessions":
|
||||
strategyFn = strategyStickySessions()
|
||||
strategyFn = strategyStickySessions(option.URL)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||
}
|
||||
@ -260,7 +268,9 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: option.DisableUDP,
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}, nil
|
||||
}
|
||||
|
@ -3,35 +3,37 @@ package outboundgroup
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
errFormat = errors.New("format error")
|
||||
errType = errors.New("unsupport type")
|
||||
errType = errors.New("unsupported type")
|
||||
errMissProxy = errors.New("`use` or `proxies` missing")
|
||||
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
||||
errDuplicateProvider = errors.New("duplicate provider name")
|
||||
)
|
||||
|
||||
type GroupCommonOption struct {
|
||||
outbound.BasicOption
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
ExcludeFilter string `group:"exclude-filter,omitempty"`
|
||||
ExcludeType string `group:"exclude-type,omitempty"`
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
ExcludeFilter string `group:"exclude-filter,omitempty"`
|
||||
ExcludeType string `group:"exclude-type,omitempty"`
|
||||
ExpectedStatus string `group:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
@ -53,30 +55,36 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
providers := []types.ProxyProvider{}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
return nil, errMissProxy
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
status := strings.TrimSpace(groupOption.ExpectedStatus)
|
||||
if status == "" {
|
||||
status = "*"
|
||||
}
|
||||
groupOption.ExpectedStatus = status
|
||||
testUrl := groupOption.URL
|
||||
|
||||
if len(groupOption.Proxies) != 0 {
|
||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
if _, ok := providersMap[groupName]; ok {
|
||||
return nil, errDuplicateProvider
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var url string
|
||||
var interval uint
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
} else {
|
||||
// select don't need health check
|
||||
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = "https://cp.cloudflare.com/generate_204"
|
||||
}
|
||||
@ -85,22 +93,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
groupOption.Interval = 300
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
url = groupOption.URL
|
||||
interval = uint(groupOption.Interval)
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
}
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
// different proxy groups use different test URL
|
||||
addTestUrlToProviders(list, testUrl, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
|
||||
providers = append(providers, list...)
|
||||
} else {
|
||||
groupOption.Filter = ""
|
||||
@ -154,3 +169,13 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
if len(providers) == 0 || len(url) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pd := range providers {
|
||||
pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,13 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
|
||||
|
||||
type URLTest struct {
|
||||
*GroupBase
|
||||
selected string
|
||||
testUrl string
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
fastSingle *singledo.Single[C.Proxy]
|
||||
selected string
|
||||
testUrl string
|
||||
expectedStatus string
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
fastSingle *singledo.Single[C.Proxy]
|
||||
}
|
||||
|
||||
func (u *URLTest) Now() string {
|
||||
@ -96,44 +97,49 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
}
|
||||
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
var s C.Proxy
|
||||
proxies := u.GetProxies(touch)
|
||||
fast := proxies[0]
|
||||
if fast.Name() == u.selected {
|
||||
s = fast
|
||||
|
||||
proxies := u.GetProxies(touch)
|
||||
if u.selected != "" {
|
||||
for _, proxy := range proxies {
|
||||
if !proxy.Alive() {
|
||||
continue
|
||||
}
|
||||
if proxy.Name() == u.selected {
|
||||
u.fastNode = proxy
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
min := fast.LastDelay()
|
||||
}
|
||||
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
fast := proxies[0]
|
||||
// min := fast.LastDelay()
|
||||
min := fast.LastDelayForTestUrl(u.testUrl)
|
||||
fastNotExist := true
|
||||
|
||||
for _, proxy := range proxies[1:] {
|
||||
|
||||
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||
fastNotExist = false
|
||||
}
|
||||
|
||||
if proxy.Name() == u.selected {
|
||||
s = proxy
|
||||
}
|
||||
if !proxy.Alive() {
|
||||
// if !proxy.Alive() {
|
||||
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
delay := proxy.LastDelay()
|
||||
// delay := proxy.LastDelay()
|
||||
delay := proxy.LastDelayForTestUrl(u.testUrl)
|
||||
if delay < min {
|
||||
fast = proxy
|
||||
min = delay
|
||||
}
|
||||
|
||||
}
|
||||
// tolerance
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
|
||||
u.fastNode = fast
|
||||
}
|
||||
if s != nil {
|
||||
if s.Alive() && s.LastDelay() < fast.LastDelay()+u.tolerance {
|
||||
u.fastNode = s
|
||||
}
|
||||
}
|
||||
return u.fastNode, nil
|
||||
})
|
||||
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
|
||||
@ -163,9 +169,11 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": u.Type().String(),
|
||||
"now": u.Now(),
|
||||
"all": all,
|
||||
"type": u.Type().String(),
|
||||
"now": u.Now(),
|
||||
"all": all,
|
||||
"testUrl": u.testUrl,
|
||||
"expected": u.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -197,9 +205,10 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
|
@ -2,6 +2,8 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
@ -10,6 +12,8 @@ import (
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -21,18 +25,33 @@ type HealthCheckOption struct {
|
||||
Interval uint
|
||||
}
|
||||
|
||||
type extraOption struct {
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
filters map[string]struct{}
|
||||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
started *atomic.Bool
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
if hc.started.Load() {
|
||||
log.Warnln("Skip start health check timer due to it's started")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||
hc.start()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@ -44,6 +63,7 @@ func (hc *HealthCheck) process() {
|
||||
}
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
hc.stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -53,6 +73,63 @@ func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
|
||||
hc.proxies = proxies
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
url = strings.TrimSpace(url)
|
||||
if len(url) == 0 || url == hc.url {
|
||||
log.Debugln("ignore invalid health check url: %s", url)
|
||||
return
|
||||
}
|
||||
|
||||
hc.mu.Lock()
|
||||
defer hc.mu.Unlock()
|
||||
|
||||
// if the provider has not set up health checks, then modify it to be the same as the group's interval
|
||||
if hc.interval == 0 {
|
||||
hc.interval = interval
|
||||
}
|
||||
|
||||
if hc.extra == nil {
|
||||
hc.extra = make(map[string]*extraOption)
|
||||
}
|
||||
|
||||
// prioritize the use of previously registered configurations, especially those from provider
|
||||
if _, ok := hc.extra[url]; ok {
|
||||
// provider default health check does not set filter
|
||||
if url != hc.url && len(filter) != 0 {
|
||||
splitAndAddFiltersToExtra(filter, hc.extra[url])
|
||||
}
|
||||
|
||||
log.Debugln("health check url: %s exists", url)
|
||||
return
|
||||
}
|
||||
|
||||
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
|
||||
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
|
||||
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
|
||||
return
|
||||
}
|
||||
|
||||
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
||||
splitAndAddFiltersToExtra(filter, option)
|
||||
hc.extra[url] = option
|
||||
|
||||
if hc.auto() && !hc.started.Load() {
|
||||
go hc.process()
|
||||
}
|
||||
}
|
||||
|
||||
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
|
||||
filter = strings.TrimSpace(filter)
|
||||
if len(filter) != 0 {
|
||||
for _, regex := range strings.Split(filter, "`") {
|
||||
regex = strings.TrimSpace(regex)
|
||||
if len(regex) != 0 {
|
||||
option.filters[regex] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) auto() bool {
|
||||
return hc.interval != 0
|
||||
}
|
||||
@ -61,41 +138,102 @@ func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) start() {
|
||||
hc.started.Store(true)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) stop() {
|
||||
hc.started.Store(false)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := utils.NewUUIDV4().String()
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking %s {%s}", p.Name(), id)
|
||||
_, _ = p.URLTest(ctx, hc.url)
|
||||
log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// execute default health check
|
||||
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
|
||||
hc.execute(b, hc.url, id, option)
|
||||
|
||||
// execute extra health check
|
||||
if len(hc.extra) != 0 {
|
||||
for url, option := range hc.extra {
|
||||
hc.execute(b, url, id, option)
|
||||
}
|
||||
}
|
||||
b.Wait()
|
||||
log.Debugln("Finish A Health Checking {%s}", id)
|
||||
return struct{}{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
|
||||
url = strings.TrimSpace(url)
|
||||
if len(url) == 0 {
|
||||
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
|
||||
return
|
||||
}
|
||||
|
||||
var filterReg *regexp2.Regexp
|
||||
var store = C.OriginalHistory
|
||||
var expectedStatus utils.IntRanges[uint16]
|
||||
if option != nil {
|
||||
if url != hc.url {
|
||||
store = C.ExtraHistory
|
||||
}
|
||||
|
||||
expectedStatus = option.expectedStatus
|
||||
if len(option.filters) != 0 {
|
||||
filters := make([]string, 0, len(option.filters))
|
||||
for filter := range option.filters {
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proxy := range hc.proxies {
|
||||
// skip proxies that do not require health check
|
||||
if filterReg != nil {
|
||||
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
|
||||
_, _ = p.URLTest(ctx, url, expectedStatus, store)
|
||||
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
||||
hc.done <- struct{}{}
|
||||
}
|
||||
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
|
||||
if len(url) == 0 {
|
||||
interval = 0
|
||||
expectedStatus = nil
|
||||
}
|
||||
|
||||
return &HealthCheck{
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
extra: map[string]*extraOption{},
|
||||
started: atomic.NewBool(false),
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
expectedStatus: expectedStatus,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
}
|
||||
}
|
||||
|
@ -6,23 +6,28 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var errVehicleType = errors.New("unsupport vehicle type")
|
||||
var (
|
||||
errVehicleType = errors.New("unsupport vehicle type")
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type healthCheckSchema struct {
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
@ -44,20 +49,33 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hcInterval uint
|
||||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus)
|
||||
|
||||
var vehicle types.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -50,6 +51,7 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
"type": pp.Type().String(),
|
||||
"vehicleType": pp.VehicleType().String(),
|
||||
"proxies": pp.Proxies(),
|
||||
"testUrl": pp.healthCheck.url,
|
||||
"updatedAt": pp.UpdatedAt,
|
||||
"subscriptionInfo": pp.subscriptionInfo,
|
||||
})
|
||||
@ -98,6 +100,10 @@ func (pp *proxySetProvider) Touch() {
|
||||
pp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
@ -141,15 +147,15 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) closeAllConnections() {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
for _, chain := range c.Chains() {
|
||||
if chain == pp.Name() {
|
||||
_ = c.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func stopProxyProvider(pd *ProxySetProvider) {
|
||||
@ -210,6 +216,7 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
"type": cp.Type().String(),
|
||||
"vehicleType": cp.VehicleType().String(),
|
||||
"proxies": cp.Proxies(),
|
||||
"testUrl": cp.healthCheck.url,
|
||||
})
|
||||
}
|
||||
|
||||
@ -249,6 +256,10 @@ func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||
pd.healthCheck.close()
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
||||
if certificate == "" || privateKey == "" {
|
||||
if certificate == "" && privateKey == "" {
|
||||
return newRandomTLSKeyPair()
|
||||
}
|
||||
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
||||
|
17
common/utils/global_id.go
Normal file
17
common/utils/global_id.go
Normal file
@ -0,0 +1,17 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var globalSeed = maphash.MakeSeed()
|
||||
|
||||
func GlobalID(material string) (id [8]byte) {
|
||||
*(*uint64)(unsafe.Pointer(&id[0])) = maphash.String(globalSeed, material)
|
||||
return
|
||||
}
|
||||
|
||||
func MapHash(material string) uint64 {
|
||||
return maphash.String(globalSeed, material)
|
||||
}
|
@ -9,36 +9,36 @@ type Range[T constraints.Ordered] struct {
|
||||
end T
|
||||
}
|
||||
|
||||
func NewRange[T constraints.Ordered](start, end T) *Range[T] {
|
||||
func NewRange[T constraints.Ordered](start, end T) Range[T] {
|
||||
if start > end {
|
||||
return &Range[T]{
|
||||
return Range[T]{
|
||||
start: end,
|
||||
end: start,
|
||||
}
|
||||
}
|
||||
|
||||
return &Range[T]{
|
||||
return Range[T]{
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Range[T]) Contains(t T) bool {
|
||||
func (r Range[T]) Contains(t T) bool {
|
||||
return t >= r.start && t <= r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) LeftContains(t T) bool {
|
||||
func (r Range[T]) LeftContains(t T) bool {
|
||||
return t >= r.start && t < r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) RightContains(t T) bool {
|
||||
func (r Range[T]) RightContains(t T) bool {
|
||||
return t > r.start && t <= r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) Start() T {
|
||||
func (r Range[T]) Start() T {
|
||||
return r.start
|
||||
}
|
||||
|
||||
func (r *Range[T]) End() T {
|
||||
func (r Range[T]) End() T {
|
||||
return r.end
|
||||
}
|
||||
|
77
common/utils/ranges.go
Normal file
77
common/utils/ranges.go
Normal file
@ -0,0 +1,77 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type IntRanges[T constraints.Integer] []Range[T]
|
||||
|
||||
var errIntRanges = errors.New("intRanges error")
|
||||
|
||||
func NewIntRanges[T constraints.Integer](expected string) (IntRanges[T], error) {
|
||||
// example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503
|
||||
expected = strings.TrimSpace(expected)
|
||||
if len(expected) == 0 || expected == "*" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list := strings.Split(expected, "/")
|
||||
if len(list) > 28 {
|
||||
return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
|
||||
}
|
||||
|
||||
return NewIntRangesFromList[T](list)
|
||||
}
|
||||
|
||||
func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], error) {
|
||||
var ranges IntRanges[T]
|
||||
for _, s := range list {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
status := strings.Split(s, "-")
|
||||
statusLen := len(status)
|
||||
if statusLen > 2 {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
start, err := strconv.ParseInt(strings.Trim(status[0], "[ ]"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
switch statusLen {
|
||||
case 1:
|
||||
ranges = append(ranges, NewRange(T(start), T(start)))
|
||||
case 2:
|
||||
end, err := strconv.ParseUint(strings.Trim(status[1], "[ ]"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
ranges = append(ranges, NewRange(T(start), T(end)))
|
||||
}
|
||||
}
|
||||
|
||||
return ranges, nil
|
||||
}
|
||||
|
||||
func (ranges IntRanges[T]) Check(status T) bool {
|
||||
if len(ranges) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, segment := range ranges {
|
||||
if segment.Contains(status) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
49
common/utils/string_unsafe.go
Normal file
49
common/utils/string_unsafe.go
Normal file
@ -0,0 +1,49 @@
|
||||
package utils
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// sliceHeader is equivalent to reflect.SliceHeader, but represents the pointer
|
||||
// to the underlying array as unsafe.Pointer rather than uintptr, allowing
|
||||
// sliceHeaders to be directly converted to slice objects.
|
||||
type sliceHeader struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
// slice returns a slice whose underlying array starts at ptr an which length
|
||||
// and capacity are len.
|
||||
func slice[T any](ptr *T, length int) []T {
|
||||
var s []T
|
||||
hdr := (*sliceHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = unsafe.Pointer(ptr)
|
||||
hdr.Len = length
|
||||
hdr.Cap = length
|
||||
return s
|
||||
}
|
||||
|
||||
// stringHeader is equivalent to reflect.StringHeader, but represents the
|
||||
// pointer to the underlying array as unsafe.Pointer rather than uintptr,
|
||||
// allowing StringHeaders to be directly converted to strings.
|
||||
type stringHeader struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
}
|
||||
|
||||
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
|
||||
// same memory backing s instead of making a heap-allocated copy. This is only
|
||||
// valid if the returned slice is never mutated.
|
||||
func ImmutableBytesFromString(s string) []byte {
|
||||
shdr := (*stringHeader)(unsafe.Pointer(&s))
|
||||
return slice((*byte)(shdr.Data), shdr.Len)
|
||||
}
|
||||
|
||||
// StringFromImmutableBytes is equivalent to string(bs), except that it uses
|
||||
// the same memory backing bs instead of making a heap-allocated copy. This is
|
||||
// only valid if bs is never mutated after StringFromImmutableBytes returns.
|
||||
func StringFromImmutableBytes(bs []byte) string {
|
||||
// This is cheaper than messing with StringHeader and SliceHeader, which as
|
||||
// of this writing produces many dead stores of zeroes. Compare
|
||||
// strings.Builder.String().
|
||||
return *(*string)(unsafe.Pointer(&bs))
|
||||
}
|
51
component/dialer/bind.go
Normal file
51
component/dialer/bind.go
Normal file
@ -0,0 +1,51 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination.IsValid() {
|
||||
if destination.Is4() || destination.Is4In6() {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
@ -7,52 +7,8 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func lookupLocalAddr(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination.IsValid() {
|
||||
if destination.Is4() {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
@ -66,7 +22,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -84,7 +40,7 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
|
||||
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, netip.Addr{}, int(local))
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
@ -23,6 +24,8 @@ var (
|
||||
|
||||
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
|
||||
|
||||
const FlagRunning = 32 // interface is in running state, compatibility with golang<1.20
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
@ -37,12 +40,21 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// if not available device like Meta, dummy0, docker0, etc.
|
||||
if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&FlagRunning == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipNets := make([]*netip.Prefix, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
ipNet := addr.(*net.IPNet)
|
||||
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
||||
|
||||
//unavailable IPv6 Address
|
||||
if ip.Is6() && strings.HasPrefix(ip.String(), "fe80") {
|
||||
continue
|
||||
}
|
||||
|
||||
ones, bits := ipNet.Mask.Size()
|
||||
if bits == 32 {
|
||||
ip = ip.Unmap()
|
||||
|
26
component/nat/proxy.go
Normal file
26
component/nat/proxy.go
Normal file
@ -0,0 +1,26 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type writeBackProxy struct {
|
||||
wb atomic.TypedValue[C.WriteBack]
|
||||
}
|
||||
|
||||
func (w *writeBackProxy) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return w.wb.Load().WriteBack(b, addr)
|
||||
}
|
||||
|
||||
func (w *writeBackProxy) UpdateWriteBack(wb C.WriteBack) {
|
||||
w.wb.Store(wb)
|
||||
}
|
||||
|
||||
func NewWriteBackProxy(wb C.WriteBack) C.WriteBackProxy {
|
||||
w := &writeBackProxy{}
|
||||
w.UpdateWriteBack(wb)
|
||||
return w
|
||||
}
|
@ -13,22 +13,24 @@ type Table struct {
|
||||
|
||||
type Entry struct {
|
||||
PacketConn C.PacketConn
|
||||
WriteBackProxy C.WriteBackProxy
|
||||
LocalUDPConnMap sync.Map
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, e C.PacketConn) {
|
||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||
t.mapping.Store(key, &Entry{
|
||||
PacketConn: e,
|
||||
WriteBackProxy: w,
|
||||
LocalUDPConnMap: sync.Map{},
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Table) Get(key string) C.PacketConn {
|
||||
func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
|
||||
entry, exist := t.getEntry(key)
|
||||
if !exist {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
return entry.PacketConn
|
||||
return entry.PacketConn, entry.WriteBackProxy
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||
|
@ -67,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
||||
log.Warnln("All PROCESS-NAMES rules will be skipped")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
@ -10,11 +10,11 @@ import (
|
||||
|
||||
type SnifferConfig struct {
|
||||
OverrideDest bool
|
||||
Ports []utils.Range[uint16]
|
||||
Ports utils.IntRanges[uint16]
|
||||
}
|
||||
|
||||
type BaseSniffer struct {
|
||||
ports []utils.Range[uint16]
|
||||
ports utils.IntRanges[uint16]
|
||||
supportNetworkType constant.NetWork
|
||||
}
|
||||
|
||||
@ -35,15 +35,10 @@ func (bs *BaseSniffer) SupportNetwork() constant.NetWork {
|
||||
|
||||
// SupportPort implements sniffer.Sniffer
|
||||
func (bs *BaseSniffer) SupportPort(port uint16) bool {
|
||||
for _, portRange := range bs.ports {
|
||||
if portRange.Contains(port) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return bs.ports.Check(port)
|
||||
}
|
||||
|
||||
func NewBaseSniffer(ports []utils.Range[uint16], networkType constant.NetWork) *BaseSniffer {
|
||||
func NewBaseSniffer(ports utils.IntRanges[uint16], networkType constant.NetWork) *BaseSniffer {
|
||||
return &BaseSniffer{
|
||||
ports: ports,
|
||||
supportNetworkType: networkType,
|
||||
|
@ -34,11 +34,9 @@ type HTTPSniffer struct {
|
||||
var _ sniffer.Sniffer = (*HTTPSniffer)(nil)
|
||||
|
||||
func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
if len(snifferConfig.Ports) == 0 {
|
||||
ports = append(ports, *utils.NewRange[uint16](80, 80))
|
||||
} else {
|
||||
ports = append(ports, snifferConfig.Ports...)
|
||||
ports := snifferConfig.Ports
|
||||
if len(ports) == 0 {
|
||||
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](80, 80)}
|
||||
}
|
||||
return &HTTPSniffer{
|
||||
BaseSniffer: NewBaseSniffer(ports, C.TCP),
|
||||
|
@ -22,11 +22,9 @@ type TLSSniffer struct {
|
||||
}
|
||||
|
||||
func NewTLSSniffer(snifferConfig SnifferConfig) (*TLSSniffer, error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
if len(snifferConfig.Ports) == 0 {
|
||||
ports = append(ports, *utils.NewRange[uint16](443, 443))
|
||||
} else {
|
||||
ports = append(ports, snifferConfig.Ports...)
|
||||
ports := snifferConfig.Ports
|
||||
if len(ports) == 0 {
|
||||
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)}
|
||||
}
|
||||
return &TLSSniffer{
|
||||
BaseSniffer: NewBaseSniffer(ports, C.TCP),
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
var trustCerts []*x509.Certificate
|
||||
var certPool *x509.CertPool
|
||||
var mutex sync.RWMutex
|
||||
var errNotMacth error = errors.New("certificate fingerprints do not match")
|
||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||
|
||||
func AddCertificate(certificate string) error {
|
||||
mutex.Lock()
|
||||
@ -79,7 +79,7 @@ func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedCh
|
||||
}
|
||||
}
|
||||
}
|
||||
return errNotMacth
|
||||
return errNotMatch
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
|
||||
utls "github.com/sagernet/utls"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
@ -37,6 +38,9 @@ type RealityConfig struct {
|
||||
ShortID [RealityMaxShortIDLen]byte
|
||||
}
|
||||
|
||||
//go:linkname aesgcmPreferred crypto/tls.aesgcmPreferred
|
||||
func aesgcmPreferred(ciphers []uint16) bool
|
||||
|
||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
||||
verifier := &realityVerifier{
|
||||
@ -61,17 +65,17 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
}
|
||||
|
||||
hello := uConn.HandshakeState.Hello
|
||||
for i := range hello.SessionId { // https://github.com/golang/go/issues/5373
|
||||
hello.SessionId[i] = 0
|
||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
||||
rawSessionID[i] = 0
|
||||
}
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||||
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[0] = 1
|
||||
hello.SessionId[1] = 8
|
||||
hello.SessionId[2] = 0
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[2] = 2
|
||||
|
||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||
|
||||
@ -84,9 +88,14 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||
var aeadCipher cipher.AEAD
|
||||
if aesgcmPreferred(hello.CipherSuites) {
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aeadCipher, _ = cipher.NewGCM(aesBlock)
|
||||
} else {
|
||||
aeadCipher, _ = chacha20poly1305.New(authKey)
|
||||
}
|
||||
aeadCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
|
||||
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
|
||||
@ -96,7 +105,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("REALITY Authentication: %v", verifier.verified)
|
||||
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher)
|
||||
|
||||
if !verifier.verified {
|
||||
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
||||
@ -137,7 +146,7 @@ type realityVerifier struct {
|
||||
verified bool
|
||||
}
|
||||
|
||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.UConn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
|
@ -23,6 +23,8 @@ type DomainSet struct {
|
||||
ranks, selects []int32
|
||||
}
|
||||
|
||||
type qElt struct{ s, e, col int }
|
||||
|
||||
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
|
||||
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||
reserveDomains := make([]string, 0)
|
||||
@ -39,7 +41,6 @@ func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||
ss := &DomainSet{}
|
||||
lIdx := 0
|
||||
|
||||
type qElt struct{ s, e, col int }
|
||||
queue := []qElt{{0, len(keys), 0}}
|
||||
for i := 0; i < len(queue); i++ {
|
||||
elt := queue[i]
|
||||
|
@ -1,8 +1,9 @@
|
||||
package trie
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type IPV6 bool
|
||||
@ -47,11 +48,10 @@ func (trie *IpCidrTrie) AddIpCidrForString(ipCidr string) error {
|
||||
}
|
||||
|
||||
func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
|
||||
ip, isIpv4 := checkAndConverterIp(ip)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
isIpv4 := len(ip) == net.IPv4len
|
||||
var groupValues []uint32
|
||||
var ipCidrNode *IpCidrNode
|
||||
|
||||
@ -71,7 +71,13 @@ func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
|
||||
}
|
||||
|
||||
func (trie *IpCidrTrie) IsContainForString(ipString string) bool {
|
||||
return trie.IsContain(net.ParseIP(ipString))
|
||||
ip := net.ParseIP(ipString)
|
||||
// deal with 4in6
|
||||
actualIp := ip.To4()
|
||||
if actualIp == nil {
|
||||
actualIp = ip
|
||||
}
|
||||
return trie.IsContain(actualIp)
|
||||
}
|
||||
|
||||
func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
|
||||
@ -82,9 +88,8 @@ func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
|
||||
isIpv4 bool
|
||||
err error
|
||||
)
|
||||
|
||||
ip, isIpv4 := checkAndConverterIp(ipNet.IP)
|
||||
ipList, newMaskSize, err = subIpCidr(ip, maskSize, isIpv4)
|
||||
isIpv4 = len(ipNet.IP) == net.IPv4len
|
||||
ipList, newMaskSize, err = subIpCidr(ipNet.IP, maskSize, isIpv4)
|
||||
|
||||
return ipList, newMaskSize, isIpv4, err
|
||||
}
|
||||
@ -238,18 +243,3 @@ func search(root *IpCidrNode, groupValues []uint32) *IpCidrNode {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return net.IP To4 or To16 and is ipv4
|
||||
func checkAndConverterIp(ip net.IP) (net.IP, bool) {
|
||||
ipResult := ip.To4()
|
||||
if ipResult == nil {
|
||||
ipResult = ip.To16()
|
||||
if ipResult == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ipResult, false
|
||||
}
|
||||
|
||||
return ipResult, true
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package trie
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
import "github.com/stretchr/testify/assert"
|
||||
|
||||
func TestIpv4AddSuccess(t *testing.T) {
|
||||
trie := NewIpCidrTrie()
|
||||
@ -96,5 +97,11 @@ func TestIpv6Search(t *testing.T) {
|
||||
assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213"))
|
||||
|
||||
assert.Equal(t, false, trie.IsContain(net.ParseIP("22233:22")))
|
||||
|
||||
}
|
||||
|
||||
func TestIpv4InIpv6(t *testing.T) {
|
||||
trie := NewIpCidrTrie()
|
||||
|
||||
// Boundary testing
|
||||
assert.NoError(t, trie.AddIpCidrForString("::ffff:198.18.5.138/128"))
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -220,16 +219,18 @@ type RawTun struct {
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
@ -356,6 +357,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
TuicServer: RawTuicServer{
|
||||
Enable: false,
|
||||
Token: nil,
|
||||
Users: nil,
|
||||
Certificate: "",
|
||||
PrivateKey: "",
|
||||
Listen: "",
|
||||
@ -655,7 +657,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
}
|
||||
ps = append(ps, proxies[v])
|
||||
}
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true, nil)
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
@ -914,7 +916,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
addr, err = hostWithDefaultPort(u.Host, "443")
|
||||
if err == nil {
|
||||
proxyName = ""
|
||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
|
||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User}
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
if len(u.Fragment) != 0 {
|
||||
@ -940,6 +942,19 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
dnsNetType = "quic" // DNS over QUIC
|
||||
case "system":
|
||||
dnsNetType = "system" // System DNS
|
||||
case "rcode":
|
||||
dnsNetType = "rcode"
|
||||
addr = u.Host
|
||||
switch addr {
|
||||
case "success",
|
||||
"format_error",
|
||||
"server_failure",
|
||||
"name_error",
|
||||
"not_implemented",
|
||||
"refused":
|
||||
default:
|
||||
err = fmt.Errorf("unsupported RCode type: %s", addr)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||
}
|
||||
@ -1282,6 +1297,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||
Enable: rawTuic.Enable,
|
||||
Listen: rawTuic.Listen,
|
||||
Token: rawTuic.Token,
|
||||
Users: rawTuic.Users,
|
||||
Certificate: rawTuic.Certificate,
|
||||
PrivateKey: rawTuic.PrivateKey,
|
||||
CongestionController: rawTuic.CongestionController,
|
||||
@ -1289,6 +1305,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||
AuthenticationTimeout: rawTuic.AuthenticationTimeout,
|
||||
ALPN: rawTuic.ALPN,
|
||||
MaxUdpRelayPacketSize: rawTuic.MaxUdpRelayPacketSize,
|
||||
CWND: rawTuic.CWND,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1304,7 +1321,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
if len(snifferRaw.Sniff) != 0 {
|
||||
for sniffType, sniffConfig := range snifferRaw.Sniff {
|
||||
find := false
|
||||
ports, err := parsePortRange(sniffConfig.Ports)
|
||||
ports, err := utils.NewIntRangesFromList[uint16](sniffConfig.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1331,7 +1348,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
// Deprecated: Use Sniff instead
|
||||
log.Warnln("Deprecated: Use Sniff instead")
|
||||
}
|
||||
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
||||
globalPorts, err := utils.NewIntRangesFromList[uint16](snifferRaw.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1376,28 +1393,3 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
|
||||
return sniffer, nil
|
||||
}
|
||||
|
||||
func parsePortRange(portRanges []string) ([]utils.Range[uint16], error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
for _, portRange := range portRanges {
|
||||
portRaws := strings.Split(portRange, "-")
|
||||
p, err := strconv.ParseUint(portRaws[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s format error", portRange)
|
||||
}
|
||||
|
||||
start := uint16(p)
|
||||
if len(portRaws) > 1 {
|
||||
p, err = strconv.ParseUint(portRaws[1], 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s format error", portRange)
|
||||
}
|
||||
|
||||
end := uint16(p)
|
||||
ports = append(ports, *utils.NewRange(start, end))
|
||||
} else {
|
||||
ports = append(ports, *utils.NewRange(start, start))
|
||||
}
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
)
|
||||
|
||||
@ -40,9 +41,10 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
DefaultUDPTimeout = DefaultTCPTimeout
|
||||
DefaultTLSTimeout = DefaultTCPTimeout
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
DefaultUDPTimeout = DefaultTCPTimeout
|
||||
DefaultTLSTimeout = DefaultTCPTimeout
|
||||
DefaultMaxHealthCheckUrlNum = 16
|
||||
)
|
||||
|
||||
var ErrNotSupport = errors.New("no support")
|
||||
@ -132,7 +134,7 @@ type ProxyAdapter interface {
|
||||
}
|
||||
|
||||
type Group interface {
|
||||
URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
|
||||
GetProxies(touch bool) []Proxy
|
||||
Touch()
|
||||
}
|
||||
@ -142,12 +144,23 @@ type DelayHistory struct {
|
||||
Delay uint16 `json:"delay"`
|
||||
}
|
||||
|
||||
type DelayHistoryStoreType int
|
||||
|
||||
const (
|
||||
OriginalHistory DelayHistoryStoreType = iota
|
||||
ExtraHistory
|
||||
DropHistory
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
ProxyAdapter
|
||||
Alive() bool
|
||||
AliveForTestUrl(url string) bool
|
||||
DelayHistory() []DelayHistory
|
||||
ExtraDelayHistory() map[string][]DelayHistory
|
||||
LastDelay() uint16
|
||||
URLTest(ctx context.Context, url string) (uint16, error)
|
||||
LastDelayForTestUrl(url string) uint16
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error)
|
||||
|
||||
// Deprecated: use DialContext instead.
|
||||
Dial(metadata *Metadata) (Conn, error)
|
||||
@ -217,7 +230,7 @@ type UDPPacket interface {
|
||||
// - variable source IP/Port is important to STUN
|
||||
// - if addr is not provided, WriteBack will write out UDP packet with SourceIP/Port equals to original Target,
|
||||
// this is important when using Fake-IP.
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
WriteBack
|
||||
|
||||
// Drop call after packet is used, could recycle buffer in this function.
|
||||
Drop()
|
||||
@ -236,10 +249,19 @@ type PacketAdapter interface {
|
||||
Metadata() *Metadata
|
||||
}
|
||||
|
||||
type NatTable interface {
|
||||
Set(key string, e PacketConn)
|
||||
type WriteBack interface {
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
}
|
||||
|
||||
Get(key string) PacketConn
|
||||
type WriteBackProxy interface {
|
||||
WriteBack
|
||||
UpdateWriteBack(wb WriteBack)
|
||||
}
|
||||
|
||||
type NatTable interface {
|
||||
Set(key string, e PacketConn, w WriteBackProxy)
|
||||
|
||||
Get(key string) (PacketConn, WriteBackProxy)
|
||||
|
||||
GetOrCreateLock(key string) (*sync.Cond, bool)
|
||||
|
||||
|
@ -171,6 +171,10 @@ func (m *Metadata) SourceDetail() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceValid() bool {
|
||||
return m.SrcPort != "" && m.SrcIP.IsValid()
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrType() int {
|
||||
switch true {
|
||||
case m.Host != "" || !m.DstIP.IsValid():
|
||||
|
@ -1,6 +1,8 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
@ -20,14 +22,15 @@ var Path = func() *path {
|
||||
if err != nil {
|
||||
homeDir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
allowUnsafePath := strings.TrimSpace(os.Getenv("SKIP_SAFE_PATH_CHECK")) == "1"
|
||||
homeDir = P.Join(homeDir, ".config", Name)
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml"}
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
||||
}()
|
||||
|
||||
type path struct {
|
||||
homeDir string
|
||||
configFile string
|
||||
homeDir string
|
||||
configFile string
|
||||
allowUnsafePath bool
|
||||
}
|
||||
|
||||
// SetHomeDir is used to set the configuration path
|
||||
@ -56,6 +59,27 @@ func (p *path) Resolve(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// IsSafePath return true if path is a subpath of homedir
|
||||
func (p *path) IsSafePath(path string) bool {
|
||||
if p.allowUnsafePath {
|
||||
return true
|
||||
}
|
||||
homedir := p.HomeDir()
|
||||
path = p.Resolve(path)
|
||||
rel, err := filepath.Rel(homedir, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.Contains(rel, "..")
|
||||
}
|
||||
|
||||
func (p *path) GetPathByHash(prefix, name string) string {
|
||||
hash := md5.Sum([]byte(name))
|
||||
filename := hex.EncodeToString(hash[:])
|
||||
return filepath.Join(p.HomeDir(), prefix, filename)
|
||||
}
|
||||
|
||||
func (p *path) MMDB() string {
|
||||
files, err := os.ReadDir(p.homeDir)
|
||||
if err != nil {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -71,6 +72,7 @@ type ProxyProvider interface {
|
||||
Touch()
|
||||
HealthCheck()
|
||||
Version() uint32
|
||||
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
|
||||
}
|
||||
|
||||
// RuleProvider interface
|
||||
|
@ -59,7 +59,8 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return batchExchange(ctx, clients, m)
|
||||
msg, _, err = batchExchange(ctx, clients, m)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
||||
|
12
dns/doh.go
12
dns/doh.go
@ -543,7 +543,17 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return quic.DialEarlyContext(ctx, conn, &udpAddr, doh.url.Host, tlsCfg, cfg)
|
||||
transport := quic.Transport{Conn: conn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
tlsCfg = tlsCfg.Clone()
|
||||
if host, _, err := net.SplitHostPort(doh.url.Host); err == nil {
|
||||
tlsCfg.ServerName = host
|
||||
} else {
|
||||
// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
|
||||
tlsCfg.ServerName = doh.url.Host
|
||||
}
|
||||
return transport.DialEarly(ctx, &udpAddr, tlsCfg, cfg)
|
||||
}
|
||||
|
||||
// probeH3 runs a test to check whether QUIC is faster than TLS for this
|
||||
|
23
dns/doq.go
23
dns/doq.go
@ -302,14 +302,6 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q
|
||||
|
||||
// openConnection opens a new QUIC connection.
|
||||
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) {
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
})
|
||||
// we're using bootstrapped address instead of what's passed to the function
|
||||
// it does not create an actual connection, but it helps us determine
|
||||
// what IP is actually reachable (when there're v4/v6 addresses).
|
||||
@ -338,7 +330,20 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err = quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, doq.getQUICConfig())
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
})
|
||||
|
||||
transport := quic.Transport{Conn: udp}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
conn, err = transport.Dial(ctx, &udpAddr, tlsConfig, doq.getQUICConfig())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
|
||||
}
|
||||
|
54
dns/rcode.go
Normal file
54
dns/rcode.go
Normal file
@ -0,0 +1,54 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func newRCodeClient(addr string) rcodeClient {
|
||||
var rcode int
|
||||
switch addr {
|
||||
case "success":
|
||||
rcode = D.RcodeSuccess
|
||||
case "format_error":
|
||||
rcode = D.RcodeFormatError
|
||||
case "server_failure":
|
||||
rcode = D.RcodeServerFailure
|
||||
case "name_error":
|
||||
rcode = D.RcodeNameError
|
||||
case "not_implemented":
|
||||
rcode = D.RcodeNotImplemented
|
||||
case "refused":
|
||||
rcode = D.RcodeRefused
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported RCode type: %s", addr))
|
||||
}
|
||||
|
||||
return rcodeClient{
|
||||
rcode: rcode,
|
||||
addr: "rcode://" + addr,
|
||||
}
|
||||
}
|
||||
|
||||
type rcodeClient struct {
|
||||
rcode int
|
||||
addr string
|
||||
}
|
||||
|
||||
var _ dnsClient = rcodeClient{}
|
||||
|
||||
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
m.Response = true
|
||||
m.Rcode = r.rcode
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
return r.Exchange(m)
|
||||
}
|
||||
|
||||
func (r rcodeClient) Address() string {
|
||||
return r.addr
|
||||
}
|
@ -165,7 +165,8 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
continueFetch = true
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
// updating TTL by subtracting common delta time from each DNS record
|
||||
updateMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -181,6 +182,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
fn := func() (result any, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
|
||||
defer cancel()
|
||||
cache := false
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@ -191,7 +193,9 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
msg := result.(*D.Msg)
|
||||
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
if cache {
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
}
|
||||
}()
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
@ -200,9 +204,11 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
}
|
||||
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
return r.batchExchange(ctx, matched, m)
|
||||
result, cache, err = r.batchExchange(ctx, matched, m)
|
||||
return
|
||||
}
|
||||
return r.batchExchange(ctx, r.main, m)
|
||||
result, cache, err = r.batchExchange(ctx, r.main, m)
|
||||
return
|
||||
}
|
||||
|
||||
ch := r.group.DoChan(q.String(), fn)
|
||||
@ -243,7 +249,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
@ -370,7 +376,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
|
||||
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
res, err := r.batchExchange(ctx, client, msg)
|
||||
res, _, err := r.batchExchange(ctx, client, msg)
|
||||
ch <- &result{Msg: res, Error: err}
|
||||
}()
|
||||
return ch
|
||||
|
@ -3,25 +3,41 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// nameserver xxx.xxx.xxx.xxx
|
||||
nameserverPattern = regexp.MustCompile(`nameserver\s+(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
|
||||
)
|
||||
const resolvConf = "/etc/resolv.conf"
|
||||
|
||||
func dnsReadConfig() (servers []string, err error) {
|
||||
content, err := os.ReadFile("/etc/resolv.conf")
|
||||
file, err := os.Open(resolvConf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to read /etc/resolv.conf: %w", err)
|
||||
err = fmt.Errorf("failed to read %s: %w", resolvConf, err)
|
||||
return
|
||||
}
|
||||
for _, line := range nameserverPattern.FindAllStringSubmatch(string(content), -1) {
|
||||
addr := line[1]
|
||||
servers = append(servers, addr)
|
||||
defer func() { _ = file.Close() }()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 1 {
|
||||
continue
|
||||
}
|
||||
switch f[0] {
|
||||
case "nameserver": // add one name server
|
||||
if len(f) > 1 {
|
||||
if addr, err := netip.ParseAddr(f[1]); err == nil {
|
||||
servers = append(servers, addr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
52
dns/util.go
52
dns/util.go
@ -21,12 +21,29 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxMsgSize = 65535
|
||||
)
|
||||
|
||||
func minimalTTL(records []D.RR) uint32 {
|
||||
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return r1.Header().Ttl < r2.Header().Ttl
|
||||
}).Header().Ttl
|
||||
}
|
||||
|
||||
func updateTTL(records []D.RR, ttl uint32) {
|
||||
if len(records) == 0 {
|
||||
return
|
||||
}
|
||||
delta := minimalTTL(records) - ttl
|
||||
for i := range records {
|
||||
records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
// skip dns cache for acme challenge
|
||||
if len(msg.Question) != 0 {
|
||||
@ -38,11 +55,11 @@ func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
var ttl uint32
|
||||
switch {
|
||||
case len(msg.Answer) != 0:
|
||||
ttl = msg.Answer[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Answer)
|
||||
case len(msg.Ns) != 0:
|
||||
ttl = msg.Ns[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Ns)
|
||||
case len(msg.Extra) != 0:
|
||||
ttl = msg.Extra[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Extra)
|
||||
default:
|
||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||
return
|
||||
@ -65,12 +82,18 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
updateTTL(msg.Answer, ttl)
|
||||
updateTTL(msg.Ns, ttl)
|
||||
updateTTL(msg.Extra, ttl)
|
||||
}
|
||||
|
||||
func isIPRequest(q D.Question) bool {
|
||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
||||
}
|
||||
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := []dnsClient{}
|
||||
ret := make([]dnsClient, 0, len(servers))
|
||||
for _, s := range servers {
|
||||
switch s.Net {
|
||||
case "https":
|
||||
@ -91,6 +114,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
}
|
||||
ret = append(ret, clients...)
|
||||
continue
|
||||
case "rcode":
|
||||
ret = append(ret, newRCodeClient(s.Addr))
|
||||
continue
|
||||
case "quic":
|
||||
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
|
||||
ret = append(ret, doq)
|
||||
@ -261,20 +287,24 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
}
|
||||
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
domain := msgToDomain(m)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
||||
_, cache = client.(rcodeClient)
|
||||
cache = !cache
|
||||
fast.Go(func() (*D.Msg, error) {
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, r.Address())
|
||||
m, err := r.ExchangeContext(ctx, m)
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, client.Address())
|
||||
m, err := client.ExchangeContext(ctx, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
|
||||
} else if cache && (m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused) {
|
||||
// currently, cache indicates whether this msg was from a RCode client,
|
||||
// so we would ignore RCode errors from RCode clients.
|
||||
return nil, errors.New("server failure")
|
||||
}
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), r.Address())
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
|
||||
return m, nil
|
||||
})
|
||||
}
|
||||
@ -285,7 +315,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||
}
|
||||
return nil, err
|
||||
return nil, true, err
|
||||
}
|
||||
msg = elm
|
||||
return
|
||||
|
182
docs/config.yaml
182
docs/config.yaml
@ -64,7 +64,7 @@ hosts:
|
||||
|
||||
profile: # 存储 select 选择记录
|
||||
store-selected: false
|
||||
|
||||
|
||||
# 持久化 fake-ip
|
||||
store-fake-ip: true
|
||||
|
||||
@ -93,10 +93,10 @@ tun:
|
||||
#- 1000
|
||||
# exclude-uid-range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto-route
|
||||
|
||||
|
||||
# include-android-user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -126,10 +126,9 @@ sniffer:
|
||||
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||
TLS:
|
||||
# ports: [443, 8443]
|
||||
|
||||
|
||||
# 默认嗅探 80
|
||||
HTTP: # 需要嗅探的端口
|
||||
|
||||
ports: [80, 8080-8880]
|
||||
# 可覆盖 sniffer.override-destination
|
||||
override-destination: true
|
||||
@ -144,7 +143,7 @@ sniffer:
|
||||
- tls
|
||||
- http
|
||||
# 强制对此域名进行嗅探
|
||||
|
||||
|
||||
# 仅对白名单中的端口进行嗅探,默认为 443,80
|
||||
# 已废弃,若 sniffer.sniff 配置则此项无效
|
||||
port-whitelist:
|
||||
@ -152,7 +151,6 @@ sniffer:
|
||||
- "443"
|
||||
# - 8000-9999
|
||||
|
||||
|
||||
tunnels: # one line config
|
||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||
@ -162,7 +160,6 @@ tunnels: # one line config
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
|
||||
# DNS配置
|
||||
dns:
|
||||
enable: false # 关闭将使用系统 DNS
|
||||
@ -177,18 +174,18 @@ dns:
|
||||
- 8.8.8.8
|
||||
- tls://1.12.12.12:853
|
||||
- tls://223.5.5.5:853
|
||||
- system # append DNS server from system configuration. If not found, it would print an error log and skip.
|
||||
- system # append DNS server from system configuration. If not found, it would print an error log and skip.
|
||||
enhanced-mode: fake-ip # or redir-host
|
||||
|
||||
|
||||
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
||||
|
||||
|
||||
# use-hosts: true # 查询 hosts
|
||||
|
||||
|
||||
# 配置不使用fake-ip的域名
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
|
||||
# DNS主要域名配置
|
||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||
@ -202,20 +199,20 @@ dns:
|
||||
- dhcp://en0 # dns from dhcp
|
||||
- quic://dns.adguard.com:784 # DNS over QUIC
|
||||
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
||||
|
||||
|
||||
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
||||
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
||||
# 确保配置 fallback 时能够正常查询
|
||||
# fallback:
|
||||
# - tcp://1.1.1.1
|
||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||
|
||||
|
||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
||||
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
||||
# proxy-server-nameserver:
|
||||
# - https://dns.google/dns-query
|
||||
# - tls://one.one.one.one
|
||||
|
||||
|
||||
# 配置 fallback 使用条件
|
||||
# fallback-filter:
|
||||
# geoip: true # 配置是否使用 geoip
|
||||
@ -230,7 +227,7 @@ dns:
|
||||
# - '+.google.com'
|
||||
# - '+.facebook.com'
|
||||
# - '+.youtube.com'
|
||||
|
||||
|
||||
# 配置查询域名使用的 DNS 服务器
|
||||
nameserver-policy:
|
||||
# 'www.baidu.com': '114.114.114.114'
|
||||
@ -238,9 +235,10 @@ dns:
|
||||
"geosite:cn,private,apple":
|
||||
- https://doh.pub/dns-query
|
||||
- https://dns.alidns.com/dns-query
|
||||
"geosite:category-ads-all": rcode://success
|
||||
"www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
|
||||
## global,dns 为 rule-providers 中的名为 global 和 dns 规则订阅,
|
||||
## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
|
||||
## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
|
||||
# "rule-set:global,dns": 8.8.8.8
|
||||
|
||||
proxies: # socks5
|
||||
@ -255,7 +253,7 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
# ip-version: ipv6
|
||||
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
@ -268,7 +266,7 @@ proxies: # socks5
|
||||
# sni: custom.com
|
||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||
# ip-version: dual
|
||||
|
||||
|
||||
# Snell
|
||||
# Beware that there's currently no UDP support yet
|
||||
- name: "snell"
|
||||
@ -280,7 +278,7 @@ proxies: # socks5
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
|
||||
# Shadowsocks
|
||||
# cipher支持:
|
||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||
@ -312,7 +310,7 @@ proxies: # socks5
|
||||
# padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
|
||||
# statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
|
||||
# only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议
|
||||
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
@ -323,7 +321,7 @@ proxies: # socks5
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
|
||||
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
server: server
|
||||
@ -343,7 +341,7 @@ proxies: # socks5
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
server: server
|
||||
@ -363,20 +361,22 @@ proxies: # socks5
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
host:
|
||||
"www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
|
||||
- name: "ss-restls-tls12"
|
||||
type: ss
|
||||
@ -384,16 +384,18 @@ proxies: # socks5
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
host:
|
||||
"vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
# vmess
|
||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
@ -416,7 +418,7 @@ proxies: # socks5
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -432,7 +434,7 @@ proxies: # socks5
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
|
||||
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -451,7 +453,7 @@ proxies: # socks5
|
||||
# Connection:
|
||||
# - keep-alive
|
||||
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
||||
|
||||
|
||||
- name: vmess-grpc
|
||||
server: server
|
||||
port: 443
|
||||
@ -467,7 +469,7 @@ proxies: # socks5
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
# ip-version: ipv4
|
||||
|
||||
|
||||
# vless
|
||||
- name: "vless-tcp"
|
||||
type: vless
|
||||
@ -480,7 +482,7 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
|
||||
|
||||
- name: "vless-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@ -493,7 +495,7 @@ proxies: # socks5
|
||||
client-fingerprint: chrome
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
|
||||
|
||||
- name: "vless-reality-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@ -508,7 +510,7 @@ proxies: # socks5
|
||||
public-key: xxx
|
||||
short-id: xxx # optional
|
||||
client-fingerprint: chrome # cannot be empty
|
||||
|
||||
|
||||
- name: "vless-reality-grpc"
|
||||
type: vless
|
||||
server: server
|
||||
@ -526,7 +528,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
server: server
|
||||
@ -543,7 +545,7 @@ proxies: # socks5
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
@ -558,7 +560,7 @@ proxies: # socks5
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
@ -571,7 +573,7 @@ proxies: # socks5
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
@ -586,7 +588,7 @@ proxies: # socks5
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
server: server
|
||||
@ -598,7 +600,7 @@ proxies: # socks5
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
|
||||
|
||||
#hysteria
|
||||
- name: "hysteria"
|
||||
type: hysteria
|
||||
@ -625,7 +627,7 @@ proxies: # socks5
|
||||
# disable_mtu_discovery: false
|
||||
# fingerprint: xxxx
|
||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||
|
||||
|
||||
# wireguard
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
@ -654,13 +656,17 @@ proxies: # socks5
|
||||
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
|
||||
# allowed_ips: ['0.0.0.0/0']
|
||||
# reserved: [209,98,59]
|
||||
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
server: www.example.com
|
||||
port: 10443
|
||||
type: tuic
|
||||
# tuicV4必须填写token (不可同时填写uuid和password)
|
||||
token: TOKEN
|
||||
# tuicV5必须填写uuid和password(不可同时填写token)
|
||||
uuid: 00000000-0000-0000-0000-000000000001
|
||||
password: PASSWORD_1
|
||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||
# heartbeat-interval: 10000
|
||||
# alpn: [h3]
|
||||
@ -669,12 +675,13 @@ proxies: # socks5
|
||||
request-timeout: 8000
|
||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||
# cwnd: 10 # default: 32
|
||||
# max-udp-relay-packet-size: 1500
|
||||
# fast-open: true
|
||||
# skip-cert-verify: true
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
# sni: example.com
|
||||
|
||||
|
||||
# ShadowsocksR
|
||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||
# The supported obfses:
|
||||
@ -706,7 +713,7 @@ proxy-groups:
|
||||
- vmess
|
||||
- ss1
|
||||
- ss2
|
||||
|
||||
|
||||
# url-test 将按照 url 测试结果使用延迟最低节点
|
||||
- name: "auto"
|
||||
type: url-test
|
||||
@ -718,7 +725,7 @@ proxy-groups:
|
||||
# lazy: true
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# fallback 将按照 url 测试结果按照节点顺序选择
|
||||
- name: "fallback-auto"
|
||||
type: fallback
|
||||
@ -728,7 +735,7 @@ proxy-groups:
|
||||
- vmess1
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# load-balance 将按照算法随机选择节点
|
||||
- name: "load-balance"
|
||||
type: load-balance
|
||||
@ -739,7 +746,7 @@ proxy-groups:
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
||||
|
||||
|
||||
# select 用户自行选择节点
|
||||
- name: Proxy
|
||||
type: select
|
||||
@ -749,7 +756,7 @@ proxy-groups:
|
||||
- ss2
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
|
||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||
- name: en1
|
||||
type: select
|
||||
@ -757,7 +764,7 @@ proxy-groups:
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
||||
@ -770,10 +777,10 @@ proxy-groups:
|
||||
# Clash 格式的节点或支持 *ray 的分享格式
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml
|
||||
path: ./provider1.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
@ -790,8 +797,8 @@ rule-providers:
|
||||
rule1:
|
||||
behavior: classical # domain ipcidr
|
||||
interval: 259200
|
||||
path: /path/to/save/file.yaml
|
||||
type: http
|
||||
path: /path/to/save/file.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5
|
||||
url: "url"
|
||||
rule2:
|
||||
behavior: classical
|
||||
@ -841,14 +848,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: http-in-1
|
||||
type: http
|
||||
port: 10809
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
port: 10810
|
||||
@ -856,14 +863,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: reidr-in-1
|
||||
type: redir
|
||||
port: 10811
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: tproxy-in-1
|
||||
type: tproxy
|
||||
port: 10812
|
||||
@ -871,7 +878,7 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: shadowsocks-in-1
|
||||
type: shadowsocks
|
||||
port: 10813
|
||||
@ -880,7 +887,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
||||
cipher: 2022-blake3-aes-256-gcm
|
||||
|
||||
|
||||
- name: vmess-in-1
|
||||
type: vmess
|
||||
port: 10814
|
||||
@ -891,15 +898,18 @@ listeners:
|
||||
- username: 1
|
||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
alterId: 1
|
||||
|
||||
|
||||
- name: tuic-in-1
|
||||
type: tuic
|
||||
port: 10815
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# token:
|
||||
# - TOKEN
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
@ -908,7 +918,7 @@ listeners:
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
||||
|
||||
- name: tunnel-in-1
|
||||
type: tunnel
|
||||
port: 10816
|
||||
@ -917,7 +927,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
network: [tcp, udp]
|
||||
target: target.com
|
||||
|
||||
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
@ -948,10 +958,10 @@ listeners:
|
||||
# - 1000
|
||||
# exclude_uid_range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto_route
|
||||
|
||||
|
||||
# include_android_user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -959,7 +969,6 @@ listeners:
|
||||
# - com.android.chrome
|
||||
# exclude_package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
|
||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
@ -969,8 +978,11 @@ listeners:
|
||||
# tuic-server:
|
||||
# enable: true
|
||||
# listen: 127.0.0.1:10443
|
||||
# token:
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
|
60
go.mod
60
go.mod
@ -7,52 +7,52 @@ require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/cilium/ebpf v0.10.0
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/dlclark/regexp2 v1.9.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/klauspost/cpuid/v2 v2.0.12
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018
|
||||
github.com/metacubex/sing-vmess v0.1.5
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/mroth/weightedrand/v2 v2.0.1
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.5-0.20230530114415-221f066dba7c
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
||||
github.com/sagernet/sing v0.2.5
|
||||
github.com/sagernet/sing-mux v0.1.0
|
||||
github.com/sagernet/sing-shadowtls v0.1.2
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.4
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/shirou/gopsutil/v3 v3.23.5
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sync v0.2.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/sys v0.9.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.1.7
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
@ -77,7 +77,7 @@ require (
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
@ -85,27 +85,25 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // 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/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b
|
||||
|
||||
replace github.com/sagernet/sing-vmess => github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b
|
||||
|
122
go.sum
122
go.sum
@ -21,8 +21,8 @@ github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@ -58,8 +58,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
@ -70,8 +68,8 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/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.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
@ -79,9 +77,8 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@ -92,22 +89,24 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c h1:D62872jiuzC6b+3aI8tqfeyc6YgbfarYKywTnnvXwEM=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58 h1:E/sNW9tugFjoBjAkth89MHlKHRaMdo43tGQ3MOPVayQ=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
|
||||
github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b h1:Bw4j3ktf5vivi5qm/ZQGtyRAgybRKSGJaMV1t3rtC+I=
|
||||
github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c h1:LpVNvlW/xE+mR8z76xJeYZlYznZXEmU4TeWeuygYdJg=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c/go.mod h1:4uQQReKMTU7KTfOykVBe/oGJ00pl38d+BYJ99+mx26s=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca h1:10qc50Q1hHrfGO4NjEJpIAgHX63Y256tHE0dFCTN8J4=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca/go.mod h1:jVDD4N22bDPPKA73NvB7aqdlLWiAwv8D+jx7HwhcWak=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e h1:7QlJQl4S3F3YXn48fYxjymMw8HkXg9bl++hLi4ZRyCY=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e/go.mod h1:u9onX49LZPYuIPQ7SdM64Gnins8y5wg4Cn6ZYRSxWHU=
|
||||
github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899 h1:iRfcuztp7REfmOyasSlCL/pqNWfUDMTJ2CwbGpxpeks=
|
||||
github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899/go.mod h1:RSt9rxGHllLdc5JUebkQwaqyWLx09Lqya37DlBe8CP8=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a h1:cWKym33Qvl6HA3hj4/YuYD8hHyqQPb47wT5cJRAPgco=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a/go.mod h1:Bsw2BvKMMMY0FhZPseDI50ZOalvoUPMKYyGpyqvIIqY=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb h1:92YTNmYXCSycERjKn/zPbeK5DiW3dd80j3+oVTEWTE8=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb/go.mod h1:6pg8+Tje9KOltnj1whuvB2i5KFUMPp1TAF3oPhc5axM=
|
||||
github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b h1:mVd3v+zMQq61rJe/pJJSh0/Iin9UnkQaZTH2NOg/2Vg=
|
||||
github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2 h1:prciO78IwtR4Sp+/CnP+aZSzpBRfL7zKaYez1S4EOnI=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2/go.mod h1:haolI+8Yc8MhNDqNuoRP4X5vaquXWNYeL1YxrQZ5kCU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0 h1:ZxPEToY1RaRtG6ljz2n13ASMVqyAM7Bh11TmWoExYu4=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0/go.mod h1:6C4EkvqMz5h7jECKrQeIByoLDHxiepsgPajIrxqxj/s=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018 h1:M7vBGA4RL4BBLSYfi15u/9QdVSqPkhuL4KRCuRhxuQY=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018/go.mod h1:DSVNjWT0rkkg8zn2+wpDvxgXuXRmMiNFDnVmnUctbAc=
|
||||
github.com/metacubex/sing-vmess v0.1.5 h1:wODu17P27aGw0GhSIb/rIZWNh3/F5ghF/1PDDt95CQY=
|
||||
github.com/metacubex/sing-vmess v0.1.5/go.mod h1:s00xTd3c/zOMQHyPec0G/pbUklndleiH0QaHZRd4Ykg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mroth/weightedrand/v2 v2.0.1 h1:zrEVDIaau/E4QLOKu02kpg8T8myweFlMGikIgbIdrRA=
|
||||
@ -136,19 +135,19 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||
github.com/sagernet/sing-mux v0.1.0 h1:xihlDRNs1J+hYwmvW9/ZmaghjDx7O0Y5dty0pOLQGB4=
|
||||
github.com/sagernet/sing-mux v0.1.0/go.mod h1:i3jKjV4pRTFTV/ly5V3oa2JMPy0SAZ5X8X4tDU9Hw94=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2 h1:wkPf4gF+cmaP0cIbArpyq+mc6GcwbMx60CssmmhEQ0s=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2/go.mod h1:rTxhbSY8jGWZOWjdeOe1vP3E+hkgen8aRA2p7YccM88=
|
||||
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/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||
@ -161,20 +160,20 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@ -184,21 +183,24 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 h1:a3Y4WVjCxwoyO4E2xdNvq577tW8lkSBgyrA8E9+2NtM=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
@ -209,10 +211,10 @@ go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -221,17 +223,19 @@ golang.org/x/mod v0.8.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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/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-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -240,14 +244,16 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.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 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -269,5 +275,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
@ -84,16 +84,18 @@ type tunSchema struct {
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen *string `yaml:"listen" json:"listen"`
|
||||
Token *[]string `yaml:"token" json:"token"`
|
||||
Certificate *string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen *string `yaml:"listen" json:"listen"`
|
||||
Token *[]string `yaml:"token" json:"token"`
|
||||
Users *map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate *string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
CWND *int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -186,6 +188,9 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
|
||||
if p.Token != nil {
|
||||
def.Token = *p.Token
|
||||
}
|
||||
if p.Users != nil {
|
||||
def.Users = *p.Users
|
||||
}
|
||||
if p.Certificate != nil {
|
||||
def.Certificate = *p.Certificate
|
||||
}
|
||||
@ -207,6 +212,9 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
|
||||
if p.MaxUdpRelayPacketSize != nil {
|
||||
def.MaxUdpRelayPacketSize = *p.MaxUdpRelayPacketSize
|
||||
}
|
||||
if p.CWND != nil {
|
||||
def.CWND = *p.CWND
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
@ -73,20 +73,16 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func closeConnection(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
if id == c.ID() {
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
if c := statistic.DefaultManager.Get(id); c != nil {
|
||||
_ = c.Close()
|
||||
}
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func closeAllConnections(w http.ResponseWriter, r *http.Request) {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
c.Close()
|
||||
}
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
_ = c.Close()
|
||||
return true
|
||||
})
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
@ -2,14 +2,16 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func GroupRouter() http.Handler {
|
||||
@ -64,10 +66,17 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url)
|
||||
dm, err := group.URLTest(ctx, url, expectedStatus)
|
||||
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
@ -112,12 +113,19 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
delay, err := proxy.URLTest(ctx, url)
|
||||
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.ExtraHistory)
|
||||
if ctx.Err() != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, ErrRequestTimeout)
|
||||
@ -126,7 +134,11 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err != nil || delay == 0 {
|
||||
render.Status(r, http.StatusServiceUnavailable)
|
||||
render.JSON(w, r, newError("An error occurred in the delay test"))
|
||||
if err != nil && delay != 0 {
|
||||
render.JSON(w, r, err)
|
||||
} else {
|
||||
render.JSON(w, r, newError("An error occurred in the delay test"))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package route
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
@ -11,9 +12,11 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
@ -149,6 +152,12 @@ func Start(addr string, tlsAddr string, secret string,
|
||||
|
||||
}
|
||||
|
||||
func safeEuqal(a, b string) bool {
|
||||
aBuf := utils.ImmutableBytesFromString(a)
|
||||
bBuf := utils.ImmutableBytesFromString(b)
|
||||
return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
|
||||
}
|
||||
|
||||
func authentication(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if serverSecret == "" {
|
||||
@ -159,7 +168,7 @@ func authentication(next http.Handler) http.Handler {
|
||||
// Browser websocket not support custom header
|
||||
if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token != serverSecret {
|
||||
if !safeEuqal(token, serverSecret) {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
render.JSON(w, r, ErrUnauthorized)
|
||||
return
|
||||
@ -172,7 +181,7 @@ func authentication(next http.Handler) http.Handler {
|
||||
bearer, token, found := strings.Cut(header, " ")
|
||||
|
||||
hasInvalidHeader := bearer != "Bearer"
|
||||
hasInvalidSecret := !found || token != serverSecret
|
||||
hasInvalidSecret := !found || !safeEuqal(token, serverSecret)
|
||||
if hasInvalidHeader || hasInvalidSecret {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
render.JSON(w, r, ErrUnauthorized)
|
||||
|
@ -5,17 +5,19 @@ import (
|
||||
)
|
||||
|
||||
type TuicServer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
MaxDatagramFrameSize int `yaml:"max-datagram-frame-size" json:"max-datagram-frame-size,omitempty"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token,omitempty"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
MaxDatagramFrameSize int `yaml:"max-datagram-frame-size" json:"max-datagram-frame-size,omitempty"`
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (t TuicServer) String() string {
|
||||
|
@ -9,14 +9,16 @@ import (
|
||||
|
||||
type TuicOption struct {
|
||||
BaseOption
|
||||
Token []string `inbound:"token"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
CongestionController string `inbound:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
||||
ALPN []string `inbound:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"`
|
||||
Token []string `inbound:"token,omitempty"`
|
||||
Users map[string]string `inbound:"users,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
CongestionController string `inbound:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
||||
ALPN []string `inbound:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"`
|
||||
CWND int `inbound:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (o TuicOption) Equal(config C.InboundConfig) bool {
|
||||
@ -42,6 +44,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Token: options.Token,
|
||||
Users: options.Users,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
CongestionController: options.CongestionController,
|
||||
@ -49,6 +52,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
|
||||
AuthenticationTimeout: options.AuthenticationTimeout,
|
||||
ALPN: options.ALPN,
|
||||
MaxUdpRelayPacketSize: options.MaxUdpRelayPacketSize,
|
||||
CWND: options.CWND,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
package redir
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,28 +29,38 @@ func parserPacket(conn net.Conn) (socks5.Addr, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr socks5.Addr
|
||||
var addr netip.AddrPort
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
addr, err = getorigdst(fd)
|
||||
if ip4 := c.LocalAddr().(*net.TCPAddr).IP.To4(); ip4 != nil {
|
||||
addr, err = getorigdst(fd)
|
||||
} else {
|
||||
addr, err = getorigdst6(fd)
|
||||
}
|
||||
})
|
||||
|
||||
return addr, err
|
||||
return socks5.AddrFromStdAddrPort(addr), err
|
||||
}
|
||||
|
||||
// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
|
||||
func getorigdst(fd uintptr) (socks5.Addr, error) {
|
||||
raw := syscall.RawSockaddrInet4{}
|
||||
siz := unsafe.Sizeof(raw)
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0)
|
||||
func getorigdst(fd uintptr) (netip.AddrPort, error) {
|
||||
addr := unix.RawSockaddrInet4{}
|
||||
size := uint32(unsafe.Sizeof(addr))
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
|
||||
addr := make([]byte, 1+net.IPv4len+2)
|
||||
addr[0] = socks5.AtypIPv4
|
||||
copy(addr[1:1+net.IPv4len], raw.Addr[:])
|
||||
port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian
|
||||
addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1]
|
||||
return addr, nil
|
||||
port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:])
|
||||
return netip.AddrPortFrom(netip.AddrFrom4(addr.Addr), port), nil
|
||||
}
|
||||
|
||||
func getorigdst6(fd uintptr) (netip.AddrPort, error) {
|
||||
addr := unix.RawSockaddrInet6{}
|
||||
size := uint32(unsafe.Sizeof(addr))
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), uintptr(unsafe.Pointer(&size)), 0)
|
||||
if err != 0 {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
port := binary.BigEndian.Uint16((*(*[2]byte)(unsafe.Pointer(&addr.Port)))[:])
|
||||
return netip.AddrPortFrom(netip.AddrFrom16(addr.Addr), port), nil
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func (l *UDPListener) LocalAddr() net.Addr {
|
||||
return l.packetConn.LocalAddr()
|
||||
}
|
||||
|
||||
func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, put func(), addr net.Addr) {
|
||||
func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) {
|
||||
tgtAddr := socks5.SplitAddr(buf)
|
||||
if tgtAddr == nil {
|
||||
// Unresolved UDP packet, return buffer to the pool
|
||||
@ -77,7 +77,7 @@ func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, pu
|
||||
put: put,
|
||||
}
|
||||
select {
|
||||
case in <- inbound.NewPacket(target, packet, C.SHADOWSOCKS):
|
||||
case in <- inbound.NewPacket(target, packet, C.SHADOWSOCKS, additions...):
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
func (c *packet) Drop() {
|
||||
if c.put != nil {
|
||||
c.put()
|
||||
c.put = nil
|
||||
}
|
||||
c.payload = nil
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
|
@ -14,8 +14,8 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
mux "github.com/sagernet/sing-mux"
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/bufio/deadline"
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
@ -53,36 +53,40 @@ func NewUDP(addr string, in chan<- C.PacketAdapter, additions ...inbound.Additio
|
||||
packetConn: l,
|
||||
addr: addr,
|
||||
}
|
||||
conn := N.NewEnhancePacketConn(l)
|
||||
go func() {
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
n, remoteAddr, err := l.ReadFrom(buf)
|
||||
data, put, remoteAddr, err := conn.WaitReadFrom()
|
||||
if err != nil {
|
||||
pool.Put(buf)
|
||||
if put != nil {
|
||||
put()
|
||||
}
|
||||
if sl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
handleSocksUDP(l, in, buf[:n], remoteAddr, additions...)
|
||||
handleSocksUDP(l, in, data, put, remoteAddr, additions...)
|
||||
}
|
||||
}()
|
||||
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, addr net.Addr, additions ...inbound.Addition) {
|
||||
func handleSocksUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, put func(), addr net.Addr, additions ...inbound.Addition) {
|
||||
target, payload, err := socks5.DecodeUDPPacket(buf)
|
||||
if err != nil {
|
||||
// Unresolved UDP packet, return buffer to the pool
|
||||
pool.Put(buf)
|
||||
if put != nil {
|
||||
put()
|
||||
}
|
||||
return
|
||||
}
|
||||
packet := &packet{
|
||||
pc: pc,
|
||||
rAddr: addr,
|
||||
payload: payload,
|
||||
bufRef: buf,
|
||||
put: put,
|
||||
}
|
||||
select {
|
||||
case in <- inbound.NewPacket(target, packet, C.SOCKS5, additions...):
|
||||
|
@ -3,7 +3,6 @@ package socks
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
@ -11,7 +10,7 @@ type packet struct {
|
||||
pc net.PacketConn
|
||||
rAddr net.Addr
|
||||
payload []byte
|
||||
bufRef []byte
|
||||
put func()
|
||||
}
|
||||
|
||||
func (c *packet) Data() []byte {
|
||||
@ -33,7 +32,11 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.bufRef)
|
||||
if c.put != nil {
|
||||
c.put()
|
||||
c.put = nil
|
||||
}
|
||||
c.payload = nil
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
|
@ -41,7 +41,8 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.buf)
|
||||
_ = pool.Put(c.buf)
|
||||
c.buf = nil
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
@ -16,6 +14,10 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/tuic"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const ServerMaxIncomingStreams = (1 << 32) - 1
|
||||
@ -52,48 +54,77 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||
MaxIncomingStreams: ServerMaxIncomingStreams,
|
||||
MaxIncomingUniStreams: ServerMaxIncomingStreams,
|
||||
EnableDatagrams: true,
|
||||
Allow0RTT: func(addr net.Addr) bool {
|
||||
return true
|
||||
},
|
||||
Allow0RTT: true,
|
||||
}
|
||||
quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
|
||||
quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
|
||||
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
|
||||
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
|
||||
|
||||
packetOverHead := tuic.PacketOverHeadV4
|
||||
if len(config.Token) == 0 {
|
||||
packetOverHead = tuic.PacketOverHeadV5
|
||||
}
|
||||
|
||||
if config.CWND == 0 {
|
||||
config.CWND = 32
|
||||
}
|
||||
|
||||
if config.MaxUdpRelayPacketSize == 0 {
|
||||
config.MaxUdpRelayPacketSize = 1500
|
||||
}
|
||||
maxDatagramFrameSize := config.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
||||
maxDatagramFrameSize := config.MaxUdpRelayPacketSize + packetOverHead
|
||||
if maxDatagramFrameSize > 1400 {
|
||||
maxDatagramFrameSize = 1400
|
||||
}
|
||||
config.MaxUdpRelayPacketSize = maxDatagramFrameSize - tuic.PacketOverHead
|
||||
config.MaxUdpRelayPacketSize = maxDatagramFrameSize - packetOverHead
|
||||
quicConfig.MaxDatagramFrameSize = int64(maxDatagramFrameSize)
|
||||
|
||||
tokens := make([][32]byte, len(config.Token))
|
||||
for i, token := range config.Token {
|
||||
tokens[i] = tuic.GenTKN(token)
|
||||
handleTcpFn := func(conn net.Conn, addr socks5.Addr, _additions ...inbound.Addition) error {
|
||||
newAdditions := additions
|
||||
if len(_additions) > 0 {
|
||||
newAdditions = slices.Clone(additions)
|
||||
newAdditions = append(newAdditions, _additions...)
|
||||
}
|
||||
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||
return nil
|
||||
}
|
||||
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
||||
newAdditions := additions
|
||||
if len(_additions) > 0 {
|
||||
newAdditions = slices.Clone(additions)
|
||||
newAdditions = append(newAdditions, _additions...)
|
||||
}
|
||||
select {
|
||||
case udpIn <- inbound.NewPacket(addr, packet, C.TUIC, newAdditions...):
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
option := &tuic.ServerOption{
|
||||
HandleTcpFn: func(conn net.Conn, addr socks5.Addr) error {
|
||||
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, additions...)
|
||||
return nil
|
||||
},
|
||||
HandleUdpFn: func(addr socks5.Addr, packet C.UDPPacket) error {
|
||||
select {
|
||||
case udpIn <- inbound.NewPacket(addr, packet, C.TUIC, additions...):
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
},
|
||||
HandleTcpFn: handleTcpFn,
|
||||
HandleUdpFn: handleUdpFn,
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Tokens: tokens,
|
||||
CongestionController: config.CongestionController,
|
||||
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
||||
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
|
||||
CWND: config.CWND,
|
||||
}
|
||||
if len(config.Token) > 0 {
|
||||
tokens := make([][32]byte, len(config.Token))
|
||||
for i, token := range config.Token {
|
||||
tokens[i] = tuic.GenTKN(token)
|
||||
}
|
||||
option.Tokens = tokens
|
||||
}
|
||||
if len(config.Users) > 0 {
|
||||
users := make(map[[16]byte]string)
|
||||
for _uuid, password := range config.Users {
|
||||
users[uuid.FromStringOrNil(_uuid)] = password
|
||||
}
|
||||
option.Users = users
|
||||
}
|
||||
|
||||
sl := &Listener{false, config, nil, nil}
|
||||
@ -113,7 +144,8 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||
|
||||
sl.udpListeners = append(sl.udpListeners, ul)
|
||||
|
||||
server, err := tuic.NewServer(option, ul)
|
||||
var server *tuic.Server
|
||||
server, err = tuic.NewServer(option, ul)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ func (c *packet) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.payload)
|
||||
_ = pool.Put(c.payload)
|
||||
c.payload = nil
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
|
@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -11,10 +10,10 @@ import (
|
||||
|
||||
type Port struct {
|
||||
*Base
|
||||
adapter string
|
||||
port string
|
||||
ruleType C.RuleType
|
||||
portList []utils.Range[uint16]
|
||||
adapter string
|
||||
port string
|
||||
ruleType C.RuleType
|
||||
portRanges utils.IntRanges[uint16]
|
||||
}
|
||||
|
||||
func (p *Port) RuleType() C.RuleType {
|
||||
@ -43,61 +42,25 @@ func (p *Port) Payload() string {
|
||||
func (p *Port) matchPortReal(portRef string) bool {
|
||||
port, _ := strconv.Atoi(portRef)
|
||||
|
||||
for _, pr := range p.portList {
|
||||
if pr.Contains(uint16(port)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return p.portRanges.Check(uint16(port))
|
||||
}
|
||||
|
||||
func NewPort(port string, adapter string, ruleType C.RuleType) (*Port, error) {
|
||||
ports := strings.Split(port, "/")
|
||||
if len(ports) > 28 {
|
||||
return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error())
|
||||
portRanges, err := utils.NewIntRanges[uint16](port)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w, %s", errPayload, err.Error())
|
||||
}
|
||||
|
||||
var portRange []utils.Range[uint16]
|
||||
for _, p := range ports {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
subPorts := strings.Split(p, "-")
|
||||
subPortsLen := len(subPorts)
|
||||
if subPortsLen > 2 {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
portStart, err := strconv.ParseUint(strings.Trim(subPorts[0], "[ ]"), 10, 16)
|
||||
if err != nil {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
switch subPortsLen {
|
||||
case 1:
|
||||
portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portStart)))
|
||||
case 2:
|
||||
portEnd, err := strconv.ParseUint(strings.Trim(subPorts[1], "[ ]"), 10, 16)
|
||||
if err != nil {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portEnd)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(portRange) == 0 {
|
||||
if len(portRanges) == 0 {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
return &Port{
|
||||
Base: &Base{},
|
||||
adapter: adapter,
|
||||
port: port,
|
||||
ruleType: ruleType,
|
||||
portList: portRange,
|
||||
Base: &Base{},
|
||||
adapter: adapter,
|
||||
port: port,
|
||||
ruleType: ruleType,
|
||||
portRanges: portRanges,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -2,57 +2,28 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Uid struct {
|
||||
*Base
|
||||
uids []utils.Range[uint32]
|
||||
uids utils.IntRanges[uint32]
|
||||
oUid string
|
||||
adapter string
|
||||
}
|
||||
|
||||
func NewUid(oUid, adapter string) (*Uid, error) {
|
||||
//if len(_uids) > 28 {
|
||||
// return nil, fmt.Errorf("%s, too many uid to use, maximum support 28 uid", errPayload.Error())
|
||||
//}
|
||||
if !(runtime.GOOS == "linux" || runtime.GOOS == "android") {
|
||||
return nil, fmt.Errorf("uid rule not support this platform")
|
||||
}
|
||||
|
||||
var uidRange []utils.Range[uint32]
|
||||
for _, u := range strings.Split(oUid, "/") {
|
||||
if u == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
subUids := strings.Split(u, "-")
|
||||
subUidsLen := len(subUids)
|
||||
if subUidsLen > 2 {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
uidStart, err := strconv.ParseUint(strings.Trim(subUids[0], "[ ]"), 10, 32)
|
||||
if err != nil {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
switch subUidsLen {
|
||||
case 1:
|
||||
uidRange = append(uidRange, *utils.NewRange(uint32(uidStart), uint32(uidStart)))
|
||||
case 2:
|
||||
uidEnd, err := strconv.ParseUint(strings.Trim(subUids[1], "[ ]"), 10, 32)
|
||||
if err != nil {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
uidRange = append(uidRange, *utils.NewRange(uint32(uidStart), uint32(uidEnd)))
|
||||
}
|
||||
uidRange, err := utils.NewIntRanges[uint32](oUid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w, %s", errPayload, err.Error())
|
||||
}
|
||||
|
||||
if len(uidRange) == 0 {
|
||||
@ -72,10 +43,8 @@ func (u *Uid) RuleType() C.RuleType {
|
||||
|
||||
func (u *Uid) Match(metadata *C.Metadata) (bool, string) {
|
||||
if metadata.Uid != 0 {
|
||||
for _, uid := range u.uids {
|
||||
if uid.Contains(metadata.Uid) {
|
||||
return true, u.adapter
|
||||
}
|
||||
if u.uids.Check(metadata.Uid) {
|
||||
return true, u.adapter
|
||||
}
|
||||
}
|
||||
log.Warnln("[UID] could not get uid from %s", metadata.String())
|
||||
|
@ -1,18 +1,24 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
P "github.com/Dreamacro/clash/constant/provider"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type ruleProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Behavior string `provider:"behavior"`
|
||||
Path string `provider:"path"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Format string `provider:"format,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
@ -48,13 +54,23 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
||||
return nil, fmt.Errorf("unsupported format type: %s", schema.Format)
|
||||
}
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
var vehicle P.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("rules", schema.URL)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||
}
|
||||
|
69
test/go.mod
69
test/go.mod
@ -6,9 +6,9 @@ require (
|
||||
github.com/Dreamacro/clash v0.0.0
|
||||
github.com/docker/docker v20.10.21+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/miekg/dns v1.1.53
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/net v0.9.0
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/stretchr/testify v1.8.3
|
||||
golang.org/x/net v0.10.0
|
||||
)
|
||||
|
||||
replace github.com/Dreamacro/clash => ../
|
||||
@ -20,11 +20,11 @@ require (
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/cilium/ebpf v0.9.3 // indirect
|
||||
github.com/cilium/ebpf v0.10.0 // indirect
|
||||
github.com/coreos/go-iptables v0.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
@ -38,27 +38,28 @@ require (
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c // indirect
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba // indirect
|
||||
github.com/metacubex/sing-tun v0.1.4 // indirect
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca // indirect
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a // indirect
|
||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.0.0 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.0.1 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/openacid/low v0.1.21 // indirect
|
||||
@ -71,44 +72,44 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a // indirect
|
||||
github.com/sagernet/sing-mux v0.0.0-20230427141602-9836fc9b052e // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b // indirect
|
||||
github.com/sagernet/sing v0.2.5-0.20230530114415-221f066dba7c // indirect
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3 // indirect
|
||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // 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/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.2 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
157
test/go.sum
157
test/go.sum
@ -15,17 +15,17 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc=
|
||||
github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A=
|
||||
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
|
||||
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog=
|
||||
github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@ -41,7 +41,7 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@ -63,8 +63,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
@ -73,8 +71,8 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/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.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
@ -84,38 +82,40 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 h1:HSkXG1bE/qcRuuPlZ2Jyf0Od8HLxOowi7CzKQqNtWn4=
|
||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7/go.mod h1:1ztDZHGbU5MjN5lNZpkpG8ygndjjWzcojp/H7r6l6QQ=
|
||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c h1:D62872jiuzC6b+3aI8tqfeyc6YgbfarYKywTnnvXwEM=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594 h1:KD96JPdTIayTGGgRl6PuVqo2Bpo6+x3LqDDyqrYDDXw=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230322045857-901b636b4594/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba h1:He8YwyK600lHAS1xxNsP4k/jnZ8zqQ34XjCGn925+Yk=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230422111054-f54786eee8ba/go.mod h1:4uQQReKMTU7KTfOykVBe/oGJ00pl38d+BYJ99+mx26s=
|
||||
github.com/metacubex/sing-tun v0.1.4 h1:OQDBNHjuPKrOprCiK+sLt97YQ0K6b9ZWmJB6z51ibZQ=
|
||||
github.com/metacubex/sing-tun v0.1.4/go.mod h1:BMfG00enVf90/CzcdX9PK3Dymgl7BZqHXJfexEyB7Cc=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb h1:92YTNmYXCSycERjKn/zPbeK5DiW3dd80j3+oVTEWTE8=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb/go.mod h1:6pg8+Tje9KOltnj1whuvB2i5KFUMPp1TAF3oPhc5axM=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c h1:LpVNvlW/xE+mR8z76xJeYZlYznZXEmU4TeWeuygYdJg=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c/go.mod h1:4uQQReKMTU7KTfOykVBe/oGJ00pl38d+BYJ99+mx26s=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca h1:10qc50Q1hHrfGO4NjEJpIAgHX63Y256tHE0dFCTN8J4=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca/go.mod h1:jVDD4N22bDPPKA73NvB7aqdlLWiAwv8D+jx7HwhcWak=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e h1:7QlJQl4S3F3YXn48fYxjymMw8HkXg9bl++hLi4ZRyCY=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e/go.mod h1:u9onX49LZPYuIPQ7SdM64Gnins8y5wg4Cn6ZYRSxWHU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a h1:cWKym33Qvl6HA3hj4/YuYD8hHyqQPb47wT5cJRAPgco=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a/go.mod h1:Bsw2BvKMMMY0FhZPseDI50ZOalvoUPMKYyGpyqvIIqY=
|
||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c h1:RC8WMpjonrBfyAh6VN/POIPtYD5tRAq0qMqCRjQNK+g=
|
||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mroth/weightedrand/v2 v2.0.0 h1:ADehnByWbliEDIazDAKFdBHoqgHSXAkgyKqM/9YsPoo=
|
||||
github.com/mroth/weightedrand/v2 v2.0.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/mroth/weightedrand/v2 v2.0.1 h1:zrEVDIaau/E4QLOKu02kpg8T8myweFlMGikIgbIdrRA=
|
||||
github.com/mroth/weightedrand/v2 v2.0.1/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/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||
@ -144,25 +144,23 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a h1:s2kkd/eR3mWGkYioknxhgQzG8uft4VRx9skhqxxeyVQ=
|
||||
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230427141602-9836fc9b052e h1:t8nuY9plpHEzlnPxOpuv64jhjz3teIvccu3YMFX4fJI=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230427141602-9836fc9b052e/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9 h1:0Dc1t9ao9EyvRil6l/950PLwND1qO1rgnxwbcctE8KE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9/go.mod h1:Dpib342FFR68SZ3CSRYxk/zWbanAqRBrCxoLuda5I0A=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||
github.com/sagernet/sing v0.2.5-0.20230530114415-221f066dba7c h1:OAwuwvyjPPsCCdSxqZA7T+ABNezeNbF68sRbcMkKT7M=
|
||||
github.com/sagernet/sing v0.2.5-0.20230530114415-221f066dba7c/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3 h1:PNwJs1F+3e/iZguYQR7YzxsH8Sm0Eu7vVuHawD89r34=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
|
||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||
@ -175,20 +173,22 @@ github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2d
|
||||
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/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@ -198,36 +198,39 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
|
||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 h1:a3Y4WVjCxwoyO4E2xdNvq577tW8lkSBgyrA8E9+2NtM=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -240,21 +243,21 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/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-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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=
|
||||
@ -266,8 +269,8 @@ golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -288,14 +291,14 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
||||
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.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
@ -17,8 +17,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/metacubex/gopacket"
|
||||
"github.com/metacubex/gopacket/layers"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -392,21 +394,35 @@ func (conn *TCPConn) SyscallConn() (syscall.RawConn, error) {
|
||||
// Dial connects to the remote TCP port,
|
||||
// and returns a single packet-oriented connection
|
||||
func Dial(network, address string) (*TCPConn, error) {
|
||||
// init gopacket.layers
|
||||
layers.Init()
|
||||
// remote address resolve
|
||||
raddr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lTcpAddr *net.TCPAddr
|
||||
var lIpAddr *net.IPAddr
|
||||
if ifaceName := dialer.DefaultInterface.Load(); len(ifaceName) > 0 {
|
||||
rAddrPort := raddr.AddrPort()
|
||||
addr, err := dialer.LookupLocalAddrFromIfaceName(ifaceName, network, rAddrPort.Addr(), int(rAddrPort.Port()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lTcpAddr = addr.(*net.TCPAddr)
|
||||
lIpAddr = &net.IPAddr{IP: lTcpAddr.IP}
|
||||
}
|
||||
|
||||
// AF_INET
|
||||
handle, err := net.DialIP("ip:tcp", nil, &net.IPAddr{IP: raddr.IP})
|
||||
handle, err := net.DialIP("ip:tcp", lIpAddr, &net.IPAddr{IP: raddr.IP})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create an established tcp connection
|
||||
// will hack this tcp connection for packet transmission
|
||||
tcpconn, err := net.DialTCP(network, nil, raddr)
|
||||
tcpconn, err := net.DialTCP(network, lTcpAddr, raddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -464,6 +480,8 @@ func Dial(network, address string) (*TCPConn, error) {
|
||||
// Listen acts like net.ListenTCP,
|
||||
// and returns a single packet-oriented connection
|
||||
func Listen(network, address string) (*TCPConn, error) {
|
||||
// init gopacket.layers
|
||||
layers.Init()
|
||||
// fields
|
||||
conn := new(TCPConn)
|
||||
conn.flowTable = make(map[string]*tcpFlow)
|
||||
|
@ -76,7 +76,10 @@ func (ct *ClientTransport) QUICDial(proto string, server string, serverPorts str
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qs, err := quic.DialContext(dialer.Context(), pktConn, serverUDPAddr, server, tlsConfig, quicConfig)
|
||||
transport := quic.Transport{Conn: pktConn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
qs, err := transport.Dial(dialer.Context(), serverUDPAddr, tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
_ = pktConn.Close()
|
||||
return nil, err
|
||||
|
46
transport/tuic/common/congestion.go
Normal file
46
transport/tuic/common/congestion.go
Normal file
@ -0,0 +1,46 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/transport/tuic/congestion"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
c "github.com/metacubex/quic-go/congestion"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
||||
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
||||
)
|
||||
|
||||
func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) {
|
||||
CWND := c.ByteCount(cwnd)
|
||||
switch cc {
|
||||
case "cubic":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
false,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
case "new_reno":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
true,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
case "bbr":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewBBRSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
CWND*congestion.InitialMaxDatagramSize,
|
||||
congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
67
transport/tuic/common/stream.go
Normal file
67
transport/tuic/common/stream.go
Normal file
@ -0,0 +1,67 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type quicStreamConn struct {
|
||||
quic.Stream
|
||||
lock sync.Mutex
|
||||
lAddr net.Addr
|
||||
rAddr net.Addr
|
||||
|
||||
closeDeferFn func()
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) Write(p []byte) (n int, err error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
return q.Stream.Write(p)
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) Close() error {
|
||||
q.closeOnce.Do(func() {
|
||||
q.closeErr = q.close()
|
||||
})
|
||||
return q.closeErr
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) close() error {
|
||||
if q.closeDeferFn != nil {
|
||||
defer q.closeDeferFn()
|
||||
}
|
||||
|
||||
// https://github.com/cloudflare/cloudflared/commit/ed2bac026db46b239699ac5ce4fcf122d7cab2cd
|
||||
// Make sure a possible writer does not block the lock forever. We need it, so we can close the writer
|
||||
// side of the stream safely.
|
||||
_ = q.Stream.SetWriteDeadline(time.Now())
|
||||
|
||||
// This lock is eventually acquired despite Write also acquiring it, because we set a deadline to writes.
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// We have to clean up the receiving stream ourselves since the Close in the bottom does not handle that.
|
||||
q.Stream.CancelRead(0)
|
||||
return q.Stream.Close()
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) LocalAddr() net.Addr {
|
||||
return q.lAddr
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) RemoteAddr() net.Addr {
|
||||
return q.rAddr
|
||||
}
|
||||
|
||||
var _ net.Conn = (*quicStreamConn)(nil)
|
||||
|
||||
func NewQuicStreamConn(stream quic.Stream, lAddr, rAddr net.Addr, closeDeferFn func()) net.Conn {
|
||||
return &quicStreamConn{Stream: stream, lAddr: lAddr, rAddr: rAddr, closeDeferFn: closeDeferFn}
|
||||
}
|
46
transport/tuic/common/type.go
Normal file
46
transport/tuic/common/type.go
Normal file
@ -0,0 +1,46 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
var (
|
||||
ClientClosed = errors.New("tuic: client closed")
|
||||
TooManyOpenStreams = errors.New("tuic: too many open streams")
|
||||
)
|
||||
|
||||
type DialFunc func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error)
|
||||
|
||||
type Client interface {
|
||||
DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error)
|
||||
ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error)
|
||||
OpenStreams() int64
|
||||
DialerRef() C.Dialer
|
||||
LastVisited() time.Time
|
||||
SetLastVisited(last time.Time)
|
||||
Close()
|
||||
}
|
||||
|
||||
type ServerHandler interface {
|
||||
AuthOk() bool
|
||||
HandleTimeout()
|
||||
HandleStream(conn *N.BufferedConn) (err error)
|
||||
HandleMessage(message []byte) (err error)
|
||||
HandleUniStream(reader *bufio.Reader) (err error)
|
||||
}
|
||||
|
||||
type UdpRelayMode uint8
|
||||
|
||||
const (
|
||||
QUIC UdpRelayMode = iota
|
||||
NATIVE
|
||||
)
|
@ -12,30 +12,34 @@ import (
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type dialResult struct {
|
||||
pc net.PacketConn
|
||||
addr net.Addr
|
||||
err error
|
||||
transport *quic.Transport
|
||||
addr net.Addr
|
||||
err error
|
||||
}
|
||||
|
||||
type PoolClient struct {
|
||||
*ClientOption
|
||||
|
||||
newClientOption *ClientOption
|
||||
dialResultMap map[C.Dialer]dialResult
|
||||
dialResultMutex *sync.Mutex
|
||||
tcpClients *list.List[*Client]
|
||||
tcpClientsMutex *sync.Mutex
|
||||
udpClients *list.List[*Client]
|
||||
udpClientsMutex *sync.Mutex
|
||||
newClientOptionV4 *ClientOptionV4
|
||||
newClientOptionV5 *ClientOptionV5
|
||||
dialResultMap map[C.Dialer]dialResult
|
||||
dialResultMutex *sync.Mutex
|
||||
tcpClients *list.List[Client]
|
||||
tcpClientsMutex *sync.Mutex
|
||||
udpClients *list.List[Client]
|
||||
udpClientsMutex *sync.Mutex
|
||||
}
|
||||
|
||||
func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
||||
conn, err := t.getClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||
newDialFn := func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
return t.dial(ctx, dialer, dialFn)
|
||||
}
|
||||
conn, err := t.getClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, newDialFn)
|
||||
if errors.Is(err, TooManyOpenStreams) {
|
||||
conn, err = t.newClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||
conn, err = t.newClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, newDialFn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -44,9 +48,12 @@ func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Meta
|
||||
}
|
||||
|
||||
func (t *PoolClient) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
|
||||
pc, err := t.getClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||
newDialFn := func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
return t.dial(ctx, dialer, dialFn)
|
||||
}
|
||||
pc, err := t.getClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, newDialFn)
|
||||
if errors.Is(err, TooManyOpenStreams) {
|
||||
pc, err = t.newClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||
pc, err = t.newClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, newDialFn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -54,43 +61,44 @@ func (t *PoolClient) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||
return N.NewRefPacketConn(pc, t), nil
|
||||
}
|
||||
|
||||
func (t *PoolClient) dial(ctx context.Context, dialer C.Dialer, dialFn DialFunc) (pc net.PacketConn, addr net.Addr, err error) {
|
||||
func (t *PoolClient) dial(ctx context.Context, dialer C.Dialer, dialFn DialFunc) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
t.dialResultMutex.Lock()
|
||||
dr, ok := t.dialResultMap[dialer]
|
||||
t.dialResultMutex.Unlock()
|
||||
if ok {
|
||||
return dr.pc, dr.addr, dr.err
|
||||
return dr.transport, dr.addr, dr.err
|
||||
}
|
||||
|
||||
pc, addr, err = dialFn(ctx, dialer)
|
||||
transport, addr, err = dialFn(ctx, dialer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if _, ok := pc.(*net.UDPConn); ok { // only cache the system's UDPConn
|
||||
dr.pc, dr.addr, dr.err = pc, addr, err
|
||||
if _, ok := transport.Conn.(*net.UDPConn); ok { // only cache the system's UDPConn
|
||||
transport.SetSingleUse(false) // don't close transport in each dial
|
||||
dr.transport, dr.addr, dr.err = transport, addr, err
|
||||
|
||||
t.dialResultMutex.Lock()
|
||||
t.dialResultMap[dialer] = dr
|
||||
t.dialResultMutex.Unlock()
|
||||
}
|
||||
|
||||
return pc, addr, err
|
||||
return transport, addr, err
|
||||
}
|
||||
|
||||
func (t *PoolClient) forceClose() {
|
||||
t.dialResultMutex.Lock()
|
||||
defer t.dialResultMutex.Unlock()
|
||||
for key := range t.dialResultMap {
|
||||
pc := t.dialResultMap[key].pc
|
||||
if pc != nil {
|
||||
_ = pc.Close()
|
||||
transport := t.dialResultMap[key].transport
|
||||
if transport != nil {
|
||||
_ = transport.Close()
|
||||
}
|
||||
delete(t.dialResultMap, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *PoolClient) newClient(udp bool, dialer C.Dialer) *Client {
|
||||
func (t *PoolClient) newClient(udp bool, dialer C.Dialer) (client Client) {
|
||||
clients := t.tcpClients
|
||||
clientsMutex := t.tcpClientsMutex
|
||||
if udp {
|
||||
@ -101,22 +109,26 @@ func (t *PoolClient) newClient(udp bool, dialer C.Dialer) *Client {
|
||||
clientsMutex.Lock()
|
||||
defer clientsMutex.Unlock()
|
||||
|
||||
client := NewClient(t.newClientOption, udp)
|
||||
client.dialerRef = dialer
|
||||
client.lastVisited = time.Now()
|
||||
if t.newClientOptionV4 != nil {
|
||||
client = NewClientV4(t.newClientOptionV4, udp, dialer)
|
||||
} else {
|
||||
client = NewClientV5(t.newClientOptionV5, udp, dialer)
|
||||
}
|
||||
|
||||
client.SetLastVisited(time.Now())
|
||||
|
||||
clients.PushFront(client)
|
||||
return client
|
||||
}
|
||||
|
||||
func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||
func (t *PoolClient) getClient(udp bool, dialer C.Dialer) Client {
|
||||
clients := t.tcpClients
|
||||
clientsMutex := t.tcpClientsMutex
|
||||
if udp {
|
||||
clients = t.udpClients
|
||||
clientsMutex = t.udpClientsMutex
|
||||
}
|
||||
var bestClient *Client
|
||||
var bestClient Client
|
||||
|
||||
func() {
|
||||
clientsMutex.Lock()
|
||||
@ -129,11 +141,11 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||
it = next
|
||||
continue
|
||||
}
|
||||
if client.dialerRef == dialer {
|
||||
if client.DialerRef() == dialer {
|
||||
if bestClient == nil {
|
||||
bestClient = client
|
||||
} else {
|
||||
if client.openStreams.Load() < bestClient.openStreams.Load() {
|
||||
if client.OpenStreams() < bestClient.OpenStreams() {
|
||||
bestClient = client
|
||||
}
|
||||
}
|
||||
@ -143,7 +155,7 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||
}()
|
||||
for it := clients.Front(); it != nil; {
|
||||
client := it.Value
|
||||
if client != bestClient && client.openStreams.Load() == 0 && time.Now().Sub(client.lastVisited) > 30*time.Minute {
|
||||
if client != bestClient && client.OpenStreams() == 0 && time.Now().Sub(client.LastVisited()) > 30*time.Minute {
|
||||
client.Close()
|
||||
next := it.Next()
|
||||
clients.Remove(it)
|
||||
@ -156,25 +168,40 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||
if bestClient == nil {
|
||||
return t.newClient(udp, dialer)
|
||||
} else {
|
||||
bestClient.lastVisited = time.Now()
|
||||
bestClient.SetLastVisited(time.Now())
|
||||
return bestClient
|
||||
}
|
||||
}
|
||||
|
||||
func NewPoolClient(clientOption *ClientOption) *PoolClient {
|
||||
func NewPoolClientV4(clientOption *ClientOptionV4) *PoolClient {
|
||||
p := &PoolClient{
|
||||
ClientOption: clientOption,
|
||||
dialResultMap: make(map[C.Dialer]dialResult),
|
||||
dialResultMutex: &sync.Mutex{},
|
||||
tcpClients: list.New[*Client](),
|
||||
tcpClients: list.New[Client](),
|
||||
tcpClientsMutex: &sync.Mutex{},
|
||||
udpClients: list.New[*Client](),
|
||||
udpClients: list.New[Client](),
|
||||
udpClientsMutex: &sync.Mutex{},
|
||||
}
|
||||
newClientOption := *clientOption
|
||||
p.newClientOption = &newClientOption
|
||||
p.newClientOptionV4 = &newClientOption
|
||||
runtime.SetFinalizer(p, closeClientPool)
|
||||
log.Debugln("New Tuic PoolClient at %p", p)
|
||||
log.Debugln("New TuicV4 PoolClient at %p", p)
|
||||
return p
|
||||
}
|
||||
|
||||
func NewPoolClientV5(clientOption *ClientOptionV5) *PoolClient {
|
||||
p := &PoolClient{
|
||||
dialResultMap: make(map[C.Dialer]dialResult),
|
||||
dialResultMutex: &sync.Mutex{},
|
||||
tcpClients: list.New[Client](),
|
||||
tcpClientsMutex: &sync.Mutex{},
|
||||
udpClients: list.New[Client](),
|
||||
udpClientsMutex: &sync.Mutex{},
|
||||
}
|
||||
newClientOption := *clientOption
|
||||
p.newClientOptionV5 = &newClientOption
|
||||
runtime.SetFinalizer(p, closeClientPool)
|
||||
log.Debugln("New TuicV5 PoolClient at %p", p)
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -2,51 +2,43 @@ package tuic
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
v4 "github.com/Dreamacro/clash/transport/tuic/v4"
|
||||
v5 "github.com/Dreamacro/clash/transport/tuic/v5"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
HandleTcpFn func(conn net.Conn, addr socks5.Addr) error
|
||||
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket) error
|
||||
HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error
|
||||
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error
|
||||
|
||||
TlsConfig *tls.Config
|
||||
QuicConfig *quic.Config
|
||||
Tokens [][32]byte
|
||||
Tokens [][32]byte // V4 special
|
||||
Users map[[16]byte]string // V5 special
|
||||
CongestionController string
|
||||
AuthenticationTimeout time.Duration
|
||||
MaxUdpRelayPacketSize int
|
||||
CWND int
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
*ServerOption
|
||||
listener quic.EarlyListener
|
||||
}
|
||||
|
||||
func NewServer(option *ServerOption, pc net.PacketConn) (*Server, error) {
|
||||
listener, err := quic.ListenEarly(pc, option.TlsConfig, option.QuicConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Server{
|
||||
ServerOption: option,
|
||||
listener: listener,
|
||||
}, err
|
||||
optionV4 *v4.ServerOption
|
||||
optionV5 *v5.ServerOption
|
||||
listener *quic.EarlyListener
|
||||
}
|
||||
|
||||
func (s *Server) Serve() error {
|
||||
@ -55,12 +47,17 @@ func (s *Server) Serve() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
SetCongestionController(conn, s.CongestionController)
|
||||
common.SetCongestionController(conn, s.CongestionController, s.CWND)
|
||||
h := &serverHandler{
|
||||
Server: s,
|
||||
quicConn: conn,
|
||||
uuid: utils.NewUUIDV4(),
|
||||
authCh: make(chan struct{}),
|
||||
}
|
||||
if h.optionV4 != nil {
|
||||
h.v4Handler = v4.NewServerHandler(h.optionV4, conn, h.uuid)
|
||||
}
|
||||
if h.optionV5 != nil {
|
||||
h.v5Handler = v5.NewServerHandler(h.optionV5, conn, h.uuid)
|
||||
}
|
||||
go h.handle()
|
||||
}
|
||||
@ -75,11 +72,8 @@ type serverHandler struct {
|
||||
quicConn quic.EarlyConnection
|
||||
uuid uuid.UUID
|
||||
|
||||
authCh chan struct{}
|
||||
authOk bool
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
v4Handler common.ServerHandler
|
||||
v5Handler common.ServerHandler
|
||||
}
|
||||
|
||||
func (s *serverHandler) handle() {
|
||||
@ -93,13 +87,27 @@ func (s *serverHandler) handle() {
|
||||
_ = s.handleMessage()
|
||||
}()
|
||||
|
||||
<-s.quicConn.HandshakeComplete().Done()
|
||||
<-s.quicConn.HandshakeComplete()
|
||||
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||
s.authOnce.Do(func() {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||
s.authOk = false
|
||||
close(s.authCh)
|
||||
})
|
||||
if s.v4Handler != nil {
|
||||
if s.v4Handler.AuthOk() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s.v5Handler != nil {
|
||||
if s.v5Handler.AuthOk() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if s.v4Handler != nil {
|
||||
s.v4Handler.HandleTimeout()
|
||||
}
|
||||
|
||||
if s.v5Handler != nil {
|
||||
s.v5Handler.HandleTimeout()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -111,49 +119,23 @@ func (s *serverHandler) handleMessage() (err error) {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
buffer := bytes.NewBuffer(message)
|
||||
packet, err := ReadPacket(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
if len(message) > 0 {
|
||||
switch message[0] {
|
||||
case v4.VER:
|
||||
if s.v4Handler != nil {
|
||||
return s.v4Handler.HandleMessage(message)
|
||||
}
|
||||
case v5.VER:
|
||||
if s.v5Handler != nil {
|
||||
return s.v5Handler.HandleMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.parsePacket(packet, "native")
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err error) {
|
||||
<-s.authCh
|
||||
if !s.authOk {
|
||||
return
|
||||
}
|
||||
var assocId uint32
|
||||
|
||||
assocId = packet.ASSOC_ID
|
||||
|
||||
v, _ := s.udpInputMap.LoadOrStore(assocId, &atomic.Bool{})
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
if writeClosed.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
pc := &quicStreamPacketConn{
|
||||
connId: assocId,
|
||||
quicConn: s.quicConn,
|
||||
inputConn: nil,
|
||||
udpRelayMode: udpRelayMode,
|
||||
maxUdpRelayPacketSize: s.MaxUdpRelayPacketSize,
|
||||
deferQuicConnFn: nil,
|
||||
closeDeferFn: nil,
|
||||
writeClosed: writeClosed,
|
||||
}
|
||||
|
||||
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
|
||||
pc: pc,
|
||||
packet: &packet,
|
||||
rAddr: N.NewCustomAddr("tuic", fmt.Sprintf("tuic-%s-%d", s.uuid, assocId), s.quicConn.RemoteAddr()), // for tunnel's handleUDPConn
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serverHandler) handleStream() (err error) {
|
||||
for {
|
||||
var quicStream quic.Stream
|
||||
@ -162,40 +144,30 @@ func (s *serverHandler) handleStream() (err error) {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
stream := &quicStreamConn{
|
||||
Stream: quicStream,
|
||||
lAddr: s.quicConn.LocalAddr(),
|
||||
rAddr: s.quicConn.RemoteAddr(),
|
||||
}
|
||||
stream := common.NewQuicStreamConn(
|
||||
quicStream,
|
||||
s.quicConn.LocalAddr(),
|
||||
s.quicConn.RemoteAddr(),
|
||||
nil,
|
||||
)
|
||||
conn := N.NewBufferedConn(stream)
|
||||
connect, err := ReadConnect(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-s.authCh
|
||||
if !s.authOk {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
err = s.HandleTcpFn(conn, connect.ADDR.SocksAddr())
|
||||
if err != nil {
|
||||
err = NewResponseFailed().WriteTo(buf)
|
||||
defer conn.Close()
|
||||
} else {
|
||||
err = NewResponseSucceed().WriteTo(buf)
|
||||
}
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
_, err = buf.WriteTo(stream)
|
||||
verBytes, err := conn.Peek(1)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
switch verBytes[0] {
|
||||
case v4.VER:
|
||||
if s.v4Handler != nil {
|
||||
return s.v4Handler.HandleStream(conn)
|
||||
}
|
||||
case v5.VER:
|
||||
if s.v5Handler != nil {
|
||||
return s.v5Handler.HandleStream(conn)
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
@ -213,86 +185,50 @@ func (s *serverHandler) handleUniStream() (err error) {
|
||||
stream.CancelRead(0)
|
||||
}()
|
||||
reader := bufio.NewReader(stream)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
verBytes, err := reader.Peek(1)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case AuthenticateType:
|
||||
var authenticate Authenticate
|
||||
authenticate, err = ReadAuthenticateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
switch verBytes[0] {
|
||||
case v4.VER:
|
||||
if s.v4Handler != nil {
|
||||
return s.v4Handler.HandleUniStream(reader)
|
||||
}
|
||||
ok := false
|
||||
for _, tkn := range s.Tokens {
|
||||
if authenticate.TKN == tkn {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
case v5.VER:
|
||||
if s.v5Handler != nil {
|
||||
return s.v5Handler.HandleUniStream(reader)
|
||||
}
|
||||
s.authOnce.Do(func() {
|
||||
if !ok {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||
}
|
||||
s.authOk = ok
|
||||
close(s.authCh)
|
||||
})
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, "quic")
|
||||
case DissociateType:
|
||||
var disassociate Dissociate
|
||||
disassociate, err = ReadDissociateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
writeClosed.Store(true)
|
||||
}
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type serverUDPPacket struct {
|
||||
pc *quicStreamPacketConn
|
||||
packet *Packet
|
||||
rAddr net.Addr
|
||||
func NewServer(option *ServerOption, pc net.PacketConn) (*Server, error) {
|
||||
listener, err := quic.ListenEarly(pc, option.TlsConfig, option.QuicConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := &Server{
|
||||
ServerOption: option,
|
||||
listener: listener,
|
||||
}
|
||||
if len(option.Tokens) > 0 {
|
||||
server.optionV4 = &v4.ServerOption{
|
||||
HandleTcpFn: option.HandleTcpFn,
|
||||
HandleUdpFn: option.HandleUdpFn,
|
||||
Tokens: option.Tokens,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
}
|
||||
}
|
||||
if len(option.Users) > 0 {
|
||||
server.optionV5 = &v5.ServerOption{
|
||||
HandleTcpFn: option.HandleTcpFn,
|
||||
HandleUdpFn: option.HandleUdpFn,
|
||||
Users: option.Users,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
}
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) InAddr() net.Addr {
|
||||
return s.pc.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) LocalAddr() net.Addr {
|
||||
return s.rAddr
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Data() []byte {
|
||||
return s.packet.DATA
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return s.pc.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Drop() {
|
||||
s.packet.DATA = nil
|
||||
}
|
||||
|
||||
var _ C.UDPPacket = (*serverUDPPacket)(nil)
|
||||
var _ C.UDPPacketInAddr = (*serverUDPPacket)(nil)
|
||||
|
39
transport/tuic/tuic.go
Normal file
39
transport/tuic/tuic.go
Normal file
@ -0,0 +1,39 @@
|
||||
package tuic
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
v4 "github.com/Dreamacro/clash/transport/tuic/v4"
|
||||
v5 "github.com/Dreamacro/clash/transport/tuic/v5"
|
||||
)
|
||||
|
||||
type ClientOptionV4 = v4.ClientOption
|
||||
type ClientOptionV5 = v5.ClientOption
|
||||
|
||||
type Client = common.Client
|
||||
|
||||
func NewClientV4(clientOption *ClientOptionV4, udp bool, dialerRef C.Dialer) Client {
|
||||
return v4.NewClient(clientOption, udp, dialerRef)
|
||||
}
|
||||
|
||||
func NewClientV5(clientOption *ClientOptionV5, udp bool, dialerRef C.Dialer) Client {
|
||||
return v5.NewClient(clientOption, udp, dialerRef)
|
||||
}
|
||||
|
||||
type DialFunc = common.DialFunc
|
||||
|
||||
var TooManyOpenStreams = common.TooManyOpenStreams
|
||||
|
||||
const DefaultStreamReceiveWindow = common.DefaultStreamReceiveWindow
|
||||
const DefaultConnectionReceiveWindow = common.DefaultConnectionReceiveWindow
|
||||
|
||||
var GenTKN = v4.GenTKN
|
||||
var PacketOverHeadV4 = v4.PacketOverHead
|
||||
var PacketOverHeadV5 = v5.PacketOverHead
|
||||
|
||||
type UdpRelayMode = common.UdpRelayMode
|
||||
|
||||
const (
|
||||
QUIC = common.QUIC
|
||||
NATIVE = common.NATIVE
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package tuic
|
||||
package v4
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -13,35 +13,30 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
atomic2 "github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/buf"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
ClientClosed = errors.New("tuic: client closed")
|
||||
TooManyOpenStreams = errors.New("tuic: too many open streams")
|
||||
)
|
||||
|
||||
type DialFunc func(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error)
|
||||
|
||||
type ClientOption struct {
|
||||
TlsConfig *tls.Config
|
||||
QuicConfig *quic.Config
|
||||
Host string
|
||||
Token [32]byte
|
||||
UdpRelayMode string
|
||||
UdpRelayMode common.UdpRelayMode
|
||||
CongestionController string
|
||||
ReduceRtt bool
|
||||
RequestTimeout time.Duration
|
||||
MaxUdpRelayPacketSize int
|
||||
FastOpen bool
|
||||
MaxOpenStreams int64
|
||||
CWND int
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
@ -58,30 +53,46 @@ type clientImpl struct {
|
||||
|
||||
// only ready for PoolClient
|
||||
dialerRef C.Dialer
|
||||
lastVisited time.Time
|
||||
lastVisited atomic2.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn DialFunc) (quic.Connection, error) {
|
||||
func (t *clientImpl) OpenStreams() int64 {
|
||||
return t.openStreams.Load()
|
||||
}
|
||||
|
||||
func (t *clientImpl) DialerRef() C.Dialer {
|
||||
return t.dialerRef
|
||||
}
|
||||
|
||||
func (t *clientImpl) LastVisited() time.Time {
|
||||
return t.lastVisited.Load()
|
||||
}
|
||||
|
||||
func (t *clientImpl) SetLastVisited(last time.Time) {
|
||||
t.lastVisited.Store(last)
|
||||
}
|
||||
|
||||
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (quic.Connection, error) {
|
||||
t.connMutex.Lock()
|
||||
defer t.connMutex.Unlock()
|
||||
if t.quicConn != nil {
|
||||
return t.quicConn, nil
|
||||
}
|
||||
pc, addr, err := dialFn(ctx, dialer)
|
||||
transport, addr, err := dialFn(ctx, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var quicConn quic.Connection
|
||||
if t.ReduceRtt {
|
||||
quicConn, err = quic.DialEarlyContext(ctx, pc, addr, t.Host, t.TlsConfig, t.QuicConfig)
|
||||
quicConn, err = transport.DialEarly(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||
} else {
|
||||
quicConn, err = quic.DialContext(ctx, pc, addr, t.Host, t.TlsConfig, t.QuicConfig)
|
||||
quicConn, err = transport.Dial(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
SetCongestionController(quicConn, t.CongestionController)
|
||||
common.SetCongestionController(quicConn, t.CongestionController, t.CWND)
|
||||
|
||||
go func() {
|
||||
_ = t.sendAuthentication(quicConn)
|
||||
@ -89,7 +100,12 @@ func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn Di
|
||||
|
||||
if t.udp {
|
||||
go func() {
|
||||
_ = t.parseUDP(quicConn)
|
||||
switch t.UdpRelayMode {
|
||||
case common.QUIC:
|
||||
_ = t.handleUniStream(quicConn)
|
||||
default: // native
|
||||
_ = t.handleMessage(quicConn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@ -123,80 +139,102 @@ func (t *clientImpl) sendAuthentication(quicConn quic.Connection) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *clientImpl) parseUDP(quicConn quic.Connection) (err error) {
|
||||
func (t *clientImpl) handleUniStream(quicConn quic.Connection) (err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
switch t.UdpRelayMode {
|
||||
case "quic":
|
||||
for {
|
||||
var stream quic.ReceiveStream
|
||||
stream, err = quicConn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint32
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
for {
|
||||
var stream quic.ReceiveStream
|
||||
stream, err = quicConn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint32
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
stream.CancelRead(0)
|
||||
}()
|
||||
reader := bufio.NewReader(stream)
|
||||
packet, err := ReadPacket(reader)
|
||||
}
|
||||
stream.CancelRead(0)
|
||||
}()
|
||||
reader := bufio.NewReader(stream)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
writer := bufio.NewWriterSize(conn, packet.BytesLen())
|
||||
_ = packet.WriteTo(writer)
|
||||
_ = writer.Flush()
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
default: // native
|
||||
for {
|
||||
var message []byte
|
||||
message, err = quicConn.ReceiveMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint32
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
if t.udp && t.UdpRelayMode == common.QUIC {
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
writer := bufio.NewWriterSize(conn, packet.BytesLen())
|
||||
_ = packet.WriteTo(writer)
|
||||
_ = writer.Flush()
|
||||
}
|
||||
}
|
||||
}()
|
||||
buffer := bytes.NewBuffer(message)
|
||||
packet, err := ReadPacket(buffer)
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) handleMessage(quicConn quic.Connection) (err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
for {
|
||||
var message []byte
|
||||
message, err = quicConn.ReceiveMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint32
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
reader := bytes.NewBuffer(message)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_, _ = conn.Write(message)
|
||||
if t.udp && t.UdpRelayMode == common.NATIVE {
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_, _ = conn.Write(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,11 +276,11 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||
func (t *clientImpl) Close() {
|
||||
t.closed.Store(true)
|
||||
if t.openStreams.Load() == 0 {
|
||||
t.forceClose(nil, ClientClosed)
|
||||
t.forceClose(nil, common.ClientClosed)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
||||
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -250,9 +288,9 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
|
||||
openStreams := t.openStreams.Add(1)
|
||||
if openStreams >= t.MaxOpenStreams {
|
||||
t.openStreams.Add(-1)
|
||||
return nil, TooManyOpenStreams
|
||||
return nil, common.TooManyOpenStreams
|
||||
}
|
||||
stream, err := func() (stream *quicStreamConn, err error) {
|
||||
stream, err := func() (stream net.Conn, err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
@ -266,19 +304,19 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream = &quicStreamConn{
|
||||
Stream: quicStream,
|
||||
lAddr: quicConn.LocalAddr(),
|
||||
rAddr: quicConn.RemoteAddr(),
|
||||
closeDeferFn: func() {
|
||||
stream = common.NewQuicStreamConn(
|
||||
quicStream,
|
||||
quicConn.LocalAddr(),
|
||||
quicConn.RemoteAddr(),
|
||||
func() {
|
||||
time.AfterFunc(C.DefaultTCPTimeout, func() {
|
||||
openStreams := t.openStreams.Add(-1)
|
||||
if openStreams == 0 && t.closed.Load() {
|
||||
t.forceClose(quicConn, ClientClosed)
|
||||
t.forceClose(quicConn, common.ClientClosed)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
_, err = buf.WriteTo(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
@ -362,7 +400,7 @@ func (conn *earlyConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
|
||||
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -370,7 +408,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||
openStreams := t.openStreams.Add(1)
|
||||
if openStreams >= t.MaxOpenStreams {
|
||||
t.openStreams.Add(-1)
|
||||
return nil, TooManyOpenStreams
|
||||
return nil, common.TooManyOpenStreams
|
||||
}
|
||||
|
||||
pipe1, pipe2 := net.Pipe()
|
||||
@ -394,7 +432,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||
time.AfterFunc(C.DefaultUDPTimeout, func() {
|
||||
openStreams := t.openStreams.Add(-1)
|
||||
if openStreams == 0 && t.closed.Load() {
|
||||
t.forceClose(quicConn, ClientClosed)
|
||||
t.forceClose(quicConn, common.ClientClosed)
|
||||
}
|
||||
})
|
||||
},
|
||||
@ -406,7 +444,7 @@ type Client struct {
|
||||
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
|
||||
}
|
||||
|
||||
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
||||
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -414,7 +452,7 @@ func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata
|
||||
return N.NewRefConn(conn, t), err
|
||||
}
|
||||
|
||||
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
|
||||
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -423,21 +461,22 @@ func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadat
|
||||
}
|
||||
|
||||
func (t *Client) forceClose() {
|
||||
t.clientImpl.forceClose(nil, ClientClosed)
|
||||
t.clientImpl.forceClose(nil, common.ClientClosed)
|
||||
}
|
||||
|
||||
func NewClient(clientOption *ClientOption, udp bool) *Client {
|
||||
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
|
||||
ci := &clientImpl{
|
||||
ClientOption: clientOption,
|
||||
udp: udp,
|
||||
dialerRef: dialerRef,
|
||||
}
|
||||
c := &Client{ci}
|
||||
runtime.SetFinalizer(c, closeClient)
|
||||
log.Debugln("New Tuic Client at %p", c)
|
||||
log.Debugln("New TuicV4 Client at %p", c)
|
||||
return c
|
||||
}
|
||||
|
||||
func closeClient(client *Client) {
|
||||
log.Debugln("Close Tuic Client at %p", client)
|
||||
log.Debugln("Close TuicV4 Client at %p", client)
|
||||
client.forceClose()
|
||||
}
|
@ -1,115 +1,24 @@
|
||||
package tuic
|
||||
package v4
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/tuic/congestion"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
||||
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
||||
)
|
||||
|
||||
func SetCongestionController(quicConn quic.Connection, cc string) {
|
||||
switch cc {
|
||||
case "cubic":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
false,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
case "new_reno":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewCubicSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
true,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
case "bbr":
|
||||
quicConn.SetCongestionControl(
|
||||
congestion.NewBBRSender(
|
||||
congestion.DefaultClock{},
|
||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||
congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize,
|
||||
congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type quicStreamConn struct {
|
||||
quic.Stream
|
||||
lock sync.Mutex
|
||||
lAddr net.Addr
|
||||
rAddr net.Addr
|
||||
|
||||
closeDeferFn func()
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) Write(p []byte) (n int, err error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
return q.Stream.Write(p)
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) Close() error {
|
||||
q.closeOnce.Do(func() {
|
||||
q.closeErr = q.close()
|
||||
})
|
||||
return q.closeErr
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) close() error {
|
||||
if q.closeDeferFn != nil {
|
||||
defer q.closeDeferFn()
|
||||
}
|
||||
|
||||
// https://github.com/cloudflare/cloudflared/commit/ed2bac026db46b239699ac5ce4fcf122d7cab2cd
|
||||
// Make sure a possible writer does not block the lock forever. We need it, so we can close the writer
|
||||
// side of the stream safely.
|
||||
_ = q.Stream.SetWriteDeadline(time.Now())
|
||||
|
||||
// This lock is eventually acquired despite Write also acquiring it, because we set a deadline to writes.
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// We have to clean up the receiving stream ourselves since the Close in the bottom does not handle that.
|
||||
q.Stream.CancelRead(0)
|
||||
return q.Stream.Close()
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) LocalAddr() net.Addr {
|
||||
return q.lAddr
|
||||
}
|
||||
|
||||
func (q *quicStreamConn) RemoteAddr() net.Addr {
|
||||
return q.rAddr
|
||||
}
|
||||
|
||||
var _ net.Conn = (*quicStreamConn)(nil)
|
||||
|
||||
type quicStreamPacketConn struct {
|
||||
connId uint32
|
||||
quicConn quic.Connection
|
||||
inputConn *N.BufferedConn
|
||||
|
||||
udpRelayMode string
|
||||
udpRelayMode common.UdpRelayMode
|
||||
maxUdpRelayPacketSize int
|
||||
|
||||
deferQuicConnFn func(quicConn quic.Connection, err error)
|
||||
@ -213,7 +122,7 @@ func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if q.udpRelayMode != "quic" && len(p) > q.maxUdpRelayPacketSize {
|
||||
if q.udpRelayMode != common.QUIC && len(p) > q.maxUdpRelayPacketSize {
|
||||
return 0, quic.ErrMessageTooLarge(q.maxUdpRelayPacketSize)
|
||||
}
|
||||
if q.closed {
|
||||
@ -239,7 +148,7 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro
|
||||
return
|
||||
}
|
||||
switch q.udpRelayMode {
|
||||
case "quic":
|
||||
case common.QUIC:
|
||||
var stream quic.SendStream
|
||||
stream, err = q.quicConn.OpenUniStream()
|
||||
if err != nil {
|
@ -1,4 +1,4 @@
|
||||
package tuic
|
||||
package v4
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
@ -36,6 +36,8 @@ const (
|
||||
ResponseType = CommandType(0xff)
|
||||
)
|
||||
|
||||
const VER byte = 0x04
|
||||
|
||||
func (c CommandType) String() string {
|
||||
switch c {
|
||||
case AuthenticateType:
|
||||
@ -66,7 +68,7 @@ type CommandHead struct {
|
||||
|
||||
func NewCommandHead(TYPE CommandType) CommandHead {
|
||||
return CommandHead{
|
||||
VER: 0x04,
|
||||
VER: VER,
|
||||
TYPE: TYPE,
|
||||
}
|
||||
}
|
218
transport/tuic/v4/server.go
Normal file
218
transport/tuic/v4/server.go
Normal file
@ -0,0 +1,218 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error
|
||||
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error
|
||||
|
||||
Tokens [][32]byte
|
||||
MaxUdpRelayPacketSize int
|
||||
}
|
||||
|
||||
func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid uuid.UUID) common.ServerHandler {
|
||||
return &serverHandler{
|
||||
ServerOption: option,
|
||||
quicConn: quicConn,
|
||||
uuid: uuid,
|
||||
authCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type serverHandler struct {
|
||||
*ServerOption
|
||||
quicConn quic.EarlyConnection
|
||||
uuid uuid.UUID
|
||||
|
||||
authCh chan struct{}
|
||||
authOk atomic.Bool
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
}
|
||||
|
||||
func (s *serverHandler) AuthOk() bool {
|
||||
return s.authOk.Load()
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleTimeout() {
|
||||
s.authOnce.Do(func() {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||
s.authOk.Store(false)
|
||||
close(s.authCh)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleMessage(message []byte) (err error) {
|
||||
buffer := bytes.NewBuffer(message)
|
||||
packet, err := ReadPacket(buffer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.NATIVE)
|
||||
}
|
||||
|
||||
func (s *serverHandler) parsePacket(packet Packet, udpRelayMode common.UdpRelayMode) (err error) {
|
||||
<-s.authCh
|
||||
if !s.authOk.Load() {
|
||||
return
|
||||
}
|
||||
var assocId uint32
|
||||
|
||||
assocId = packet.ASSOC_ID
|
||||
|
||||
v, _ := s.udpInputMap.LoadOrStore(assocId, &atomic.Bool{})
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
if writeClosed.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
pc := &quicStreamPacketConn{
|
||||
connId: assocId,
|
||||
quicConn: s.quicConn,
|
||||
inputConn: nil,
|
||||
udpRelayMode: udpRelayMode,
|
||||
maxUdpRelayPacketSize: s.MaxUdpRelayPacketSize,
|
||||
deferQuicConnFn: nil,
|
||||
closeDeferFn: nil,
|
||||
writeClosed: writeClosed,
|
||||
}
|
||||
|
||||
return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{
|
||||
pc: pc,
|
||||
packet: &packet,
|
||||
rAddr: N.NewCustomAddr("tuic", fmt.Sprintf("tuic-%s-%d", s.uuid, assocId), s.quicConn.RemoteAddr()), // for tunnel's handleUDPConn
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleStream(conn *N.BufferedConn) (err error) {
|
||||
connect, err := ReadConnect(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-s.authCh
|
||||
if !s.authOk.Load() {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
err = s.HandleTcpFn(conn, connect.ADDR.SocksAddr())
|
||||
if err != nil {
|
||||
err = NewResponseFailed().WriteTo(buf)
|
||||
defer conn.Close()
|
||||
} else {
|
||||
err = NewResponseSucceed().WriteTo(buf)
|
||||
}
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
_, err = buf.WriteTo(conn)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) {
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case AuthenticateType:
|
||||
var authenticate Authenticate
|
||||
authenticate, err = ReadAuthenticateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
authOk := false
|
||||
for _, tkn := range s.Tokens {
|
||||
if authenticate.TKN == tkn {
|
||||
authOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
s.authOnce.Do(func() {
|
||||
if !authOk {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||
}
|
||||
s.authOk.Store(authOk)
|
||||
close(s.authCh)
|
||||
})
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.QUIC)
|
||||
case DissociateType:
|
||||
var disassociate Dissociate
|
||||
disassociate, err = ReadDissociateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
writeClosed.Store(true)
|
||||
}
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type serverUDPPacket struct {
|
||||
pc *quicStreamPacketConn
|
||||
packet *Packet
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) InAddr() net.Addr {
|
||||
return s.pc.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) LocalAddr() net.Addr {
|
||||
return s.rAddr
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Data() []byte {
|
||||
return s.packet.DATA
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return s.pc.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Drop() {
|
||||
s.packet.DATA = nil
|
||||
}
|
||||
|
||||
var _ C.UDPPacket = (*serverUDPPacket)(nil)
|
||||
var _ C.UDPPacketInAddr = (*serverUDPPacket)(nil)
|
419
transport/tuic/v5/client.go
Normal file
419
transport/tuic/v5/client.go
Normal file
@ -0,0 +1,419 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
atomic2 "github.com/Dreamacro/clash/common/atomic"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type ClientOption struct {
|
||||
TlsConfig *tls.Config
|
||||
QuicConfig *quic.Config
|
||||
Uuid [16]byte
|
||||
Password string
|
||||
UdpRelayMode common.UdpRelayMode
|
||||
CongestionController string
|
||||
ReduceRtt bool
|
||||
MaxUdpRelayPacketSize int
|
||||
MaxOpenStreams int64
|
||||
CWND int
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
*ClientOption
|
||||
udp bool
|
||||
|
||||
quicConn quic.Connection
|
||||
connMutex sync.Mutex
|
||||
|
||||
openStreams atomic.Int64
|
||||
closed atomic.Bool
|
||||
|
||||
udpInputMap sync.Map
|
||||
|
||||
// only ready for PoolClient
|
||||
dialerRef C.Dialer
|
||||
lastVisited atomic2.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func (t *clientImpl) OpenStreams() int64 {
|
||||
return t.openStreams.Load()
|
||||
}
|
||||
|
||||
func (t *clientImpl) DialerRef() C.Dialer {
|
||||
return t.dialerRef
|
||||
}
|
||||
|
||||
func (t *clientImpl) LastVisited() time.Time {
|
||||
return t.lastVisited.Load()
|
||||
}
|
||||
|
||||
func (t *clientImpl) SetLastVisited(last time.Time) {
|
||||
t.lastVisited.Store(last)
|
||||
}
|
||||
|
||||
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (quic.Connection, error) {
|
||||
t.connMutex.Lock()
|
||||
defer t.connMutex.Unlock()
|
||||
if t.quicConn != nil {
|
||||
return t.quicConn, nil
|
||||
}
|
||||
transport, addr, err := dialFn(ctx, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var quicConn quic.Connection
|
||||
if t.ReduceRtt {
|
||||
quicConn, err = transport.DialEarly(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||
} else {
|
||||
quicConn, err = transport.Dial(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
common.SetCongestionController(quicConn, t.CongestionController, t.CWND)
|
||||
|
||||
go func() {
|
||||
_ = t.sendAuthentication(quicConn)
|
||||
}()
|
||||
|
||||
if t.udp && t.UdpRelayMode == common.QUIC {
|
||||
go func() {
|
||||
_ = t.handleUniStream(quicConn)
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
_ = t.handleMessage(quicConn) // always handleMessage because tuicV5 using datagram to send the Heartbeat
|
||||
}()
|
||||
|
||||
t.quicConn = quicConn
|
||||
t.openStreams.Store(0)
|
||||
return quicConn, nil
|
||||
}
|
||||
|
||||
func (t *clientImpl) sendAuthentication(quicConn quic.Connection) (err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
stream, err := quicConn.OpenUniStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
token, err := GenToken(quicConn.ConnectionState(), t.Uuid, t.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = NewAuthenticate(t.Uuid, token).WriteTo(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = buf.WriteTo(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *clientImpl) handleUniStream(quicConn quic.Connection) (err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
for {
|
||||
var stream quic.ReceiveStream
|
||||
stream, err = quicConn.AcceptUniStream(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint16
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
stream.CancelRead(0)
|
||||
}()
|
||||
reader := bufio.NewReader(stream)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if t.udp && t.UdpRelayMode == common.QUIC {
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
writer := bufio.NewWriterSize(conn, packet.BytesLen())
|
||||
_ = packet.WriteTo(writer)
|
||||
_ = writer.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) handleMessage(quicConn quic.Connection) (err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
for {
|
||||
var message []byte
|
||||
message, err = quicConn.ReceiveMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() (err error) {
|
||||
var assocId uint16
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
if err != nil && assocId != 0 {
|
||||
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
reader := bytes.NewBuffer(message)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if t.udp && t.UdpRelayMode == common.NATIVE {
|
||||
assocId = packet.ASSOC_ID
|
||||
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||
if conn, ok := val.(net.Conn); ok {
|
||||
_, _ = conn.Write(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) deferQuicConn(quicConn quic.Connection, err error) {
|
||||
var netError net.Error
|
||||
if err != nil && errors.As(err, &netError) {
|
||||
t.forceClose(quicConn, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||
t.connMutex.Lock()
|
||||
defer t.connMutex.Unlock()
|
||||
if quicConn == nil {
|
||||
quicConn = t.quicConn
|
||||
}
|
||||
if quicConn != nil {
|
||||
if quicConn == t.quicConn {
|
||||
t.quicConn = nil
|
||||
}
|
||||
}
|
||||
errStr := ""
|
||||
if err != nil {
|
||||
errStr = err.Error()
|
||||
}
|
||||
if quicConn != nil {
|
||||
_ = quicConn.CloseWithError(ProtocolError, errStr)
|
||||
}
|
||||
udpInputMap := &t.udpInputMap
|
||||
udpInputMap.Range(func(key, value any) bool {
|
||||
if conn, ok := value.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
udpInputMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (t *clientImpl) Close() {
|
||||
t.closed.Store(true)
|
||||
if t.openStreams.Load() == 0 {
|
||||
t.forceClose(nil, common.ClientClosed)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openStreams := t.openStreams.Add(1)
|
||||
if openStreams >= t.MaxOpenStreams {
|
||||
t.openStreams.Add(-1)
|
||||
return nil, common.TooManyOpenStreams
|
||||
}
|
||||
stream, err := func() (stream net.Conn, err error) {
|
||||
defer func() {
|
||||
t.deferQuicConn(quicConn, err)
|
||||
}()
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
err = NewConnect(NewAddress(metadata)).WriteTo(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quicStream, err := quicConn.OpenStream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream = common.NewQuicStreamConn(
|
||||
quicStream,
|
||||
quicConn.LocalAddr(),
|
||||
quicConn.RemoteAddr(),
|
||||
func() {
|
||||
time.AfterFunc(C.DefaultTCPTimeout, func() {
|
||||
openStreams := t.openStreams.Add(-1)
|
||||
if openStreams == 0 && t.closed.Load() {
|
||||
t.forceClose(quicConn, common.ClientClosed)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
_, err = buf.WriteTo(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
return stream, err
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openStreams := t.openStreams.Add(1)
|
||||
if openStreams >= t.MaxOpenStreams {
|
||||
t.openStreams.Add(-1)
|
||||
return nil, common.TooManyOpenStreams
|
||||
}
|
||||
|
||||
pipe1, pipe2 := net.Pipe()
|
||||
var connId uint16
|
||||
for {
|
||||
connId = uint16(fastrand.Intn(0xFFFF))
|
||||
_, loaded := t.udpInputMap.LoadOrStore(connId, pipe1)
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
}
|
||||
pc := &quicStreamPacketConn{
|
||||
connId: connId,
|
||||
quicConn: quicConn,
|
||||
inputConn: N.NewBufferedConn(pipe2),
|
||||
udpRelayMode: t.UdpRelayMode,
|
||||
maxUdpRelayPacketSize: t.MaxUdpRelayPacketSize,
|
||||
deferQuicConnFn: t.deferQuicConn,
|
||||
closeDeferFn: func() {
|
||||
t.udpInputMap.Delete(connId)
|
||||
time.AfterFunc(C.DefaultUDPTimeout, func() {
|
||||
openStreams := t.openStreams.Add(-1)
|
||||
if openStreams == 0 && t.closed.Load() {
|
||||
t.forceClose(quicConn, common.ClientClosed)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
|
||||
}
|
||||
|
||||
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return N.NewRefConn(conn, t), err
|
||||
}
|
||||
|
||||
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return N.NewRefPacketConn(pc, t), nil
|
||||
}
|
||||
|
||||
func (t *Client) forceClose() {
|
||||
t.clientImpl.forceClose(nil, common.ClientClosed)
|
||||
}
|
||||
|
||||
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
|
||||
ci := &clientImpl{
|
||||
ClientOption: clientOption,
|
||||
udp: udp,
|
||||
dialerRef: dialerRef,
|
||||
}
|
||||
c := &Client{ci}
|
||||
runtime.SetFinalizer(c, closeClient)
|
||||
log.Debugln("New TuicV5 Client at %p", c)
|
||||
return c
|
||||
}
|
||||
|
||||
func closeClient(client *Client) {
|
||||
log.Debugln("Close TuicV5 Client at %p", client)
|
||||
client.forceClose()
|
||||
}
|
80
transport/tuic/v5/frag.go
Normal file
80
transport/tuic/v5/frag.go
Normal file
@ -0,0 +1,80 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, fragSize int) (err error) {
|
||||
fullPayload := packet.DATA
|
||||
off := 0
|
||||
fragID := uint8(0)
|
||||
fragCount := uint8((len(fullPayload) + fragSize - 1) / fragSize) // round up
|
||||
packet.FRAG_TOTAL = fragCount
|
||||
for off < len(fullPayload) {
|
||||
payloadSize := len(fullPayload) - off
|
||||
if payloadSize > fragSize {
|
||||
payloadSize = fragSize
|
||||
}
|
||||
frag := packet
|
||||
frag.FRAG_ID = fragID
|
||||
frag.SIZE = uint16(payloadSize)
|
||||
frag.DATA = fullPayload[off : off+payloadSize]
|
||||
off += payloadSize
|
||||
fragID++
|
||||
buf.Reset()
|
||||
err = frag.WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := buf.Bytes()
|
||||
err = quicConn.SendMessage(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
packet.ADDR.TYPE = AtypNone // avoid "fragment 2/2: address in non-first fragment"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type deFragger struct {
|
||||
pkgID uint16
|
||||
frags []*Packet
|
||||
count uint8
|
||||
}
|
||||
|
||||
func (d *deFragger) Feed(m Packet) *Packet {
|
||||
if m.FRAG_TOTAL <= 1 {
|
||||
return &m
|
||||
}
|
||||
if m.FRAG_ID >= m.FRAG_TOTAL {
|
||||
// wtf is this?
|
||||
return nil
|
||||
}
|
||||
if d.count == 0 || m.PKT_ID != d.pkgID {
|
||||
// new message, clear previous state
|
||||
d.pkgID = m.PKT_ID
|
||||
d.frags = make([]*Packet, m.FRAG_TOTAL)
|
||||
d.count = 1
|
||||
d.frags[m.FRAG_ID] = &m
|
||||
} else if d.frags[m.FRAG_ID] == nil {
|
||||
d.frags[m.FRAG_ID] = &m
|
||||
d.count++
|
||||
if int(d.count) == len(d.frags) {
|
||||
// all fragments received, assemble
|
||||
var data []byte
|
||||
for _, frag := range d.frags {
|
||||
data = append(data, frag.DATA...)
|
||||
}
|
||||
p := d.frags[0] // recover from first fragment
|
||||
p.SIZE = uint16(len(data))
|
||||
p.DATA = data
|
||||
p.FRAG_ID = 0
|
||||
p.FRAG_TOTAL = 1
|
||||
d.count = 0
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
209
transport/tuic/v5/packet.go
Normal file
209
transport/tuic/v5/packet.go
Normal file
@ -0,0 +1,209 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
type quicStreamPacketConn struct {
|
||||
connId uint16
|
||||
quicConn quic.Connection
|
||||
inputConn *N.BufferedConn
|
||||
|
||||
udpRelayMode common.UdpRelayMode
|
||||
maxUdpRelayPacketSize int
|
||||
|
||||
deferQuicConnFn func(quicConn quic.Connection, err error)
|
||||
closeDeferFn func()
|
||||
writeClosed *atomic.Bool
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
closed bool
|
||||
|
||||
deFragger
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) Close() error {
|
||||
q.closeOnce.Do(func() {
|
||||
q.closed = true
|
||||
q.closeErr = q.close()
|
||||
})
|
||||
return q.closeErr
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) close() (err error) {
|
||||
if q.closeDeferFn != nil {
|
||||
defer q.closeDeferFn()
|
||||
}
|
||||
if q.deferQuicConnFn != nil {
|
||||
defer func() {
|
||||
q.deferQuicConnFn(q.quicConn, err)
|
||||
}()
|
||||
}
|
||||
if q.inputConn != nil {
|
||||
_ = q.inputConn.Close()
|
||||
q.inputConn = nil
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
err = NewDissociate(q.connId).WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var stream quic.SendStream
|
||||
stream, err = q.quicConn.OpenUniStream()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = buf.WriteTo(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = stream.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) SetDeadline(t time.Time) error {
|
||||
//TODO implement me
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) SetReadDeadline(t time.Time) error {
|
||||
if q.inputConn != nil {
|
||||
return q.inputConn.SetReadDeadline(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
//TODO implement me
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
if q.inputConn != nil {
|
||||
for {
|
||||
var packet Packet
|
||||
packet, err = ReadPacket(q.inputConn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if packetPtr := q.deFragger.Feed(packet); packetPtr != nil {
|
||||
n = copy(p, packet.DATA)
|
||||
addr = packetPtr.ADDR.UDPAddr()
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = net.ErrClosed
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
|
||||
if q.inputConn != nil {
|
||||
for {
|
||||
var packet Packet
|
||||
packet, err = ReadPacket(q.inputConn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if packetPtr := q.deFragger.Feed(packet); packetPtr != nil {
|
||||
data = packetPtr.DATA
|
||||
addr = packetPtr.ADDR.UDPAddr()
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = net.ErrClosed
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if len(p) > 0xffff { // uint16 max
|
||||
return 0, quic.ErrMessageTooLarge(0xffff)
|
||||
}
|
||||
if q.closed {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
if q.writeClosed != nil && q.writeClosed.Load() {
|
||||
_ = q.Close()
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
if q.deferQuicConnFn != nil {
|
||||
defer func() {
|
||||
q.deferQuicConnFn(q.quicConn, err)
|
||||
}()
|
||||
}
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
address, err := NewAddressNetAddr(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pktId := uint16(fastrand.Uint32())
|
||||
packet := NewPacket(q.connId, pktId, 1, 0, uint16(len(p)), address, p)
|
||||
switch q.udpRelayMode {
|
||||
case common.QUIC:
|
||||
err = packet.WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var stream quic.SendStream
|
||||
stream, err = q.quicConn.OpenUniStream()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
_, err = buf.WriteTo(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default: // native
|
||||
if len(p) > q.maxUdpRelayPacketSize {
|
||||
err = fragWriteNative(q.quicConn, packet, buf, q.maxUdpRelayPacketSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = packet.WriteTo(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := buf.Bytes()
|
||||
err = q.quicConn.SendMessage(data)
|
||||
|
||||
var tooLarge quic.ErrMessageTooLarge
|
||||
if errors.As(err, &tooLarge) {
|
||||
err = fragWriteNative(q.quicConn, packet, buf, int(tooLarge)-PacketOverHead)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
n = len(p)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (q *quicStreamPacketConn) LocalAddr() net.Addr {
|
||||
return q.quicConn.LocalAddr()
|
||||
}
|
||||
|
||||
var _ net.PacketConn = (*quicStreamPacketConn)(nil)
|
585
transport/tuic/v5/protocol.go
Normal file
585
transport/tuic/v5/protocol.go
Normal file
@ -0,0 +1,585 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type BufferedReader interface {
|
||||
io.Reader
|
||||
io.ByteReader
|
||||
}
|
||||
|
||||
type BufferedWriter interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
}
|
||||
|
||||
type CommandType byte
|
||||
|
||||
const (
|
||||
AuthenticateType = CommandType(0x00)
|
||||
ConnectType = CommandType(0x01)
|
||||
PacketType = CommandType(0x02)
|
||||
DissociateType = CommandType(0x03)
|
||||
HeartbeatType = CommandType(0x04)
|
||||
)
|
||||
|
||||
const VER byte = 0x05
|
||||
|
||||
func (c CommandType) String() string {
|
||||
switch c {
|
||||
case AuthenticateType:
|
||||
return "Authenticate"
|
||||
case ConnectType:
|
||||
return "Connect"
|
||||
case PacketType:
|
||||
return "Packet"
|
||||
case DissociateType:
|
||||
return "Dissociate"
|
||||
case HeartbeatType:
|
||||
return "Heartbeat"
|
||||
default:
|
||||
return fmt.Sprintf("UnknowCommand: %#x", byte(c))
|
||||
}
|
||||
}
|
||||
|
||||
func (c CommandType) BytesLen() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
type CommandHead struct {
|
||||
VER byte
|
||||
TYPE CommandType
|
||||
}
|
||||
|
||||
func NewCommandHead(TYPE CommandType) CommandHead {
|
||||
return CommandHead{
|
||||
VER: VER,
|
||||
TYPE: TYPE,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadCommandHead(reader BufferedReader) (c CommandHead, err error) {
|
||||
c.VER, err = reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
TYPE, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.TYPE = CommandType(TYPE)
|
||||
return
|
||||
}
|
||||
|
||||
func (c CommandHead) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = writer.WriteByte(c.VER)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = writer.WriteByte(byte(c.TYPE))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c CommandHead) BytesLen() int {
|
||||
return 1 + c.TYPE.BytesLen()
|
||||
}
|
||||
|
||||
type Authenticate struct {
|
||||
CommandHead
|
||||
UUID [16]byte
|
||||
TOKEN [32]byte
|
||||
}
|
||||
|
||||
func NewAuthenticate(UUID [16]byte, TOKEN [32]byte) Authenticate {
|
||||
return Authenticate{
|
||||
CommandHead: NewCommandHead(AuthenticateType),
|
||||
UUID: UUID,
|
||||
TOKEN: TOKEN,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) {
|
||||
c.CommandHead = head
|
||||
if c.CommandHead.TYPE != AuthenticateType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
}
|
||||
_, err = io.ReadFull(reader, c.UUID[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = io.ReadFull(reader, c.TOKEN[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAuthenticate(reader BufferedReader) (c Authenticate, err error) {
|
||||
head, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReadAuthenticateWithHead(head, reader)
|
||||
}
|
||||
|
||||
func GenToken(state quic.ConnectionState, uuid [16]byte, password string) (token [32]byte, err error) {
|
||||
var tokenBytes []byte
|
||||
tokenBytes, err = state.TLS.ExportKeyingMaterial(utils.StringFromImmutableBytes(uuid[:]), utils.ImmutableBytesFromString(password), 32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
copy(token[:], tokenBytes)
|
||||
return
|
||||
}
|
||||
|
||||
func (c Authenticate) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = c.CommandHead.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = writer.Write(c.UUID[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = writer.Write(c.TOKEN[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Authenticate) BytesLen() int {
|
||||
return c.CommandHead.BytesLen() + 16 + 32
|
||||
}
|
||||
|
||||
type Connect struct {
|
||||
CommandHead
|
||||
ADDR Address
|
||||
}
|
||||
|
||||
func NewConnect(ADDR Address) Connect {
|
||||
return Connect{
|
||||
CommandHead: NewCommandHead(ConnectType),
|
||||
ADDR: ADDR,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
|
||||
c.CommandHead = head
|
||||
if c.CommandHead.TYPE != ConnectType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
}
|
||||
c.ADDR, err = ReadAddress(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadConnect(reader BufferedReader) (c Connect, err error) {
|
||||
head, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReadConnectWithHead(head, reader)
|
||||
}
|
||||
|
||||
func (c Connect) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = c.CommandHead.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.ADDR.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Connect) BytesLen() int {
|
||||
return c.CommandHead.BytesLen() + c.ADDR.BytesLen()
|
||||
}
|
||||
|
||||
type Packet struct {
|
||||
CommandHead
|
||||
ASSOC_ID uint16
|
||||
PKT_ID uint16
|
||||
FRAG_TOTAL uint8
|
||||
FRAG_ID uint8
|
||||
SIZE uint16
|
||||
ADDR Address
|
||||
DATA []byte
|
||||
}
|
||||
|
||||
func NewPacket(ASSOC_ID uint16, PKT_ID uint16, FRGA_TOTAL uint8, FRAG_ID uint8, SIZE uint16, ADDR Address, DATA []byte) Packet {
|
||||
return Packet{
|
||||
CommandHead: NewCommandHead(PacketType),
|
||||
ASSOC_ID: ASSOC_ID,
|
||||
PKT_ID: PKT_ID,
|
||||
FRAG_ID: FRAG_ID,
|
||||
FRAG_TOTAL: FRGA_TOTAL,
|
||||
SIZE: SIZE,
|
||||
ADDR: ADDR,
|
||||
DATA: DATA,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) {
|
||||
c.CommandHead = head
|
||||
if c.CommandHead.TYPE != PacketType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.ASSOC_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.PKT_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.FRAG_TOTAL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.FRAG_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.SIZE)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.ADDR, err = ReadAddress(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.DATA = make([]byte, c.SIZE)
|
||||
_, err = io.ReadFull(reader, c.DATA)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadPacket(reader BufferedReader) (c Packet, err error) {
|
||||
head, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReadPacketWithHead(head, reader)
|
||||
}
|
||||
|
||||
func (c Packet) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = c.CommandHead.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.ASSOC_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.PKT_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.FRAG_TOTAL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.FRAG_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.SIZE)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.ADDR.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = writer.Write(c.DATA)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Packet) BytesLen() int {
|
||||
return c.CommandHead.BytesLen() + 4 + 2 + c.ADDR.BytesLen() + len(c.DATA)
|
||||
}
|
||||
|
||||
var PacketOverHead = NewPacket(0, 0, 0, 0, 0, NewAddressAddrPort(netip.AddrPortFrom(netip.IPv6Unspecified(), 0)), nil).BytesLen()
|
||||
|
||||
type Dissociate struct {
|
||||
CommandHead
|
||||
ASSOC_ID uint16
|
||||
}
|
||||
|
||||
func NewDissociate(ASSOC_ID uint16) Dissociate {
|
||||
return Dissociate{
|
||||
CommandHead: NewCommandHead(DissociateType),
|
||||
ASSOC_ID: ASSOC_ID,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
|
||||
c.CommandHead = head
|
||||
if c.CommandHead.TYPE != DissociateType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.ASSOC_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadDissociate(reader BufferedReader) (c Dissociate, err error) {
|
||||
head, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReadDissociateWithHead(head, reader)
|
||||
}
|
||||
|
||||
func (c Dissociate) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = c.CommandHead.WriteTo(writer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.ASSOC_ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Dissociate) BytesLen() int {
|
||||
return c.CommandHead.BytesLen() + 4
|
||||
}
|
||||
|
||||
type Heartbeat struct {
|
||||
CommandHead
|
||||
}
|
||||
|
||||
func NewHeartbeat() Heartbeat {
|
||||
return Heartbeat{
|
||||
CommandHead: NewCommandHead(HeartbeatType),
|
||||
}
|
||||
}
|
||||
|
||||
func ReadHeartbeatWithHead(head CommandHead, reader BufferedReader) (c Heartbeat, err error) {
|
||||
c.CommandHead = head
|
||||
if c.CommandHead.TYPE != HeartbeatType {
|
||||
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadHeartbeat(reader BufferedReader) (c Heartbeat, err error) {
|
||||
head, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ReadHeartbeatWithHead(head, reader)
|
||||
}
|
||||
|
||||
// Addr types
|
||||
const (
|
||||
AtypDomainName byte = 0
|
||||
AtypIPv4 byte = 1
|
||||
AtypIPv6 byte = 2
|
||||
AtypNone byte = 255 // Address type None is used in Packet commands that is not the first fragment of a UDP packet.
|
||||
)
|
||||
|
||||
type Address struct {
|
||||
TYPE byte
|
||||
ADDR []byte
|
||||
PORT uint16
|
||||
}
|
||||
|
||||
func NewAddress(metadata *C.Metadata) Address {
|
||||
var addrType byte
|
||||
var addr []byte
|
||||
switch metadata.AddrType() {
|
||||
case socks5.AtypIPv4:
|
||||
addrType = AtypIPv4
|
||||
addr = metadata.DstIP.AsSlice()
|
||||
case socks5.AtypIPv6:
|
||||
addrType = AtypIPv6
|
||||
addr = metadata.DstIP.AsSlice()
|
||||
case socks5.AtypDomainName:
|
||||
addrType = AtypDomainName
|
||||
addr = make([]byte, len(metadata.Host)+1)
|
||||
addr[0] = byte(len(metadata.Host))
|
||||
copy(addr[1:], metadata.Host)
|
||||
}
|
||||
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
|
||||
return Address{
|
||||
TYPE: addrType,
|
||||
ADDR: addr,
|
||||
PORT: uint16(port),
|
||||
}
|
||||
}
|
||||
|
||||
func NewAddressNetAddr(addr net.Addr) (Address, error) {
|
||||
if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok {
|
||||
if addrPort := addr.AddrPort(); addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName
|
||||
return NewAddressAddrPort(addrPort), nil
|
||||
}
|
||||
}
|
||||
addrStr := addr.String()
|
||||
if addrPort, err := netip.ParseAddrPort(addrStr); err == nil {
|
||||
return NewAddressAddrPort(addrPort), nil
|
||||
}
|
||||
metadata := &C.Metadata{}
|
||||
if err := metadata.SetRemoteAddress(addrStr); err != nil {
|
||||
return Address{}, err
|
||||
}
|
||||
return NewAddress(metadata), nil
|
||||
}
|
||||
|
||||
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
|
||||
var addrType byte
|
||||
port := addrPort.Port()
|
||||
addr := addrPort.Addr().Unmap()
|
||||
if addr.Is4() {
|
||||
addrType = AtypIPv4
|
||||
} else {
|
||||
addrType = AtypIPv6
|
||||
}
|
||||
return Address{
|
||||
TYPE: addrType,
|
||||
ADDR: addr.AsSlice(),
|
||||
PORT: port,
|
||||
}
|
||||
}
|
||||
|
||||
func ReadAddress(reader BufferedReader) (c Address, err error) {
|
||||
c.TYPE, err = reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch c.TYPE {
|
||||
case AtypIPv4:
|
||||
c.ADDR = make([]byte, net.IPv4len)
|
||||
_, err = io.ReadFull(reader, c.ADDR)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case AtypIPv6:
|
||||
c.ADDR = make([]byte, net.IPv6len)
|
||||
_, err = io.ReadFull(reader, c.ADDR)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case AtypDomainName:
|
||||
var addrLen byte
|
||||
addrLen, err = reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.ADDR = make([]byte, addrLen+1)
|
||||
c.ADDR[0] = addrLen
|
||||
_, err = io.ReadFull(reader, c.ADDR[1:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if c.TYPE == AtypNone {
|
||||
return
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &c.PORT)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Address) WriteTo(writer BufferedWriter) (err error) {
|
||||
err = writer.WriteByte(c.TYPE)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.TYPE == AtypNone {
|
||||
return
|
||||
}
|
||||
_, err = writer.Write(c.ADDR[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, c.PORT)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c Address) String() string {
|
||||
switch c.TYPE {
|
||||
case AtypDomainName:
|
||||
return net.JoinHostPort(string(c.ADDR[1:]), strconv.Itoa(int(c.PORT)))
|
||||
default:
|
||||
addr, _ := netip.AddrFromSlice(c.ADDR)
|
||||
addrPort := netip.AddrPortFrom(addr, c.PORT)
|
||||
return addrPort.String()
|
||||
}
|
||||
}
|
||||
|
||||
func (c Address) SocksAddr() socks5.Addr {
|
||||
addr := make([]byte, 1+len(c.ADDR)+2)
|
||||
switch c.TYPE {
|
||||
case AtypIPv4:
|
||||
addr[0] = socks5.AtypIPv4
|
||||
case AtypIPv6:
|
||||
addr[0] = socks5.AtypIPv6
|
||||
case AtypDomainName:
|
||||
addr[0] = socks5.AtypDomainName
|
||||
}
|
||||
copy(addr[1:], c.ADDR)
|
||||
binary.BigEndian.PutUint16(addr[len(addr)-2:], c.PORT)
|
||||
return addr
|
||||
}
|
||||
|
||||
func (c Address) UDPAddr() *net.UDPAddr {
|
||||
return &net.UDPAddr{
|
||||
IP: c.ADDR,
|
||||
Port: int(c.PORT),
|
||||
Zone: "",
|
||||
}
|
||||
}
|
||||
|
||||
func (c Address) BytesLen() int {
|
||||
return 1 + len(c.ADDR) + 2
|
||||
}
|
||||
|
||||
const (
|
||||
ProtocolError = quic.ApplicationErrorCode(0xfffffff0)
|
||||
AuthenticationFailed = quic.ApplicationErrorCode(0xfffffff1)
|
||||
AuthenticationTimeout = quic.ApplicationErrorCode(0xfffffff2)
|
||||
BadCommand = quic.ApplicationErrorCode(0xfffffff3)
|
||||
)
|
229
transport/tuic/v5/server.go
Normal file
229
transport/tuic/v5/server.go
Normal file
@ -0,0 +1,229 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error
|
||||
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error
|
||||
|
||||
Users map[[16]byte]string
|
||||
MaxUdpRelayPacketSize int
|
||||
}
|
||||
|
||||
func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid uuid.UUID) common.ServerHandler {
|
||||
return &serverHandler{
|
||||
ServerOption: option,
|
||||
quicConn: quicConn,
|
||||
uuid: uuid,
|
||||
authCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type serverHandler struct {
|
||||
*ServerOption
|
||||
quicConn quic.EarlyConnection
|
||||
uuid uuid.UUID
|
||||
|
||||
authCh chan struct{}
|
||||
authOk atomic.Bool
|
||||
authUUID atomic.TypedValue[string]
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
}
|
||||
|
||||
func (s *serverHandler) AuthOk() bool {
|
||||
return s.authOk.Load()
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleTimeout() {
|
||||
s.authOnce.Do(func() {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||
s.authOk.Store(false)
|
||||
close(s.authCh)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleMessage(message []byte) (err error) {
|
||||
reader := bytes.NewBuffer(message)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.NATIVE)
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serverHandler) parsePacket(packet Packet, udpRelayMode common.UdpRelayMode) (err error) {
|
||||
<-s.authCh
|
||||
if !s.authOk.Load() {
|
||||
return
|
||||
}
|
||||
var assocId uint16
|
||||
|
||||
assocId = packet.ASSOC_ID
|
||||
|
||||
v, _ := s.udpInputMap.LoadOrStore(assocId, &serverUDPInput{})
|
||||
input := v.(*serverUDPInput)
|
||||
if input.writeClosed.Load() {
|
||||
return nil
|
||||
}
|
||||
packetPtr := input.Feed(packet)
|
||||
if packetPtr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pc := &quicStreamPacketConn{
|
||||
connId: assocId,
|
||||
quicConn: s.quicConn,
|
||||
inputConn: nil,
|
||||
udpRelayMode: udpRelayMode,
|
||||
maxUdpRelayPacketSize: s.MaxUdpRelayPacketSize,
|
||||
deferQuicConnFn: nil,
|
||||
closeDeferFn: nil,
|
||||
writeClosed: &input.writeClosed,
|
||||
}
|
||||
|
||||
return s.HandleUdpFn(packetPtr.ADDR.SocksAddr(), &serverUDPPacket{
|
||||
pc: pc,
|
||||
packet: packetPtr,
|
||||
rAddr: N.NewCustomAddr("tuic", fmt.Sprintf("tuic-%s-%d", s.uuid, assocId), s.quicConn.RemoteAddr()), // for tunnel's handleUDPConn
|
||||
}, inbound.WithInUser(s.authUUID.Load()))
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleStream(conn *N.BufferedConn) (err error) {
|
||||
connect, err := ReadConnect(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-s.authCh
|
||||
if !s.authOk.Load() {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
err = s.HandleTcpFn(conn, connect.ADDR.SocksAddr(), inbound.WithInUser(s.authUUID.Load()))
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) {
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case AuthenticateType:
|
||||
var authenticate Authenticate
|
||||
authenticate, err = ReadAuthenticateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
authOk := false
|
||||
var authUUID uuid.UUID
|
||||
var token [32]byte
|
||||
if password, ok := s.Users[authenticate.UUID]; ok {
|
||||
token, err = GenToken(s.quicConn.ConnectionState(), authenticate.UUID, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if token == authenticate.TOKEN {
|
||||
authOk = true
|
||||
authUUID = authenticate.UUID
|
||||
}
|
||||
}
|
||||
s.authOnce.Do(func() {
|
||||
if !authOk {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||
}
|
||||
s.authOk.Store(authOk)
|
||||
s.authUUID.Store(authUUID.String())
|
||||
close(s.authCh)
|
||||
})
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.QUIC)
|
||||
case DissociateType:
|
||||
var disassociate Dissociate
|
||||
disassociate, err = ReadDissociateWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
input := v.(*serverUDPInput)
|
||||
input.writeClosed.Store(true)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type serverUDPInput struct {
|
||||
writeClosed atomic.Bool
|
||||
deFragger
|
||||
}
|
||||
|
||||
type serverUDPPacket struct {
|
||||
pc *quicStreamPacketConn
|
||||
packet *Packet
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) InAddr() net.Addr {
|
||||
return s.pc.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) LocalAddr() net.Addr {
|
||||
return s.rAddr
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Data() []byte {
|
||||
return s.packet.DATA
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return s.pc.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
func (s *serverUDPPacket) Drop() {
|
||||
s.packet.DATA = nil
|
||||
}
|
||||
|
||||
var _ C.UDPPacket = (*serverUDPPacket)(nil)
|
||||
var _ C.UDPPacketInAddr = (*serverUDPPacket)(nil)
|
@ -203,24 +203,27 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) {
|
||||
needHandshake: true,
|
||||
}
|
||||
|
||||
if !dst.UDP && client.Addons != nil {
|
||||
if client.Addons != nil {
|
||||
switch client.Addons.Flow {
|
||||
case XRO, XRD, XRS:
|
||||
if xtlsConn, ok := conn.(*xtls.Conn); ok {
|
||||
xtlsConn.RPRX = true
|
||||
xtlsConn.SHOW = client.XTLSShow
|
||||
xtlsConn.MARK = "XTLS"
|
||||
if client.Addons.Flow == XRS {
|
||||
client.Addons.Flow = XRD
|
||||
}
|
||||
if !dst.UDP {
|
||||
if xtlsConn, ok := conn.(*xtls.Conn); ok {
|
||||
xtlsConn.RPRX = true
|
||||
xtlsConn.SHOW = client.XTLSShow
|
||||
xtlsConn.MARK = "XTLS"
|
||||
if client.Addons.Flow == XRS {
|
||||
client.Addons.Flow = XRD
|
||||
}
|
||||
|
||||
if client.Addons.Flow == XRD {
|
||||
xtlsConn.DirectMode = true
|
||||
if client.Addons.Flow == XRD {
|
||||
xtlsConn.DirectMode = true
|
||||
}
|
||||
c.addons = client.Addons
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||
}
|
||||
c.addons = client.Addons
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||
}
|
||||
|
||||
case XRV:
|
||||
visionConn, err := vision.NewConn(c, c.id)
|
||||
if err != nil {
|
||||
|
@ -26,7 +26,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUDPToLocal(packet C.UDPPacket, pc N.EnhancePacketConn, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) {
|
||||
func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) {
|
||||
defer func() {
|
||||
_ = pc.Close()
|
||||
closeAllLocalCoon(key)
|
||||
@ -59,7 +59,7 @@ func handleUDPToLocal(packet C.UDPPacket, pc N.EnhancePacketConn, key string, oA
|
||||
log.Warnln("server return a [%T](%s) which isn't a *net.UDPAddr, force replace to (%s), this may be caused by a wrongly implemented server", from, from, oAddrPort)
|
||||
}
|
||||
|
||||
_, err = packet.WriteBack(data, fromUDPAddr)
|
||||
_, err = writeBack.WriteBack(data, fromUDPAddr)
|
||||
if put != nil {
|
||||
put()
|
||||
}
|
||||
|
@ -38,14 +38,27 @@ type Manager struct {
|
||||
memory uint64
|
||||
}
|
||||
|
||||
func (m *Manager) Join(c tracker) {
|
||||
func (m *Manager) Join(c Tracker) {
|
||||
m.connections.Store(c.ID(), c)
|
||||
}
|
||||
|
||||
func (m *Manager) Leave(c tracker) {
|
||||
func (m *Manager) Leave(c Tracker) {
|
||||
m.connections.Delete(c.ID())
|
||||
}
|
||||
|
||||
func (m *Manager) Get(id string) (c Tracker) {
|
||||
if value, ok := m.connections.Load(id); ok {
|
||||
c = value.(Tracker)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Manager) Range(f func(c Tracker) bool) {
|
||||
m.connections.Range(func(key, value any) bool {
|
||||
return f(value.(Tracker))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) PushUploaded(size int64) {
|
||||
m.uploadTemp.Add(size)
|
||||
m.uploadTotal.Add(size)
|
||||
@ -66,9 +79,9 @@ func (m *Manager) Memory() uint64 {
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() *Snapshot {
|
||||
connections := []tracker{}
|
||||
m.connections.Range(func(key, value any) bool {
|
||||
connections = append(connections, value.(tracker))
|
||||
var connections []*TrackerInfo
|
||||
m.Range(func(c Tracker) bool {
|
||||
connections = append(connections, c.Info())
|
||||
return true
|
||||
})
|
||||
return &Snapshot{
|
||||
@ -108,8 +121,8 @@ func (m *Manager) handle() {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
DownloadTotal int64 `json:"downloadTotal"`
|
||||
UploadTotal int64 `json:"uploadTotal"`
|
||||
Connections []tracker `json:"connections"`
|
||||
Memory uint64 `json:"memory"`
|
||||
DownloadTotal int64 `json:"downloadTotal"`
|
||||
UploadTotal int64 `json:"uploadTotal"`
|
||||
Connections []*TrackerInfo `json:"connections"`
|
||||
Memory uint64 `json:"memory"`
|
||||
}
|
||||
|
@ -15,13 +15,14 @@ import (
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type tracker interface {
|
||||
type Tracker interface {
|
||||
ID() string
|
||||
Close() error
|
||||
Info() *TrackerInfo
|
||||
C.Connection
|
||||
}
|
||||
|
||||
type trackerInfo struct {
|
||||
type TrackerInfo struct {
|
||||
UUID uuid.UUID `json:"id"`
|
||||
Metadata *C.Metadata `json:"metadata"`
|
||||
UploadTotal *atomic.Int64 `json:"upload"`
|
||||
@ -34,7 +35,7 @@ type trackerInfo struct {
|
||||
|
||||
type tcpTracker struct {
|
||||
C.Conn `json:"-"`
|
||||
*trackerInfo
|
||||
*TrackerInfo
|
||||
manager *Manager
|
||||
|
||||
pushToManager bool `json:"-"`
|
||||
@ -44,6 +45,10 @@ func (tt *tcpTracker) ID() string {
|
||||
return tt.UUID.String()
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Info() *TrackerInfo {
|
||||
return tt.TrackerInfo
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Read(b []byte) (int, error) {
|
||||
n, err := tt.Conn.Read(b)
|
||||
download := int64(n)
|
||||
@ -134,7 +139,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
||||
t := &tcpTracker{
|
||||
Conn: conn,
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
TrackerInfo: &TrackerInfo{
|
||||
UUID: utils.NewUUIDV4(),
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
@ -156,8 +161,8 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
t.trackerInfo.Rule = rule.RuleType().String()
|
||||
t.trackerInfo.RulePayload = rule.Payload()
|
||||
t.TrackerInfo.Rule = rule.RuleType().String()
|
||||
t.TrackerInfo.RulePayload = rule.Payload()
|
||||
}
|
||||
|
||||
manager.Join(t)
|
||||
@ -166,7 +171,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
||||
|
||||
type udpTracker struct {
|
||||
C.PacketConn `json:"-"`
|
||||
*trackerInfo
|
||||
*TrackerInfo
|
||||
manager *Manager
|
||||
|
||||
pushToManager bool `json:"-"`
|
||||
@ -176,6 +181,10 @@ func (ut *udpTracker) ID() string {
|
||||
return ut.UUID.String()
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Info() *TrackerInfo {
|
||||
return ut.TrackerInfo
|
||||
}
|
||||
|
||||
func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
n, addr, err := ut.PacketConn.ReadFrom(b)
|
||||
download := int64(n)
|
||||
@ -221,7 +230,7 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
||||
ut := &udpTracker{
|
||||
PacketConn: conn,
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
TrackerInfo: &TrackerInfo{
|
||||
UUID: utils.NewUUIDV4(),
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
@ -243,8 +252,8 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
ut.trackerInfo.Rule = rule.RuleType().String()
|
||||
ut.trackerInfo.RulePayload = rule.Payload()
|
||||
ut.TrackerInfo.Rule = rule.RuleType().String()
|
||||
ut.TrackerInfo.RulePayload = rule.Payload()
|
||||
}
|
||||
|
||||
manager.Join(ut)
|
||||
|
@ -303,8 +303,11 @@ func handleUDPConn(packet C.PacketAdapter) {
|
||||
key := packet.LocalAddr().String()
|
||||
|
||||
handle := func() bool {
|
||||
pc := natTable.Get(key)
|
||||
pc, proxy := natTable.Get(key)
|
||||
if pc != nil {
|
||||
if proxy != nil {
|
||||
proxy.UpdateWriteBack(packet)
|
||||
}
|
||||
_ = handleUDPToRemote(packet, pc, metadata)
|
||||
return true
|
||||
}
|
||||
@ -384,9 +387,10 @@ func handleUDPConn(packet C.PacketAdapter) {
|
||||
}
|
||||
|
||||
oAddrPort := metadata.AddrPort()
|
||||
natTable.Set(key, pc)
|
||||
writeBackProxy := nat.NewWriteBackProxy(packet)
|
||||
natTable.Set(key, pc, writeBackProxy)
|
||||
|
||||
go handleUDPToLocal(packet, pc, key, oAddrPort, fAddr)
|
||||
go handleUDPToLocal(writeBackProxy, pc, key, oAddrPort, fAddr)
|
||||
|
||||
handle()
|
||||
}()
|
||||
@ -535,8 +539,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
configMux.RLock()
|
||||
defer configMux.RUnlock()
|
||||
var (
|
||||
resolved bool
|
||||
processFound bool
|
||||
resolved bool
|
||||
attemptProcessLookup = true
|
||||
)
|
||||
|
||||
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
||||
@ -560,8 +564,9 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
}()
|
||||
}
|
||||
|
||||
if !findProcessMode.Off() && !processFound && (findProcessMode.Always() || rule.ShouldFindProcess()) {
|
||||
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||
if attemptProcessLookup && !findProcessMode.Off() && (findProcessMode.Always() || rule.ShouldFindProcess()) {
|
||||
attemptProcessLookup = false
|
||||
srcPort, _ := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
||||
if err != nil {
|
||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||
@ -569,7 +574,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
metadata.Process = filepath.Base(path)
|
||||
metadata.ProcessPath = path
|
||||
metadata.Uid = uid
|
||||
processFound = true
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user