Compare commits

..

44 Commits

Author SHA1 Message Date
Skyxim
47ad8e08be chore: Random only if the certificate and private-key are empty 2023-06-03 10:02:31 +00:00
H1JK
e1af4ddda3 chore: Reject packet conn implement wait read 2023-06-03 10:01:50 +00:00
Larvan2
58e05c42c9 fix: handle manually select in url-test 2023-06-03 09:55:29 +00:00
Larvan2
880cc90e10 Merge branch 'Alpha' into Meta 2023-06-03 09:54:25 +00:00
Skyxim
a4334e1d52 fix: wildcard matching problem (#536) 2023-04-29 01:10:55 +08:00
Larvan2
3ba94842cc chore: update genReleaseNote.sh 2023-04-28 07:29:58 +00:00
Larvan2
a266589faf Merge branch 'Alpha' into Meta 2023-04-28 07:05:21 +00:00
Skyxim
d9319ec09a chore: update bug_report 2023-04-27 12:19:59 +00:00
Larvan2
070f8f8949 Merge branch 'Alpha' into Meta 2023-04-26 14:14:46 +00:00
Larvan2
bf3c6a044c chore: update templates 2023-04-14 05:26:44 +00:00
Larvan2
d6d2d90502 add issue templates 2023-04-04 06:45:21 +00:00
Larvan2
a1d0f4c6ee chore: rename delete.yml 2023-03-31 06:34:23 +00:00
Larvan2
d569d8186d chore: add genReleaseNote.sh 2023-03-30 16:43:05 +00:00
Larvan2
9b7aab1fc7 chore: rename delete.yml 2023-03-30 16:04:09 +00:00
Larvan2
3c717097cb Merge branch 'Alpha' into Meta 2023-03-30 16:03:47 +00:00
wwqgtxx
8293b7fdae Merge branch 'Beta' into Meta
# Conflicts:
#	.github/workflows/docker.yaml
#	.github/workflows/prerelease.yml
2023-02-19 01:25:34 +08:00
wwqgtxx
0ba415866e Merge branch 'Alpha' into Beta 2023-02-19 01:25:01 +08:00
Larvan2
53b41ca166 Chore: Add action for deleting old workflow 2023-01-30 18:17:22 +08:00
metacubex
8a75f78e63 chore: adjust Dockerfile 2023-01-12 02:24:12 +08:00
metacubex
d9692c6366 Merge branch 'Beta' into Meta 2023-01-12 02:15:14 +08:00
metacubex
f4b0062dfc Merge branch 'Alpha' into Beta 2023-01-12 02:14:49 +08:00
metacubex
b9ffc82e53 Merge branch 'Beta' into Meta 2023-01-12 01:33:56 +08:00
metacubex
78aaea6a45 Merge branch 'Alpha' into Beta 2023-01-12 01:33:16 +08:00
cubemaze
3645fbf161 Merge pull request #327 from Rasphino/Meta
Update flake.nix hash
2023-01-08 00:29:29 +08:00
Rasphino
a1d0f22132 fix: update flake.nix hash 2023-01-07 23:38:32 +08:00
metacubex
fa73b0f4bf Merge remote-tracking branch 'origin/Beta' into Meta 2023-01-01 19:41:36 +08:00
metacubex
3b76a8b839 Merge remote-tracking branch 'origin/Alpha' into Beta 2023-01-01 19:40:36 +08:00
cubemaze
667f42dcdc Merge pull request #282 from tdjnodj/Meta
Update README.md
2022-12-03 17:23:51 +08:00
tdjnodj
dfbe09860f Update README.md 2022-12-03 17:17:08 +08:00
metacubex
9e20f9c26a chore: update dependencies 2022-11-28 20:33:10 +08:00
Skimmle
f968d0cb82 chore: update github action 2022-11-26 20:16:12 +08:00
metacubex
2ad84f4379 Merge branch 'Beta' into Meta 2022-11-02 18:08:22 +08:00
metacubex
c7aa16426f Merge branch 'Alpha' into Beta 2022-11-02 18:07:29 +08:00
Skyxim
5987f8e3b5 Merge branch 'Beta' into Meta 2022-08-29 13:08:29 +08:00
Skyxim
3a8eb72de2 Merge branch 'Alpha' into Beta 2022-08-29 13:08:22 +08:00
zhudan
33abbdfd24 Merge pull request #174 from MetaCubeX/Alpha
Alpha
2022-08-29 11:24:07 +08:00
zhudan
0703d6cbff Merge pull request #173 from MetaCubeX/Alpha
Alpha
2022-08-29 11:22:14 +08:00
Skyxim
10d2d14938 Merge branch 'Beta' into Meta
# Conflicts:
#	rules/provider/classical_strategy.go
2022-07-02 10:41:41 +08:00
wwqgtxx
691cf1d8d6 Merge pull request #94 from bash99/Meta
Update README.md
2022-06-15 19:15:51 +08:00
bash99
d1decb8e58 Update README.md
add permissions for systemctl services
clash-dashboard change to updated one
2022-06-15 14:00:05 +08:00
Skyxim
7d04904109 fix: leak dns when domain in hosts list 2022-06-11 18:51:26 +08:00
Skyxim
a5acd3aa97 refactor: clear linkname,reduce cycle dependencies,transport init geosite function 2022-06-11 18:51:22 +08:00
Skyxim
eea9a12560 fix: 规则匹配默认策略组返回错误 2022-06-09 14:18:35 +08:00
adlyq
0a4570b55c fix: group filter touch provider 2022-06-09 14:18:29 +08:00
198 changed files with 2941 additions and 8616 deletions

82
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View 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

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

1
.github/genReleaseNote.sh vendored Executable file
View File

@@ -0,0 +1 @@
git log --pretty=format:"* %s by @%an" v1.14.x..v1.14.y | sort -f | uniq > release.md

View File

@@ -128,7 +128,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: "1.21" go-version: "1.20"
check-latest: true check-latest: true
- name: Test - name: Test
@@ -285,7 +285,6 @@ jobs:
generate_release_notes: true generate_release_notes: true
Docker: Docker:
if: ${{ github.event_name != 'pull_request' }}
permissions: write-all permissions: write-all
needs: [Build] needs: [Build]
runs-on: ubuntu-latest runs-on: ubuntu-latest

15
.github/workflows/delete.yml vendored Normal file
View 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

View File

@@ -4,9 +4,9 @@ RUN echo "I'm building for $TARGETPLATFORM"
RUN apk add --no-cache gzip && \ RUN apk add --no-cache gzip && \
mkdir /clash-config && \ mkdir /clash-config && \
wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \ wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
wget -O /clash-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \ wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
wget -O /clash-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
COPY docker/file-name.sh /clash/file-name.sh COPY docker/file-name.sh /clash/file-name.sh
WORKDIR /clash WORKDIR /clash

View File

@@ -31,8 +31,6 @@ PLATFORM_LIST = \
linux-mips-hardfloat \ linux-mips-hardfloat \
linux-mipsle-softfloat \ linux-mipsle-softfloat \
linux-mipsle-hardfloat \ linux-mipsle-hardfloat \
linux-riscv64 \
linux-loong64 \
android-arm64 \ android-arm64 \
freebsd-386 \ freebsd-386 \
freebsd-amd64 \ freebsd-amd64 \
@@ -106,9 +104,6 @@ linux-mips64le:
linux-riscv64: linux-riscv64:
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-loong64:
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64: android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@

View File

@@ -3,42 +3,25 @@ package adapter
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"net/url" "net/url"
"strconv"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/queue" "github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/puzpuzpuz/xsync/v2"
) )
var UnifiedDelay = atomic.NewBool(false) var UnifiedDelay = atomic.NewBool(false)
const (
defaultHistoriesNum = 10
)
type extraProxyState struct {
history *queue.Queue[C.DelayHistory]
alive *atomic.Bool
}
type Proxy struct { type Proxy struct {
C.ProxyAdapter C.ProxyAdapter
history *queue.Queue[C.DelayHistory] history *queue.Queue[C.DelayHistory]
alive *atomic.Bool alive *atomic.Bool
url string
extra *xsync.MapOf[string, *extraProxyState]
} }
// Alive implements C.Proxy // Alive implements C.Proxy
@@ -46,15 +29,6 @@ func (p *Proxy) Alive() bool {
return p.alive.Load() return p.alive.Load()
} }
// AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok {
return state.alive.Load()
}
return p.alive.Load()
}
// Dial implements C.Proxy // Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
@@ -88,51 +62,9 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
for _, item := range queueM { for _, item := range queueM {
histories = append(histories, item) histories = append(histories, item)
} }
return histories return histories
} }
// DelayHistoryForTestUrl implements C.Proxy
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
var queueM []C.DelayHistory
if state, ok := p.extra.Load(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 {
extraHistory := map[string][]C.DelayHistory{}
p.extra.Range(func(k string, v *extraProxyState) bool {
testUrl := k
state := v
histories := []C.DelayHistory{}
queueM := state.history.Copy()
for _, item := range queueM {
histories = append(histories, item)
}
extraHistory[testUrl] = histories
return true
})
return extraHistory
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16. // LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy // implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) { func (p *Proxy) LastDelay() (delay uint16) {
@@ -148,28 +80,6 @@ func (p *Proxy) LastDelay() (delay uint16) {
return history.Delay 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 state, ok := p.extra.Load(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 // MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) { func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON() inner, err := p.ProxyAdapter.MarshalJSON()
@@ -180,8 +90,6 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
mapping := map[string]any{} mapping := map[string]any{}
_ = json.Unmarshal(inner, &mapping) _ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistory()
mapping["alive"] = p.Alive()
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP() mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP() mapping["xudp"] = p.SupportXUDP()
@@ -191,50 +99,17 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
// URLTest get the delay for the specified URL // URLTest get the delay for the specified URL
// implements C.Proxy // implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) { func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() { defer func() {
alive := err == nil p.alive.Store(err == nil)
store = p.determineFinalStoreType(store, url)
switch store {
case C.OriginalHistory:
p.alive.Store(alive)
record := C.DelayHistory{Time: time.Now()} record := C.DelayHistory{Time: time.Now()}
if alive { if err == nil {
record.Delay = t record.Delay = t
} }
p.history.Put(record) p.history.Put(record)
if p.history.Len() > defaultHistoriesNum { if p.history.Len() > 10 {
p.history.Pop() 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
}
state, ok := p.extra.Load(url)
if !ok {
state = &extraProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
p.extra.Store(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)
}
}() }()
unifiedDelay := UnifiedDelay.Load() unifiedDelay := UnifiedDelay.Load()
@@ -297,22 +172,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
} }
} }
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) t = uint16(time.Since(start) / time.Millisecond)
return return
} }
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{ return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
ProxyAdapter: adapter,
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
url: "",
extra: xsync.NewMapOf[*extraProxyState]()}
} }
func urlToMetadata(rawURL string) (addr C.Metadata, err error) { func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
@@ -333,36 +198,11 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
return return
} }
} }
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return
}
addr = C.Metadata{ addr = C.Metadata{
Host: u.Hostname(), Host: u.Hostname(),
DstIP: netip.Addr{}, DstIP: netip.Addr{},
DstPort: uint16(uintPort), DstPort: port,
} }
return 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.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
return C.ExtraHistory
}
_, ok := p.extra.Load(url)
if ok {
return C.ExtraHistory
}
return store
}

View File

@@ -17,10 +17,6 @@ func SetTfo(open bool) {
lc.DisableTFO = !open lc.DisableTFO = !open
} }
func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open)
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) { func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
return lc.Listen(ctx, network, address) return lc.Listen(ctx, network, address)
} }

View File

@@ -1,22 +0,0 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewMitm receive mitm request and return MitmContext
func NewMitm(target socks5.Addr, source net.Addr, userAgent string, conn net.Conn) *context.ConnContext {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = C.MITM
metadata.UserAgent = userAgent
if ip, port, err := parseAddr(source); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@@ -1,10 +0,0 @@
//go:build !go1.21
package inbound
import "net"
const multipathTCPAvailable = false
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
}

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package inbound
import "net"
const multipathTCPAvailable = true
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open)
}

View File

@@ -3,7 +3,6 @@ package inbound
import ( import (
"net" "net"
"net/netip" "net/netip"
"strconv"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/context"
@@ -38,9 +37,7 @@ func NewInner(conn net.Conn, address string) *context.ConnContext {
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
metadata.Process = C.ClashName metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(address); err == nil { if h, port, err := net.SplitHostPort(address); err == nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil { metadata.DstPort = port
metadata.DstPort = uint16(port)
}
if ip, err := netip.ParseAddr(h); err == nil { if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip metadata.DstIP = ip
} else { } else {

View File

@@ -20,14 +20,14 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
case socks5.AtypDomainName: case socks5.AtypDomainName:
// trim for FQDN // trim for FQDN
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4: case socks5.AtypIPv4:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len])) metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6: case socks5.AtypIPv6:
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len]) ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
metadata.DstIP = ip6.Unmap() metadata.DstIP = ip6.Unmap()
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
} }
return metadata return metadata
@@ -43,16 +43,11 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
// trim FQDN (#737) // trim FQDN (#737)
host = strings.TrimRight(host, ".") host = strings.TrimRight(host, ".")
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
}
metadata := &C.Metadata{ metadata := &C.Metadata{
NetWork: C.TCP, NetWork: C.TCP,
Host: host, Host: host,
DstIP: netip.Addr{}, DstIP: netip.Addr{},
DstPort: uint16Port, DstPort: port,
} }
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
@@ -63,10 +58,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) { func parseAddr(addr net.Addr) (netip.Addr, string, error) {
// Filter when net.Addr interface is nil // Filter when net.Addr interface is nil
if addr == nil { if addr == nil {
return netip.Addr{}, 0, errors.New("nil addr") return netip.Addr{}, "", errors.New("nil addr")
} }
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok { if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
ip, port, err := parseAddr(rawAddr.RawAddr()) ip, port, err := parseAddr(rawAddr.RawAddr())
@@ -77,14 +72,9 @@ func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
addrStr := addr.String() addrStr := addr.String()
host, port, err := net.SplitHostPort(addrStr) host, port, err := net.SplitHostPort(addrStr)
if err != nil { if err != nil {
return netip.Addr{}, 0, err return netip.Addr{}, "", err
}
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
} }
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
return ip, uint16Port, err return ip, port, err
} }

View File

@@ -21,7 +21,6 @@ type Base struct {
udp bool udp bool
xudp bool xudp bool
tfo bool tfo bool
mpTcp bool
rmark int rmark int
id string id string
prefer C.DNSPrefer prefer C.DNSPrefer
@@ -144,16 +143,11 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
opts = append(opts, dialer.WithTFO(true)) opts = append(opts, dialer.WithTFO(true))
} }
if b.mpTcp {
opts = append(opts, dialer.WithMPTCP(true))
}
return opts return opts
} }
type BasicOption struct { type BasicOption struct {
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"` TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty" group:"mptcp,omitempty"`
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"` IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
@@ -167,7 +161,6 @@ type BaseOption struct {
UDP bool UDP bool
XUDP bool XUDP bool
TFO bool TFO bool
MPTCP bool
Interface string Interface string
RoutingMark int RoutingMark int
Prefer C.DNSPrefer Prefer C.DNSPrefer
@@ -181,7 +174,6 @@ func NewBase(opt BaseOption) *Base {
udp: opt.UDP, udp: opt.UDP,
xudp: opt.XUDP, xudp: opt.XUDP,
tfo: opt.TFO, tfo: opt.TFO,
mpTcp: opt.MPTCP,
iface: opt.Interface, iface: opt.Interface,
rmark: opt.RoutingMark, rmark: opt.RoutingMark,
prefer: opt.Prefer, prefer: opt.Prefer,

View File

@@ -3,8 +3,6 @@ package outbound
import ( import (
"context" "context"
"errors" "errors"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@@ -14,11 +12,6 @@ type Direct struct {
*Base *Base
} }
type DirectOption struct {
BasicOption
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
@@ -26,7 +19,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
return NewConn(c, d), nil return NewConn(c, d), nil
} }
@@ -47,21 +40,6 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
return newPacketConn(pc, d), nil return newPacketConn(pc, d), nil
} }
func NewDirectWithOption(option DirectOption) *Direct {
return &Direct{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
}
}
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{ return &Direct{
Base: &Base{ Base: &Base{

View File

@@ -7,13 +7,11 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
@@ -76,7 +74,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -113,10 +111,6 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
tempHeaders["Proxy-Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) tempHeaders["Proxy-Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
} }
if metadata.Type == C.MITM {
tempHeaders["Origin-Request-Source-Address"] = metadata.SourceAddress()
}
for key, value := range tempHeaders { for key, value := range tempHeaders {
HeaderString += key + ": " + value + "\r\n" HeaderString += key + ": " + value + "\r\n"
} }
@@ -183,7 +177,6 @@ func NewHttp(option HttpOption) (*Http, error) {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@@ -1,50 +0,0 @@
package outbound
import (
"context"
"net"
"time"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Mitm struct {
*Base
serverAddr *net.TCPAddr
httpProxyClient *Http
}
// DialContext implements C.ProxyAdapter
func (m *Mitm) DialContext(ctx context.Context, metadata *C.Metadata, _ ...dialer.Option) (C.Conn, error) {
c, err := net.DialTCP("tcp", nil, m.serverAddr)
if err != nil {
return nil, err
}
_ = c.SetKeepAlive(true)
_ = c.SetKeepAlivePeriod(60 * time.Second)
metadata.Type = C.MITM
hc, err := m.httpProxyClient.StreamConnContext(ctx, c, metadata)
if err != nil {
_ = c.Close()
return nil, err
}
return NewConn(hc, m), nil
}
func NewMitm(serverAddr string) *Mitm {
tcpAddr, _ := net.ResolveTCPAddr("tcp", serverAddr)
http, _ := NewHttp(HttpOption{})
return &Mitm{
Base: &Base{
name: "Mitm",
tp: C.Mitm,
},
serverAddr: tcpAddr,
httpProxyClient: http,
}
}

View File

@@ -19,7 +19,7 @@ import (
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go" restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks2" "github.com/metacubex/sing-shadowsocks2"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/uot"
) )
@@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -294,6 +294,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
restlsConfig.SessionTicketsDisabled = true
if err != nil { if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }
@@ -314,7 +315,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -181,7 +181,6 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@@ -97,7 +97,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
func (s *SingMux) SupportUDP() bool { func (s *SingMux) SupportUDP() bool {
if s.onlyTcp { if s.onlyTcp {
return s.ProxyAdapter.SupportUDP() return s.ProxyAdapter.SupportUOT()
} }
return true return true
} }

View File

@@ -6,7 +6,6 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
@@ -60,7 +59,8 @@ func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
err := snell.WriteUDPHeader(c, s.version) err := snell.WriteUDPHeader(c, s.version)
return c, err return c, err
} }
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version) port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
return c, err return c, err
} }
@@ -72,7 +72,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return nil, err return nil, err
} }
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil { port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close() c.Close()
return nil, err return nil, err
} }
@@ -94,7 +95,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err) return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -122,7 +123,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version) err = snell.WriteUDPHeader(c, s.version)
@@ -182,7 +183,6 @@ func NewSnell(option SnellOption) (*Snell, error) {
tp: C.Snell, tp: C.Snell,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@@ -208,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, err return nil, err
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
}) })
} }

View File

@@ -9,7 +9,6 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
@@ -81,7 +80,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -127,7 +126,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c) tcpKeepAlive(c)
var user *socks5.User var user *socks5.User
if ss.user != "" { if ss.user != "" {
user = &socks5.User{ user = &socks5.User{
@@ -197,7 +196,6 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@@ -8,13 +8,13 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/trojan"
"github.com/Dreamacro/clash/transport/vless"
) )
type Trojan struct { type Trojan struct {
@@ -45,6 +45,8 @@ type TrojanOption struct {
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"` ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
@@ -93,6 +95,11 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
return nil, err
}
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err return c, err
@@ -110,6 +117,12 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err return nil, err
} }
c, err = t.instance.PresetXTLSConn(c)
if err != nil {
c.Close()
return nil, err
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close() c.Close()
return nil, err return nil, err
@@ -132,7 +145,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -185,7 +198,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c) tcpKeepAlive(c)
c, err = t.plainStream(ctx, c) c, err = t.plainStream(ctx, c)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -224,10 +237,24 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow,
Fingerprint: option.Fingerprint, Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint, ClientFingerprint: option.ClientFingerprint,
} }
switch option.Network {
case "", "tcp":
if len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
tOption.Flow = option.Flow
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
}
}
if option.SNI != "" { if option.SNI != "" {
tOption.ServerName = option.SNI tOption.ServerName = option.SNI
} }
@@ -239,7 +266,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tp: C.Trojan, tp: C.Trojan,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@@ -269,7 +295,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -6,7 +6,6 @@ import (
"crypto/tls" "crypto/tls"
"encoding/hex" "encoding/hex"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"math" "math"
"net" "net"
@@ -14,17 +13,13 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/metacubex/quic-go"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic" "github.com/Dreamacro/clash/transport/tuic"
"github.com/gofrs/uuid/v5"
"github.com/metacubex/quic-go"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot"
) )
type Tuic struct { type Tuic struct {
@@ -38,9 +33,7 @@ type TuicOption struct {
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Token string `proxy:"token,omitempty"` Token string `proxy:"token"`
UUID string `proxy:"uuid,omitempty"`
Password string `proxy:"password,omitempty"`
Ip string `proxy:"ip,omitempty"` Ip string `proxy:"ip,omitempty"`
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"` HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
ALPN []string `proxy:"alpn,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
@@ -53,7 +46,6 @@ type TuicOption struct {
FastOpen bool `proxy:"fast-open,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"` MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` CustomCA string `proxy:"ca,omitempty"`
@@ -63,9 +55,6 @@ type TuicOption struct {
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@@ -89,32 +78,6 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if t.option.UDPOverStream {
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
uotMetadata := *metadata
uotMetadata.Host = uotDestination.Fqdn
uotMetadata.DstPort = uotDestination.Port
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
if err != nil {
return nil, err
}
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
}
}
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer) pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -127,7 +90,11 @@ func (t *Tuic) SupportWithDialer() C.NetWork {
return C.ALLNet return C.ALLNet
} }
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) { 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) {
if len(t.option.DialerProxy) > 0 { if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer) dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil { if err != nil {
@@ -139,14 +106,10 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
return nil, nil, err return nil, nil, err
} }
addr = udpAddr addr = udpAddr
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort()) pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
transport = &quic.Transport{Conn: pc}
transport.SetCreatedConn(true) // auto close conn
transport.SetSingleUse(true) // auto close transport
return return
} }
@@ -195,7 +158,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} }
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN tlsConfig.NextProtos = option.ALPN
} else { } else {
tlsConfig.NextProtos = []string{"h3"} tlsConfig.NextProtos = []string{"h3"}
@@ -209,9 +172,8 @@ func NewTuic(option TuicOption) (*Tuic, error) {
option.HeartbeatInterval = 10000 option.HeartbeatInterval = 10000
} }
udpRelayMode := tuic.QUIC
if option.UdpRelayMode != "quic" { if option.UdpRelayMode != "quic" {
udpRelayMode = tuic.NATIVE option.UdpRelayMode = "native"
} }
if option.MaxUdpRelayPacketSize == 0 { if option.MaxUdpRelayPacketSize == 0 {
@@ -222,23 +184,14 @@ func NewTuic(option TuicOption) (*Tuic, error) {
option.MaxOpenStreams = 100 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 { if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
} }
if option.MaxDatagramFrameSize > 1400 { if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400 option.MaxDatagramFrameSize = 1400
} }
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
// ensure server's incoming stream can handle correctly, increase to 1.1x // ensure server's incoming stream can handle correctly, increase to 1.1x
quicMaxOpenStreams := int64(option.MaxOpenStreams) quicMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -267,18 +220,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
if len(option.Ip) > 0 { if len(option.Ip) > 0 {
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port)) addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
} }
host := option.Server
if option.DisableSni { if option.DisableSni {
host = ""
tlsConfig.ServerName = "" tlsConfig.ServerName = ""
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
}
switch option.UDPOverStreamVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverStreamVersion = uot.LegacyVersion
default:
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
} }
tkn := tuic.GenTKN(option.Token)
t := &Tuic{ t := &Tuic{
Base: &Base{ Base: &Base{
@@ -304,40 +251,21 @@ func NewTuic(option TuicOption) (*Tuic, error) {
if clientMaxOpenStreams < 1 { if clientMaxOpenStreams < 1 {
clientMaxOpenStreams = 1 clientMaxOpenStreams = 1
} }
clientOption := &tuic.ClientOption{
if len(option.Token) > 0 {
tkn := tuic.GenTKN(option.Token)
clientOption := &tuic.ClientOptionV4{
TlsConfig: tlsConfig, TlsConfig: tlsConfig,
QuicConfig: quicConfig, QuicConfig: quicConfig,
Host: host,
Token: tkn, Token: tkn,
UdpRelayMode: udpRelayMode, UdpRelayMode: option.UdpRelayMode,
CongestionController: option.CongestionController, CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt, ReduceRtt: option.ReduceRtt,
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond, RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize, MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
FastOpen: option.FastOpen, FastOpen: option.FastOpen,
MaxOpenStreams: clientMaxOpenStreams, MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
} }
t.client = tuic.NewPoolClientV4(clientOption) t.client = tuic.NewPoolClient(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 return t, nil
} }

View File

@@ -4,9 +4,12 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
xtls "github.com/xtls/go"
"net" "net"
"net/netip" "net/netip"
"strconv"
"sync" "sync"
"time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@@ -15,9 +18,17 @@ import (
var ( var (
globalClientSessionCache tls.ClientSessionCache globalClientSessionCache tls.ClientSessionCache
globalClientXSessionCache xtls.ClientSessionCache
once sync.Once once sync.Once
) )
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache { func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() { once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128) globalClientSessionCache = tls.NewLRUClientSessionCache(128)
@@ -25,11 +36,18 @@ func getClientSessionCache() tls.ClientSessionCache {
return globalClientSessionCache return globalClientSessionCache
} }
func getClientXSessionCache() xtls.ClientSessionCache {
once.Do(func() {
globalClientXSessionCache = xtls.NewLRUClientSessionCache(128)
})
return globalClientXSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
addrType := metadata.AddrType() addrType := metadata.AddrType()
aType := uint8(addrType) aType := uint8(addrType)
p := uint(metadata.DstPort) p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
port := []byte{uint8(p >> 8), uint8(p & 0xff)} port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch addrType { switch addrType {
case socks5.AtypDomainName: case socks5.AtypDomainName:

View File

@@ -14,7 +14,6 @@ import (
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@@ -26,8 +25,8 @@ import (
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
vmessSing "github.com/metacubex/sing-vmess" vmessSing "github.com/sagernet/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
@@ -56,8 +55,8 @@ type VlessOption struct {
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"` Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"` PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"` XUDP bool `proxy:"xudp,omitempty"`
@@ -133,7 +132,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts) c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "http": case "http":
// readability first, so just copy default TLS logic // readability first, so just copy default TLS logic
c, err = v.streamTLSConn(ctx, c, false) c, err = v.streamTLSOrXTLSConn(ctx, c, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -148,7 +147,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c = vmess.StreamHTTPConn(c, httpOpts) c = vmess.StreamHTTPConn(c, httpOpts)
case "h2": case "h2":
c, err = v.streamTLSConn(ctx, c, true) c, err = v.streamTLSOrXTLSConn(ctx, c, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -163,8 +162,8 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default: default:
// default tcp network // default tcp network
// handle TLS // handle TLS And XTLS
c, err = v.streamTLSConn(ctx, c, false) c, err = v.streamTLSOrXTLSConn(ctx, c, false)
} }
if err != nil { if err != nil {
@@ -180,7 +179,7 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
metadata = &C.Metadata{ metadata = &C.Metadata{
NetWork: C.UDP, NetWork: C.UDP,
Host: packetaddr.SeqPacketMagicAddress, Host: packetaddr.SeqPacketMagicAddress,
DstPort: 443, DstPort: "443",
} }
} else { } else {
metadata = &C.Metadata{ // a clear metadata only contains ip metadata = &C.Metadata{ // a clear metadata only contains ip
@@ -202,17 +201,29 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
return return
} }
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) { func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
if v.isLegacyXTLSEnabled() && !isH2 {
xtlsOpts := vless.XTLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
Fingerprint: v.option.Fingerprint,
}
if v.option.ServerName != "" {
xtlsOpts.Host = v.option.ServerName
}
return vless.StreamXTLSConn(ctx, conn, &xtlsOpts)
} else if v.option.TLS {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN,
} }
if isH2 { if isH2 {
@@ -229,6 +240,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
return conn, nil return conn, nil
} }
func (v *Vless) isLegacyXTLSEnabled() bool {
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
}
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport // gun transport
@@ -263,7 +278,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -328,7 +343,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -358,14 +373,8 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
} }
if v.option.XUDP { if v.option.XUDP {
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
return newPacketConn(N.NewThreadSafePacketConn( return newPacketConn(N.NewThreadSafePacketConn(
vmessSing.NewXUDPConn(c, vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())),
globalID,
M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil ), v), nil
} else if v.option.PacketAddr { } else if v.option.PacketAddr {
return newPacketConn(N.NewThreadSafePacketConn( return newPacketConn(N.NewThreadSafePacketConn(
@@ -401,11 +410,12 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
copy(addr[1:], metadata.Host) copy(addr[1:], metadata.Host)
} }
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
return &vless.DstAddr{ return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP, UDP: metadata.NetWork == C.UDP,
AddrType: addrType, AddrType: addrType,
Addr: addr, Addr: addr,
Port: metadata.DstPort, Port: uint16(port),
Mux: metadata.NetWork == C.UDP && xudp, Mux: metadata.NetWork == C.UDP && xudp,
} }
} }
@@ -509,11 +519,11 @@ func NewVless(option VlessOption) (*Vless, error) {
switch option.Flow { switch option.Flow {
case vless.XRV: case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV) log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
fallthrough
case vless.XRO, vless.XRD, vless.XRS:
addons = &vless.Addons{ addons = &vless.Addons{
Flow: option.Flow, Flow: option.Flow,
} }
case vless.XRO, vless.XRD, vless.XRS:
log.Fatalln("Legacy XTLS protocol %s is deprecated and no longer supported", option.Flow)
default: default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
} }
@@ -532,7 +542,7 @@ func NewVless(option VlessOption) (*Vless, error) {
option.PacketAddr = false option.PacketAddr = false
} }
client, err := vless.NewClient(option.UUID, addons) client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -545,7 +555,6 @@ func NewVless(option VlessOption) (*Vless, error) {
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP, xudp: option.XUDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@@ -578,7 +587,7 @@ func NewVless(option VlessOption) (*Vless, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -12,18 +12,16 @@ import (
"sync" "sync"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/ntp"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess" clashVMess "github.com/Dreamacro/clash/transport/vmess"
vmess "github.com/metacubex/sing-vmess" vmess "github.com/sagernet/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr" "github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
@@ -53,7 +51,6 @@ type VmessOption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"` ServerName string `proxy:"servername,omitempty"`
@@ -151,7 +148,6 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@@ -208,7 +204,6 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@@ -228,44 +223,30 @@ 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) { func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
if v.option.XUDP { if v.option.XUDP {
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyXUDPPacketConn(c, conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
} else { } else {
conn, err = v.client.DialXUDPPacketConn(c, conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
} }
} else if v.option.PacketAddr { } else if v.option.PacketAddr {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} else { } else {
conn, err = v.client.DialPacketConn(c, conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} }
conn = packetaddr.NewBindConn(conn) conn = packetaddr.NewBindConn(conn)
} else { } else {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
M.SocksaddrFromNet(metadata.UDPAddr()))
} else { } else {
conn, err = v.client.DialPacketConn(c, conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
M.SocksaddrFromNet(metadata.UDPAddr()))
} }
} }
} else { } else {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c, conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddr(metadata.RemoteAddress()))
} else { } else {
conn, err = v.client.DialConn(c, conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} }
if err != nil { if err != nil {
@@ -308,7 +289,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -369,7 +350,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -417,7 +398,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if option.AuthenticatedLength { if option.AuthenticatedLength {
options = append(options, vmess.ClientWithAuthenticatedLength()) options = append(options, vmess.ClientWithAuthenticatedLength())
} }
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...) client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -441,7 +421,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP, xudp: option.XUDP,
tfo: option.TFO, tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@@ -469,7 +448,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c) tcpKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -302,7 +302,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{ outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
@@ -374,7 +374,8 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice})) options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else { } else {
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap()) port, _ := strconv.Atoi(metadata.DstPort)
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -411,7 +412,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
} }
metadata.DstIP = ip metadata.DstIP = ip
} }
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap()) port, _ := strconv.Atoi(metadata.DstPort)
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback" "github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@@ -20,7 +19,6 @@ type Fallback struct {
disableUDP bool disableUDP bool
testUrl string testUrl string
selected string selected string
expectedStatus string
} }
func (f *Fallback) Now() string { func (f *Fallback) Now() string {
@@ -87,8 +85,6 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
"type": f.Type().String(), "type": f.Type().String(),
"now": f.Now(), "now": f.Now(),
"all": all, "all": all,
"testUrl": f.testUrl,
"expected": f.expectedStatus,
}) })
} }
@@ -102,14 +98,12 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch) proxies := f.GetProxies(touch)
for _, proxy := range proxies { for _, proxy := range proxies {
if len(f.selected) == 0 { if len(f.selected) == 0 {
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} }
} else { } else {
if proxy.Name() == f.selected { if proxy.Name() == f.selected {
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy return proxy
} else { } else {
f.selected = "" f.selected = ""
@@ -135,12 +129,10 @@ func (f *Fallback) Set(name string) error {
} }
f.selected = name 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)) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel() defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus) _, _ = p.URLTest(ctx, f.testUrl)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory)
} }
return nil return nil
@@ -166,6 +158,5 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
}), }),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL, testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
} }
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/atomic" "github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
@@ -193,7 +192,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
return proxies return proxies
} }
func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) { func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
var wg sync.WaitGroup var wg sync.WaitGroup
var lock sync.Mutex var lock sync.Mutex
mp := map[string]uint16{} mp := map[string]uint16{}
@@ -202,7 +201,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
proxy := proxy proxy := proxy
wg.Add(1) wg.Add(1)
go func() { go func() {
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory) delay, err := proxy.URLTest(ctx, url)
if err == nil { if err == nil {
lock.Lock() lock.Lock()
mp[proxy.Name()] = delay mp[proxy.Name()] = delay

View File

@@ -12,8 +12,8 @@ import (
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/callback" "github.com/Dreamacro/clash/common/callback"
"github.com/Dreamacro/clash/common/murmur3"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@@ -27,8 +27,6 @@ type LoadBalance struct {
*GroupBase *GroupBase
disableUDP bool disableUDP bool
strategyFn strategyFn strategyFn strategyFn
testUrl string
expectedStatus string
} }
var errStrategy = errors.New("unsupported strategy") var errStrategy = errors.New("unsupported strategy")
@@ -131,7 +129,7 @@ func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
return lb.Unwrap(metadata, false).IsL3Protocol(metadata) return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
} }
func strategyRoundRobin(url string) strategyFn { func strategyRoundRobin() strategyFn {
idx := 0 idx := 0
idxMutex := sync.Mutex{} idxMutex := sync.Mutex{}
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
@@ -150,8 +148,7 @@ func strategyRoundRobin(url string) strategyFn {
for ; i < length; i++ { for ; i < length; i++ {
id := (idx + i) % length id := (idx + i) % length
proxy := proxies[id] proxy := proxies[id]
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
i++ i++
return proxy return proxy
} }
@@ -161,24 +158,22 @@ func strategyRoundRobin(url string) strategyFn {
} }
} }
func strategyConsistentHashing(url string) strategyFn { func strategyConsistentHashing() strategyFn {
maxRetry := 5 maxRetry := 5
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := utils.MapHash(getKey(metadata)) key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(proxies)) buckets := int32(len(proxies))
for i := 0; i < maxRetry; i, key = i+1, key+1 { for i := 0; i < maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets) idx := jumpHash(key, buckets)
proxy := proxies[idx] proxy := proxies[idx]
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy return proxy
} }
} }
// when availability is poor, traverse the entire list to get the available nodes // when availability is poor, traverse the entire list to get the available nodes
for _, proxy := range proxies { for _, proxy := range proxies {
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy return proxy
} }
} }
@@ -187,14 +182,14 @@ func strategyConsistentHashing(url string) strategyFn {
} }
} }
func strategyStickySessions(url string) strategyFn { func strategyStickySessions() strategyFn {
ttl := time.Minute * 10 ttl := time.Minute * 10
maxRetry := 5 maxRetry := 5
lruCache := cache.New[uint64, int]( lruCache := cache.New[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())), cache.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000)) cache.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := utils.MapHash(getKeyWithSrcAndDst(metadata)) key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
length := len(proxies) length := len(proxies)
idx, has := lruCache.Get(key) idx, has := lruCache.Get(key)
if !has { if !has {
@@ -204,8 +199,7 @@ func strategyStickySessions(url string) strategyFn {
nowIdx := idx nowIdx := idx
for i := 1; i < maxRetry; i++ { for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx] proxy := proxies[nowIdx]
// if proxy.Alive() { if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
if nowIdx != idx { if nowIdx != idx {
lruCache.Delete(key) lruCache.Delete(key)
lruCache.Set(key, nowIdx) lruCache.Set(key, nowIdx)
@@ -238,8 +232,6 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{ return json.Marshal(map[string]any{
"type": lb.Type().String(), "type": lb.Type().String(),
"all": all, "all": all,
"testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus,
}) })
} }
@@ -247,11 +239,11 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
var strategyFn strategyFn var strategyFn strategyFn
switch strategy { switch strategy {
case "consistent-hashing": case "consistent-hashing":
strategyFn = strategyConsistentHashing(option.URL) strategyFn = strategyConsistentHashing()
case "round-robin": case "round-robin":
strategyFn = strategyRoundRobin(option.URL) strategyFn = strategyRoundRobin()
case "sticky-sessions": case "sticky-sessions":
strategyFn = strategyStickySessions(option.URL) strategyFn = strategyStickySessions()
default: default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy) return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
} }
@@ -270,7 +262,5 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
}), }),
strategyFn: strategyFn, strategyFn: strategyFn,
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
}, nil }, nil
} }

View File

@@ -3,20 +3,19 @@ package outboundgroup
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
var ( var (
errFormat = errors.New("format error") errFormat = errors.New("format error")
errType = errors.New("unsupported type") errType = errors.New("unsupport type")
errMissProxy = errors.New("`use` or `proxies` missing") errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("duplicate provider name") errDuplicateProvider = errors.New("duplicate provider name")
) )
@@ -33,7 +32,6 @@ type GroupCommonOption struct {
Filter string `group:"filter,omitempty"` Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,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) { func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
@@ -55,36 +53,30 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providers := []types.ProxyProvider{} providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) return nil, 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 { if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies) ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, err
} }
if _, ok := providersMap[groupName]; ok { if _, ok := providersMap[groupName]; ok {
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) return nil, errDuplicateProvider
} }
var url string
var interval uint
// select don't need health check // select don't need health check
if groupOption.Type != "select" && groupOption.Type != "relay" { 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
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" { if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204" groupOption.URL = "https://cp.cloudflare.com/generate_204"
} }
@@ -93,29 +85,22 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
groupOption.Interval = 300 groupOption.Interval = 300
} }
url = groupOption.URL hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
interval = uint(groupOption.Interval)
}
hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc) pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, err
} }
providers = append(providers, pd) providers = append(providers, pd)
providersMap[groupName] = pd providersMap[groupName] = pd
} }
}
if len(groupOption.Use) != 0 { if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use) list, err := getProviders(providersMap, groupOption.Use)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err) return nil, err
} }
// different proxy groups use different test URL
addTestUrlToProviders(list, testUrl, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
providers = append(providers, list...) providers = append(providers, list...)
} else { } else {
groupOption.Filter = "" groupOption.Filter = ""
@@ -169,13 +154,3 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
} }
return ps, nil 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)
}
}

View File

@@ -27,7 +27,6 @@ type URLTest struct {
*GroupBase *GroupBase
selected string selected string
testUrl string testUrl string
expectedStatus string
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
@@ -113,8 +112,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
fast := proxies[0] fast := proxies[0]
// min := fast.LastDelay() min := fast.LastDelay()
min := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true fastNotExist := true
for _, proxy := range proxies[1:] { for _, proxy := range proxies[1:] {
@@ -122,13 +120,11 @@ func (u *URLTest) fast(touch bool) C.Proxy {
fastNotExist = false fastNotExist = false
} }
// if !proxy.Alive() { if !proxy.Alive() {
if !proxy.AliveForTestUrl(u.testUrl) {
continue continue
} }
// delay := proxy.LastDelay() delay := proxy.LastDelay()
delay := proxy.LastDelayForTestUrl(u.testUrl)
if delay < min { if delay < min {
fast = proxy fast = proxy
min = delay min = delay
@@ -136,8 +132,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
} }
// tolerance // 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 u.fastNode = fast
} }
return u.fastNode, nil return u.fastNode, nil
@@ -172,8 +167,6 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
"type": u.Type().String(), "type": u.Type().String(),
"now": u.Now(), "now": u.Now(),
"all": all, "all": all,
"testUrl": u.testUrl,
"expected": u.expectedStatus,
}) })
} }
@@ -208,7 +201,6 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL, testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
} }
for _, option := range options { for _, option := range options {

View File

@@ -1,5 +1,17 @@
package outboundgroup package outboundgroup
import (
"net"
"time"
)
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
type SelectAble interface { type SelectAble interface {
Set(string) error Set(string) error
ForceSet(name string) ForceSet(name string)

View File

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

View File

@@ -2,8 +2,6 @@ package provider
import ( import (
"context" "context"
"strings"
"sync"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "github.com/Dreamacro/clash/common/atomic"
@@ -12,8 +10,6 @@ import (
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/dlclark/regexp2"
) )
const ( const (
@@ -25,33 +21,18 @@ type HealthCheckOption struct {
Interval uint Interval uint
} }
type extraOption struct {
expectedStatus utils.IntRanges[uint16]
filters map[string]struct{}
}
type HealthCheck struct { type HealthCheck struct {
url string url string
extra map[string]*extraOption
mu sync.Mutex
started *atomic.Bool
proxies []C.Proxy proxies []C.Proxy
interval uint interval uint
lazy bool lazy bool
expectedStatus utils.IntRanges[uint16]
lastTouch *atomic.Int64 lastTouch *atomic.Int64
done chan struct{} done chan struct{}
singleDo *singledo.Single[struct{}] singleDo *singledo.Single[struct{}]
} }
func (hc *HealthCheck) process() { 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) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
hc.start()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
@@ -63,7 +44,6 @@ func (hc *HealthCheck) process() {
} }
case <-hc.done: case <-hc.done:
ticker.Stop() ticker.Stop()
hc.stop()
return return
} }
} }
@@ -73,63 +53,6 @@ func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies 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 { func (hc *HealthCheck) auto() bool {
return hc.interval != 0 return hc.interval != 0
} }
@@ -138,100 +61,39 @@ func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now().Unix()) 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() { func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies {
// execute default health check p := proxy
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus} b.Go(p.Name(), func() (bool, error) {
hc.execute(b, hc.url, id, option) ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
// execute extra health check log.Debugln("Health Checking %s {%s}", p.Name(), id)
if len(hc.extra) != 0 { _, _ = p.URLTest(ctx, hc.url)
for url, option := range hc.extra { log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
hc.execute(b, url, id, option) return false, nil
} })
} }
b.Wait() b.Wait()
log.Debugln("Finish A Health Checking {%s}", id) log.Debugln("Finish A Health Checking {%s}", id)
return struct{}{}, nil 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() { func (hc *HealthCheck) close() {
hc.done <- struct{}{} hc.done <- struct{}{}
} }
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck { func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
if len(url) == 0 {
interval = 0
expectedStatus = nil
}
return &HealthCheck{ return &HealthCheck{
proxies: proxies, proxies: proxies,
url: url, url: url,
extra: map[string]*extraOption{},
started: atomic.NewBool(false),
interval: interval, interval: interval,
lazy: lazy, lazy: lazy,
expectedStatus: expectedStatus,
lastTouch: atomic.NewInt64(0), lastTouch: atomic.NewInt64(0),
done: make(chan struct{}, 1), done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second), singleDo: singledo.NewSingle[struct{}](time.Second),

View File

@@ -6,28 +6,23 @@ import (
"time" "time"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/resource" "github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
var ( var errVehicleType = errors.New("unsupport vehicle type")
errVehicleType = errors.New("unsupport vehicle type")
errSubPath = errors.New("path is not subpath of home directory")
)
type healthCheckSchema struct { type healthCheckSchema struct {
Enable bool `provider:"enable"` Enable bool `provider:"enable"`
URL string `provider:"url"` URL string `provider:"url"`
Interval int `provider:"interval"` Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"` Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"`
} }
type proxyProviderSchema struct { type proxyProviderSchema struct {
Type string `provider:"type"` Type string `provider:"type"`
Path string `provider:"path,omitempty"` Path string `provider:"path"`
URL string `provider:"url,omitempty"` URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"` Filter string `provider:"filter,omitempty"`
@@ -49,33 +44,20 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
return nil, err return nil, err
} }
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus)
if err != nil {
return nil, err
}
var hcInterval uint var hcInterval uint
if schema.HealthCheck.Enable { if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval) hcInterval = uint(schema.HealthCheck.Interval)
} }
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus) hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
path := C.Path.Resolve(schema.Path)
var vehicle types.Vehicle var vehicle types.Vehicle
switch schema.Type { switch schema.Type {
case "file": case "file":
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path) vehicle = resource.NewFileVehicle(path)
case "http": case "http":
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path) vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
}
default: default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/common/utils"
clashHttp "github.com/Dreamacro/clash/component/http" clashHttp "github.com/Dreamacro/clash/component/http"
"github.com/Dreamacro/clash/component/resource" "github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@@ -51,7 +50,6 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
"type": pp.Type().String(), "type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(), "vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(), "proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url,
"updatedAt": pp.UpdatedAt, "updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo, "subscriptionInfo": pp.subscriptionInfo,
}) })
@@ -100,10 +98,6 @@ func (pp *proxySetProvider) Touch() {
pp.healthCheck.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) { func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
@@ -147,15 +141,15 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
} }
func (pp *proxySetProvider) closeAllConnections() { func (pp *proxySetProvider) closeAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool { snapshot := statistic.DefaultManager.Snapshot()
for _, c := range snapshot.Connections {
for _, chain := range c.Chains() { for _, chain := range c.Chains() {
if chain == pp.Name() { if chain == pp.Name() {
_ = c.Close() _ = c.Close()
break break
} }
} }
return true }
})
} }
func stopProxyProvider(pd *ProxySetProvider) { func stopProxyProvider(pd *ProxySetProvider) {
@@ -216,7 +210,6 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
"type": cp.Type().String(), "type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(), "vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(), "proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url,
}) })
} }
@@ -256,10 +249,6 @@ func (cp *compatibleProvider) Touch() {
cp.healthCheck.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) { func stopCompatibleProvider(pd *CompatibleProvider) {
pd.healthCheck.close() pd.healthCheck.close()
} }
@@ -299,7 +288,7 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if err := yaml.Unmarshal(buf, schema); err != nil { if err := yaml.Unmarshal(buf, schema); err != nil {
proxies, err1 := convert.ConvertsV2Ray(buf) proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil { if err1 != nil {
return nil, fmt.Errorf("%w, %w", err, err1) return nil, fmt.Errorf("%s, %w", err.Error(), err1)
} }
schema.Proxies = proxies schema.Proxies = proxies
} }

View File

@@ -11,9 +11,18 @@ type Buffer = buf.Buffer
var New = buf.New var New = buf.New
var NewSize = buf.NewSize var NewSize = buf.NewSize
var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize
var With = buf.With var With = buf.With
var As = buf.As var As = buf.As
var KeepAlive = common.KeepAlive
//go:norace
func Dup[T any](obj T) T {
return common.Dup(obj)
}
var ( var (
Must = common.Must Must = common.Must
Error = common.Error Error = common.Error

View File

@@ -7,8 +7,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/common/generics/list" "github.com/Dreamacro/clash/common/generics/list"
"github.com/samber/lo"
) )
// Option is part of Functional Options Pattern // Option is part of Functional Options Pattern
@@ -84,27 +82,9 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
// Get returns the any representation of a cached response and a bool // Get returns the any representation of a cached response and a bool
// set to true if the key was found. // set to true if the key was found.
func (c *LruCache[K, V]) Get(key K) (V, bool) { func (c *LruCache[K, V]) Get(key K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key) el := c.get(key)
if el == nil { if el == nil {
return lo.Empty[V](), false return getZero[V](), false
}
value := el.value
return value, true
}
func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
value := constructor()
c.set(key, value)
return value, false
} }
value := el.value value := el.value
@@ -116,12 +96,9 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
// and a bool set to true if the key was found. // and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires. // This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) { func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key) el := c.get(key)
if el == nil { if el == nil {
return lo.Empty[V](), time.Time{}, false return getZero[V](), time.Time{}, false
} }
return el.value, time.Unix(el.expires, 0), true return el.value, time.Unix(el.expires, 0), true
@@ -138,18 +115,11 @@ func (c *LruCache[K, V]) Exist(key K) bool {
// Set stores the any representation of a response for a given key. // Set stores the any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) { func (c *LruCache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.set(key, value)
}
func (c *LruCache[K, V]) set(key K, value V) {
expires := int64(0) expires := int64(0)
if c.maxAge > 0 { if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge expires = time.Now().Unix() + c.maxAge
} }
c.setWithExpire(key, value, time.Unix(expires, 0)) c.SetWithExpire(key, value, time.Unix(expires, 0))
} }
// SetWithExpire stores the any representation of a response for a given key and given expires. // SetWithExpire stores the any representation of a response for a given key and given expires.
@@ -158,10 +128,6 @@ func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.setWithExpire(key, value, expires)
}
func (c *LruCache[K, V]) setWithExpire(key K, value V, expires time.Time) {
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le) c.lru.MoveToBack(le)
e := le.Value e := le.Value
@@ -199,6 +165,9 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
} }
func (c *LruCache[K, V]) get(key K) *entry[K, V] { func (c *LruCache[K, V]) get(key K) *entry[K, V] {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key] le, ok := c.cache[key]
if !ok { if !ok {
return nil return nil
@@ -222,11 +191,12 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
// Delete removes the value associated with a key. // Delete removes the value associated with a key.
func (c *LruCache[K, V]) Delete(key K) { func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok { if le, ok := c.cache[key]; ok {
c.deleteElement(le) c.deleteElement(le)
} }
c.mu.Unlock()
} }
func (c *LruCache[K, V]) maybeDeleteOldest() { func (c *LruCache[K, V]) maybeDeleteOldest() {
@@ -249,10 +219,10 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
func (c *LruCache[K, V]) Clear() error { func (c *LruCache[K, V]) Clear() error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[K]*list.Element[*entry[K, V]]) c.cache = make(map[K]*list.Element[*entry[K, V]])
c.mu.Unlock()
return nil return nil
} }
@@ -261,3 +231,8 @@ type entry[K comparable, V any] struct {
value V value V
expires int64 expires int64
} }
func getZero[T any]() T {
var result T
return result
}

View File

@@ -1,303 +0,0 @@
package cert
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"strings"
"sync/atomic"
"time"
)
var currentSerialNumber = time.Now().Unix()
type Config struct {
ca *x509.Certificate
caPrivateKey *rsa.PrivateKey
roots *x509.CertPool
privateKey *rsa.PrivateKey
validity time.Duration
keyID []byte
organization string
certsStorage CertsStorage
}
type CertsStorage interface {
Get(key string) (*tls.Certificate, bool)
Set(key string, cert *tls.Certificate)
}
func NewAuthority(name, organization string, validity time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
pub := privateKey.Public()
pkixPub, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, nil, err
}
h := sha1.New()
_, err = h.Write(pkixPub)
if err != nil {
return nil, nil, err
}
keyID := h.Sum(nil)
serial := atomic.AddInt64(&currentSerialNumber, 1)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(serial),
Subject: pkix.Name{
CommonName: name,
Organization: []string{organization},
},
SubjectKeyId: keyID,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
NotBefore: time.Now().Add(-validity),
NotAfter: time.Now().Add(validity),
DNSNames: []string{name},
IsCA: true,
}
raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, privateKey)
if err != nil {
return nil, nil, err
}
x509c, err := x509.ParseCertificate(raw)
if err != nil {
return nil, nil, err
}
return x509c, privateKey, nil
}
func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey) (*Config, error) {
roots := x509.NewCertPool()
roots.AddCert(ca)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
pub := privateKey.Public()
pkixPub, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
h := sha1.New()
_, err = h.Write(pkixPub)
if err != nil {
return nil, err
}
keyID := h.Sum(nil)
return &Config{
ca: ca,
caPrivateKey: caPrivateKey,
privateKey: privateKey,
keyID: keyID,
validity: time.Hour,
organization: "Clash",
certsStorage: NewDomainTrieCertsStorage(),
roots: roots,
}, nil
}
func (c *Config) GetCA() *x509.Certificate {
return c.ca
}
func (c *Config) SetOrganization(organization string) {
c.organization = organization
}
func (c *Config) SetValidity(validity time.Duration) {
c.validity = validity
}
func (c *Config) NewTLSConfigForHost(hostname string) *tls.Config {
tlsConfig := &tls.Config{
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
host := clientHello.ServerName
if host == "" {
host = hostname
}
return c.GetOrCreateCert(host)
},
NextProtos: []string{"http/1.1"},
}
tlsConfig.InsecureSkipVerify = true
return tlsConfig
}
func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certificate, error) {
var leaf *x509.Certificate
tlsCertificate, ok := c.certsStorage.Get(hostname)
if ok {
leaf = tlsCertificate.Leaf
if _, err := leaf.Verify(x509.VerifyOptions{
DNSName: hostname,
Roots: c.roots,
}); err == nil {
return tlsCertificate, nil
}
}
var (
key = hostname
topHost = hostname
wildcardHost = "*." + hostname
dnsNames []string
)
if ip := net.ParseIP(hostname); ip != nil {
ips = append(ips, ip)
} else {
parts := strings.Split(hostname, ".")
l := len(parts)
if leaf != nil {
dnsNames = append(dnsNames, leaf.DNSNames...)
}
if l > 2 {
topIndex := l - 2
topHost = strings.Join(parts[topIndex:], ".")
for i := topIndex; i > 0; i-- {
wildcardHost = "*." + strings.Join(parts[i:], ".")
if i == topIndex && (len(dnsNames) == 0 || dnsNames[0] != topHost) {
dnsNames = append(dnsNames, topHost, wildcardHost)
} else if !hasDnsNames(dnsNames, wildcardHost) {
dnsNames = append(dnsNames, wildcardHost)
}
}
} else {
dnsNames = append(dnsNames, topHost, wildcardHost)
}
key = "+." + topHost
}
serial := atomic.AddInt64(&currentSerialNumber, 1)
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(serial),
Subject: pkix.Name{
CommonName: topHost,
Organization: []string{c.organization},
},
SubjectKeyId: c.keyID,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
NotBefore: time.Now().Add(-c.validity),
NotAfter: time.Now().Add(c.validity),
DNSNames: dnsNames,
IPAddresses: ips,
}
raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.privateKey.Public(), c.caPrivateKey)
if err != nil {
return nil, err
}
x509c, err := x509.ParseCertificate(raw)
if err != nil {
return nil, err
}
tlsCertificate = &tls.Certificate{
Certificate: [][]byte{raw, c.ca.Raw},
PrivateKey: c.privateKey,
Leaf: x509c,
}
c.certsStorage.Set(key, tlsCertificate)
return tlsCertificate, nil
}
// GenerateAndSave generate CA private key and CA certificate and dump them to file
func GenerateAndSave(caPath string, caKeyPath string) error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
Country: []string{"US"},
CommonName: "Clash Root CA",
Organization: []string{"Clash Trust Services"},
},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
NotBefore: time.Now().Add(-(time.Hour * 24 * 60)),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 25),
BasicConstraintsValid: true,
IsCA: true,
}
caRaw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, privateKey.Public(), privateKey)
if err != nil {
return err
}
caOut, err := os.OpenFile(caPath, os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return err
}
defer func(caOut *os.File) {
_ = caOut.Close()
}(caOut)
if err = pem.Encode(caOut, &pem.Block{Type: "CERTIFICATE", Bytes: caRaw}); err != nil {
return err
}
caKeyOut, err := os.OpenFile(caKeyPath, os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return err
}
defer func(caKeyOut *os.File) {
_ = caKeyOut.Close()
}(caKeyOut)
if err = pem.Encode(caKeyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil {
return err
}
return nil
}
func hasDnsNames(dnsNames []string, hostname string) bool {
for _, name := range dnsNames {
if name == hostname {
return true
}
}
return false
}

View File

@@ -1,32 +0,0 @@
package cert
import (
"crypto/tls"
"github.com/Dreamacro/clash/component/trie"
)
// DomainTrieCertsStorage cache wildcard certificates
type DomainTrieCertsStorage struct {
certsCache *trie.DomainTrie[*tls.Certificate]
}
// Get gets the certificate from the storage
func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
ca := c.certsCache.Search(key)
if ca == nil {
return nil, false
}
return ca.Data(), true
}
// Set saves the certificate to the storage
func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) {
_ = c.certsCache.Insert(key, cert)
}
func NewDomainTrieCertsStorage() *DomainTrieCertsStorage {
return &DomainTrieCertsStorage{
certsCache: trie.New[*tls.Certificate](),
}
}

View File

@@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
func TestExecCmd(t *testing.T) { func TestExecCmd(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
_, err := ExecCmd("cmd -c 'dir'") _, err := ExecCmd("dir")
assert.Nil(t, err) assert.Nil(t, err)
return return
} }

View File

@@ -50,9 +50,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
hysteria["port"] = urlHysteria.Port() hysteria["port"] = urlHysteria.Port()
hysteria["sni"] = query.Get("peer") hysteria["sni"] = query.Get("peer")
hysteria["obfs"] = query.Get("obfs") hysteria["obfs"] = query.Get("obfs")
if alpn := query.Get("alpn"); alpn != "" { hysteria["alpn"] = []string{query.Get("alpn")}
hysteria["alpn"] = strings.Split(alpn, ",")
}
hysteria["auth_str"] = query.Get("auth") hysteria["auth_str"] = query.Get("auth")
hysteria["protocol"] = query.Get("protocol") hysteria["protocol"] = query.Get("protocol")
up := query.Get("up") up := query.Get("up")
@@ -69,47 +67,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
proxies = append(proxies, hysteria) proxies = append(proxies, hysteria)
case "tuic":
// A temporary unofficial TUIC share link standard
// Modified from https://github.com/daeuniverse/dae/discussions/182
// Changes:
// 1. Support TUICv4, just replace uuid:password with token
// 2. Remove `allow_insecure` field
urlTUIC, err := url.Parse(line)
if err != nil {
continue
}
query := urlTUIC.Query()
tuic := make(map[string]any, 20)
tuic["name"] = uniqueName(names, urlTUIC.Fragment)
tuic["type"] = scheme
tuic["server"] = urlTUIC.Hostname()
tuic["port"] = urlTUIC.Port()
tuic["udp"] = true
password, v5 := urlTUIC.User.Password()
if v5 {
tuic["uuid"] = urlTUIC.User.Username()
tuic["password"] = password
} else {
tuic["token"] = urlTUIC.User.Username()
}
if cc := query.Get("congestion_control"); cc != "" {
tuic["congestion-controller"] = cc
}
if alpn := query.Get("alpn"); alpn != "" {
tuic["alpn"] = strings.Split(alpn, ",")
}
if sni := query.Get("sni"); sni != "" {
tuic["sni"] = sni
}
if query.Get("disable_sni") == "1" {
tuic["disable-sni"] = true
}
if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" {
tuic["udp-relay-mode"] = udpRelayMode
}
case "trojan": case "trojan":
urlTrojan, err := url.Parse(line) urlTrojan, err := url.Parse(line)
if err != nil { if err != nil {
@@ -129,12 +86,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
trojan["udp"] = true trojan["udp"] = true
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure")) trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
if sni := query.Get("sni"); sni != "" { sni := query.Get("sni")
if sni != "" {
trojan["sni"] = sni trojan["sni"] = sni
} }
if alpn := query.Get("alpn"); alpn != "" {
trojan["alpn"] = strings.Split(alpn, ",")
}
network := strings.ToLower(query.Get("type")) network := strings.ToLower(query.Get("type"))
if network != "" { if network != "" {
@@ -262,9 +217,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if strings.HasSuffix(tls, "tls") { if strings.HasSuffix(tls, "tls") {
vmess["tls"] = true vmess["tls"] = true
} }
if alpn, ok := values["alpn"].(string); ok {
vmess["alpn"] = strings.Split(alpn, ",")
}
} }
switch network { switch network {
@@ -380,7 +332,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
} }
proxies = append(proxies, ss) proxies = append(proxies, ss)
case "ssr": case "ssr":
dcBuf, err := encRaw.DecodeString(body) dcBuf, err := encRaw.DecodeString(body)
if err != nil { if err != nil {

View File

@@ -24,6 +24,8 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
proxy["port"] = url.Port() proxy["port"] = url.Port()
proxy["uuid"] = url.User.Username() proxy["uuid"] = url.User.Username()
proxy["udp"] = true proxy["udp"] = true
proxy["skip-cert-verify"] = false
proxy["tls"] = false
tls := strings.ToLower(query.Get("security")) tls := strings.ToLower(query.Get("security"))
if strings.HasSuffix(tls, "tls") || tls == "reality" { if strings.HasSuffix(tls, "tls") || tls == "reality" {
proxy["tls"] = true proxy["tls"] = true
@@ -32,9 +34,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
} else { } else {
proxy["client-fingerprint"] = fingerprint proxy["client-fingerprint"] = fingerprint
} }
if alpn := query.Get("alpn"); alpn != "" {
proxy["alpn"] = strings.Split(alpn, ",")
}
} }
if sni := query.Get("sni"); sni != "" { if sni := query.Get("sni"); sni != "" {
proxy["servername"] = sni proxy["servername"] = sni

View File

@@ -4,11 +4,8 @@ import (
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time"
) )
var KeepAliveInterval time.Duration
func SplitNetworkType(s string) (string, string, error) { func SplitNetworkType(s string) (string, string, error) {
var ( var (
shecme string shecme string
@@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
host, port, err = net.SplitHostPort(temp) host, port, err = net.SplitHostPort(temp)
return return
} }
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval * time.Second)
}
}

View File

@@ -47,7 +47,6 @@ func (p *Picker[T]) Wait() T {
p.wg.Wait() p.wg.Wait()
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
p.cancel = nil
} }
return p.result return p.result
} }
@@ -70,7 +69,6 @@ func (p *Picker[T]) Go(f func() (T, error)) {
p.result = ret p.result = ret
if p.cancel != nil { if p.cancel != nil {
p.cancel() p.cancel()
p.cancel = nil
} }
}) })
} else { } else {
@@ -80,13 +78,3 @@ func (p *Picker[T]) Go(f func() (T, error)) {
} }
}() }()
} }
// Close cancels the picker context and releases resources associated with it.
// If Wait has been called, then there is no need to call Close.
func (p *Picker[T]) Close() error {
if p.cancel != nil {
p.cancel()
p.cancel = nil
}
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/samber/lo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -16,7 +15,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
case <-timer.C: case <-timer.C:
return input, nil return input, nil
case <-ctx.Done(): case <-ctx.Done():
return lo.Empty[T](), ctx.Err() return getZero[T](), ctx.Err()
} }
} }
} }
@@ -36,6 +35,11 @@ func TestPicker_Timeout(t *testing.T) {
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 20, 1))
number := picker.Wait() number := picker.Wait()
assert.Equal(t, number, lo.Empty[int]()) assert.Equal(t, number, getZero[int]())
assert.NotNil(t, picker.Error()) assert.NotNil(t, picker.Error())
} }
func getZero[T any]() T {
var result T
return result
}

View File

@@ -32,14 +32,10 @@ func NewAllocator() *Allocator {
// Get a []byte from pool with most appropriate cap // Get a []byte from pool with most appropriate cap
func (alloc *Allocator) Get(size int) []byte { func (alloc *Allocator) Get(size int) []byte {
switch { if size <= 0 || size > 65536 {
case size < 0:
panic("alloc.Get: len out of range")
case size == 0:
return nil return nil
case size > 65536: }
return make([]byte, size)
default:
bits := msb(size) bits := msb(size)
if size == 1<<bits { if size == 1<<bits {
return alloc.buffers[bits].Get().([]byte)[:size] return alloc.buffers[bits].Get().([]byte)[:size]
@@ -47,17 +43,12 @@ func (alloc *Allocator) Get(size int) []byte {
return alloc.buffers[bits+1].Get().([]byte)[:size] return alloc.buffers[bits+1].Get().([]byte)[:size]
} }
}
// Put returns a []byte to pool for future use, // Put returns a []byte to pool for future use,
// which the cap must be exactly 2^n // which the cap must be exactly 2^n
func (alloc *Allocator) Put(buf []byte) error { func (alloc *Allocator) Put(buf []byte) error {
if cap(buf) == 0 || cap(buf) > 65536 {
return nil
}
bits := msb(cap(buf)) bits := msb(cap(buf))
if cap(buf) != 1<<bits { if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
return errors.New("allocator Put() incorrect buffer size") return errors.New("allocator Put() incorrect buffer size")
} }

View File

@@ -19,17 +19,17 @@ func TestAllocGet(t *testing.T) {
assert.Equal(t, 1024, cap(alloc.Get(1023))) assert.Equal(t, 1024, cap(alloc.Get(1023)))
assert.Equal(t, 1024, len(alloc.Get(1024))) assert.Equal(t, 1024, len(alloc.Get(1024)))
assert.Equal(t, 65536, len(alloc.Get(65536))) assert.Equal(t, 65536, len(alloc.Get(65536)))
assert.Equal(t, 65537, len(alloc.Get(65537))) assert.Nil(t, alloc.Get(65537))
} }
func TestAllocPut(t *testing.T) { func TestAllocPut(t *testing.T) {
alloc := NewAllocator() alloc := NewAllocator()
assert.Nil(t, alloc.Put(nil), "put nil misbehavior") assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior") assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior") assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior") assert.Nil(t, alloc.Put(make([]byte, 1023, 1024)), "put elem:1024 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior") assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior") assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
} }
func TestAllocPutThenGet(t *testing.T) { func TestAllocPutThenGet(t *testing.T) {

View File

@@ -2,8 +2,6 @@ package queue
import ( import (
"sync" "sync"
"github.com/samber/lo"
) )
// Queue is a simple concurrent safe queue // Queue is a simple concurrent safe queue
@@ -26,7 +24,7 @@ func (q *Queue[T]) Put(items ...T) {
// Pop returns the head of items. // Pop returns the head of items.
func (q *Queue[T]) Pop() T { func (q *Queue[T]) Pop() T {
if len(q.items) == 0 { if len(q.items) == 0 {
return lo.Empty[T]() return GetZero[T]()
} }
q.lock.Lock() q.lock.Lock()
@@ -39,7 +37,7 @@ func (q *Queue[T]) Pop() T {
// Last returns the last of item. // Last returns the last of item.
func (q *Queue[T]) Last() T { func (q *Queue[T]) Last() T {
if len(q.items) == 0 { if len(q.items) == 0 {
return lo.Empty[T]() return GetZero[T]()
} }
q.lock.RLock() q.lock.RLock()
@@ -71,3 +69,8 @@ func New[T any](hint int64) *Queue[T] {
items: make([]T, 0, hint), items: make([]T, 0, hint),
} }
} }
func GetZero[T any]() T {
var result T
return result
}

View File

@@ -96,11 +96,6 @@ func (d *Decoder) decode(name string, data any, val reflect.Value) error {
return d.decodeFloat(name, data, val) return d.decodeFloat(name, data, val)
} }
switch kind { switch kind {
case reflect.Pointer:
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
return d.decode(name, data, val.Elem())
case reflect.String: case reflect.String:
return d.decodeString(name, data, val) return d.decodeString(name, data, val)
case reflect.Bool: case reflect.Bool:
@@ -287,9 +282,6 @@ func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
} }
valSlice := val valSlice := val
// make a new slice with cap(val)==cap(dataVal)
// the caller can determine whether the original configuration contains this item by judging whether the value is nil.
valSlice = reflect.MakeSlice(valType, 0, dataVal.Len())
for i := 0; i < dataVal.Len(); i++ { for i := 0; i < dataVal.Len(); i++ {
currentData := dataVal.Index(i).Interface() currentData := dataVal.Index(i).Interface()
for valSlice.Len() <= i { for valSlice.Len() <= i {

View File

@@ -1,17 +0,0 @@
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)
}

View File

@@ -9,36 +9,36 @@ type Range[T constraints.Ordered] struct {
end T 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 { if start > end {
return Range[T]{ return &Range[T]{
start: end, start: end,
end: start, end: start,
} }
} }
return Range[T]{ return &Range[T]{
start: start, start: start,
end: end, 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 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 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 return t > r.start && t <= r.end
} }
func (r Range[T]) Start() T { func (r *Range[T]) Start() T {
return r.start return r.start
} }
func (r Range[T]) End() T { func (r *Range[T]) End() T {
return r.end return r.end
} }

View File

@@ -1,77 +0,0 @@
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
}

View File

@@ -1,21 +0,0 @@
package utils
import "unsafe"
// 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 {
b := unsafe.StringData(s)
return unsafe.Slice(b, len(s))
}
// 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 {
if len(bs) == 0 {
return ""
}
return unsafe.String(&bs[0], len(bs))
}

View File

@@ -1,7 +1,7 @@
package auth package auth
import ( import (
"github.com/puzpuzpuz/xsync/v2" "sync"
) )
type Authenticator interface { type Authenticator interface {
@@ -15,7 +15,7 @@ type AuthUser struct {
} }
type inMemoryAuthenticator struct { type inMemoryAuthenticator struct {
storage *xsync.MapOf[string, string] storage *sync.Map
usernames []string usernames []string
} }
@@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
return nil return nil
} }
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()} au := &inMemoryAuthenticator{storage: &sync.Map{}}
for _, user := range users { for _, user := range users {
au.storage.Store(user.User, user.Pass) au.storage.Store(user.User, user.Pass)
} }
usernames := make([]string, 0, len(users)) usernames := make([]string, 0, len(users))
au.storage.Range(func(key string, value string) bool { au.storage.Range(func(key, value any) bool {
usernames = append(usernames, key) usernames = append(usernames, key.(string))
return true return true
}) })
au.usernames = usernames au.usernames = usernames

View File

@@ -1,51 +0,0 @@
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
}

View File

@@ -7,8 +7,52 @@ import (
"net/netip" "net/netip"
"strconv" "strconv"
"strings" "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 { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
if !destination.IsGlobalUnicast() { if !destination.IsGlobalUnicast() {
return nil return nil
@@ -22,7 +66,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
} }
} }
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local)) addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
if err != nil { if err != nil {
return err return err
} }
@@ -40,7 +84,7 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
local, _ := strconv.ParseUint(port, 10, 16) local, _ := strconv.ParseUint(port, 10, 16)
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local)) addr, err := lookupLocalAddr(ifaceName, network, netip.Addr{}, int(local))
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -20,20 +20,3 @@ func addControlToListenConfig(lc *net.ListenConfig, fn controlFn) {
return fn(context.Background(), network, address, c) return fn(context.Background(), network, address, c)
} }
} }
func addControlToDialer(d *net.Dialer, fn controlFn) {
ld := *d
d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
switch {
case ld.ControlContext != nil:
if err = ld.ControlContext(ctx, network, address, c); err != nil {
return
}
case ld.Control != nil:
if err = ld.Control(network, address, c); err != nil {
return
}
}
return fn(ctx, network, address, c)
}
}

View File

@@ -0,0 +1,22 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
"syscall"
)
func addControlToDialer(d *net.Dialer, fn controlFn) {
ld := *d
d.Control = func(network, address string, c syscall.RawConn) (err error) {
switch {
case ld.Control != nil:
if err = ld.Control(network, address, c); err != nil {
return
}
}
return fn(context.Background(), network, address, c)
}
}

View File

@@ -0,0 +1,26 @@
//go:build go1.20
package dialer
import (
"context"
"net"
"syscall"
)
func addControlToDialer(d *net.Dialer, fn controlFn) {
ld := *d
d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
switch {
case ld.ControlContext != nil:
if err = ld.ControlContext(ctx, network, address, c); err != nil {
return
}
case ld.Control != nil:
if err = ld.Control(network, address, c); err != nil {
return
}
}
return fn(ctx, network, address, c)
}
}

View File

@@ -2,7 +2,6 @@ package dialer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@@ -132,9 +131,6 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if opt.routingMark != 0 { if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination) bindMarkToDialer(opt.routingMark, dialer, network, destination)
} }
if opt.mpTcp {
setMultiPathTCP(dialer)
}
if opt.tfo { if opt.tfo {
return dialTFO(ctx, *dialer, network, address) return dialTFO(ctx, *dialer, network, address)
} }
@@ -162,22 +158,14 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
ipv4s, ipv6s := resolver.SortationAddr(ips) ipv4s, ipv6s := resolver.SortationAddr(ips)
if len(ipv4s) == 0 && len(ipv6s) == 0 {
return nil, ErrorNoIpAddress
}
preferIPVersion := opt.prefer preferIPVersion := opt.prefer
fallbackTicker := time.NewTicker(fallbackTimeout) fallbackTicker := time.NewTicker(fallbackTimeout)
defer fallbackTicker.Stop() defer fallbackTicker.Stop()
results := make(chan dialResult) results := make(chan dialResult)
returned := make(chan struct{}) returned := make(chan struct{})
defer close(returned) defer close(returned)
var wg sync.WaitGroup
racer := func(ips []netip.Addr, isPrimary bool) { racer := func(ips []netip.Addr, isPrimary bool) {
defer wg.Done()
result := dialResult{isPrimary: isPrimary} result := dialResult{isPrimary: isPrimary}
defer func() { defer func() {
select { select {
@@ -190,36 +178,18 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
}() }()
result.Conn, result.error = dialFn(ctx, network, ips, port, opt) result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
} }
if len(ipv4s) != 0 {
wg.Add(1)
go racer(ipv4s, preferIPVersion != 6) go racer(ipv4s, preferIPVersion != 6)
}
if len(ipv6s) != 0 {
wg.Add(1)
go racer(ipv6s, preferIPVersion != 4) go racer(ipv6s, preferIPVersion != 4)
}
go func() {
wg.Wait()
close(results)
}()
var fallback dialResult var fallback dialResult
var errs []error var errs []error
for i := 0; i < 2; {
loop:
for {
select { select {
case <-fallbackTicker.C: case <-fallbackTicker.C:
if fallback.error == nil && fallback.Conn != nil { if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil return fallback.Conn, nil
} }
case res, ok := <-results: case res := <-results:
if !ok { i++
break loop
}
if res.error == nil { if res.error == nil {
if res.isPrimary { if res.isPrimary {
return res.Conn, nil return res.Conn, nil
@@ -234,11 +204,10 @@ loop:
} }
} }
} }
if fallback.error == nil && fallback.Conn != nil { if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil return fallback.Conn, nil
} }
return nil, errors.Join(errs...) return nil, errorsJoin(errs...)
} }
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
@@ -275,7 +244,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
} }
if len(errs) > 0 { if len(errs) > 0 {
return nil, errors.Join(errs...) return nil, errorsJoin(errs...)
} }
return nil, os.ErrDeadlineExceeded return nil, os.ErrDeadlineExceeded
} }
@@ -292,7 +261,7 @@ func serialDialContext(ctx context.Context, network string, ips []netip.Addr, po
errs = append(errs, err) errs = append(errs, err)
} }
} }
return nil, errors.Join(errs...) return nil, errorsJoin(errs...)
} }
type dialResult struct { type dialResult struct {

View File

@@ -2,9 +2,17 @@ package dialer
import ( import (
"errors" "errors"
E "github.com/sagernet/sing/common/exceptions"
) )
var ( var (
ErrorNoIpAddress = errors.New("no ip address") ErrorNoIpAddress = errors.New("no ip address")
ErrorInvalidedNetworkStack = errors.New("invalided network stack") ErrorInvalidedNetworkStack = errors.New("invalided network stack")
) )
func errorsJoin(errs ...error) error {
// compatibility with golang<1.20
// maybe use errors.Join(errs...) is better after we drop the old version's support
return E.Errors(errs...)
}

View File

@@ -1,12 +0,0 @@
//go:build !go1.21
package dialer
import (
"net"
)
const multipathTCPAvailable = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package dialer
import "net"
const multipathTCPAvailable = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -25,7 +25,6 @@ type option struct {
network int network int
prefer int prefer int
tfo bool tfo bool
mpTcp bool
resolver resolver.Resolver resolver resolver.Resolver
netDialer NetDialer netDialer NetDialer
} }
@@ -84,12 +83,6 @@ func WithTFO(tfo bool) Option {
} }
} }
func WithMPTCP(mpTcp bool) Option {
return func(opt *option) {
opt.mpTcp = mpTcp
}
}
func WithNetDialer(netDialer NetDialer) Option { func WithNetDialer(netDialer NetDialer) Option {
return func(opt *option) { return func(opt *option) {
opt.netDialer = netDialer opt.netDialer = netDialer

View File

@@ -32,7 +32,7 @@ func (g GeoIPCache) Set(key string, value *router.GeoIP) {
} }
func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) { func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) {
asset := C.Path.Resolve(filename) asset := C.Path.GetAssetLocation(filename)
idx := strings.ToLower(asset + ":" + code) idx := strings.ToLower(asset + ":" + code)
if g.Has(idx) { if g.Has(idx) {
return g.Get(idx), nil return g.Get(idx), nil
@@ -97,7 +97,7 @@ func (g GeoSiteCache) Set(key string, value *router.GeoSite) {
} }
func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error) { func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error) {
asset := C.Path.Resolve(filename) asset := C.Path.GetAssetLocation(filename)
idx := strings.ToLower(asset + ":" + code) idx := strings.ToLower(asset + ":" + code)
if g.Has(idx) { if g.Has(idx) {
return g.Get(idx), nil return g.Get(idx), nil

View File

@@ -26,7 +26,7 @@ func ReadFile(path string) ([]byte, error) {
} }
func ReadAsset(file string) ([]byte, error) { func ReadAsset(file string) ([]byte, error) {
return ReadFile(C.Path.Resolve(file)) return ReadFile(C.Path.GetAssetLocation(file))
} }
func loadIP(geoipBytes []byte, country string) ([]*router.CIDR, error) { func loadIP(geoipBytes []byte, country string) ([]*router.CIDR, error) {

View File

@@ -4,7 +4,6 @@ import (
"errors" "errors"
"net" "net"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
@@ -38,21 +37,12 @@ func ResolveInterface(name string) (*Interface, error) {
if err != nil { if err != nil {
continue continue
} }
// if not available device like Meta, dummy0, docker0, etc.
if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&net.FlagRunning == 0) {
continue
}
ipNets := make([]*netip.Prefix, 0, len(addrs)) ipNets := make([]*netip.Prefix, 0, len(addrs))
for _, addr := range addrs { for _, addr := range addrs {
ipNet := addr.(*net.IPNet) ipNet := addr.(*net.IPNet)
ip, _ := netip.AddrFromSlice(ipNet.IP) ip, _ := netip.AddrFromSlice(ipNet.IP)
//unavailable IPv6 Address
if ip.Is6() && strings.HasPrefix(ip.String(), "fe80") {
continue
}
ones, bits := ipNet.Mask.Size() ones, bits := ipNet.Mask.Size()
if bits == 32 { if bits == 32 {
ip = ip.Unmap() ip = ip.Unmap()

View File

@@ -12,68 +12,42 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/geoip2-golang"
)
type databaseType = uint8
const (
typeMaxmind databaseType = iota
typeSing
typeMetaV0
) )
var ( var (
reader Reader mmdb *geoip2.Reader
once sync.Once once sync.Once
) )
func LoadFromBytes(buffer []byte) { func LoadFromBytes(buffer []byte) {
once.Do(func() { once.Do(func() {
mmdb, err := maxminddb.FromBytes(buffer) var err error
mmdb, err = geoip2.FromBytes(buffer)
if err != nil { if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error()) log.Fatalln("Can't load mmdb: %s", err.Error())
} }
reader = Reader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
}
}) })
} }
func Verify() bool { func Verify() bool {
instance, err := maxminddb.Open(C.Path.MMDB()) instance, err := geoip2.Open(C.Path.MMDB())
if err == nil { if err == nil {
instance.Close() instance.Close()
} }
return err == nil return err == nil
} }
func Instance() Reader { func Instance() *geoip2.Reader {
once.Do(func() { once.Do(func() {
mmdbPath := C.Path.MMDB() var err error
log.Debugln("Load MMDB file: %s", mmdbPath) mmdb, err = geoip2.Open(C.Path.MMDB())
mmdb, err := maxminddb.Open(mmdbPath)
if err != nil { if err != nil {
log.Fatalln("Can't load MMDB: %s", err.Error()) log.Fatalln("Can't load mmdb: %s", err.Error())
}
reader = Reader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType {
case "sing-geoip":
reader.databaseType = typeSing
case "Meta-geoip0":
reader.databaseType = typeMetaV0
default:
reader.databaseType = typeMaxmind
} }
}) })
return reader return mmdb
} }
func DownloadMMDB(path string) (err error) { func DownloadMMDB(path string) (err error) {

View File

@@ -1,56 +0,0 @@
package mmdb
import (
"fmt"
"net"
"github.com/oschwald/maxminddb-golang"
"github.com/sagernet/sing/common"
)
type geoip2Country struct {
Country struct {
IsoCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
}
type Reader struct {
*maxminddb.Reader
databaseType
}
func (r Reader) LookupCode(ipAddress net.IP) []string {
switch r.databaseType {
case typeMaxmind:
var country geoip2Country
_ = r.Lookup(ipAddress, &country)
if country.Country.IsoCode == "" {
return []string{}
}
return []string{country.Country.IsoCode}
case typeSing:
var code string
_ = r.Lookup(ipAddress, &code)
if code == "" {
return []string{}
}
return []string{code}
case typeMetaV0:
var record any
_ = r.Lookup(ipAddress, &record)
switch record := record.(type) {
case string:
return []string{record}
case []any: // lookup returned type of slice is []any
return common.Map(record, func(it any) string {
return it.(string)
})
}
return []string{}
default:
panic(fmt.Sprint("unknown geoip database type:", r.databaseType))
}
}

View File

@@ -1,26 +0,0 @@
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
}

View File

@@ -5,53 +5,42 @@ import (
"sync" "sync"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/puzpuzpuz/xsync/v2"
) )
type Table struct { type Table struct {
mapping *xsync.MapOf[string, *Entry] mapping sync.Map
lockMap *xsync.MapOf[string, *sync.Cond]
} }
type Entry struct { type Entry struct {
PacketConn C.PacketConn PacketConn C.PacketConn
WriteBackProxy C.WriteBackProxy LocalUDPConnMap sync.Map
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
LocalLockMap *xsync.MapOf[string, *sync.Cond]
} }
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) { func (t *Table) Set(key string, e C.PacketConn) {
t.mapping.Store(key, &Entry{ t.mapping.Store(key, &Entry{
PacketConn: e, PacketConn: e,
WriteBackProxy: w, LocalUDPConnMap: sync.Map{},
LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](),
LocalLockMap: xsync.NewMapOf[*sync.Cond](),
}) })
} }
func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) { func (t *Table) Get(key string) C.PacketConn {
entry, exist := t.getEntry(key) entry, exist := t.getEntry(key)
if !exist { if !exist {
return nil, nil return nil
} }
return entry.PacketConn, entry.WriteBackProxy return entry.PacketConn
} }
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.lockMap.LoadOrCompute(key, makeLock) item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item, loaded return item.(*sync.Cond), loaded
} }
func (t *Table) Delete(key string) { func (t *Table) Delete(key string) {
t.mapping.Delete(key) t.mapping.Delete(key)
} }
func (t *Table) DeleteLock(lockKey string) { func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
t.lockMap.Delete(lockKey)
}
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
entry, exist := t.getEntry(lAddr) entry, exist := t.getEntry(lAddr)
if !exist { if !exist {
return nil return nil
@@ -60,10 +49,10 @@ func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
if !exist { if !exist {
return nil return nil
} }
return item return item.(*net.UDPConn)
} }
func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool { func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
entry, exist := t.getEntry(lAddr) entry, exist := t.getEntry(lAddr)
if !exist { if !exist {
return false return false
@@ -72,7 +61,7 @@ func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
return true return true
} }
func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) { func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
entry, exist := t.getEntry(lAddr) entry, exist := t.getEntry(lAddr)
if !exist { if !exist {
return return
@@ -85,11 +74,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
if !loaded { if !loaded {
return nil, false return nil, false
} }
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock) item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item, loaded return item.(*sync.Cond), loaded
} }
func (t *Table) DeleteForLocalConn(lAddr, key string) { func (t *Table) DeleteLocalConnMap(lAddr, key string) {
entry, loaded := t.getEntry(lAddr) entry, loaded := t.getEntry(lAddr)
if !loaded { if !loaded {
return return
@@ -97,26 +86,17 @@ func (t *Table) DeleteForLocalConn(lAddr, key string) {
entry.LocalUDPConnMap.Delete(key) entry.LocalUDPConnMap.Delete(key)
} }
func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
entry, loaded := t.getEntry(lAddr)
if !loaded {
return
}
entry.LocalLockMap.Delete(key)
}
func (t *Table) getEntry(key string) (*Entry, bool) { func (t *Table) getEntry(key string) (*Entry, bool) {
return t.mapping.Load(key) item, ok := t.mapping.Load(key)
// This should not happen usually since this function called after PacketConn created
if !ok {
return nil, false
} }
entry, ok := item.(*Entry)
func makeLock() *sync.Cond { return entry, ok
return sync.NewCond(&sync.Mutex{})
} }
// New return *Cache // New return *Cache
func New() *Table { func New() *Table {
return &Table{ return &Table{}
mapping: xsync.NewMapOf[*Entry](),
lockMap: xsync.NewMapOf[*sync.Cond](),
}
} }

View File

@@ -67,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
log.Warnln("All PROCESS-NAMES rules will be skipped") log.Warnln("All PROCESS-NAMES rules will be skiped")
return return
} }
}) })

View File

@@ -9,8 +9,6 @@ import (
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/samber/lo"
) )
var ( var (
@@ -67,7 +65,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
} }
if err != nil { if err != nil {
return lo.Empty[V](), err return getZero[V](), err
} }
var contents V var contents V
@@ -87,18 +85,18 @@ func (f *Fetcher[V]) Initial() (V, error) {
if err != nil { if err != nil {
if !isLocal { if !isLocal {
return lo.Empty[V](), err return getZero[V](), err
} }
// parse local file error, fallback to remote // parse local file error, fallback to remote
buf, err = f.vehicle.Read() buf, err = f.vehicle.Read()
if err != nil { if err != nil {
return lo.Empty[V](), err return getZero[V](), err
} }
contents, err = f.parser(buf) contents, err = f.parser(buf)
if err != nil { if err != nil {
return lo.Empty[V](), err return getZero[V](), err
} }
isLocal = false isLocal = false
@@ -106,7 +104,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
if f.vehicle.Type() != types.File && !isLocal { if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return lo.Empty[V](), err return getZero[V](), err
} }
} }
@@ -123,7 +121,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
func (f *Fetcher[V]) Update() (V, bool, error) { func (f *Fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read() buf, err := f.vehicle.Read()
if err != nil { if err != nil {
return lo.Empty[V](), false, err return getZero[V](), false, err
} }
now := time.Now() now := time.Now()
@@ -131,17 +129,17 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
if bytes.Equal(f.hash[:], hash[:]) { if bytes.Equal(f.hash[:], hash[:]) {
f.UpdatedAt = &now f.UpdatedAt = &now
_ = os.Chtimes(f.vehicle.Path(), now, now) _ = os.Chtimes(f.vehicle.Path(), now, now)
return lo.Empty[V](), true, nil return getZero[V](), true, nil
} }
contents, err := f.parser(buf) contents, err := f.parser(buf)
if err != nil { if err != nil {
return lo.Empty[V](), false, err return getZero[V](), false, err
} }
if f.vehicle.Type() != types.File { if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return lo.Empty[V](), false, err return getZero[V](), false, err
} }
} }
@@ -212,3 +210,8 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
interval: interval, interval: interval,
} }
} }
func getZero[V any]() V {
var result V
return result
}

View File

@@ -2,14 +2,12 @@ package resource
import ( import (
"context" "context"
"errors" clashHttp "github.com/Dreamacro/clash/component/http"
types "github.com/Dreamacro/clash/constant/provider"
"io" "io"
"net/http" "net/http"
"os" "os"
"time" "time"
clashHttp "github.com/Dreamacro/clash/component/http"
types "github.com/Dreamacro/clash/constant/provider"
) )
type FileVehicle struct { type FileVehicle struct {
@@ -56,10 +54,8 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, errors.New(resp.Status)
}
buf, err := io.ReadAll(resp.Body) buf, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -10,11 +10,11 @@ import (
type SnifferConfig struct { type SnifferConfig struct {
OverrideDest bool OverrideDest bool
Ports utils.IntRanges[uint16] Ports []utils.Range[uint16]
} }
type BaseSniffer struct { type BaseSniffer struct {
ports utils.IntRanges[uint16] ports []utils.Range[uint16]
supportNetworkType constant.NetWork supportNetworkType constant.NetWork
} }
@@ -35,10 +35,15 @@ func (bs *BaseSniffer) SupportNetwork() constant.NetWork {
// SupportPort implements sniffer.Sniffer // SupportPort implements sniffer.Sniffer
func (bs *BaseSniffer) SupportPort(port uint16) bool { func (bs *BaseSniffer) SupportPort(port uint16) bool {
return bs.ports.Check(port) for _, portRange := range bs.ports {
if portRange.Contains(port) {
return true
}
}
return false
} }
func NewBaseSniffer(ports utils.IntRanges[uint16], networkType constant.NetWork) *BaseSniffer { func NewBaseSniffer(ports []utils.Range[uint16], networkType constant.NetWork) *BaseSniffer {
return &BaseSniffer{ return &BaseSniffer{
ports: ports, ports: ports,
supportNetworkType: networkType, supportNetworkType: networkType,

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"strconv"
"sync" "sync"
"time" "time"
@@ -37,11 +38,17 @@ type SnifferDispatcher struct {
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil {
log.Debugln("[Sniffer] Dst port is error")
return
}
inWhitelist := false inWhitelist := false
overrideDest := false overrideDest := false
for sniffer, config := range sd.sniffers { for sniffer, config := range sd.sniffers {
if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet { if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet {
inWhitelist = sniffer.SupportPort(metadata.DstPort) inWhitelist = sniffer.SupportPort(uint16(port))
if inWhitelist { if inWhitelist {
overrideDest = config.OverrideDest overrideDest = config.OverrideDest
break break
@@ -54,7 +61,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
} }
sd.rwMux.RLock() sd.rwMux.RLock()
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
if count, ok := sd.skipList.Get(dst); ok && count > 5 { if count, ok := sd.skipList.Get(dst); ok && count > 5 {
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst) log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
defer sd.rwMux.RUnlock() defer sd.rwMux.RUnlock()
@@ -64,7 +71,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
if host, err := sd.sniffDomain(conn, metadata); err != nil { if host, err := sd.sniffDomain(conn, metadata); err != nil {
sd.cacheSniffFailed(metadata) sd.cacheSniffFailed(metadata)
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return return
} else { } else {
if sd.skipSNI.Has(host) { if sd.skipSNI.Has(host) {
@@ -142,7 +149,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) { func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) {
sd.rwMux.Lock() sd.rwMux.Lock()
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort) dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
count, _ := sd.skipList.Get(dst) count, _ := sd.skipList.Get(dst)
if count <= 5 { if count <= 5 {
count++ count++

View File

@@ -34,9 +34,11 @@ type HTTPSniffer struct {
var _ sniffer.Sniffer = (*HTTPSniffer)(nil) var _ sniffer.Sniffer = (*HTTPSniffer)(nil)
func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) { func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) {
ports := snifferConfig.Ports ports := make([]utils.Range[uint16], 0)
if len(ports) == 0 { if len(snifferConfig.Ports) == 0 {
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](80, 80)} ports = append(ports, *utils.NewRange[uint16](80, 80))
} else {
ports = append(ports, snifferConfig.Ports...)
} }
return &HTTPSniffer{ return &HTTPSniffer{
BaseSniffer: NewBaseSniffer(ports, C.TCP), BaseSniffer: NewBaseSniffer(ports, C.TCP),

View File

@@ -22,9 +22,11 @@ type TLSSniffer struct {
} }
func NewTLSSniffer(snifferConfig SnifferConfig) (*TLSSniffer, error) { func NewTLSSniffer(snifferConfig SnifferConfig) (*TLSSniffer, error) {
ports := snifferConfig.Ports ports := make([]utils.Range[uint16], 0)
if len(ports) == 0 { if len(snifferConfig.Ports) == 0 {
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)} ports = append(ports, *utils.NewRange[uint16](443, 443))
} else {
ports = append(ports, snifferConfig.Ports...)
} }
return &TLSSniffer{ return &TLSSniffer{
BaseSniffer: NewBaseSniffer(ports, C.TCP), BaseSniffer: NewBaseSniffer(ports, C.TCP),

View File

@@ -10,12 +10,14 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
xtls "github.com/xtls/go"
) )
var trustCerts []*x509.Certificate var trustCerts []*x509.Certificate
var certPool *x509.CertPool var certPool *x509.CertPool
var mutex sync.RWMutex var mutex sync.RWMutex
var errNotMatch = errors.New("certificate fingerprints do not match") var errNotMacth error = errors.New("certificate fingerprints do not match")
func AddCertificate(certificate string) error { func AddCertificate(certificate string) error {
mutex.Lock() mutex.Lock()
@@ -77,7 +79,7 @@ func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedCh
} }
} }
} }
return errNotMatch return errNotMacth
} }
} }
@@ -120,3 +122,27 @@ func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
return tlsConfig return tlsConfig
} }
// GetSpecifiedFingerprintXTLSConfig specified fingerprint
func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint string) (*xtls.Config, error) {
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
return nil, err
} else {
tlsConfig = GetGlobalXTLSConfig(tlsConfig)
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
}
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
certPool := getCertPool()
if tlsConfig == nil {
return &xtls.Config{
RootCAs: certPool,
}
}
tlsConfig.RootCAs = certPool
return tlsConfig
}

View File

@@ -22,11 +22,9 @@ import (
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/ntp"
utls "github.com/sagernet/utls" utls "github.com/sagernet/utls"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@@ -39,9 +37,6 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte 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) { func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists { if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
verifier := &realityVerifier{ verifier := &realityVerifier{
@@ -66,17 +61,17 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
} }
hello := uConn.HandshakeState.Hello hello := uConn.HandshakeState.Hello
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID for i := range hello.SessionId { // https://github.com/golang/go/issues/5373
for i := range rawSessionID { // https://github.com/golang/go/issues/5373 hello.SessionId[i] = 0
rawSessionID[i] = 0
} }
copy(hello.Raw[39:], hello.SessionId)
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix())) binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
copy(hello.SessionId[8:], realityConfig.ShortID[:])
hello.SessionId[0] = 1 hello.SessionId[0] = 1
hello.SessionId[1] = 8 hello.SessionId[1] = 8
hello.SessionId[2] = 2 hello.SessionId[2] = 0
copy(hello.SessionId[8:], realityConfig.ShortID[:])
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16]) //log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
@@ -89,14 +84,9 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
if err != nil { if err != nil {
return nil, err return nil, err
} }
var aeadCipher cipher.AEAD
if aesgcmPreferred(hello.CipherSuites) {
aesBlock, _ := aes.NewCipher(authKey) aesBlock, _ := aes.NewCipher(authKey)
aeadCipher, _ = cipher.NewGCM(aesBlock) aesGcmCipher, _ := cipher.NewGCM(aesBlock)
} else { aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
aeadCipher, _ = chacha20poly1305.New(authKey)
}
aeadCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
copy(hello.Raw[39:], hello.SessionId) copy(hello.Raw[39:], hello.SessionId)
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId) //log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
//log.Debugln("REALITY uConn.AuthKey: %v", authKey) //log.Debugln("REALITY uConn.AuthKey: %v", authKey)
@@ -106,7 +96,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
return nil, err return nil, err
} }
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher) log.Debugln("REALITY Authentication: %v", verifier.verified)
if !verifier.verified { if !verifier.verified {
go realityClientFallback(uConn, uConfig.ServerName, clientID) go realityClientFallback(uConn, uConfig.ServerName, clientID)
@@ -147,7 +137,7 @@ type realityVerifier struct {
verified bool verified bool
} }
var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset var pOffset = utils.MustOK(reflect.TypeOf((*utls.UConn)(nil)).Elem().FieldByName("peerCertificates")).Offset
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") //p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")

View File

@@ -23,8 +23,6 @@ type DomainSet struct {
ranks, selects []int32 ranks, selects []int32
} }
type qElt struct{ s, e, col int }
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie. // NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
func (t *DomainTrie[T]) NewDomainSet() *DomainSet { func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
reserveDomains := make([]string, 0) reserveDomains := make([]string, 0)
@@ -41,6 +39,7 @@ func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
ss := &DomainSet{} ss := &DomainSet{}
lIdx := 0 lIdx := 0
type qElt struct{ s, e, col int }
queue := []qElt{{0, len(keys), 0}} queue := []qElt{{0, len(keys), 0}}
for i := 0; i < len(queue); i++ { for i := 0; i < len(queue); i++ {
elt := queue[i] elt := queue[i]

View File

@@ -1,9 +1,8 @@
package trie package trie
import ( import (
"net"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"net"
) )
type IPV6 bool type IPV6 bool
@@ -48,10 +47,11 @@ func (trie *IpCidrTrie) AddIpCidrForString(ipCidr string) error {
} }
func (trie *IpCidrTrie) IsContain(ip net.IP) bool { func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
ip, isIpv4 := checkAndConverterIp(ip)
if ip == nil { if ip == nil {
return false return false
} }
isIpv4 := len(ip) == net.IPv4len
var groupValues []uint32 var groupValues []uint32
var ipCidrNode *IpCidrNode var ipCidrNode *IpCidrNode
@@ -71,13 +71,7 @@ func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
} }
func (trie *IpCidrTrie) IsContainForString(ipString string) bool { func (trie *IpCidrTrie) IsContainForString(ipString string) bool {
ip := net.ParseIP(ipString) return trie.IsContain(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) { func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
@@ -88,8 +82,9 @@ func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
isIpv4 bool isIpv4 bool
err error err error
) )
isIpv4 = len(ipNet.IP) == net.IPv4len
ipList, newMaskSize, err = subIpCidr(ipNet.IP, maskSize, isIpv4) ip, isIpv4 := checkAndConverterIp(ipNet.IP)
ipList, newMaskSize, err = subIpCidr(ip, maskSize, isIpv4)
return ipList, newMaskSize, isIpv4, err return ipList, newMaskSize, isIpv4, err
} }
@@ -243,3 +238,18 @@ func search(root *IpCidrNode, groupValues []uint32) *IpCidrNode {
return nil 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
}

View File

@@ -3,9 +3,8 @@ package trie
import ( import (
"net" "net"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
import "github.com/stretchr/testify/assert"
func TestIpv4AddSuccess(t *testing.T) { func TestIpv4AddSuccess(t *testing.T) {
trie := NewIpCidrTrie() trie := NewIpCidrTrie()
@@ -97,11 +96,5 @@ func TestIpv6Search(t *testing.T) {
assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213")) assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213"))
assert.Equal(t, false, trie.IsContain(net.ParseIP("22233:22"))) 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"))
} }

View File

@@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"os" "os"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@@ -16,7 +17,6 @@ import (
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
"github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/adapter/provider"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@@ -35,7 +35,6 @@ import (
L "github.com/Dreamacro/clash/listener" L "github.com/Dreamacro/clash/listener"
LC "github.com/Dreamacro/clash/listener/config" LC "github.com/Dreamacro/clash/listener/config"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
rewrites "github.com/Dreamacro/clash/rewrite"
R "github.com/Dreamacro/clash/rules" R "github.com/Dreamacro/clash/rules"
RP "github.com/Dreamacro/clash/rules/provider" RP "github.com/Dreamacro/clash/rules/provider"
T "github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
@@ -53,7 +52,6 @@ type General struct {
IPv6 bool `json:"ipv6"` IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"` Interface string `json:"interface-name"`
RoutingMark int `json:"-"` RoutingMark int `json:"-"`
GeoXUrl GeoXUrl `json:"geox-url"`
GeodataMode bool `json:"geodata-mode"` GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"` GeodataLoader string `json:"geodata-loader"`
TCPConcurrent bool `json:"tcp-concurrent"` TCPConcurrent bool `json:"tcp-concurrent"`
@@ -61,7 +59,6 @@ type General struct {
Sniffing bool `json:"sniffing"` Sniffing bool `json:"sniffing"`
EBpf EBpf `json:"-"` EBpf EBpf `json:"-"`
GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalClientFingerprint string `json:"global-client-fingerprint"`
KeepAliveInterval int `json:"keep-alive-interval"`
} }
// Inbound config // Inbound config
@@ -79,8 +76,6 @@ type Inbound struct {
AllowLan bool `json:"allow-lan"` AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"` BindAddress string `json:"bind-address"`
InboundTfo bool `json:"inbound-tfo"` InboundTfo bool `json:"inbound-tfo"`
InboundMPTCP bool `json:"inbound-mptcp"`
MitmPort int `json:"mitm-port"`
} }
// Controller config // Controller config
@@ -91,14 +86,6 @@ type Controller struct {
Secret string `json:"-"` Secret string `json:"-"`
} }
// NTP config
type NTP struct {
Enable bool `yaml:"enable"`
Server string `yaml:"server"`
Port int `yaml:"port"`
Interval int `yaml:"interval"`
}
// DNS config // DNS config
type DNS struct { type DNS struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
@@ -154,12 +141,6 @@ type Sniffer struct {
ParsePureIp bool ParsePureIp bool
} }
// Mitm config
type Mitm struct {
Port int `yaml:"port" json:"port"`
Rules C.RewriteRule `yaml:"rules" json:"rules"`
}
// Experimental config // Experimental config
type Experimental struct { type Experimental struct {
Fingerprints []string `yaml:"fingerprints"` Fingerprints []string `yaml:"fingerprints"`
@@ -169,8 +150,6 @@ type Experimental struct {
type Config struct { type Config struct {
General *General General *General
IPTables *IPTables IPTables *IPTables
Mitm *Mitm
NTP *NTP
DNS *DNS DNS *DNS
Experimental *Experimental Experimental *Experimental
Hosts *trie.DomainTrie[resolver.HostValue] Hosts *trie.DomainTrie[resolver.HostValue]
@@ -187,13 +166,6 @@ type Config struct {
TLS *TLS TLS *TLS
} }
type RawNTP struct {
Enable bool `yaml:"enable"`
Server string `yaml:"server"`
ServerPort int `yaml:"server-port"`
Interval int `yaml:"interval"`
}
type RawDNS struct { type RawDNS struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
PreferH3 bool `yaml:"prefer-h3"` PreferH3 bool `yaml:"prefer-h3"`
@@ -251,7 +223,6 @@ type RawTuicServer struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Listen string `yaml:"listen" json:"listen"` Listen string `yaml:"listen" json:"listen"`
Token []string `yaml:"token" json:"token"` Token []string `yaml:"token" json:"token"`
Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` PrivateKey string `yaml:"private-key" json:"private-key"`
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"` CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
@@ -259,12 +230,6 @@ type RawTuicServer struct {
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"` AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
ALPN []string `yaml:"alpn" json:"alpn,omitempty"` ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"` MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
}
type RawMitm struct {
Port int `yaml:"port" json:"port"`
Rules []rewrites.RawMitmRule `yaml:"rules" json:"rules"`
} }
type RawConfig struct { type RawConfig struct {
@@ -273,11 +238,9 @@ type RawConfig struct {
RedirPort int `yaml:"redir-port"` RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port"` TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port"` MixedPort int `yaml:"mixed-port"`
MitmPort int `yaml:"mitm-port"`
ShadowSocksConfig string `yaml:"ss-config"` ShadowSocksConfig string `yaml:"ss-config"`
VmessConfig string `yaml:"vmess-config"` VmessConfig string `yaml:"vmess-config"`
InboundTfo bool `yaml:"inbound-tfo"` InboundTfo bool `yaml:"inbound-tfo"`
InboundMPTCP bool `yaml:"inbound-mptcp"`
Authentication []string `yaml:"authentication"` Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"` BindAddress string `yaml:"bind-address"`
@@ -297,22 +260,19 @@ type RawConfig struct {
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
GlobalClientFingerprint string `yaml:"global-client-fingerprint"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
KeepAliveInterval int `yaml:"keep-alive-interval"`
Sniffer RawSniffer `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
RuleProvider map[string]map[string]any `yaml:"rule-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"`
Hosts map[string]any `yaml:"hosts"` Hosts map[string]any `yaml:"hosts"`
NTP RawNTP `yaml:"ntp"`
DNS RawDNS `yaml:"dns"` DNS RawDNS `yaml:"dns"`
Tun RawTun `yaml:"tun"` Tun RawTun `yaml:"tun"`
TuicServer RawTuicServer `yaml:"tuic-server"` TuicServer RawTuicServer `yaml:"tuic-server"`
EBpf EBpf `yaml:"ebpf"` EBpf EBpf `yaml:"ebpf"`
IPTables IPTables `yaml:"iptables"` IPTables IPTables `yaml:"iptables"`
MITM RawMitm `yaml:"mitm"`
Experimental Experimental `yaml:"experimental"` Experimental Experimental `yaml:"experimental"`
Profile Profile `yaml:"profile"` Profile Profile `yaml:"profile"`
GeoXUrl GeoXUrl `yaml:"geox-url"` GeoXUrl RawGeoXUrl `yaml:"geox-url"`
Proxy []map[string]any `yaml:"proxies"` Proxy []map[string]any `yaml:"proxies"`
ProxyGroup []map[string]any `yaml:"proxy-groups"` ProxyGroup []map[string]any `yaml:"proxy-groups"`
Rule []string `yaml:"rules"` Rule []string `yaml:"rules"`
@@ -321,7 +281,7 @@ type RawConfig struct {
Listeners []map[string]any `yaml:"listeners"` Listeners []map[string]any `yaml:"listeners"`
} }
type GeoXUrl struct { type RawGeoXUrl struct {
GeoIp string `yaml:"geoip" json:"geoip"` GeoIp string `yaml:"geoip" json:"geoip"`
Mmdb string `yaml:"mmdb" json:"mmdb"` Mmdb string `yaml:"mmdb" json:"mmdb"`
GeoSite string `yaml:"geosite" json:"geosite"` GeoSite string `yaml:"geosite" json:"geosite"`
@@ -396,7 +356,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
TuicServer: RawTuicServer{ TuicServer: RawTuicServer{
Enable: false, Enable: false,
Token: nil, Token: nil,
Users: nil,
Certificate: "", Certificate: "",
PrivateKey: "", PrivateKey: "",
Listen: "", Listen: "",
@@ -454,17 +413,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
ParsePureIp: true, ParsePureIp: true,
OverrideDest: true, OverrideDest: true,
}, },
MITM: RawMitm{
Port: 0,
Rules: []rewrites.RawMitmRule{},
},
Profile: Profile{ Profile: Profile{
StoreSelected: true, StoreSelected: true,
}, },
GeoXUrl: GeoXUrl{ GeoXUrl: RawGeoXUrl{
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb", Mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb",
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", GeoIp: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", GeoSite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
}, },
} }
@@ -533,9 +488,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.Hosts = hosts config.Hosts = hosts
ntpCfg := paresNTP(rawCfg)
config.NTP = ntpCfg
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders) dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -552,12 +504,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
return nil, err return nil, err
} }
mitm, err := parseMitm(rawCfg.MITM)
if err != nil {
return nil, err
}
config.Mitm = mitm
config.Users = parseAuthentication(rawCfg.Authentication) config.Users = parseAuthentication(rawCfg.Authentication)
config.Tunnels = rawCfg.Tunnels config.Tunnels = rawCfg.Tunnels
@@ -584,15 +530,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
externalUI := cfg.ExternalUI externalUI := cfg.ExternalUI
geodata.SetLoader(cfg.GeodataLoader) geodata.SetLoader(cfg.GeodataLoader)
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
C.MmdbUrl = cfg.GeoXUrl.Mmdb
C.GeodataMode = cfg.GeodataMode
if cfg.KeepAliveInterval == 0 {
cfg.KeepAliveInterval = 30
}
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
log.Infoln("Keep Alive Interval set %+v", N.KeepAliveInterval)
// checkout externalUI exist // checkout externalUI exist
if externalUI != "" { if externalUI != "" {
externalUI = C.Path.Resolve(externalUI) externalUI = C.Path.Resolve(externalUI)
@@ -608,13 +545,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RedirPort: cfg.RedirPort, RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort, TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort, MixedPort: cfg.MixedPort,
MitmPort: cfg.MitmPort,
ShadowSocksConfig: cfg.ShadowSocksConfig, ShadowSocksConfig: cfg.ShadowSocksConfig,
VmessConfig: cfg.VmessConfig, VmessConfig: cfg.VmessConfig,
AllowLan: cfg.AllowLan, AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress, BindAddress: cfg.BindAddress,
InboundTfo: cfg.InboundTfo, InboundTfo: cfg.InboundTfo,
InboundMPTCP: cfg.InboundMPTCP,
}, },
Controller: Controller{ Controller: Controller{
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
@@ -628,14 +563,12 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
Interface: cfg.Interface, Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark, RoutingMark: cfg.RoutingMark,
GeoXUrl: cfg.GeoXUrl,
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf, EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalClientFingerprint: cfg.GlobalClientFingerprint,
KeepAliveInterval: cfg.KeepAliveInterval,
}, nil }, nil
} }
@@ -656,11 +589,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxies["PASS"] = adapter.NewProxy(outbound.NewPass()) proxies["PASS"] = adapter.NewProxy(outbound.NewPass())
proxyList = append(proxyList, "DIRECT", "REJECT") proxyList = append(proxyList, "DIRECT", "REJECT")
if cfg.MITM.Port != 0 {
proxies["MITM"] = adapter.NewProxy(outbound.NewMitm(fmt.Sprintf("127.0.0.1:%d", cfg.MITM.Port)))
proxyList = append(proxyList, "MITM")
}
// parse proxy // parse proxy
for idx, mapping := range proxiesConfig { for idx, mapping := range proxiesConfig {
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
@@ -727,7 +655,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
} }
ps = append(ps, proxies[v]) ps = append(ps, proxies[v])
} }
hc := provider.NewHealthCheck(ps, "", 0, true, nil) hc := provider.NewHealthCheck(ps, "", 0, true)
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
providersMap[provider.ReservedName] = pd providersMap[provider.ReservedName] = pd
@@ -782,9 +710,6 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) { func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) {
subRules = map[string][]C.Rule{} subRules = map[string][]C.Rule{}
for name := range cfg.SubRules {
subRules[name] = make([]C.Rule, 0)
}
for name, rawRules := range cfg.SubRules { for name, rawRules := range cfg.SubRules {
if len(name) == 0 { if len(name) == 0 {
return nil, fmt.Errorf("sub-rule name is empty") return nil, fmt.Errorf("sub-rule name is empty")
@@ -941,14 +866,6 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
_ = tree.Insert(domain, value) _ = tree.Insert(domain, value)
} }
} }
if cfg.MITM.Port != 0 {
value, _ := resolver.NewHostValue("8.8.9.9")
if err := tree.Insert("mitm.clash", value); err != nil {
log.Errorln("insert mitm.clash to host error: %s", err.Error())
}
}
tree.Optimize() tree.Optimize()
return tree, nil return tree, nil
@@ -997,7 +914,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
addr, err = hostWithDefaultPort(u.Host, "443") addr, err = hostWithDefaultPort(u.Host, "443")
if err == nil { if err == nil {
proxyName = "" proxyName = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User} clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
if len(u.Fragment) != 0 { if len(u.Fragment) != 0 {
@@ -1023,19 +940,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
dnsNetType = "quic" // DNS over QUIC dnsNetType = "quic" // DNS over QUIC
case "system": case "system":
dnsNetType = "system" // System DNS 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: default:
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
} }
@@ -1201,29 +1105,6 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
return sites, nil return sites, nil
} }
func paresNTP(rawCfg *RawConfig) *NTP {
var server = "time.apple.com"
var port = 123
var interval = 30
cfg := rawCfg.NTP
if len(cfg.Server) != 0 {
server = cfg.Server
}
if cfg.ServerPort != 0 {
port = cfg.ServerPort
}
if cfg.Interval != 0 {
interval = cfg.Interval
}
ntpCfg := &NTP{
Enable: cfg.Enable,
Server: server,
Port: port,
Interval: interval,
}
return ntpCfg
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) { func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
@@ -1401,7 +1282,6 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
Enable: rawTuic.Enable, Enable: rawTuic.Enable,
Listen: rawTuic.Listen, Listen: rawTuic.Listen,
Token: rawTuic.Token, Token: rawTuic.Token,
Users: rawTuic.Users,
Certificate: rawTuic.Certificate, Certificate: rawTuic.Certificate,
PrivateKey: rawTuic.PrivateKey, PrivateKey: rawTuic.PrivateKey,
CongestionController: rawTuic.CongestionController, CongestionController: rawTuic.CongestionController,
@@ -1409,7 +1289,6 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
AuthenticationTimeout: rawTuic.AuthenticationTimeout, AuthenticationTimeout: rawTuic.AuthenticationTimeout,
ALPN: rawTuic.ALPN, ALPN: rawTuic.ALPN,
MaxUdpRelayPacketSize: rawTuic.MaxUdpRelayPacketSize, MaxUdpRelayPacketSize: rawTuic.MaxUdpRelayPacketSize,
CWND: rawTuic.CWND,
} }
return nil return nil
} }
@@ -1425,7 +1304,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
if len(snifferRaw.Sniff) != 0 { if len(snifferRaw.Sniff) != 0 {
for sniffType, sniffConfig := range snifferRaw.Sniff { for sniffType, sniffConfig := range snifferRaw.Sniff {
find := false find := false
ports, err := utils.NewIntRangesFromList[uint16](sniffConfig.Ports) ports, err := parsePortRange(sniffConfig.Ports)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1452,7 +1331,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
// Deprecated: Use Sniff instead // Deprecated: Use Sniff instead
log.Warnln("Deprecated: Use Sniff instead") log.Warnln("Deprecated: Use Sniff instead")
} }
globalPorts, err := utils.NewIntRangesFromList[uint16](snifferRaw.Ports) globalPorts, err := parsePortRange(snifferRaw.Ports)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1498,27 +1377,27 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
return sniffer, nil return sniffer, nil
} }
func parseMitm(rawMitm RawMitm) (*Mitm, error) { func parsePortRange(portRanges []string) ([]utils.Range[uint16], error) {
var ( ports := make([]utils.Range[uint16], 0)
req []C.Rewrite for _, portRange := range portRanges {
res []C.Rewrite portRaws := strings.Split(portRange, "-")
) p, err := strconv.ParseUint(portRaws[0], 10, 16)
for _, line := range rawMitm.Rules {
rule, err := rewrites.ParseRewrite(line)
if err != nil { if err != nil {
return nil, fmt.Errorf("parse rewrite rule failure: %w", err) return nil, fmt.Errorf("%s format error", portRange)
} }
if rule.RuleType() == C.MitmResponseHeader || rule.RuleType() == C.MitmResponseBody { start := uint16(p)
res = append(res, rule) 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 { } else {
req = append(req, rule) ports = append(ports, *utils.NewRange(start, start))
} }
} }
return ports, nil
return &Mitm{
Port: rawMitm.Port,
Rules: rewrites.NewRewriteRules(req, res),
}, nil
} }

View File

@@ -2,6 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/component/geodata"
"os" "os"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@@ -27,6 +28,23 @@ func Init(dir string) error {
f.Write([]byte(`mixed-port: 7890`)) f.Write([]byte(`mixed-port: 7890`))
f.Close() f.Close()
} }
buf, _ := os.ReadFile(C.Path.Config())
rawCfg, err := UnmarshalRawConfig(buf)
if err != nil {
log.Errorln(err.Error())
fmt.Printf("configuration file %s test failed\n", C.Path.Config())
os.Exit(1)
}
if !C.GeodataMode {
C.GeodataMode = rawCfg.GeodataMode
}
C.GeoIpUrl = rawCfg.GeoXUrl.GeoIp
C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite
C.MmdbUrl = rawCfg.GeoXUrl.Mmdb
// initial GeoIP
if err := geodata.InitGeoIP(); err != nil {
return fmt.Errorf("can't initial GeoIP: %w", err)
}
return nil return nil
} }

View File

@@ -14,7 +14,7 @@ import (
clashHttp "github.com/Dreamacro/clash/component/http" clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/maxminddb-golang" "github.com/oschwald/geoip2-golang"
) )
func UpdateGeoDatabases() error { func UpdateGeoDatabases() error {
@@ -44,7 +44,7 @@ func UpdateGeoDatabases() error {
return fmt.Errorf("can't download MMDB database file: %w", err) return fmt.Errorf("can't download MMDB database file: %w", err)
} }
instance, err := maxminddb.FromBytes(data) instance, err := geoip2.FromBytes(data)
if err != nil { if err != nil {
return fmt.Errorf("invalid MMDB database file: %s", err) return fmt.Errorf("invalid MMDB database file: %s", err)
} }

View File

@@ -10,7 +10,6 @@ import (
"time" "time"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
) )
@@ -19,7 +18,6 @@ const (
Direct AdapterType = iota Direct AdapterType = iota
Reject Reject
Compatible Compatible
Mitm
Pass Pass
Relay Relay
@@ -45,7 +43,6 @@ const (
DefaultTCPTimeout = 5 * time.Second DefaultTCPTimeout = 5 * time.Second
DefaultUDPTimeout = DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout
DefaultTLSTimeout = DefaultTCPTimeout DefaultTLSTimeout = DefaultTCPTimeout
DefaultMaxHealthCheckUrlNum = 16
) )
var ErrNotSupport = errors.New("no support") var ErrNotSupport = errors.New("no support")
@@ -135,7 +132,7 @@ type ProxyAdapter interface {
} }
type Group interface { type Group interface {
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error) URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
GetProxies(touch bool) []Proxy GetProxies(touch bool) []Proxy
Touch() Touch()
} }
@@ -145,23 +142,12 @@ type DelayHistory struct {
Delay uint16 `json:"delay"` Delay uint16 `json:"delay"`
} }
type DelayHistoryStoreType int
const (
OriginalHistory DelayHistoryStoreType = iota
ExtraHistory
DropHistory
)
type Proxy interface { type Proxy interface {
ProxyAdapter ProxyAdapter
Alive() bool Alive() bool
AliveForTestUrl(url string) bool
DelayHistory() []DelayHistory DelayHistory() []DelayHistory
ExtraDelayHistory() map[string][]DelayHistory
LastDelay() uint16 LastDelay() uint16
LastDelayForTestUrl(url string) uint16 URLTest(ctx context.Context, url string) (uint16, error)
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error)
// Deprecated: use DialContext instead. // Deprecated: use DialContext instead.
Dial(metadata *Metadata) (Conn, error) Dial(metadata *Metadata) (Conn, error)
@@ -183,8 +169,6 @@ func (at AdapterType) String() string {
return "Compatible" return "Compatible"
case Pass: case Pass:
return "Pass" return "Pass"
case Mitm:
return "Mitm"
case Shadowsocks: case Shadowsocks:
return "Shadowsocks" return "Shadowsocks"
case ShadowsocksR: case ShadowsocksR:
@@ -233,7 +217,7 @@ type UDPPacket interface {
// - variable source IP/Port is important to STUN // - 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, // - 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. // this is important when using Fake-IP.
WriteBack WriteBack(b []byte, addr net.Addr) (n int, err error)
// Drop call after packet is used, could recycle buffer in this function. // Drop call after packet is used, could recycle buffer in this function.
Drop() Drop()
@@ -252,35 +236,22 @@ type PacketAdapter interface {
Metadata() *Metadata Metadata() *Metadata
} }
type WriteBack interface {
WriteBack(b []byte, addr net.Addr) (n int, err error)
}
type WriteBackProxy interface {
WriteBack
UpdateWriteBack(wb WriteBack)
}
type NatTable interface { type NatTable interface {
Set(key string, e PacketConn, w WriteBackProxy) Set(key string, e PacketConn)
Get(key string) (PacketConn, WriteBackProxy) Get(key string) PacketConn
GetOrCreateLock(key string) (*sync.Cond, bool) GetOrCreateLock(key string) (*sync.Cond, bool)
Delete(key string) Delete(key string)
DeleteLock(key string) GetLocalConn(lAddr, rAddr string) *net.UDPConn
GetForLocalConn(lAddr, rAddr string) *net.UDPConn AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool RangeLocalConn(lAddr string, f func(key, value any) bool)
RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool) DeleteLocalConnMap(lAddr, key string)
DeleteForLocalConn(lAddr, key string)
DeleteLockForLocalConn(lAddr, key string)
} }

View File

@@ -31,7 +31,6 @@ const (
TUN TUN
TUIC TUIC
INNER INNER
MITM
) )
type NetWork int type NetWork int
@@ -81,8 +80,6 @@ func (t Type) String() string {
return "Tuic" return "Tuic"
case INNER: case INNER:
return "Inner" return "Inner"
case MITM:
return "Mitm"
default: default:
return "Unknown" return "Unknown"
} }
@@ -131,10 +128,10 @@ type Metadata struct {
Type Type `json:"type"` Type Type `json:"type"`
SrcIP netip.Addr `json:"sourceIP"` SrcIP netip.Addr `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"` DstIP netip.Addr `json:"destinationIP"`
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output SrcPort string `json:"sourcePort"`
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output DstPort string `json:"destinationPort"`
InIP netip.Addr `json:"inboundIP"` InIP netip.Addr `json:"inboundIP"`
InPort uint16 `json:"inboundPort,string"` // `,string` is used to compatible with old version json output InPort string `json:"inboundPort"`
InName string `json:"inboundName"` InName string `json:"inboundName"`
InUser string `json:"inboundUser"` InUser string `json:"inboundUser"`
Host string `json:"host"` Host string `json:"host"`
@@ -147,23 +144,19 @@ type Metadata struct {
RemoteDst string `json:"remoteDestination"` RemoteDst string `json:"remoteDestination"`
// Only domain rule // Only domain rule
SniffHost string `json:"sniffHost"` SniffHost string `json:"sniffHost"`
// Only Mitm rule
UserAgent string `json:"userAgent"`
} }
func (m *Metadata) RemoteAddress() string { func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), strconv.FormatUint(uint64(m.DstPort), 10)) return net.JoinHostPort(m.String(), m.DstPort)
} }
func (m *Metadata) SourceAddress() string { func (m *Metadata) SourceAddress() string {
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10)) return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
} }
func (m *Metadata) SourceDetail() string { func (m *Metadata) SourceDetail() string {
if m.Type == INNER { if m.Type == INNER {
return fmt.Sprintf("%s", ClashName) return fmt.Sprintf("%s", ClashName)
} else if m.Type == MITM {
return fmt.Sprintf("%s-MITM", ClashName)
} }
switch { switch {
@@ -178,10 +171,6 @@ func (m *Metadata) SourceDetail() string {
} }
} }
func (m *Metadata) SourceValid() bool {
return m.SrcPort != 0 && m.SrcIP.IsValid()
}
func (m *Metadata) AddrType() int { func (m *Metadata) AddrType() int {
switch true { switch true {
case m.Host != "" || !m.DstIP.IsValid(): case m.Host != "" || !m.DstIP.IsValid():
@@ -218,7 +207,8 @@ func (m *Metadata) Pure() *Metadata {
} }
func (m *Metadata) AddrPort() netip.AddrPort { func (m *Metadata) AddrPort() netip.AddrPort {
return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort) port, _ := strconv.ParseUint(m.DstPort, 10, 16)
return netip.AddrPortFrom(m.DstIP.Unmap(), uint16(port))
} }
func (m *Metadata) UDPAddr() *net.UDPAddr { func (m *Metadata) UDPAddr() *net.UDPAddr {
@@ -248,11 +238,6 @@ func (m *Metadata) SetRemoteAddress(rawAddress string) error {
return err return err
} }
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
}
if ip, err := netip.ParseAddr(host); err != nil { if ip, err := netip.ParseAddr(host); err != nil {
m.Host = host m.Host = host
m.DstIP = netip.Addr{} m.DstIP = netip.Addr{}
@@ -260,7 +245,7 @@ func (m *Metadata) SetRemoteAddress(rawAddress string) error {
m.Host = "" m.Host = ""
m.DstIP = ip.Unmap() m.DstIP = ip.Unmap()
} }
m.DstPort = uint16Port m.DstPort = port
return nil return nil
} }

View File

@@ -1,12 +1,9 @@
package constant package constant
import ( import (
"crypto/md5"
"encoding/hex"
"os" "os"
P "path" P "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
) )
@@ -23,15 +20,14 @@ var Path = func() *path {
if err != nil { if err != nil {
homeDir, _ = os.Getwd() homeDir, _ = os.Getwd()
} }
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
homeDir = P.Join(homeDir, ".config", Name) homeDir = P.Join(homeDir, ".config", Name)
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath} return &path{homeDir: homeDir, configFile: "config.yaml"}
}() }()
type path struct { type path struct {
homeDir string homeDir string
configFile string configFile string
allowUnsafePath bool
} }
// SetHomeDir is used to set the configuration path // SetHomeDir is used to set the configuration path
@@ -60,27 +56,6 @@ func (p *path) Resolve(path string) string {
return path 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 { func (p *path) MMDB() string {
files, err := os.ReadDir(p.homeDir) files, err := os.ReadDir(p.homeDir)
if err != nil { if err != nil {
@@ -91,15 +66,13 @@ func (p *path) MMDB() string {
// 目录则直接跳过 // 目录则直接跳过
continue continue
} else { } else {
if strings.EqualFold(fi.Name(), "Country.mmdb") || if strings.EqualFold(fi.Name(), "Country.mmdb") {
strings.EqualFold(fi.Name(), "geoip.db") ||
strings.EqualFold(fi.Name(), "geoip.metadb") {
GeoipName = fi.Name() GeoipName = fi.Name()
return P.Join(p.homeDir, fi.Name()) return P.Join(p.homeDir, fi.Name())
} }
} }
} }
return P.Join(p.homeDir, "geoip.metadb") return P.Join(p.homeDir, "Country.mmdb")
} }
func (p *path) OldCache() string { func (p *path) OldCache() string {
@@ -148,12 +121,8 @@ func (p *path) GeoSite() string {
return P.Join(p.homeDir, "GeoSite.dat") return P.Join(p.homeDir, "GeoSite.dat")
} }
func (p *path) RootCA() string { func (p *path) GetAssetLocation(file string) string {
return p.Resolve("mitm_ca.crt") return P.Join(p.homeDir, file)
}
func (p *path) CAKey() string {
return p.Resolve("mitm_ca.key")
} }
func (p *path) GetExecutableFullPath() string { func (p *path) GetExecutableFullPath() string {

View File

@@ -1,7 +1,6 @@
package provider package provider
import ( import (
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant"
) )
@@ -72,7 +71,6 @@ type ProxyProvider interface {
Touch() Touch()
HealthCheck() HealthCheck()
Version() uint32 Version() uint32
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
} }
// RuleProvider interface // RuleProvider interface

View File

@@ -1,120 +0,0 @@
package constant
import (
"encoding/json"
"errors"
regexp "github.com/dlclark/regexp2"
)
var RewriteTypeMapping = map[string]RewriteType{
MitmReject.String(): MitmReject,
MitmReject200.String(): MitmReject200,
MitmRejectImg.String(): MitmRejectImg,
MitmRejectDict.String(): MitmRejectDict,
MitmRejectArray.String(): MitmRejectArray,
Mitm302.String(): Mitm302,
Mitm307.String(): Mitm307,
MitmRequestHeader.String(): MitmRequestHeader,
MitmRequestBody.String(): MitmRequestBody,
MitmResponseHeader.String(): MitmResponseHeader,
MitmResponseBody.String(): MitmResponseBody,
}
const (
MitmReject RewriteType = iota
MitmReject200
MitmRejectImg
MitmRejectDict
MitmRejectArray
Mitm302
Mitm307
MitmRequestHeader
MitmRequestBody
MitmResponseHeader
MitmResponseBody
)
type RewriteType int
// UnmarshalYAML unserialize RewriteType with yaml
func (e *RewriteType) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := RewriteTypeMapping[tp]
if !exist {
return errors.New("invalid MITM Action")
}
*e = mode
return nil
}
// MarshalYAML serialize RewriteType with yaml
func (e RewriteType) MarshalYAML() (any, error) {
return e.String(), nil
}
// UnmarshalJSON unserialize RewriteType with json
func (e *RewriteType) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := RewriteTypeMapping[tp]
if !exist {
return errors.New("invalid MITM Action")
}
*e = mode
return nil
}
// MarshalJSON serialize RewriteType with json
func (e RewriteType) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
func (rt RewriteType) String() string {
switch rt {
case MitmReject:
return "reject" // 404
case MitmReject200:
return "reject-200"
case MitmRejectImg:
return "reject-img"
case MitmRejectDict:
return "reject-dict"
case MitmRejectArray:
return "reject-array"
case Mitm302:
return "302"
case Mitm307:
return "307"
case MitmRequestHeader:
return "request-header"
case MitmRequestBody:
return "request-body"
case MitmResponseHeader:
return "response-header"
case MitmResponseBody:
return "response-body"
default:
return "Unknown"
}
}
type Rewrite interface {
ID() string
URLRegx() *regexp.Regexp
RuleType() RewriteType
RuleRegx() *regexp.Regexp
RulePayload() string
ReplaceURLPayload([]string) string
ReplaceSubPayload(string) string
}
type RewriteRule interface {
SearchInRequest(func(Rewrite) bool) bool
SearchInResponse(func(Rewrite) bool) bool
}

View File

@@ -23,7 +23,6 @@ const (
Network Network
Uid Uid
SubRules SubRules
UserAgent
MATCH MATCH
AND AND
OR OR
@@ -68,8 +67,6 @@ func (rt RuleType) String() string {
return "Process" return "Process"
case ProcessPath: case ProcessPath:
return "ProcessPath" return "ProcessPath"
case UserAgent:
return "UserAgent"
case MATCH: case MATCH:
return "Match" return "Match"
case RuleSet: case RuleSet:

View File

@@ -10,14 +10,12 @@ var StackTypeMapping = map[string]TUNStack{
strings.ToLower(TunGvisor.String()): TunGvisor, strings.ToLower(TunGvisor.String()): TunGvisor,
strings.ToLower(TunSystem.String()): TunSystem, strings.ToLower(TunSystem.String()): TunSystem,
strings.ToLower(TunLWIP.String()): TunLWIP, strings.ToLower(TunLWIP.String()): TunLWIP,
strings.ToLower(TunMixed.String()): TunMixed,
} }
const ( const (
TunGvisor TUNStack = iota TunGvisor TUNStack = iota
TunSystem TunSystem
TunLWIP TunLWIP
TunMixed
) )
type TUNStack int type TUNStack int
@@ -66,8 +64,6 @@ func (e TUNStack) String() string {
return "System" return "System"
case TunLWIP: case TunLWIP:
return "LWIP" return "LWIP"
case TunMixed:
return "Mixed"
default: default:
return "unknown" return "unknown"
} }

View File

@@ -59,8 +59,7 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg,
return nil, err return nil, err
} }
msg, _, err = batchExchange(ctx, clients, m) return batchExchange(ctx, clients, m)
return
} }
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) { func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {

View File

@@ -543,17 +543,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
if err != nil { if err != nil {
return nil, err return nil, err
} }
transport := quic.Transport{Conn: conn} return quic.DialEarlyContext(ctx, conn, &udpAddr, doh.url.Host, tlsCfg, cfg)
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 // probeH3 runs a test to check whether QUIC is faster than TLS for this

View File

@@ -302,6 +302,14 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q
// openConnection opens a new QUIC connection. // openConnection opens a new QUIC connection.
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) { 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 // 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 // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses). // what IP is actually reachable (when there're v4/v6 addresses).
@@ -330,20 +338,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
return nil, err return nil, err
} }
tlsConfig := tlsC.GetGlobalTLSConfig( conn, err = quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, doq.getQUICConfig())
&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 { if err != nil {
return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err) return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
} }

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