Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
454dbb1772 | |||
44d1d2e0c2 | |||
54fee7bd3a | |||
414d8f2162 | |||
86cf1dd54b | |||
d099375200 | |||
9536372cfb | |||
630a17cf90 | |||
0a7b7894bd | |||
3a9fc39cd9 | |||
1181fd4560 | |||
b8a60261ef | |||
db68d55a0e | |||
574efb4526 | |||
03b0252589 | |||
ed09df4e13 | |||
f89ecd97d6 | |||
3093fc4f33 | |||
984fca4726 | |||
cc42d787d4 | |||
e2e0fd4eba | |||
bad9f2e6dc | |||
68bf6f16ac | |||
cca701c641 | |||
09ec7c8a62 | |||
68f312288d | |||
191243a1d2 | |||
b0fed73236 | |||
f125e1ce9e | |||
e2216b7824 | |||
7632827177 | |||
b0e76ec791 | |||
a82745f544 | |||
cbb8ef5dfe | |||
a181e35865 | |||
014537e1ea | |||
9b50f56e7c | |||
9cbca162a0 | |||
f73f32e41c | |||
cfc30753af | |||
081e94c738 | |||
5dd57bab67 | |||
492a731ec1 | |||
0b1aff5759 | |||
8f1475d5d0 | |||
c6b84b0f20 | |||
02ba78ab90 | |||
57db8dfe23 | |||
8e16738465 | |||
db6b2b7702 | |||
603d0809b4 | |||
614cc93cac | |||
1cb75350e2 | |||
42ef4fedfa | |||
2284acce94 | |||
919daf0dbb | |||
6d824c8745 | |||
1d94546902 | |||
ad7508f203 | |||
d391fda051 | |||
fe0f2d9ef9 | |||
b9110c164d | |||
6c8631d5cc | |||
61734e5cac | |||
77fb9a9c01 | |||
af28b99b2a | |||
4f79bb7931 | |||
644abcf071 | |||
183f2d974c | |||
e914317bef | |||
5e20fedf5f | |||
54337ecdf3 | |||
c7de0e0253 | |||
b72219c06a | |||
64b23257db | |||
c57f17d094 | |||
cd44901e90 | |||
766d08a8eb | |||
c3ef05b257 | |||
093453582f | |||
767aa182b9 | |||
ad11a2b813 | |||
dafecebdc0 | |||
e7174866e5 | |||
fdaa6a22a4 | |||
fd0c71a485 | |||
3c1f9a9953 | |||
3ef81afc76 | |||
03d0c8620e | |||
63b5387164 | |||
2af758e5f1 | |||
2c44b4e170 | |||
7906fbfee6 | |||
17565ec93b | |||
26acaee424 | |||
9b6e56a65e |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -128,7 +128,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20"
|
||||
go-version: "1.21"
|
||||
check-latest: true
|
||||
|
||||
- name: Test
|
||||
@ -285,6 +285,7 @@ jobs:
|
||||
generate_release_notes: true
|
||||
|
||||
Docker:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
permissions: write-all
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -4,9 +4,9 @@ RUN echo "I'm building for $TARGETPLATFORM"
|
||||
|
||||
RUN apk add --no-cache gzip && \
|
||||
mkdir /clash-config && \
|
||||
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
|
||||
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://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \
|
||||
wget -O /clash-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \
|
||||
wget -O /clash-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
|
||||
|
||||
COPY docker/file-name.sh /clash/file-name.sh
|
||||
WORKDIR /clash
|
||||
|
5
Makefile
5
Makefile
@ -31,6 +31,8 @@ PLATFORM_LIST = \
|
||||
linux-mips-hardfloat \
|
||||
linux-mipsle-softfloat \
|
||||
linux-mipsle-hardfloat \
|
||||
linux-riscv64 \
|
||||
linux-loong64 \
|
||||
android-arm64 \
|
||||
freebsd-386 \
|
||||
freebsd-amd64 \
|
||||
@ -103,6 +105,9 @@ linux-mips64le:
|
||||
|
||||
linux-riscv64:
|
||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-loong64:
|
||||
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
android-arm64:
|
||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
@ -3,25 +3,41 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/queue"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
|
||||
const (
|
||||
defaultHistoriesNum = 10
|
||||
)
|
||||
|
||||
type extraProxyState struct {
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
url string
|
||||
extra sync.Map
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
@ -29,6 +45,15 @@ func (p *Proxy) Alive() bool {
|
||||
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.(*extraProxyState).alive.Load()
|
||||
}
|
||||
|
||||
return p.alive.Load()
|
||||
}
|
||||
|
||||
// Dial implements C.Proxy
|
||||
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||
@ -62,9 +87,51 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
|
||||
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.(*extraProxyState).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, v interface{}) bool {
|
||||
|
||||
testUrl := k.(string)
|
||||
state := v.(*extraProxyState)
|
||||
|
||||
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.
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) LastDelay() (delay uint16) {
|
||||
@ -80,6 +147,28 @@ func (p *Proxy) LastDelay() (delay uint16) {
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// LastDelayForTestUrl implements C.Proxy
|
||||
func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
|
||||
var max uint16 = 0xffff
|
||||
|
||||
alive := p.alive.Load()
|
||||
history := p.history.Last()
|
||||
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
alive = state.(*extraProxyState).alive.Load()
|
||||
history = state.(*extraProxyState).history.Last()
|
||||
}
|
||||
|
||||
if !alive {
|
||||
return max
|
||||
}
|
||||
|
||||
if history.Delay == 0 {
|
||||
return max
|
||||
}
|
||||
return history.Delay
|
||||
}
|
||||
|
||||
// MarshalJSON implements C.ProxyAdapter
|
||||
func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
inner, err := p.ProxyAdapter.MarshalJSON()
|
||||
@ -90,6 +179,8 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping := map[string]any{}
|
||||
_ = json.Unmarshal(inner, &mapping)
|
||||
mapping["history"] = p.DelayHistory()
|
||||
mapping["extra"] = p.ExtraDelayHistory()
|
||||
mapping["alive"] = p.Alive()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
mapping["xudp"] = p.SupportXUDP()
|
||||
@ -99,16 +190,49 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// URLTest get the delay for the specified URL
|
||||
// implements C.Proxy
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) {
|
||||
defer func() {
|
||||
p.alive.Store(err == nil)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if err == nil {
|
||||
record.Delay = t
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > 10 {
|
||||
p.history.Pop()
|
||||
alive := err == nil
|
||||
store = p.determineFinalStoreType(store, url)
|
||||
|
||||
switch store {
|
||||
case C.OriginalHistory:
|
||||
p.alive.Store(alive)
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if alive {
|
||||
record.Delay = t
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > defaultHistoriesNum {
|
||||
p.history.Pop()
|
||||
}
|
||||
|
||||
// test URL configured by the proxy provider
|
||||
if len(p.url) == 0 {
|
||||
p.url = url
|
||||
}
|
||||
case C.ExtraHistory:
|
||||
record := C.DelayHistory{Time: time.Now()}
|
||||
if alive {
|
||||
record.Delay = t
|
||||
}
|
||||
|
||||
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.(*extraProxyState).alive.Store(alive)
|
||||
state.(*extraProxyState).history.Put(record)
|
||||
if state.(*extraProxyState).history.Len() > defaultHistoriesNum {
|
||||
state.(*extraProxyState).history.Pop()
|
||||
}
|
||||
default:
|
||||
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -172,12 +296,22 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if expectedStatus != nil && !expectedStatus.Check(uint16(resp.StatusCode)) {
|
||||
// maybe another value should be returned for differentiation
|
||||
err = errors.New("response status is inconsistent with the expected status")
|
||||
}
|
||||
|
||||
t = uint16(time.Since(start) / time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New[C.DelayHistory](10), atomic.NewBool(true)}
|
||||
return &Proxy{
|
||||
ProxyAdapter: adapter,
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
url: "",
|
||||
extra: sync.Map{}}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
@ -198,11 +332,46 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
Host: u.Hostname(),
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: port,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
length := 0
|
||||
p.extra.Range(func(_, _ interface{}) bool {
|
||||
length++
|
||||
return length < 2*C.DefaultMaxHealthCheckUrlNum
|
||||
})
|
||||
|
||||
if length == 0 {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
_, ok := p.extra.Load(url)
|
||||
if ok {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
if length < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ func SetTfo(open bool) {
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func SetMPTCP(open bool) {
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
return lc.Listen(ctx, network, address)
|
||||
}
|
||||
|
10
adapter/inbound/mptcp_go120.go
Normal file
10
adapter/inbound/mptcp_go120.go
Normal file
@ -0,0 +1,10 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package inbound
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = false
|
||||
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
}
|
11
adapter/inbound/mptcp_go121.go
Normal file
11
adapter/inbound/mptcp_go121.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build go1.21
|
||||
|
||||
package inbound
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = true
|
||||
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
listenConfig.SetMultipathTCP(open)
|
||||
}
|
@ -3,6 +3,7 @@ package inbound
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
@ -37,7 +38,9 @@ func NewInner(conn net.Conn, address string) *context.ConnContext {
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
metadata.Process = C.ClashName
|
||||
if h, port, err := net.SplitHostPort(address); err == nil {
|
||||
metadata.DstPort = port
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
metadata.DstPort = uint16(port)
|
||||
}
|
||||
if ip, err := netip.ParseAddr(h); err == nil {
|
||||
metadata.DstIP = ip
|
||||
} else {
|
||||
|
@ -20,14 +20,14 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
||||
case socks5.AtypDomainName:
|
||||
// trim for FQDN
|
||||
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
|
||||
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
case socks5.AtypIPv4:
|
||||
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
|
||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||
case socks5.AtypIPv6:
|
||||
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
||||
metadata.DstIP = ip6.Unmap()
|
||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||
}
|
||||
|
||||
return metadata
|
||||
@ -43,11 +43,16 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
// trim FQDN (#737)
|
||||
host = strings.TrimRight(host, ".")
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: port,
|
||||
DstPort: uint16Port,
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
@ -58,10 +63,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
return metadata
|
||||
}
|
||||
|
||||
func parseAddr(addr net.Addr) (netip.Addr, string, error) {
|
||||
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
|
||||
// Filter when net.Addr interface is nil
|
||||
if addr == nil {
|
||||
return netip.Addr{}, "", errors.New("nil addr")
|
||||
return netip.Addr{}, 0, errors.New("nil addr")
|
||||
}
|
||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
||||
ip, port, err := parseAddr(rawAddr.RawAddr())
|
||||
@ -72,9 +77,14 @@ func parseAddr(addr net.Addr) (netip.Addr, string, error) {
|
||||
addrStr := addr.String()
|
||||
host, port, err := net.SplitHostPort(addrStr)
|
||||
if err != nil {
|
||||
return netip.Addr{}, "", err
|
||||
return netip.Addr{}, 0, err
|
||||
}
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
return ip, port, err
|
||||
return ip, uint16Port, err
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type Base struct {
|
||||
udp bool
|
||||
xudp bool
|
||||
tfo bool
|
||||
mpTcp bool
|
||||
rmark int
|
||||
id string
|
||||
prefer C.DNSPrefer
|
||||
@ -143,11 +144,16 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||
opts = append(opts, dialer.WithTFO(true))
|
||||
}
|
||||
|
||||
if b.mpTcp {
|
||||
opts = append(opts, dialer.WithMPTCP(true))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
type BasicOption struct {
|
||||
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"`
|
||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
|
||||
@ -161,6 +167,7 @@ type BaseOption struct {
|
||||
UDP bool
|
||||
XUDP bool
|
||||
TFO bool
|
||||
MPTCP bool
|
||||
Interface string
|
||||
RoutingMark int
|
||||
Prefer C.DNSPrefer
|
||||
@ -174,6 +181,7 @@ func NewBase(opt BaseOption) *Base {
|
||||
udp: opt.UDP,
|
||||
xudp: opt.XUDP,
|
||||
tfo: opt.TFO,
|
||||
mpTcp: opt.MPTCP,
|
||||
iface: opt.Interface,
|
||||
rmark: opt.RoutingMark,
|
||||
prefer: opt.Prefer,
|
||||
|
@ -12,6 +12,11 @@ type Direct struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
type DirectOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
||||
@ -40,6 +45,21 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
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 {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
|
@ -177,6 +177,7 @@ func NewHttp(option HttpOption) (*Http, error) {
|
||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||
tp: C.Http,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -78,8 +78,11 @@ type nopPacketConn struct{}
|
||||
|
||||
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||
func (npc nopPacketConn) Close() error { return nil }
|
||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
|
||||
return nil, nil, nil, io.EOF
|
||||
}
|
||||
func (npc nopPacketConn) Close() error { return nil }
|
||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
restlsC "github.com/3andne/restls-client-go"
|
||||
"github.com/metacubex/sing-shadowsocks2"
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
@ -294,7 +294,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
|
||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
||||
restlsConfig.SessionTicketsDisabled = true
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||
}
|
||||
@ -315,6 +314,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
tp: C.Shadowsocks,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -181,6 +181,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
tp: C.ShadowsocksR,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -97,7 +97,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
|
||||
func (s *SingMux) SupportUDP() bool {
|
||||
if s.onlyTcp {
|
||||
return s.ProxyAdapter.SupportUOT()
|
||||
return s.ProxyAdapter.SupportUDP()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -59,8 +59,7 @@ func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
err := snell.WriteUDPHeader(c, s.version)
|
||||
return c, err
|
||||
}
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
|
||||
return c, err
|
||||
}
|
||||
|
||||
@ -72,8 +71,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
@ -183,6 +181,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
tp: C.Snell,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -196,6 +196,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
tp: C.Socks5,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/trojan"
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
@ -45,8 +44,6 @@ type TrojanOption struct {
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-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"`
|
||||
}
|
||||
|
||||
@ -95,11 +92,6 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
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 {
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
@ -117,12 +109,6 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
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 {
|
||||
c.Close()
|
||||
return nil, err
|
||||
@ -237,24 +223,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
ALPN: option.ALPN,
|
||||
ServerName: option.Server,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
FlowShow: option.FlowShow,
|
||||
Fingerprint: option.Fingerprint,
|
||||
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 != "" {
|
||||
tOption.ServerName = option.SNI
|
||||
}
|
||||
@ -266,6 +238,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
tp: C.Trojan,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@ -13,13 +14,17 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"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 {
|
||||
@ -33,7 +38,9 @@ type TuicOption struct {
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Token string `proxy:"token"`
|
||||
Token string `proxy:"token,omitempty"`
|
||||
UUID string `proxy:"uuid,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
Ip string `proxy:"ip,omitempty"`
|
||||
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
@ -46,6 +53,7 @@ type TuicOption struct {
|
||||
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
@ -55,6 +63,9 @@ type TuicOption struct {
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,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
|
||||
@ -78,6 +89,32 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -90,11 +127,7 @@ func (t *Tuic) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
|
||||
return t.dialWithDialer(ctx, dialer.NewDialer(opts...))
|
||||
}
|
||||
|
||||
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
|
||||
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
|
||||
if len(t.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
@ -106,10 +139,14 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.Pack
|
||||
return nil, nil, err
|
||||
}
|
||||
addr = udpAddr
|
||||
var pc net.PacketConn
|
||||
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
transport = &quic.Transport{Conn: pc}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
return
|
||||
}
|
||||
|
||||
@ -158,7 +195,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{"h3"}
|
||||
@ -172,8 +209,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
option.HeartbeatInterval = 10000
|
||||
}
|
||||
|
||||
udpRelayMode := tuic.QUIC
|
||||
if option.UdpRelayMode != "quic" {
|
||||
option.UdpRelayMode = "native"
|
||||
udpRelayMode = tuic.NATIVE
|
||||
}
|
||||
|
||||
if option.MaxUdpRelayPacketSize == 0 {
|
||||
@ -184,14 +222,23 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
option.MaxOpenStreams = 100
|
||||
}
|
||||
|
||||
if option.CWND == 0 {
|
||||
option.CWND = 32
|
||||
}
|
||||
|
||||
packetOverHead := tuic.PacketOverHeadV4
|
||||
if len(option.Token) == 0 {
|
||||
packetOverHead = tuic.PacketOverHeadV5
|
||||
}
|
||||
|
||||
if option.MaxDatagramFrameSize == 0 {
|
||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
|
||||
}
|
||||
|
||||
if option.MaxDatagramFrameSize > 1400 {
|
||||
option.MaxDatagramFrameSize = 1400
|
||||
}
|
||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
|
||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
|
||||
|
||||
// ensure server's incoming stream can handle correctly, increase to 1.1x
|
||||
quicMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||
@ -220,12 +267,18 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if len(option.Ip) > 0 {
|
||||
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
|
||||
}
|
||||
host := option.Server
|
||||
if option.DisableSni {
|
||||
host = ""
|
||||
tlsConfig.ServerName = ""
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
|
||||
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{
|
||||
Base: &Base{
|
||||
@ -251,21 +304,40 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if clientMaxOpenStreams < 1 {
|
||||
clientMaxOpenStreams = 1
|
||||
}
|
||||
clientOption := &tuic.ClientOption{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Host: host,
|
||||
Token: tkn,
|
||||
UdpRelayMode: option.UdpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
FastOpen: option.FastOpen,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClient(clientOption)
|
||||
if len(option.Token) > 0 {
|
||||
tkn := tuic.GenTKN(option.Token)
|
||||
clientOption := &tuic.ClientOptionV4{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Token: tkn,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
FastOpen: option.FastOpen,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV4(clientOption)
|
||||
} else {
|
||||
clientOption := &tuic.ClientOptionV5{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||
Password: option.Password,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
t.client = tuic.NewPoolClientV5(clientOption)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
@ -4,10 +4,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
xtls "github.com/xtls/go"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -17,9 +15,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
globalClientXSessionCache xtls.ClientSessionCache
|
||||
once sync.Once
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
@ -36,18 +33,11 @@ func getClientSessionCache() tls.ClientSessionCache {
|
||||
return globalClientSessionCache
|
||||
}
|
||||
|
||||
func getClientXSessionCache() xtls.ClientSessionCache {
|
||||
once.Do(func() {
|
||||
globalClientXSessionCache = xtls.NewLRUClientSessionCache(128)
|
||||
})
|
||||
return globalClientXSessionCache
|
||||
}
|
||||
|
||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||
var buf [][]byte
|
||||
addrType := metadata.AddrType()
|
||||
aType := uint8(addrType)
|
||||
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
p := uint(metadata.DstPort)
|
||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||
switch addrType {
|
||||
case socks5.AtypDomainName:
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@ -25,8 +26,8 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/vless"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
vmessSing "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
vmessSing "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
@ -55,8 +56,8 @@ type VlessOption struct {
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
Flow string `proxy:"flow,omitempty"`
|
||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
@ -132,7 +133,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||
case "http":
|
||||
// readability first, so just copy default TLS logic
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
|
||||
c, err = v.streamTLSConn(ctx, c, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,7 +148,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||
case "h2":
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, true)
|
||||
c, err = v.streamTLSConn(ctx, c, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -162,8 +163,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)
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS And XTLS
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
|
||||
// handle TLS
|
||||
c, err = v.streamTLSConn(ctx, c, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -179,7 +180,7 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||
metadata = &C.Metadata{
|
||||
NetWork: C.UDP,
|
||||
Host: packetaddr.SeqPacketMagicAddress,
|
||||
DstPort: "443",
|
||||
DstPort: 443,
|
||||
}
|
||||
} else {
|
||||
metadata = &C.Metadata{ // a clear metadata only contains ip
|
||||
@ -201,29 +202,17 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
if v.option.TLS {
|
||||
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{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if isH2 {
|
||||
@ -240,10 +229,6 @@ func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 boo
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (v *Vless) isLegacyXTLSEnabled() bool {
|
||||
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
// gun transport
|
||||
@ -373,8 +358,14 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
|
||||
}
|
||||
|
||||
if v.option.XUDP {
|
||||
var globalID [8]byte
|
||||
if metadata.SourceValid() {
|
||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||
}
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
vmessSing.NewXUDPConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
), v), nil
|
||||
} else if v.option.PacketAddr {
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
@ -410,12 +401,11 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
copy(addr[1:], metadata.Host)
|
||||
}
|
||||
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
return &vless.DstAddr{
|
||||
UDP: metadata.NetWork == C.UDP,
|
||||
AddrType: addrType,
|
||||
Addr: addr,
|
||||
Port: uint16(port),
|
||||
Port: metadata.DstPort,
|
||||
Mux: metadata.NetWork == C.UDP && xudp,
|
||||
}
|
||||
}
|
||||
@ -519,11 +509,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
switch option.Flow {
|
||||
case 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{
|
||||
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:
|
||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||
}
|
||||
@ -542,7 +532,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option.PacketAddr = false
|
||||
}
|
||||
|
||||
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
|
||||
client, err := vless.NewClient(option.UUID, addons)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -555,6 +545,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -12,16 +12,18 @@ import (
|
||||
"sync"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
vmess "github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
@ -51,6 +53,7 @@ type VmessOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
@ -148,6 +151,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -204,6 +208,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -223,30 +228,44 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
||||
if metadata.NetWork == C.UDP {
|
||||
if v.option.XUDP {
|
||||
var globalID [8]byte
|
||||
if metadata.SourceValid() {
|
||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||
}
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn = v.client.DialEarlyXUDPPacketConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
} else {
|
||||
conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn, err = v.client.DialXUDPPacketConn(c,
|
||||
globalID,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
}
|
||||
} else if v.option.PacketAddr {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
} else {
|
||||
conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
conn, err = v.client.DialPacketConn(c,
|
||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
}
|
||||
conn = packetaddr.NewBindConn(conn)
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
} else {
|
||||
conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
conn, err = v.client.DialPacketConn(c,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
conn = v.client.DialEarlyConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
conn, err = v.client.DialConn(c,
|
||||
M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@ -398,6 +417,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if option.AuthenticatedLength {
|
||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
||||
}
|
||||
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
|
||||
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -421,6 +441,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
udp: option.UDP,
|
||||
xudp: option.XUDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -302,7 +302,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
@ -374,8 +374,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
||||
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
||||
} else {
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -412,8 +411,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -16,9 +17,10 @@ import (
|
||||
|
||||
type Fallback struct {
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
testUrl string
|
||||
selected string
|
||||
disableUDP bool
|
||||
testUrl string
|
||||
selected string
|
||||
expectedStatus string
|
||||
}
|
||||
|
||||
func (f *Fallback) Now() string {
|
||||
@ -82,9 +84,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": f.Type().String(),
|
||||
"now": f.Now(),
|
||||
"all": all,
|
||||
"type": f.Type().String(),
|
||||
"now": f.Now(),
|
||||
"all": all,
|
||||
"testUrl": f.testUrl,
|
||||
"expected": f.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -98,12 +102,14 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.GetProxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if len(f.selected) == 0 {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(f.testUrl) {
|
||||
return proxy
|
||||
}
|
||||
} else {
|
||||
if proxy.Name() == f.selected {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(f.testUrl) {
|
||||
return proxy
|
||||
} else {
|
||||
f.selected = ""
|
||||
@ -129,10 +135,12 @@ func (f *Fallback) Set(name string) error {
|
||||
}
|
||||
|
||||
f.selected = name
|
||||
if !p.Alive() {
|
||||
// if !p.Alive() {
|
||||
if !p.AliveForTestUrl(f.testUrl) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
|
||||
defer cancel()
|
||||
_, _ = p.URLTest(ctx, f.testUrl)
|
||||
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
|
||||
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -156,7 +164,8 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
@ -192,7 +193,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
return proxies
|
||||
}
|
||||
|
||||
func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) {
|
||||
func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
|
||||
var wg sync.WaitGroup
|
||||
var lock sync.Mutex
|
||||
mp := map[string]uint16{}
|
||||
@ -201,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
||||
proxy := proxy
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
delay, err := proxy.URLTest(ctx, url)
|
||||
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory)
|
||||
if err == nil {
|
||||
lock.Lock()
|
||||
mp[proxy.Name()] = delay
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/callback"
|
||||
"github.com/Dreamacro/clash/common/murmur3"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
@ -25,8 +25,10 @@ type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Pr
|
||||
|
||||
type LoadBalance struct {
|
||||
*GroupBase
|
||||
disableUDP bool
|
||||
strategyFn strategyFn
|
||||
disableUDP bool
|
||||
strategyFn strategyFn
|
||||
testUrl string
|
||||
expectedStatus string
|
||||
}
|
||||
|
||||
var errStrategy = errors.New("unsupported strategy")
|
||||
@ -129,7 +131,7 @@ func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
|
||||
}
|
||||
|
||||
func strategyRoundRobin() strategyFn {
|
||||
func strategyRoundRobin(url string) strategyFn {
|
||||
idx := 0
|
||||
idxMutex := sync.Mutex{}
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
@ -148,7 +150,8 @@ func strategyRoundRobin() strategyFn {
|
||||
for ; i < length; i++ {
|
||||
id := (idx + i) % length
|
||||
proxy := proxies[id]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
i++
|
||||
return proxy
|
||||
}
|
||||
@ -158,22 +161,24 @@ func strategyRoundRobin() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
func strategyConsistentHashing() strategyFn {
|
||||
func strategyConsistentHashing(url string) strategyFn {
|
||||
maxRetry := 5
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||
key := utils.MapHash(getKey(metadata))
|
||||
buckets := int32(len(proxies))
|
||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||
idx := jumpHash(key, buckets)
|
||||
proxy := proxies[idx]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
// when availability is poor, traverse the entire list to get the available nodes
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
@ -182,14 +187,14 @@ func strategyConsistentHashing() strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
func strategyStickySessions() strategyFn {
|
||||
func strategyStickySessions(url string) strategyFn {
|
||||
ttl := time.Minute * 10
|
||||
maxRetry := 5
|
||||
lruCache := cache.New[uint64, int](
|
||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||
cache.WithSize[uint64, int](1000))
|
||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||
key := utils.MapHash(getKeyWithSrcAndDst(metadata))
|
||||
length := len(proxies)
|
||||
idx, has := lruCache.Get(key)
|
||||
if !has {
|
||||
@ -199,7 +204,8 @@ func strategyStickySessions() strategyFn {
|
||||
nowIdx := idx
|
||||
for i := 1; i < maxRetry; i++ {
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.Alive() {
|
||||
// if proxy.Alive() {
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
if nowIdx != idx {
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, nowIdx)
|
||||
@ -230,8 +236,10 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": lb.Type().String(),
|
||||
"all": all,
|
||||
"type": lb.Type().String(),
|
||||
"all": all,
|
||||
"testUrl": lb.testUrl,
|
||||
"expectedStatus": lb.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -239,11 +247,11 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
||||
var strategyFn strategyFn
|
||||
switch strategy {
|
||||
case "consistent-hashing":
|
||||
strategyFn = strategyConsistentHashing()
|
||||
strategyFn = strategyConsistentHashing(option.URL)
|
||||
case "round-robin":
|
||||
strategyFn = strategyRoundRobin()
|
||||
strategyFn = strategyRoundRobin(option.URL)
|
||||
case "sticky-sessions":
|
||||
strategyFn = strategyStickySessions()
|
||||
strategyFn = strategyStickySessions(option.URL)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
|
||||
}
|
||||
@ -260,7 +268,9 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: option.DisableUDP,
|
||||
strategyFn: strategyFn,
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}, nil
|
||||
}
|
||||
|
@ -3,35 +3,37 @@ package outboundgroup
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
errFormat = errors.New("format error")
|
||||
errType = errors.New("unsupport type")
|
||||
errType = errors.New("unsupported type")
|
||||
errMissProxy = errors.New("`use` or `proxies` missing")
|
||||
errMissHealthCheck = errors.New("`url` or `interval` missing")
|
||||
errDuplicateProvider = errors.New("duplicate provider name")
|
||||
)
|
||||
|
||||
type GroupCommonOption struct {
|
||||
outbound.BasicOption
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
ExcludeFilter string `group:"exclude-filter,omitempty"`
|
||||
ExcludeType string `group:"exclude-type,omitempty"`
|
||||
Name string `group:"name"`
|
||||
Type string `group:"type"`
|
||||
Proxies []string `group:"proxies,omitempty"`
|
||||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
Filter string `group:"filter,omitempty"`
|
||||
ExcludeFilter string `group:"exclude-filter,omitempty"`
|
||||
ExcludeType string `group:"exclude-type,omitempty"`
|
||||
ExpectedStatus string `group:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
@ -53,30 +55,36 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
providers := []types.ProxyProvider{}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
return nil, errMissProxy
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
status := strings.TrimSpace(groupOption.ExpectedStatus)
|
||||
if status == "" {
|
||||
status = "*"
|
||||
}
|
||||
groupOption.ExpectedStatus = status
|
||||
testUrl := groupOption.URL
|
||||
|
||||
if len(groupOption.Proxies) != 0 {
|
||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
if _, ok := providersMap[groupName]; ok {
|
||||
return nil, errDuplicateProvider
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var url string
|
||||
var interval uint
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
} else {
|
||||
// select don't need health check
|
||||
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = "https://cp.cloudflare.com/generate_204"
|
||||
}
|
||||
@ -85,22 +93,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
groupOption.Interval = 300
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
url = groupOption.URL
|
||||
interval = uint(groupOption.Interval)
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providersMap[groupName] = pd
|
||||
}
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
// different proxy groups use different test URL
|
||||
addTestUrlToProviders(list, testUrl, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
|
||||
providers = append(providers, list...)
|
||||
} else {
|
||||
groupOption.Filter = ""
|
||||
@ -154,3 +169,13 @@ func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]type
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
if len(providers) == 0 || len(url) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, pd := range providers {
|
||||
pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,13 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
|
||||
|
||||
type URLTest struct {
|
||||
*GroupBase
|
||||
selected string
|
||||
testUrl string
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
fastSingle *singledo.Single[C.Proxy]
|
||||
selected string
|
||||
testUrl string
|
||||
expectedStatus string
|
||||
tolerance uint16
|
||||
disableUDP bool
|
||||
fastNode C.Proxy
|
||||
fastSingle *singledo.Single[C.Proxy]
|
||||
}
|
||||
|
||||
func (u *URLTest) Now() string {
|
||||
@ -96,44 +97,49 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
}
|
||||
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
var s C.Proxy
|
||||
proxies := u.GetProxies(touch)
|
||||
fast := proxies[0]
|
||||
if fast.Name() == u.selected {
|
||||
s = fast
|
||||
|
||||
proxies := u.GetProxies(touch)
|
||||
if u.selected != "" {
|
||||
for _, proxy := range proxies {
|
||||
if !proxy.Alive() {
|
||||
continue
|
||||
}
|
||||
if proxy.Name() == u.selected {
|
||||
u.fastNode = proxy
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
min := fast.LastDelay()
|
||||
}
|
||||
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
fast := proxies[0]
|
||||
// min := fast.LastDelay()
|
||||
min := fast.LastDelayForTestUrl(u.testUrl)
|
||||
fastNotExist := true
|
||||
|
||||
for _, proxy := range proxies[1:] {
|
||||
|
||||
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
|
||||
fastNotExist = false
|
||||
}
|
||||
|
||||
if proxy.Name() == u.selected {
|
||||
s = proxy
|
||||
}
|
||||
if !proxy.Alive() {
|
||||
// if !proxy.Alive() {
|
||||
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
delay := proxy.LastDelay()
|
||||
// delay := proxy.LastDelay()
|
||||
delay := proxy.LastDelayForTestUrl(u.testUrl)
|
||||
if delay < min {
|
||||
fast = proxy
|
||||
min = delay
|
||||
}
|
||||
|
||||
}
|
||||
// tolerance
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
||||
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
|
||||
u.fastNode = fast
|
||||
}
|
||||
if s != nil {
|
||||
if s.Alive() && s.LastDelay() < fast.LastDelay()+u.tolerance {
|
||||
u.fastNode = s
|
||||
}
|
||||
}
|
||||
return u.fastNode, nil
|
||||
})
|
||||
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
|
||||
@ -163,9 +169,11 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"type": u.Type().String(),
|
||||
"now": u.Now(),
|
||||
"all": all,
|
||||
"type": u.Type().String(),
|
||||
"now": u.Now(),
|
||||
"all": all,
|
||||
"testUrl": u.testUrl,
|
||||
"expected": u.expectedStatus,
|
||||
})
|
||||
}
|
||||
|
||||
@ -197,9 +205,10 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
|
||||
option.ExcludeType,
|
||||
providers,
|
||||
}),
|
||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
|
||||
disableUDP: option.DisableUDP,
|
||||
testUrl: option.URL,
|
||||
expectedStatus: option.ExpectedStatus,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
|
@ -106,6 +106,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewTuic(*tuicOption)
|
||||
case "direct":
|
||||
directOption := &outbound.DirectOption{}
|
||||
err = decoder.Decode(mapping, directOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewDirectWithOption(*directOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
@ -10,6 +12,8 @@ import (
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -21,18 +25,33 @@ type HealthCheckOption struct {
|
||||
Interval uint
|
||||
}
|
||||
|
||||
type extraOption struct {
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
filters map[string]struct{}
|
||||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
started *atomic.Bool
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
if hc.started.Load() {
|
||||
log.Warnln("Skip start health check timer due to it's started")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
|
||||
hc.start()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@ -44,6 +63,7 @@ func (hc *HealthCheck) process() {
|
||||
}
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
hc.stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -53,6 +73,63 @@ func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
|
||||
hc.proxies = proxies
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
url = strings.TrimSpace(url)
|
||||
if len(url) == 0 || url == hc.url {
|
||||
log.Debugln("ignore invalid health check url: %s", url)
|
||||
return
|
||||
}
|
||||
|
||||
hc.mu.Lock()
|
||||
defer hc.mu.Unlock()
|
||||
|
||||
// if the provider has not set up health checks, then modify it to be the same as the group's interval
|
||||
if hc.interval == 0 {
|
||||
hc.interval = interval
|
||||
}
|
||||
|
||||
if hc.extra == nil {
|
||||
hc.extra = make(map[string]*extraOption)
|
||||
}
|
||||
|
||||
// prioritize the use of previously registered configurations, especially those from provider
|
||||
if _, ok := hc.extra[url]; ok {
|
||||
// provider default health check does not set filter
|
||||
if url != hc.url && len(filter) != 0 {
|
||||
splitAndAddFiltersToExtra(filter, hc.extra[url])
|
||||
}
|
||||
|
||||
log.Debugln("health check url: %s exists", url)
|
||||
return
|
||||
}
|
||||
|
||||
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
|
||||
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
|
||||
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
|
||||
return
|
||||
}
|
||||
|
||||
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
||||
splitAndAddFiltersToExtra(filter, option)
|
||||
hc.extra[url] = option
|
||||
|
||||
if hc.auto() && !hc.started.Load() {
|
||||
go hc.process()
|
||||
}
|
||||
}
|
||||
|
||||
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
|
||||
filter = strings.TrimSpace(filter)
|
||||
if len(filter) != 0 {
|
||||
for _, regex := range strings.Split(filter, "`") {
|
||||
regex = strings.TrimSpace(regex)
|
||||
if len(regex) != 0 {
|
||||
option.filters[regex] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) auto() bool {
|
||||
return hc.interval != 0
|
||||
}
|
||||
@ -61,41 +138,102 @@ func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) start() {
|
||||
hc.started.Store(true)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) stop() {
|
||||
hc.started.Store(false)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := utils.NewUUIDV4().String()
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking %s {%s}", p.Name(), id)
|
||||
_, _ = p.URLTest(ctx, hc.url)
|
||||
log.Debugln("Health Checked %s : %t %d ms {%s}", p.Name(), p.Alive(), p.LastDelay(), id)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
// execute default health check
|
||||
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
|
||||
hc.execute(b, hc.url, id, option)
|
||||
|
||||
// execute extra health check
|
||||
if len(hc.extra) != 0 {
|
||||
for url, option := range hc.extra {
|
||||
hc.execute(b, url, id, option)
|
||||
}
|
||||
}
|
||||
b.Wait()
|
||||
log.Debugln("Finish A Health Checking {%s}", id)
|
||||
return struct{}{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
|
||||
url = strings.TrimSpace(url)
|
||||
if len(url) == 0 {
|
||||
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
|
||||
return
|
||||
}
|
||||
|
||||
var filterReg *regexp2.Regexp
|
||||
var store = C.OriginalHistory
|
||||
var expectedStatus utils.IntRanges[uint16]
|
||||
if option != nil {
|
||||
if url != hc.url {
|
||||
store = C.ExtraHistory
|
||||
}
|
||||
|
||||
expectedStatus = option.expectedStatus
|
||||
if len(option.filters) != 0 {
|
||||
filters := make([]string, 0, len(option.filters))
|
||||
for filter := range option.filters {
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proxy := range hc.proxies {
|
||||
// skip proxies that do not require health check
|
||||
if filterReg != nil {
|
||||
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
|
||||
_, _ = p.URLTest(ctx, url, expectedStatus, store)
|
||||
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
||||
hc.done <- struct{}{}
|
||||
}
|
||||
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
|
||||
if len(url) == 0 {
|
||||
interval = 0
|
||||
expectedStatus = nil
|
||||
}
|
||||
|
||||
return &HealthCheck{
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
extra: map[string]*extraOption{},
|
||||
started: atomic.NewBool(false),
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
expectedStatus: expectedStatus,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
}
|
||||
}
|
||||
|
@ -6,23 +6,28 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
var errVehicleType = errors.New("unsupport vehicle type")
|
||||
var (
|
||||
errVehicleType = errors.New("unsupport vehicle type")
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type healthCheckSchema struct {
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
@ -44,20 +49,33 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hcInterval uint
|
||||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus)
|
||||
|
||||
var vehicle types.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
"github.com/Dreamacro/clash/component/resource"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -50,6 +51,7 @@ func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
"type": pp.Type().String(),
|
||||
"vehicleType": pp.VehicleType().String(),
|
||||
"proxies": pp.Proxies(),
|
||||
"testUrl": pp.healthCheck.url,
|
||||
"updatedAt": pp.UpdatedAt,
|
||||
"subscriptionInfo": pp.subscriptionInfo,
|
||||
})
|
||||
@ -98,6 +100,10 @@ func (pp *proxySetProvider) Touch() {
|
||||
pp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
@ -141,15 +147,15 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) closeAllConnections() {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
for _, chain := range c.Chains() {
|
||||
if chain == pp.Name() {
|
||||
_ = c.Close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func stopProxyProvider(pd *ProxySetProvider) {
|
||||
@ -210,6 +216,7 @@ func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
"type": cp.Type().String(),
|
||||
"vehicleType": cp.VehicleType().String(),
|
||||
"proxies": cp.Proxies(),
|
||||
"testUrl": cp.healthCheck.url,
|
||||
})
|
||||
}
|
||||
|
||||
@ -249,6 +256,10 @@ func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||
pd.healthCheck.close()
|
||||
}
|
||||
@ -288,7 +299,7 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
if err := yaml.Unmarshal(buf, schema); err != nil {
|
||||
proxies, err1 := convert.ConvertsV2Ray(buf)
|
||||
if err1 != nil {
|
||||
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
|
||||
return nil, fmt.Errorf("%w, %w", err, err1)
|
||||
}
|
||||
schema.Proxies = proxies
|
||||
}
|
||||
|
@ -11,18 +11,9 @@ type Buffer = buf.Buffer
|
||||
|
||||
var New = buf.New
|
||||
var NewSize = buf.NewSize
|
||||
var StackNew = buf.StackNew
|
||||
var StackNewSize = buf.StackNewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
|
||||
var KeepAlive = common.KeepAlive
|
||||
|
||||
//go:norace
|
||||
func Dup[T any](obj T) T {
|
||||
return common.Dup(obj)
|
||||
}
|
||||
|
||||
var (
|
||||
Must = common.Must
|
||||
Error = common.Error
|
||||
|
53
common/cache/lrucache.go
vendored
53
common/cache/lrucache.go
vendored
@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/generics/list"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Option is part of Functional Options Pattern
|
||||
@ -82,9 +84,27 @@ 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
|
||||
// set to true if the key was found.
|
||||
func (c *LruCache[K, V]) Get(key K) (V, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
el := c.get(key)
|
||||
if el == nil {
|
||||
return getZero[V](), false
|
||||
return lo.Empty[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
|
||||
|
||||
@ -96,9 +116,12 @@ func (c *LruCache[K, V]) Get(key K) (V, bool) {
|
||||
// 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.
|
||||
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
el := c.get(key)
|
||||
if el == nil {
|
||||
return getZero[V](), time.Time{}, false
|
||||
return lo.Empty[V](), time.Time{}, false
|
||||
}
|
||||
|
||||
return el.value, time.Unix(el.expires, 0), true
|
||||
@ -115,11 +138,18 @@ func (c *LruCache[K, V]) Exist(key K) bool {
|
||||
|
||||
// Set stores the any representation of a response for a given key.
|
||||
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)
|
||||
if c.maxAge > 0 {
|
||||
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.
|
||||
@ -128,6 +158,10 @@ func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
|
||||
c.mu.Lock()
|
||||
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 {
|
||||
c.lru.MoveToBack(le)
|
||||
e := le.Value
|
||||
@ -165,9 +199,6 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[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]
|
||||
if !ok {
|
||||
return nil
|
||||
@ -191,12 +222,11 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
|
||||
// Delete removes the value associated with a key.
|
||||
func (c *LruCache[K, V]) Delete(key K) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if le, ok := c.cache[key]; ok {
|
||||
c.deleteElement(le)
|
||||
}
|
||||
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) maybeDeleteOldest() {
|
||||
@ -219,10 +249,10 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
|
||||
|
||||
func (c *LruCache[K, V]) Clear() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.cache = make(map[K]*list.Element[*entry[K, V]])
|
||||
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -231,8 +261,3 @@ type entry[K comparable, V any] struct {
|
||||
value V
|
||||
expires int64
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -50,7 +50,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["port"] = urlHysteria.Port()
|
||||
hysteria["sni"] = query.Get("peer")
|
||||
hysteria["obfs"] = query.Get("obfs")
|
||||
hysteria["alpn"] = []string{query.Get("alpn")}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
hysteria["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
hysteria["auth_str"] = query.Get("auth")
|
||||
hysteria["protocol"] = query.Get("protocol")
|
||||
up := query.Get("up")
|
||||
@ -67,6 +69,47 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
|
||||
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":
|
||||
urlTrojan, err := url.Parse(line)
|
||||
if err != nil {
|
||||
@ -86,10 +129,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
trojan["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
@ -217,6 +262,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if strings.HasSuffix(tls, "tls") {
|
||||
vmess["tls"] = true
|
||||
}
|
||||
if alpn, ok := values["alpn"].(string); ok {
|
||||
vmess["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
@ -332,6 +380,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, ss)
|
||||
|
||||
case "ssr":
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
if err != nil {
|
||||
|
@ -24,8 +24,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
proxy["port"] = url.Port()
|
||||
proxy["uuid"] = url.User.Username()
|
||||
proxy["udp"] = true
|
||||
proxy["skip-cert-verify"] = false
|
||||
proxy["tls"] = false
|
||||
tls := strings.ToLower(query.Get("security"))
|
||||
if strings.HasSuffix(tls, "tls") || tls == "reality" {
|
||||
proxy["tls"] = true
|
||||
@ -34,6 +32,9 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
} else {
|
||||
proxy["client-fingerprint"] = fingerprint
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
proxy["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
}
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
proxy["servername"] = sni
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
|
||||
if certificate == "" || privateKey == "" {
|
||||
if certificate == "" && privateKey == "" {
|
||||
return newRandomTLSKeyPair()
|
||||
}
|
||||
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
||||
|
@ -47,6 +47,7 @@ func (p *Picker[T]) Wait() T {
|
||||
p.wg.Wait()
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
p.cancel = nil
|
||||
}
|
||||
return p.result
|
||||
}
|
||||
@ -69,6 +70,7 @@ func (p *Picker[T]) Go(f func() (T, error)) {
|
||||
p.result = ret
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
p.cancel = nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -78,3 +80,13 @@ 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
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -15,7 +16,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
|
||||
case <-timer.C:
|
||||
return input, nil
|
||||
case <-ctx.Done():
|
||||
return getZero[T](), ctx.Err()
|
||||
return lo.Empty[T](), ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,11 +36,6 @@ func TestPicker_Timeout(t *testing.T) {
|
||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||
|
||||
number := picker.Wait()
|
||||
assert.Equal(t, number, getZero[int]())
|
||||
assert.Equal(t, number, lo.Empty[int]())
|
||||
assert.NotNil(t, picker.Error())
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -32,23 +32,32 @@ func NewAllocator() *Allocator {
|
||||
|
||||
// Get a []byte from pool with most appropriate cap
|
||||
func (alloc *Allocator) Get(size int) []byte {
|
||||
if size <= 0 || size > 65536 {
|
||||
switch {
|
||||
case size < 0:
|
||||
panic("alloc.Get: len out of range")
|
||||
case size == 0:
|
||||
return nil
|
||||
}
|
||||
case size > 65536:
|
||||
return make([]byte, size)
|
||||
default:
|
||||
bits := msb(size)
|
||||
if size == 1<<bits {
|
||||
return alloc.buffers[bits].Get().([]byte)[:size]
|
||||
}
|
||||
|
||||
bits := msb(size)
|
||||
if size == 1<<bits {
|
||||
return alloc.buffers[bits].Get().([]byte)[:size]
|
||||
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,
|
||||
// which the cap must be exactly 2^n
|
||||
func (alloc *Allocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bits := msb(cap(buf))
|
||||
if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1<<bits {
|
||||
if cap(buf) != 1<<bits {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
|
@ -19,17 +19,17 @@ func TestAllocGet(t *testing.T) {
|
||||
assert.Equal(t, 1024, cap(alloc.Get(1023)))
|
||||
assert.Equal(t, 1024, len(alloc.Get(1024)))
|
||||
assert.Equal(t, 65536, len(alloc.Get(65536)))
|
||||
assert.Nil(t, alloc.Get(65537))
|
||||
assert.Equal(t, 65537, len(alloc.Get(65537)))
|
||||
}
|
||||
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.Nil(t, alloc.Put(nil), "put nil 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, 1023, 1024)), "put elem:1024 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65536)), "put elem:65536 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
|
@ -2,6 +2,8 @@ package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Queue is a simple concurrent safe queue
|
||||
@ -24,7 +26,7 @@ func (q *Queue[T]) Put(items ...T) {
|
||||
// Pop returns the head of items.
|
||||
func (q *Queue[T]) Pop() T {
|
||||
if len(q.items) == 0 {
|
||||
return GetZero[T]()
|
||||
return lo.Empty[T]()
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
@ -37,7 +39,7 @@ func (q *Queue[T]) Pop() T {
|
||||
// Last returns the last of item.
|
||||
func (q *Queue[T]) Last() T {
|
||||
if len(q.items) == 0 {
|
||||
return GetZero[T]()
|
||||
return lo.Empty[T]()
|
||||
}
|
||||
|
||||
q.lock.RLock()
|
||||
@ -69,8 +71,3 @@ func New[T any](hint int64) *Queue[T] {
|
||||
items: make([]T, 0, hint),
|
||||
}
|
||||
}
|
||||
|
||||
func GetZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -96,6 +96,11 @@ func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
||||
return d.decodeFloat(name, data, val)
|
||||
}
|
||||
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:
|
||||
return d.decodeString(name, data, val)
|
||||
case reflect.Bool:
|
||||
@ -282,6 +287,9 @@ func (d *Decoder) decodeSlice(name string, data any, val reflect.Value) error {
|
||||
}
|
||||
|
||||
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++ {
|
||||
currentData := dataVal.Index(i).Interface()
|
||||
for valSlice.Len() <= i {
|
||||
|
17
common/utils/global_id.go
Normal file
17
common/utils/global_id.go
Normal file
@ -0,0 +1,17 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"hash/maphash"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var globalSeed = maphash.MakeSeed()
|
||||
|
||||
func GlobalID(material string) (id [8]byte) {
|
||||
*(*uint64)(unsafe.Pointer(&id[0])) = maphash.String(globalSeed, material)
|
||||
return
|
||||
}
|
||||
|
||||
func MapHash(material string) uint64 {
|
||||
return maphash.String(globalSeed, material)
|
||||
}
|
@ -9,36 +9,36 @@ type Range[T constraints.Ordered] struct {
|
||||
end T
|
||||
}
|
||||
|
||||
func NewRange[T constraints.Ordered](start, end T) *Range[T] {
|
||||
func NewRange[T constraints.Ordered](start, end T) Range[T] {
|
||||
if start > end {
|
||||
return &Range[T]{
|
||||
return Range[T]{
|
||||
start: end,
|
||||
end: start,
|
||||
}
|
||||
}
|
||||
|
||||
return &Range[T]{
|
||||
return Range[T]{
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Range[T]) Contains(t T) bool {
|
||||
func (r Range[T]) Contains(t T) bool {
|
||||
return t >= r.start && t <= r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) LeftContains(t T) bool {
|
||||
func (r Range[T]) LeftContains(t T) bool {
|
||||
return t >= r.start && t < r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) RightContains(t T) bool {
|
||||
func (r Range[T]) RightContains(t T) bool {
|
||||
return t > r.start && t <= r.end
|
||||
}
|
||||
|
||||
func (r *Range[T]) Start() T {
|
||||
func (r Range[T]) Start() T {
|
||||
return r.start
|
||||
}
|
||||
|
||||
func (r *Range[T]) End() T {
|
||||
func (r Range[T]) End() T {
|
||||
return r.end
|
||||
}
|
||||
|
77
common/utils/ranges.go
Normal file
77
common/utils/ranges.go
Normal file
@ -0,0 +1,77 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type IntRanges[T constraints.Integer] []Range[T]
|
||||
|
||||
var errIntRanges = errors.New("intRanges error")
|
||||
|
||||
func NewIntRanges[T constraints.Integer](expected string) (IntRanges[T], error) {
|
||||
// example: 200 or 200/302 or 200-400 or 200/204/401-429/501-503
|
||||
expected = strings.TrimSpace(expected)
|
||||
if len(expected) == 0 || expected == "*" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
list := strings.Split(expected, "/")
|
||||
if len(list) > 28 {
|
||||
return nil, fmt.Errorf("%w, too many ranges to use, maximum support 28 ranges", errIntRanges)
|
||||
}
|
||||
|
||||
return NewIntRangesFromList[T](list)
|
||||
}
|
||||
|
||||
func NewIntRangesFromList[T constraints.Integer](list []string) (IntRanges[T], error) {
|
||||
var ranges IntRanges[T]
|
||||
for _, s := range list {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
status := strings.Split(s, "-")
|
||||
statusLen := len(status)
|
||||
if statusLen > 2 {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
start, err := strconv.ParseInt(strings.Trim(status[0], "[ ]"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
switch statusLen {
|
||||
case 1:
|
||||
ranges = append(ranges, NewRange(T(start), T(start)))
|
||||
case 2:
|
||||
end, err := strconv.ParseUint(strings.Trim(status[1], "[ ]"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, errIntRanges
|
||||
}
|
||||
|
||||
ranges = append(ranges, NewRange(T(start), T(end)))
|
||||
}
|
||||
}
|
||||
|
||||
return ranges, nil
|
||||
}
|
||||
|
||||
func (ranges IntRanges[T]) Check(status T) bool {
|
||||
if len(ranges) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, segment := range ranges {
|
||||
if segment.Contains(status) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
21
common/utils/string_unsafe.go
Normal file
21
common/utils/string_unsafe.go
Normal file
@ -0,0 +1,21 @@
|
||||
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))
|
||||
}
|
51
component/dialer/bind.go
Normal file
51
component/dialer/bind.go
Normal file
@ -0,0 +1,51 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func LookupLocalAddrFromIfaceName(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination.IsValid() {
|
||||
if destination.Is4() || destination.Is4In6() {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
@ -7,52 +7,8 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
)
|
||||
|
||||
func lookupLocalAddr(ifaceName string, network string, destination netip.Addr, port int) (net.Addr, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *netip.Prefix
|
||||
switch network {
|
||||
case "udp4", "tcp4":
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
case "tcp6", "udp6":
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
default:
|
||||
if destination.IsValid() {
|
||||
if destination.Is4() {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv6Addr(destination)
|
||||
}
|
||||
} else {
|
||||
addr, err = ifaceObj.PickIPv4Addr(destination)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(network, "tcp") {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
} else if strings.HasPrefix(network, "udp") {
|
||||
return &net.UDPAddr{
|
||||
IP: addr.Addr().AsSlice(),
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, iface.ErrAddrNotFound
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, destination netip.Addr) error {
|
||||
if !destination.IsGlobalUnicast() {
|
||||
return nil
|
||||
@ -66,7 +22,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, destination, int(local))
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, destination, int(local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -84,7 +40,7 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
|
||||
|
||||
local, _ := strconv.ParseUint(port, 10, 16)
|
||||
|
||||
addr, err := lookupLocalAddr(ifaceName, network, netip.Addr{}, int(local))
|
||||
addr, err := LookupLocalAddrFromIfaceName(ifaceName, network, netip.Addr{}, int(local))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -20,3 +20,20 @@ func addControlToListenConfig(lc *net.ListenConfig, fn controlFn) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
//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)
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
//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)
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@ -131,6 +132,9 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
if opt.mpTcp {
|
||||
setMultiPathTCP(dialer)
|
||||
}
|
||||
if opt.tfo {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
@ -158,14 +162,22 @@ 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) {
|
||||
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
||||
preferIPVersion := opt.prefer
|
||||
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
|
||||
preferIPVersion := opt.prefer
|
||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
||||
defer fallbackTicker.Stop()
|
||||
|
||||
results := make(chan dialResult)
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
||||
defer wg.Done()
|
||||
result := dialResult{isPrimary: isPrimary}
|
||||
defer func() {
|
||||
select {
|
||||
@ -178,18 +190,36 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
||||
}()
|
||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
||||
}
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
|
||||
if len(ipv4s) != 0 {
|
||||
wg.Add(1)
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
}
|
||||
|
||||
if len(ipv6s) != 0 {
|
||||
wg.Add(1)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var fallback dialResult
|
||||
var errs []error
|
||||
for i := 0; i < 2; {
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-fallbackTicker.C:
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
case res := <-results:
|
||||
i++
|
||||
case res, ok := <-results:
|
||||
if !ok {
|
||||
break loop
|
||||
}
|
||||
if res.error == nil {
|
||||
if res.isPrimary {
|
||||
return res.Conn, nil
|
||||
@ -204,10 +234,11 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
return nil, errorsJoin(errs...)
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
@ -244,7 +275,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errorsJoin(errs...)
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
@ -261,7 +292,7 @@ func serialDialContext(ctx context.Context, network string, ips []netip.Addr, po
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil, errorsJoin(errs...)
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
type dialResult struct {
|
||||
|
@ -2,17 +2,9 @@ package dialer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorNoIpAddress = errors.New("no ip address")
|
||||
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...)
|
||||
}
|
||||
|
12
component/dialer/mptcp_go120.go
Normal file
12
component/dialer/mptcp_go120.go
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
const multipathTCPAvailable = false
|
||||
|
||||
func setMultiPathTCP(dialer *net.Dialer) {
|
||||
}
|
11
component/dialer/mptcp_go121.go
Normal file
11
component/dialer/mptcp_go121.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build go1.21
|
||||
|
||||
package dialer
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = true
|
||||
|
||||
func setMultiPathTCP(dialer *net.Dialer) {
|
||||
dialer.SetMultipathTCP(true)
|
||||
}
|
@ -25,6 +25,7 @@ type option struct {
|
||||
network int
|
||||
prefer int
|
||||
tfo bool
|
||||
mpTcp bool
|
||||
resolver resolver.Resolver
|
||||
netDialer NetDialer
|
||||
}
|
||||
@ -83,6 +84,12 @@ func WithTFO(tfo bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMPTCP(mpTcp bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.mpTcp = mpTcp
|
||||
}
|
||||
}
|
||||
|
||||
func WithNetDialer(netDialer NetDialer) Option {
|
||||
return func(opt *option) {
|
||||
opt.netDialer = netDialer
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
@ -37,12 +38,21 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// if not available device like Meta, dummy0, docker0, etc.
|
||||
if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&net.FlagRunning == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipNets := make([]*netip.Prefix, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
ipNet := addr.(*net.IPNet)
|
||||
ip, _ := netip.AddrFromSlice(ipNet.IP)
|
||||
|
||||
//unavailable IPv6 Address
|
||||
if ip.Is6() && strings.HasPrefix(ip.String(), "fe80") {
|
||||
continue
|
||||
}
|
||||
|
||||
ones, bits := ipNet.Mask.Size()
|
||||
if bits == 32 {
|
||||
ip = ip.Unmap()
|
||||
|
@ -12,42 +12,68 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
type databaseType = uint8
|
||||
|
||||
const (
|
||||
typeMaxmind databaseType = iota
|
||||
typeSing
|
||||
typeMetaV0
|
||||
)
|
||||
|
||||
var (
|
||||
mmdb *geoip2.Reader
|
||||
once sync.Once
|
||||
reader Reader
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func LoadFromBytes(buffer []byte) {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
mmdb, err = geoip2.FromBytes(buffer)
|
||||
mmdb, err := maxminddb.FromBytes(buffer)
|
||||
if err != nil {
|
||||
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 {
|
||||
instance, err := geoip2.Open(C.Path.MMDB())
|
||||
instance, err := maxminddb.Open(C.Path.MMDB())
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func Instance() *geoip2.Reader {
|
||||
func Instance() Reader {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
mmdb, err = geoip2.Open(C.Path.MMDB())
|
||||
mmdbPath := C.Path.MMDB()
|
||||
log.Debugln("Load MMDB file: %s", mmdbPath)
|
||||
mmdb, err := maxminddb.Open(mmdbPath)
|
||||
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 mmdb
|
||||
return reader
|
||||
}
|
||||
|
||||
func DownloadMMDB(path string) (err error) {
|
||||
|
56
component/mmdb/reader.go
Normal file
56
component/mmdb/reader.go
Normal file
@ -0,0 +1,56 @@
|
||||
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))
|
||||
}
|
||||
}
|
26
component/nat/proxy.go
Normal file
26
component/nat/proxy.go
Normal file
@ -0,0 +1,26 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type writeBackProxy struct {
|
||||
wb atomic.TypedValue[C.WriteBack]
|
||||
}
|
||||
|
||||
func (w *writeBackProxy) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return w.wb.Load().WriteBack(b, addr)
|
||||
}
|
||||
|
||||
func (w *writeBackProxy) UpdateWriteBack(wb C.WriteBack) {
|
||||
w.wb.Store(wb)
|
||||
}
|
||||
|
||||
func NewWriteBackProxy(wb C.WriteBack) C.WriteBackProxy {
|
||||
w := &writeBackProxy{}
|
||||
w.UpdateWriteBack(wb)
|
||||
return w
|
||||
}
|
@ -13,22 +13,24 @@ type Table struct {
|
||||
|
||||
type Entry struct {
|
||||
PacketConn C.PacketConn
|
||||
WriteBackProxy C.WriteBackProxy
|
||||
LocalUDPConnMap sync.Map
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, e C.PacketConn) {
|
||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||
t.mapping.Store(key, &Entry{
|
||||
PacketConn: e,
|
||||
WriteBackProxy: w,
|
||||
LocalUDPConnMap: sync.Map{},
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Table) Get(key string) C.PacketConn {
|
||||
func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
|
||||
entry, exist := t.getEntry(key)
|
||||
if !exist {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
return entry.PacketConn
|
||||
return entry.PacketConn, entry.WriteBackProxy
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||
|
@ -67,7 +67,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
|
||||
err := initWin32API()
|
||||
if err != nil {
|
||||
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
|
||||
log.Warnln("All PROCESS-NAMES rules will be skiped")
|
||||
log.Warnln("All PROCESS-NAMES rules will be skipped")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -65,7 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
var contents V
|
||||
@ -85,18 +87,18 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
contents, err = f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
@ -104,7 +106,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if f.vehicle.Type() != types.File && !isLocal {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +123,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
buf, err := f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -129,17 +131,17 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
f.UpdatedAt = &now
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return getZero[V](), true, nil
|
||||
return lo.Empty[V](), true, nil
|
||||
}
|
||||
|
||||
contents, err := f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,8 +212,3 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
func getZero[V any]() V {
|
||||
var result V
|
||||
return result
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type FileVehicle struct {
|
||||
@ -54,8 +56,10 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
buf, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -10,11 +10,11 @@ import (
|
||||
|
||||
type SnifferConfig struct {
|
||||
OverrideDest bool
|
||||
Ports []utils.Range[uint16]
|
||||
Ports utils.IntRanges[uint16]
|
||||
}
|
||||
|
||||
type BaseSniffer struct {
|
||||
ports []utils.Range[uint16]
|
||||
ports utils.IntRanges[uint16]
|
||||
supportNetworkType constant.NetWork
|
||||
}
|
||||
|
||||
@ -35,15 +35,10 @@ func (bs *BaseSniffer) SupportNetwork() constant.NetWork {
|
||||
|
||||
// SupportPort implements sniffer.Sniffer
|
||||
func (bs *BaseSniffer) SupportPort(port uint16) bool {
|
||||
for _, portRange := range bs.ports {
|
||||
if portRange.Contains(port) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return bs.ports.Check(port)
|
||||
}
|
||||
|
||||
func NewBaseSniffer(ports []utils.Range[uint16], networkType constant.NetWork) *BaseSniffer {
|
||||
func NewBaseSniffer(ports utils.IntRanges[uint16], networkType constant.NetWork) *BaseSniffer {
|
||||
return &BaseSniffer{
|
||||
ports: ports,
|
||||
supportNetworkType: networkType,
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -26,29 +25,23 @@ var (
|
||||
var Dispatcher *SnifferDispatcher
|
||||
|
||||
type SnifferDispatcher struct {
|
||||
enable bool
|
||||
sniffers map[sniffer.Sniffer]SnifferConfig
|
||||
forceDomain *trie.DomainSet
|
||||
skipSNI *trie.DomainSet
|
||||
skipList *cache.LruCache[string, uint8]
|
||||
rwMux sync.RWMutex
|
||||
forceDnsMapping bool
|
||||
parsePureIp bool
|
||||
enable bool
|
||||
sniffers map[sniffer.Sniffer]SnifferConfig
|
||||
forceDomain *trie.DomainSet
|
||||
skipSNI *trie.DomainSet
|
||||
skipList *cache.LruCache[string, uint8]
|
||||
rwMux sync.RWMutex
|
||||
forceDnsMapping bool
|
||||
parsePureIp bool
|
||||
}
|
||||
|
||||
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) {
|
||||
port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err != nil {
|
||||
log.Debugln("[Sniffer] Dst port is error")
|
||||
return
|
||||
}
|
||||
|
||||
inWhitelist := false
|
||||
overrideDest := false
|
||||
for sniffer, config := range sd.sniffers {
|
||||
if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet {
|
||||
inWhitelist = sniffer.SupportPort(uint16(port))
|
||||
inWhitelist = sniffer.SupportPort(metadata.DstPort)
|
||||
if inWhitelist {
|
||||
overrideDest = config.OverrideDest
|
||||
break
|
||||
@ -61,7 +54,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
|
||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||
defer sd.rwMux.RUnlock()
|
||||
@ -71,7 +64,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
|
||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return
|
||||
} else {
|
||||
if sd.skipSNI.Has(host) {
|
||||
@ -149,7 +142,7 @@ func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metad
|
||||
|
||||
func (sd *SnifferDispatcher) cacheSniffFailed(metadata *C.Metadata) {
|
||||
sd.rwMux.Lock()
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
|
||||
count, _ := sd.skipList.Get(dst)
|
||||
if count <= 5 {
|
||||
count++
|
||||
|
@ -34,11 +34,9 @@ type HTTPSniffer struct {
|
||||
var _ sniffer.Sniffer = (*HTTPSniffer)(nil)
|
||||
|
||||
func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
if len(snifferConfig.Ports) == 0 {
|
||||
ports = append(ports, *utils.NewRange[uint16](80, 80))
|
||||
} else {
|
||||
ports = append(ports, snifferConfig.Ports...)
|
||||
ports := snifferConfig.Ports
|
||||
if len(ports) == 0 {
|
||||
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](80, 80)}
|
||||
}
|
||||
return &HTTPSniffer{
|
||||
BaseSniffer: NewBaseSniffer(ports, C.TCP),
|
||||
|
@ -22,11 +22,9 @@ type TLSSniffer struct {
|
||||
}
|
||||
|
||||
func NewTLSSniffer(snifferConfig SnifferConfig) (*TLSSniffer, error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
if len(snifferConfig.Ports) == 0 {
|
||||
ports = append(ports, *utils.NewRange[uint16](443, 443))
|
||||
} else {
|
||||
ports = append(ports, snifferConfig.Ports...)
|
||||
ports := snifferConfig.Ports
|
||||
if len(ports) == 0 {
|
||||
ports = utils.IntRanges[uint16]{utils.NewRange[uint16](443, 443)}
|
||||
}
|
||||
return &TLSSniffer{
|
||||
BaseSniffer: NewBaseSniffer(ports, C.TCP),
|
||||
|
@ -10,14 +10,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
var trustCerts []*x509.Certificate
|
||||
var certPool *x509.CertPool
|
||||
var mutex sync.RWMutex
|
||||
var errNotMacth error = errors.New("certificate fingerprints do not match")
|
||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||
|
||||
func AddCertificate(certificate string) error {
|
||||
mutex.Lock()
|
||||
@ -79,7 +77,7 @@ func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedCh
|
||||
}
|
||||
}
|
||||
}
|
||||
return errNotMacth
|
||||
return errNotMatch
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,27 +120,3 @@ func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
tlsConfig.RootCAs = certPool
|
||||
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
|
||||
}
|
||||
|
@ -22,9 +22,11 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
|
||||
utls "github.com/sagernet/utls"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
@ -37,6 +39,9 @@ type RealityConfig struct {
|
||||
ShortID [RealityMaxShortIDLen]byte
|
||||
}
|
||||
|
||||
//go:linkname aesgcmPreferred crypto/tls.aesgcmPreferred
|
||||
func aesgcmPreferred(ciphers []uint16) bool
|
||||
|
||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
||||
verifier := &realityVerifier{
|
||||
@ -61,17 +66,17 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
}
|
||||
|
||||
hello := uConn.HandshakeState.Hello
|
||||
for i := range hello.SessionId { // https://github.com/golang/go/issues/5373
|
||||
hello.SessionId[i] = 0
|
||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
||||
rawSessionID[i] = 0
|
||||
}
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
|
||||
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[0] = 1
|
||||
hello.SessionId[1] = 8
|
||||
hello.SessionId[2] = 0
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[2] = 2
|
||||
|
||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||
|
||||
@ -84,9 +89,14 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||
var aeadCipher cipher.AEAD
|
||||
if aesgcmPreferred(hello.CipherSuites) {
|
||||
aesBlock, _ := aes.NewCipher(authKey)
|
||||
aeadCipher, _ = cipher.NewGCM(aesBlock)
|
||||
} else {
|
||||
aeadCipher, _ = chacha20poly1305.New(authKey)
|
||||
}
|
||||
aeadCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
|
||||
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
|
||||
@ -96,7 +106,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("REALITY Authentication: %v", verifier.verified)
|
||||
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher)
|
||||
|
||||
if !verifier.verified {
|
||||
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
||||
@ -137,7 +147,7 @@ type realityVerifier struct {
|
||||
verified bool
|
||||
}
|
||||
|
||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.UConn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
|
@ -23,6 +23,8 @@ type DomainSet struct {
|
||||
ranks, selects []int32
|
||||
}
|
||||
|
||||
type qElt struct{ s, e, col int }
|
||||
|
||||
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
|
||||
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||
reserveDomains := make([]string, 0)
|
||||
@ -39,7 +41,6 @@ func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
|
||||
ss := &DomainSet{}
|
||||
lIdx := 0
|
||||
|
||||
type qElt struct{ s, e, col int }
|
||||
queue := []qElt{{0, len(keys), 0}}
|
||||
for i := 0; i < len(queue); i++ {
|
||||
elt := queue[i]
|
||||
|
@ -1,8 +1,9 @@
|
||||
package trie
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type IPV6 bool
|
||||
@ -47,11 +48,10 @@ func (trie *IpCidrTrie) AddIpCidrForString(ipCidr string) error {
|
||||
}
|
||||
|
||||
func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
|
||||
ip, isIpv4 := checkAndConverterIp(ip)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
isIpv4 := len(ip) == net.IPv4len
|
||||
var groupValues []uint32
|
||||
var ipCidrNode *IpCidrNode
|
||||
|
||||
@ -71,7 +71,13 @@ func (trie *IpCidrTrie) IsContain(ip net.IP) bool {
|
||||
}
|
||||
|
||||
func (trie *IpCidrTrie) IsContainForString(ipString string) bool {
|
||||
return trie.IsContain(net.ParseIP(ipString))
|
||||
ip := net.ParseIP(ipString)
|
||||
// deal with 4in6
|
||||
actualIp := ip.To4()
|
||||
if actualIp == nil {
|
||||
actualIp = ip
|
||||
}
|
||||
return trie.IsContain(actualIp)
|
||||
}
|
||||
|
||||
func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
|
||||
@ -82,9 +88,8 @@ func ipCidrToSubIpCidr(ipNet *net.IPNet) ([]net.IP, int, bool, error) {
|
||||
isIpv4 bool
|
||||
err error
|
||||
)
|
||||
|
||||
ip, isIpv4 := checkAndConverterIp(ipNet.IP)
|
||||
ipList, newMaskSize, err = subIpCidr(ip, maskSize, isIpv4)
|
||||
isIpv4 = len(ipNet.IP) == net.IPv4len
|
||||
ipList, newMaskSize, err = subIpCidr(ipNet.IP, maskSize, isIpv4)
|
||||
|
||||
return ipList, newMaskSize, isIpv4, err
|
||||
}
|
||||
@ -238,18 +243,3 @@ func search(root *IpCidrNode, groupValues []uint32) *IpCidrNode {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return net.IP To4 or To16 and is ipv4
|
||||
func checkAndConverterIp(ip net.IP) (net.IP, bool) {
|
||||
ipResult := ip.To4()
|
||||
if ipResult == nil {
|
||||
ipResult = ip.To16()
|
||||
if ipResult == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ipResult, false
|
||||
}
|
||||
|
||||
return ipResult, true
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package trie
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
import "github.com/stretchr/testify/assert"
|
||||
|
||||
func TestIpv4AddSuccess(t *testing.T) {
|
||||
trie := NewIpCidrTrie()
|
||||
@ -96,5 +97,11 @@ func TestIpv6Search(t *testing.T) {
|
||||
assert.Equal(t, true, trie.IsContainForString("2001:67c:4e8:9666::1213"))
|
||||
|
||||
assert.Equal(t, false, trie.IsContain(net.ParseIP("22233:22")))
|
||||
|
||||
}
|
||||
|
||||
func TestIpv4InIpv6(t *testing.T) {
|
||||
trie := NewIpCidrTrie()
|
||||
|
||||
// Boundary testing
|
||||
assert.NoError(t, trie.AddIpCidrForString("::ffff:198.18.5.138/128"))
|
||||
}
|
||||
|
141
config/config.go
141
config/config.go
@ -9,7 +9,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -52,6 +51,7 @@ type General struct {
|
||||
IPv6 bool `json:"ipv6"`
|
||||
Interface string `json:"interface-name"`
|
||||
RoutingMark int `json:"-"`
|
||||
GeoXUrl GeoXUrl `json:"geox-url"`
|
||||
GeodataMode bool `json:"geodata-mode"`
|
||||
GeodataLoader string `json:"geodata-loader"`
|
||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||
@ -76,6 +76,7 @@ type Inbound struct {
|
||||
AllowLan bool `json:"allow-lan"`
|
||||
BindAddress string `json:"bind-address"`
|
||||
InboundTfo bool `json:"inbound-tfo"`
|
||||
InboundMPTCP bool `json:"inbound-mptcp"`
|
||||
}
|
||||
|
||||
// Controller config
|
||||
@ -86,6 +87,14 @@ type Controller struct {
|
||||
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
|
||||
type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
@ -150,6 +159,7 @@ type Experimental struct {
|
||||
type Config struct {
|
||||
General *General
|
||||
IPTables *IPTables
|
||||
NTP *NTP
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
@ -166,6 +176,13 @@ type Config struct {
|
||||
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 {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
@ -220,16 +237,18 @@ type RawTun struct {
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Token []string `yaml:"token" json:"token"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
@ -241,6 +260,7 @@ type RawConfig struct {
|
||||
ShadowSocksConfig string `yaml:"ss-config"`
|
||||
VmessConfig string `yaml:"vmess-config"`
|
||||
InboundTfo bool `yaml:"inbound-tfo"`
|
||||
InboundMPTCP bool `yaml:"inbound-mptcp"`
|
||||
Authentication []string `yaml:"authentication"`
|
||||
AllowLan bool `yaml:"allow-lan"`
|
||||
BindAddress string `yaml:"bind-address"`
|
||||
@ -265,6 +285,7 @@ type RawConfig struct {
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||
Hosts map[string]any `yaml:"hosts"`
|
||||
NTP RawNTP `yaml:"ntp"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||
@ -272,7 +293,7 @@ type RawConfig struct {
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
GeoXUrl RawGeoXUrl `yaml:"geox-url"`
|
||||
GeoXUrl GeoXUrl `yaml:"geox-url"`
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
@ -281,7 +302,7 @@ type RawConfig struct {
|
||||
Listeners []map[string]any `yaml:"listeners"`
|
||||
}
|
||||
|
||||
type RawGeoXUrl struct {
|
||||
type GeoXUrl struct {
|
||||
GeoIp string `yaml:"geoip" json:"geoip"`
|
||||
Mmdb string `yaml:"mmdb" json:"mmdb"`
|
||||
GeoSite string `yaml:"geosite" json:"geosite"`
|
||||
@ -356,6 +377,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
TuicServer: RawTuicServer{
|
||||
Enable: false,
|
||||
Token: nil,
|
||||
Users: nil,
|
||||
Certificate: "",
|
||||
PrivateKey: "",
|
||||
Listen: "",
|
||||
@ -416,10 +438,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: RawGeoXUrl{
|
||||
Mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb",
|
||||
GeoIp: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
GeoXUrl: GeoXUrl{
|
||||
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
|
||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
},
|
||||
}
|
||||
|
||||
@ -446,7 +468,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
config.General = general
|
||||
|
||||
if len(config.General.GlobalClientFingerprint) != 0 {
|
||||
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
|
||||
log.Debugln("GlobalClientFingerprint: %s", config.General.GlobalClientFingerprint)
|
||||
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
|
||||
}
|
||||
|
||||
@ -488,6 +510,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.Hosts = hosts
|
||||
|
||||
ntpCfg := paresNTP(rawCfg)
|
||||
config.NTP = ntpCfg
|
||||
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -530,6 +555,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
externalUI := cfg.ExternalUI
|
||||
geodata.SetLoader(cfg.GeodataLoader)
|
||||
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
||||
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
|
||||
C.MmdbUrl = cfg.GeoXUrl.Mmdb
|
||||
C.GeodataMode = cfg.GeodataMode
|
||||
// checkout externalUI exist
|
||||
if externalUI != "" {
|
||||
externalUI = C.Path.Resolve(externalUI)
|
||||
@ -550,6 +579,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
InboundTfo: cfg.InboundTfo,
|
||||
InboundMPTCP: cfg.InboundMPTCP,
|
||||
},
|
||||
Controller: Controller{
|
||||
ExternalController: cfg.ExternalController,
|
||||
@ -563,6 +593,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
IPv6: cfg.IPv6,
|
||||
Interface: cfg.Interface,
|
||||
RoutingMark: cfg.RoutingMark,
|
||||
GeoXUrl: cfg.GeoXUrl,
|
||||
GeodataMode: cfg.GeodataMode,
|
||||
GeodataLoader: cfg.GeodataLoader,
|
||||
TCPConcurrent: cfg.TCPConcurrent,
|
||||
@ -655,7 +686,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
}
|
||||
ps = append(ps, proxies[v])
|
||||
}
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true, nil)
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
@ -710,6 +741,9 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.
|
||||
|
||||
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) {
|
||||
subRules = map[string][]C.Rule{}
|
||||
for name := range cfg.SubRules {
|
||||
subRules[name] = make([]C.Rule, 0)
|
||||
}
|
||||
for name, rawRules := range cfg.SubRules {
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("sub-rule name is empty")
|
||||
@ -914,7 +948,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
addr, err = hostWithDefaultPort(u.Host, "443")
|
||||
if err == nil {
|
||||
proxyName = ""
|
||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
|
||||
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path, User: u.User}
|
||||
addr = clearURL.String()
|
||||
dnsNetType = "https" // DNS over HTTPS
|
||||
if len(u.Fragment) != 0 {
|
||||
@ -940,6 +974,19 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
||||
dnsNetType = "quic" // DNS over QUIC
|
||||
case "system":
|
||||
dnsNetType = "system" // System DNS
|
||||
case "rcode":
|
||||
dnsNetType = "rcode"
|
||||
addr = u.Host
|
||||
switch addr {
|
||||
case "success",
|
||||
"format_error",
|
||||
"server_failure",
|
||||
"name_error",
|
||||
"not_implemented",
|
||||
"refused":
|
||||
default:
|
||||
err = fmt.Errorf("unsupported RCode type: %s", addr)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||
}
|
||||
@ -1105,6 +1152,29 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
||||
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) {
|
||||
cfg := rawCfg.DNS
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
@ -1282,6 +1352,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||
Enable: rawTuic.Enable,
|
||||
Listen: rawTuic.Listen,
|
||||
Token: rawTuic.Token,
|
||||
Users: rawTuic.Users,
|
||||
Certificate: rawTuic.Certificate,
|
||||
PrivateKey: rawTuic.PrivateKey,
|
||||
CongestionController: rawTuic.CongestionController,
|
||||
@ -1289,6 +1360,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||
AuthenticationTimeout: rawTuic.AuthenticationTimeout,
|
||||
ALPN: rawTuic.ALPN,
|
||||
MaxUdpRelayPacketSize: rawTuic.MaxUdpRelayPacketSize,
|
||||
CWND: rawTuic.CWND,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1304,7 +1376,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
if len(snifferRaw.Sniff) != 0 {
|
||||
for sniffType, sniffConfig := range snifferRaw.Sniff {
|
||||
find := false
|
||||
ports, err := parsePortRange(sniffConfig.Ports)
|
||||
ports, err := utils.NewIntRangesFromList[uint16](sniffConfig.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1331,7 +1403,7 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
// Deprecated: Use Sniff instead
|
||||
log.Warnln("Deprecated: Use Sniff instead")
|
||||
}
|
||||
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
||||
globalPorts, err := utils.NewIntRangesFromList[uint16](snifferRaw.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1376,28 +1448,3 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
|
||||
return sniffer, nil
|
||||
}
|
||||
|
||||
func parsePortRange(portRanges []string) ([]utils.Range[uint16], error) {
|
||||
ports := make([]utils.Range[uint16], 0)
|
||||
for _, portRange := range portRanges {
|
||||
portRaws := strings.Split(portRange, "-")
|
||||
p, err := strconv.ParseUint(portRaws[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s format error", portRange)
|
||||
}
|
||||
|
||||
start := uint16(p)
|
||||
if len(portRaws) > 1 {
|
||||
p, err = strconv.ParseUint(portRaws[1], 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s format error", portRange)
|
||||
}
|
||||
|
||||
end := uint16(p)
|
||||
ports = append(ports, *utils.NewRange(start, end))
|
||||
} else {
|
||||
ports = append(ports, *utils.NewRange(start, start))
|
||||
}
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"os"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -28,23 +27,6 @@ func Init(dir string) error {
|
||||
f.Write([]byte(`mixed-port: 7890`))
|
||||
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
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
@ -44,7 +44,7 @@ func UpdateGeoDatabases() error {
|
||||
return fmt.Errorf("can't download MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
instance, err := geoip2.FromBytes(data)
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MMDB database file: %s", err)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
)
|
||||
|
||||
@ -40,9 +41,10 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
DefaultUDPTimeout = DefaultTCPTimeout
|
||||
DefaultTLSTimeout = DefaultTCPTimeout
|
||||
DefaultTCPTimeout = 5 * time.Second
|
||||
DefaultUDPTimeout = DefaultTCPTimeout
|
||||
DefaultTLSTimeout = DefaultTCPTimeout
|
||||
DefaultMaxHealthCheckUrlNum = 16
|
||||
)
|
||||
|
||||
var ErrNotSupport = errors.New("no support")
|
||||
@ -132,7 +134,7 @@ type ProxyAdapter interface {
|
||||
}
|
||||
|
||||
type Group interface {
|
||||
URLTest(ctx context.Context, url string) (mp map[string]uint16, err error)
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
|
||||
GetProxies(touch bool) []Proxy
|
||||
Touch()
|
||||
}
|
||||
@ -142,12 +144,23 @@ type DelayHistory struct {
|
||||
Delay uint16 `json:"delay"`
|
||||
}
|
||||
|
||||
type DelayHistoryStoreType int
|
||||
|
||||
const (
|
||||
OriginalHistory DelayHistoryStoreType = iota
|
||||
ExtraHistory
|
||||
DropHistory
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
ProxyAdapter
|
||||
Alive() bool
|
||||
AliveForTestUrl(url string) bool
|
||||
DelayHistory() []DelayHistory
|
||||
ExtraDelayHistory() map[string][]DelayHistory
|
||||
LastDelay() uint16
|
||||
URLTest(ctx context.Context, url string) (uint16, error)
|
||||
LastDelayForTestUrl(url string) uint16
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error)
|
||||
|
||||
// Deprecated: use DialContext instead.
|
||||
Dial(metadata *Metadata) (Conn, error)
|
||||
@ -217,7 +230,7 @@ type UDPPacket interface {
|
||||
// - variable source IP/Port is important to STUN
|
||||
// - if addr is not provided, WriteBack will write out UDP packet with SourceIP/Port equals to original Target,
|
||||
// this is important when using Fake-IP.
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
WriteBack
|
||||
|
||||
// Drop call after packet is used, could recycle buffer in this function.
|
||||
Drop()
|
||||
@ -236,10 +249,19 @@ type PacketAdapter interface {
|
||||
Metadata() *Metadata
|
||||
}
|
||||
|
||||
type NatTable interface {
|
||||
Set(key string, e PacketConn)
|
||||
type WriteBack interface {
|
||||
WriteBack(b []byte, addr net.Addr) (n int, err error)
|
||||
}
|
||||
|
||||
Get(key string) PacketConn
|
||||
type WriteBackProxy interface {
|
||||
WriteBack
|
||||
UpdateWriteBack(wb WriteBack)
|
||||
}
|
||||
|
||||
type NatTable interface {
|
||||
Set(key string, e PacketConn, w WriteBackProxy)
|
||||
|
||||
Get(key string) (PacketConn, WriteBackProxy)
|
||||
|
||||
GetOrCreateLock(key string) (*sync.Cond, bool)
|
||||
|
||||
|
@ -128,10 +128,10 @@ type Metadata struct {
|
||||
Type Type `json:"type"`
|
||||
SrcIP netip.Addr `json:"sourceIP"`
|
||||
DstIP netip.Addr `json:"destinationIP"`
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output
|
||||
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output
|
||||
InIP netip.Addr `json:"inboundIP"`
|
||||
InPort string `json:"inboundPort"`
|
||||
InPort uint16 `json:"inboundPort,string"` // `,string` is used to compatible with old version json output
|
||||
InName string `json:"inboundName"`
|
||||
InUser string `json:"inboundUser"`
|
||||
Host string `json:"host"`
|
||||
@ -147,11 +147,11 @@ type Metadata struct {
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
return net.JoinHostPort(m.String(), m.DstPort)
|
||||
return net.JoinHostPort(m.String(), strconv.FormatUint(uint64(m.DstPort), 10))
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceAddress() string {
|
||||
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
|
||||
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10))
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceDetail() string {
|
||||
@ -171,6 +171,10 @@ func (m *Metadata) SourceDetail() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceValid() bool {
|
||||
return m.SrcPort != 0 && m.SrcIP.IsValid()
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrType() int {
|
||||
switch true {
|
||||
case m.Host != "" || !m.DstIP.IsValid():
|
||||
@ -207,8 +211,7 @@ func (m *Metadata) Pure() *Metadata {
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrPort() netip.AddrPort {
|
||||
port, _ := strconv.ParseUint(m.DstPort, 10, 16)
|
||||
return netip.AddrPortFrom(m.DstIP.Unmap(), uint16(port))
|
||||
return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||
@ -238,6 +241,11 @@ func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
||||
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 {
|
||||
m.Host = host
|
||||
m.DstIP = netip.Addr{}
|
||||
@ -245,7 +253,7 @@ func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
||||
m.Host = ""
|
||||
m.DstIP = ip.Unmap()
|
||||
}
|
||||
m.DstPort = port
|
||||
m.DstPort = uint16Port
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -20,14 +23,15 @@ var Path = func() *path {
|
||||
if err != nil {
|
||||
homeDir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
|
||||
homeDir = P.Join(homeDir, ".config", Name)
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml"}
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
||||
}()
|
||||
|
||||
type path struct {
|
||||
homeDir string
|
||||
configFile string
|
||||
homeDir string
|
||||
configFile string
|
||||
allowUnsafePath bool
|
||||
}
|
||||
|
||||
// SetHomeDir is used to set the configuration path
|
||||
@ -56,6 +60,27 @@ func (p *path) Resolve(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// IsSafePath return true if path is a subpath of homedir
|
||||
func (p *path) IsSafePath(path string) bool {
|
||||
if p.allowUnsafePath {
|
||||
return true
|
||||
}
|
||||
homedir := p.HomeDir()
|
||||
path = p.Resolve(path)
|
||||
rel, err := filepath.Rel(homedir, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.Contains(rel, "..")
|
||||
}
|
||||
|
||||
func (p *path) GetPathByHash(prefix, name string) string {
|
||||
hash := md5.Sum([]byte(name))
|
||||
filename := hex.EncodeToString(hash[:])
|
||||
return filepath.Join(p.HomeDir(), prefix, filename)
|
||||
}
|
||||
|
||||
func (p *path) MMDB() string {
|
||||
files, err := os.ReadDir(p.homeDir)
|
||||
if err != nil {
|
||||
@ -66,13 +91,15 @@ func (p *path) MMDB() string {
|
||||
// 目录则直接跳过
|
||||
continue
|
||||
} 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()
|
||||
return P.Join(p.homeDir, fi.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return P.Join(p.homeDir, "Country.mmdb")
|
||||
return P.Join(p.homeDir, "geoip.metadb")
|
||||
}
|
||||
|
||||
func (p *path) OldCache() string {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -71,6 +72,7 @@ type ProxyProvider interface {
|
||||
Touch()
|
||||
HealthCheck()
|
||||
Version() uint32
|
||||
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
|
||||
}
|
||||
|
||||
// RuleProvider interface
|
||||
|
@ -10,12 +10,14 @@ var StackTypeMapping = map[string]TUNStack{
|
||||
strings.ToLower(TunGvisor.String()): TunGvisor,
|
||||
strings.ToLower(TunSystem.String()): TunSystem,
|
||||
strings.ToLower(TunLWIP.String()): TunLWIP,
|
||||
strings.ToLower(TunMixed.String()): TunMixed,
|
||||
}
|
||||
|
||||
const (
|
||||
TunGvisor TUNStack = iota
|
||||
TunSystem
|
||||
TunLWIP
|
||||
TunMixed
|
||||
)
|
||||
|
||||
type TUNStack int
|
||||
@ -64,6 +66,8 @@ func (e TUNStack) String() string {
|
||||
return "System"
|
||||
case TunLWIP:
|
||||
return "LWIP"
|
||||
case TunMixed:
|
||||
return "Mixed"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return batchExchange(ctx, clients, m)
|
||||
msg, _, err = batchExchange(ctx, clients, m)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
||||
|
12
dns/doh.go
12
dns/doh.go
@ -543,7 +543,17 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return quic.DialEarlyContext(ctx, conn, &udpAddr, doh.url.Host, tlsCfg, cfg)
|
||||
transport := quic.Transport{Conn: conn}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
tlsCfg = tlsCfg.Clone()
|
||||
if host, _, err := net.SplitHostPort(doh.url.Host); err == nil {
|
||||
tlsCfg.ServerName = host
|
||||
} else {
|
||||
// It's ok if net.SplitHostPort returns an error - it could be a hostname/IP address without a port.
|
||||
tlsCfg.ServerName = doh.url.Host
|
||||
}
|
||||
return transport.DialEarly(ctx, &udpAddr, tlsCfg, cfg)
|
||||
}
|
||||
|
||||
// probeH3 runs a test to check whether QUIC is faster than TLS for this
|
||||
|
23
dns/doq.go
23
dns/doq.go
@ -302,14 +302,6 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q
|
||||
|
||||
// openConnection opens a new QUIC connection.
|
||||
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) {
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
})
|
||||
// we're using bootstrapped address instead of what's passed to the function
|
||||
// it does not create an actual connection, but it helps us determine
|
||||
// what IP is actually reachable (when there're v4/v6 addresses).
|
||||
@ -338,7 +330,20 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err = quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, doq.getQUICConfig())
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
})
|
||||
|
||||
transport := quic.Transport{Conn: udp}
|
||||
transport.SetCreatedConn(true) // auto close conn
|
||||
transport.SetSingleUse(true) // auto close transport
|
||||
conn, err = transport.Dial(ctx, &udpAddr, tlsConfig, doq.getQUICConfig())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer {
|
||||
|
||||
if cfg.EnhancedMode != C.DNSNormal {
|
||||
fakePool = cfg.Pool
|
||||
mapping = cache.New(cache.WithSize[netip.Addr, string](4096), cache.WithStale[netip.Addr, string](true))
|
||||
mapping = cache.New(cache.WithSize[netip.Addr, string](4096))
|
||||
}
|
||||
|
||||
return &ResolverEnhancer{
|
||||
|
@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@ -9,7 +10,6 @@ import (
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fallbackIPFilter interface {
|
||||
@ -24,8 +24,13 @@ var geoIPMatcher *router.GeoIPMatcher
|
||||
|
||||
func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
||||
if !C.GeodataMode {
|
||||
record, _ := mmdb.Instance().Country(ip.AsSlice())
|
||||
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
|
||||
codes := mmdb.Instance().LookupCode(ip.AsSlice())
|
||||
for _, code := range codes {
|
||||
if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if geoIPMatcher == nil {
|
||||
|
@ -129,6 +129,10 @@ func withMapping(mapping *cache.LruCache[netip.Addr, string]) middleware {
|
||||
continue
|
||||
}
|
||||
|
||||
if ttl < 1 {
|
||||
ttl = 1
|
||||
}
|
||||
|
||||
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*time.Duration(ttl)))
|
||||
}
|
||||
|
||||
|
54
dns/rcode.go
Normal file
54
dns/rcode.go
Normal file
@ -0,0 +1,54 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func newRCodeClient(addr string) rcodeClient {
|
||||
var rcode int
|
||||
switch addr {
|
||||
case "success":
|
||||
rcode = D.RcodeSuccess
|
||||
case "format_error":
|
||||
rcode = D.RcodeFormatError
|
||||
case "server_failure":
|
||||
rcode = D.RcodeServerFailure
|
||||
case "name_error":
|
||||
rcode = D.RcodeNameError
|
||||
case "not_implemented":
|
||||
rcode = D.RcodeNotImplemented
|
||||
case "refused":
|
||||
rcode = D.RcodeRefused
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported RCode type: %s", addr))
|
||||
}
|
||||
|
||||
return rcodeClient{
|
||||
rcode: rcode,
|
||||
addr: "rcode://" + addr,
|
||||
}
|
||||
}
|
||||
|
||||
type rcodeClient struct {
|
||||
rcode int
|
||||
addr string
|
||||
}
|
||||
|
||||
var _ dnsClient = rcodeClient{}
|
||||
|
||||
func (r rcodeClient) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
m.Response = true
|
||||
m.Rcode = r.rcode
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
return r.Exchange(m)
|
||||
}
|
||||
|
||||
func (r rcodeClient) Address() string {
|
||||
return r.addr
|
||||
}
|
@ -165,7 +165,8 @@ func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, e
|
||||
setMsgTTL(msg, uint32(1)) // Continue fetch
|
||||
continueFetch = true
|
||||
} else {
|
||||
setMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
// updating TTL by subtracting common delta time from each DNS record
|
||||
updateMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -181,6 +182,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
fn := func() (result any, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
|
||||
defer cancel()
|
||||
cache := false
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@ -191,7 +193,9 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
msg := result.(*D.Msg)
|
||||
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
if cache {
|
||||
putMsgToCache(r.lruCache, q.String(), msg)
|
||||
}
|
||||
}()
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
@ -200,9 +204,11 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
}
|
||||
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
return r.batchExchange(ctx, matched, m)
|
||||
result, cache, err = r.batchExchange(ctx, matched, m)
|
||||
return
|
||||
}
|
||||
return r.batchExchange(ctx, r.main, m)
|
||||
result, cache, err = r.batchExchange(ctx, r.main, m)
|
||||
return
|
||||
}
|
||||
|
||||
ch := r.group.DoChan(q.String(), fn)
|
||||
@ -243,7 +249,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||
defer cancel()
|
||||
|
||||
@ -370,7 +376,7 @@ func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (i
|
||||
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result, 1)
|
||||
go func() {
|
||||
res, err := r.batchExchange(ctx, client, msg)
|
||||
res, _, err := r.batchExchange(ctx, client, msg)
|
||||
ch <- &result{Msg: res, Error: err}
|
||||
}()
|
||||
return ch
|
||||
|
@ -3,25 +3,41 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// nameserver xxx.xxx.xxx.xxx
|
||||
nameserverPattern = regexp.MustCompile(`nameserver\s+(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
|
||||
)
|
||||
const resolvConf = "/etc/resolv.conf"
|
||||
|
||||
func dnsReadConfig() (servers []string, err error) {
|
||||
content, err := os.ReadFile("/etc/resolv.conf")
|
||||
file, err := os.Open(resolvConf)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to read /etc/resolv.conf: %w", err)
|
||||
err = fmt.Errorf("failed to read %s: %w", resolvConf, err)
|
||||
return
|
||||
}
|
||||
for _, line := range nameserverPattern.FindAllStringSubmatch(string(content), -1) {
|
||||
addr := line[1]
|
||||
servers = append(servers, addr)
|
||||
defer func() { _ = file.Close() }()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 1 {
|
||||
continue
|
||||
}
|
||||
switch f[0] {
|
||||
case "nameserver": // add one name server
|
||||
if len(f) > 1 {
|
||||
if addr, err := netip.ParseAddr(f[1]); err == nil {
|
||||
servers = append(servers, addr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
81
dns/util.go
81
dns/util.go
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -21,12 +22,29 @@ import (
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxMsgSize = 65535
|
||||
)
|
||||
|
||||
func minimalTTL(records []D.RR) uint32 {
|
||||
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return r1.Header().Ttl < r2.Header().Ttl
|
||||
}).Header().Ttl
|
||||
}
|
||||
|
||||
func updateTTL(records []D.RR, ttl uint32) {
|
||||
if len(records) == 0 {
|
||||
return
|
||||
}
|
||||
delta := minimalTTL(records) - ttl
|
||||
for i := range records {
|
||||
records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
// skip dns cache for acme challenge
|
||||
if len(msg.Question) != 0 {
|
||||
@ -38,11 +56,11 @@ func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
var ttl uint32
|
||||
switch {
|
||||
case len(msg.Answer) != 0:
|
||||
ttl = msg.Answer[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Answer)
|
||||
case len(msg.Ns) != 0:
|
||||
ttl = msg.Ns[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Ns)
|
||||
case len(msg.Extra) != 0:
|
||||
ttl = msg.Extra[0].Header().Ttl
|
||||
ttl = minimalTTL(msg.Extra)
|
||||
default:
|
||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||
return
|
||||
@ -65,12 +83,18 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func updateMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
updateTTL(msg.Answer, ttl)
|
||||
updateTTL(msg.Ns, ttl)
|
||||
updateTTL(msg.Extra, ttl)
|
||||
}
|
||||
|
||||
func isIPRequest(q D.Question) bool {
|
||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
||||
}
|
||||
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := []dnsClient{}
|
||||
ret := make([]dnsClient, 0, len(servers))
|
||||
for _, s := range servers {
|
||||
switch s.Net {
|
||||
case "https":
|
||||
@ -91,6 +115,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
}
|
||||
ret = append(ret, clients...)
|
||||
continue
|
||||
case "rcode":
|
||||
ret = append(ret, newRCodeClient(s.Addr))
|
||||
continue
|
||||
case "quic":
|
||||
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
|
||||
ret = append(ret, doq)
|
||||
@ -167,6 +194,10 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
var ok bool
|
||||
proxyAdapter, ok = tunnel.Proxies()[proxyName]
|
||||
@ -180,7 +211,7 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstPort: port,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter != nil {
|
||||
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
|
||||
@ -205,7 +236,7 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: port,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.DialContext(ctx, network, addr, opts...)
|
||||
@ -231,6 +262,10 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
var ok bool
|
||||
proxyAdapter, ok = tunnel.Proxies()[proxyName]
|
||||
@ -248,7 +283,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: port,
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
|
||||
@ -261,32 +296,38 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
}
|
||||
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
||||
cache = true
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
defer fast.Close()
|
||||
domain := msgToDomain(m)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||
msg, err = client.Exchange(m)
|
||||
return msg, false, err
|
||||
}
|
||||
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
||||
fast.Go(func() (*D.Msg, error) {
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, r.Address())
|
||||
m, err := r.ExchangeContext(ctx, m)
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, client.Address())
|
||||
m, err := client.ExchangeContext(ctx, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
|
||||
return nil, errors.New("server failure")
|
||||
} else if cache && (m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused) {
|
||||
// currently, cache indicates whether this msg was from a RCode client,
|
||||
// so we would ignore RCode errors from RCode clients.
|
||||
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
||||
}
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), r.Address())
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
|
||||
return m, nil
|
||||
})
|
||||
}
|
||||
|
||||
elm := fast.Wait()
|
||||
if elm == nil {
|
||||
err := errors.New("all DNS requests failed")
|
||||
msg = fast.Wait()
|
||||
if msg == nil {
|
||||
err = errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
msg = elm
|
||||
return
|
||||
}
|
||||
|
193
docs/config.yaml
193
docs/config.yaml
@ -19,9 +19,9 @@ mode: rule
|
||||
|
||||
#自定义 geodata url
|
||||
geox-url:
|
||||
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||
mmdb: "https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
|
||||
geoip: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
|
||||
geosite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
|
||||
mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb"
|
||||
|
||||
log-level: debug # 日志等级 silent/error/warning/info/debug
|
||||
|
||||
@ -64,7 +64,7 @@ hosts:
|
||||
|
||||
profile: # 存储 select 选择记录
|
||||
store-selected: false
|
||||
|
||||
|
||||
# 持久化 fake-ip
|
||||
store-fake-ip: true
|
||||
|
||||
@ -93,10 +93,10 @@ tun:
|
||||
#- 1000
|
||||
# exclude-uid-range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto-route
|
||||
|
||||
|
||||
# include-android-user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -126,10 +126,9 @@ sniffer:
|
||||
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
||||
TLS:
|
||||
# ports: [443, 8443]
|
||||
|
||||
|
||||
# 默认嗅探 80
|
||||
HTTP: # 需要嗅探的端口
|
||||
|
||||
ports: [80, 8080-8880]
|
||||
# 可覆盖 sniffer.override-destination
|
||||
override-destination: true
|
||||
@ -144,7 +143,7 @@ sniffer:
|
||||
- tls
|
||||
- http
|
||||
# 强制对此域名进行嗅探
|
||||
|
||||
|
||||
# 仅对白名单中的端口进行嗅探,默认为 443,80
|
||||
# 已废弃,若 sniffer.sniff 配置则此项无效
|
||||
port-whitelist:
|
||||
@ -152,7 +151,6 @@ sniffer:
|
||||
- "443"
|
||||
# - 8000-9999
|
||||
|
||||
|
||||
tunnels: # one line config
|
||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||
@ -162,7 +160,6 @@ tunnels: # one line config
|
||||
target: target.com
|
||||
proxy: proxy
|
||||
|
||||
|
||||
# DNS配置
|
||||
dns:
|
||||
enable: false # 关闭将使用系统 DNS
|
||||
@ -177,18 +174,18 @@ dns:
|
||||
- 8.8.8.8
|
||||
- tls://1.12.12.12:853
|
||||
- tls://223.5.5.5:853
|
||||
- system # append DNS server from system configuration. If not found, it would print an error log and skip.
|
||||
- system # append DNS server from system configuration. If not found, it would print an error log and skip.
|
||||
enhanced-mode: fake-ip # or redir-host
|
||||
|
||||
|
||||
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
||||
|
||||
|
||||
# use-hosts: true # 查询 hosts
|
||||
|
||||
|
||||
# 配置不使用fake-ip的域名
|
||||
# fake-ip-filter:
|
||||
# - '*.lan'
|
||||
# - localhost.ptlogin2.qq.com
|
||||
|
||||
|
||||
# DNS主要域名配置
|
||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||
@ -202,20 +199,20 @@ dns:
|
||||
- dhcp://en0 # dns from dhcp
|
||||
- quic://dns.adguard.com:784 # DNS over QUIC
|
||||
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
||||
|
||||
|
||||
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
||||
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
||||
# 确保配置 fallback 时能够正常查询
|
||||
# fallback:
|
||||
# - tcp://1.1.1.1
|
||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||
|
||||
|
||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
||||
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
||||
# proxy-server-nameserver:
|
||||
# - https://dns.google/dns-query
|
||||
# - tls://one.one.one.one
|
||||
|
||||
|
||||
# 配置 fallback 使用条件
|
||||
# fallback-filter:
|
||||
# geoip: true # 配置是否使用 geoip
|
||||
@ -230,7 +227,7 @@ dns:
|
||||
# - '+.google.com'
|
||||
# - '+.facebook.com'
|
||||
# - '+.youtube.com'
|
||||
|
||||
|
||||
# 配置查询域名使用的 DNS 服务器
|
||||
nameserver-policy:
|
||||
# 'www.baidu.com': '114.114.114.114'
|
||||
@ -238,9 +235,10 @@ dns:
|
||||
"geosite:cn,private,apple":
|
||||
- https://doh.pub/dns-query
|
||||
- https://dns.alidns.com/dns-query
|
||||
"geosite:category-ads-all": rcode://success
|
||||
"www.baidu.com,+.google.cn": [223.5.5.5, https://dns.alidns.com/dns-query]
|
||||
## global,dns 为 rule-providers 中的名为 global 和 dns 规则订阅,
|
||||
## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
|
||||
## 且 behavior 必须为 domain/classical,当为 classical 时仅会生效域名类规则
|
||||
# "rule-set:global,dns": 8.8.8.8
|
||||
|
||||
proxies: # socks5
|
||||
@ -255,7 +253,7 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# udp: true
|
||||
# ip-version: ipv6
|
||||
|
||||
|
||||
# http
|
||||
- name: "http"
|
||||
type: http
|
||||
@ -268,7 +266,7 @@ proxies: # socks5
|
||||
# sni: custom.com
|
||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||
# ip-version: dual
|
||||
|
||||
|
||||
# Snell
|
||||
# Beware that there's currently no UDP support yet
|
||||
- name: "snell"
|
||||
@ -280,7 +278,7 @@ proxies: # socks5
|
||||
# obfs-opts:
|
||||
# mode: http # or tls
|
||||
# host: bing.com
|
||||
|
||||
|
||||
# Shadowsocks
|
||||
# cipher支持:
|
||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||
@ -312,7 +310,7 @@ proxies: # socks5
|
||||
# padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
|
||||
# statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
|
||||
# only-tcp: false # 如果设置为true, smux的设置将不会对udp生效,udp连接会直接走底层协议
|
||||
|
||||
|
||||
- name: "ss2"
|
||||
type: ss
|
||||
server: server
|
||||
@ -323,7 +321,7 @@ proxies: # socks5
|
||||
plugin-opts:
|
||||
mode: tls # or http
|
||||
# host: bing.com
|
||||
|
||||
|
||||
- name: "ss3"
|
||||
type: ss
|
||||
server: server
|
||||
@ -343,7 +341,7 @@ proxies: # socks5
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
|
||||
- name: "ss4-shadow-tls"
|
||||
type: ss
|
||||
server: server
|
||||
@ -363,20 +361,22 @@ proxies: # socks5
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
host:
|
||||
"www.microsoft.com" # Must be a TLS 1.3 server
|
||||
# 应当是一个TLS 1.3 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls13"
|
||||
# Control your post-handshake traffic through restls-script
|
||||
# Hide proxy behaviors like "tls in tls".
|
||||
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
|
||||
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
|
||||
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
|
||||
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
|
||||
|
||||
- name: "ss-restls-tls12"
|
||||
type: ss
|
||||
@ -384,16 +384,18 @@ proxies: # socks5
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: [YOUR_SS_PASSWORD]
|
||||
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
client-fingerprint:
|
||||
chrome # One of: chrome, ios, firefox or safari
|
||||
# 可以是chrome, ios, firefox, safari中的一个
|
||||
plugin: restls
|
||||
plugin-opts:
|
||||
host: "vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
host:
|
||||
"vscode.dev" # Must be a TLS 1.2 server
|
||||
# 应当是一个TLS 1.2 服务器
|
||||
password: [YOUR_RESTLS_PASSWORD]
|
||||
version-hint: "tls12"
|
||||
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
|
||||
|
||||
# vmess
|
||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||
- name: "vmess"
|
||||
@ -416,7 +418,7 @@ proxies: # socks5
|
||||
# Host: v2ray.com
|
||||
# max-early-data: 2048
|
||||
# early-data-header-name: Sec-WebSocket-Protocol
|
||||
|
||||
|
||||
- name: "vmess-h2"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -432,7 +434,7 @@ proxies: # socks5
|
||||
- http.example.com
|
||||
- http-alt.example.com
|
||||
path: /
|
||||
|
||||
|
||||
- name: "vmess-http"
|
||||
type: vmess
|
||||
server: server
|
||||
@ -451,7 +453,7 @@ proxies: # socks5
|
||||
# Connection:
|
||||
# - keep-alive
|
||||
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
||||
|
||||
|
||||
- name: vmess-grpc
|
||||
server: server
|
||||
port: 443
|
||||
@ -467,7 +469,7 @@ proxies: # socks5
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
# ip-version: ipv4
|
||||
|
||||
|
||||
# vless
|
||||
- name: "vless-tcp"
|
||||
type: vless
|
||||
@ -480,7 +482,7 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
|
||||
|
||||
- name: "vless-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@ -493,7 +495,7 @@ proxies: # socks5
|
||||
client-fingerprint: chrome
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
|
||||
|
||||
- name: "vless-reality-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@ -508,7 +510,7 @@ proxies: # socks5
|
||||
public-key: xxx
|
||||
short-id: xxx # optional
|
||||
client-fingerprint: chrome # cannot be empty
|
||||
|
||||
|
||||
- name: "vless-reality-grpc"
|
||||
type: vless
|
||||
server: server
|
||||
@ -526,7 +528,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
server: server
|
||||
@ -543,7 +545,7 @@ proxies: # socks5
|
||||
path: "/"
|
||||
headers:
|
||||
Host: example.com
|
||||
|
||||
|
||||
# Trojan
|
||||
- name: "trojan"
|
||||
type: trojan
|
||||
@ -558,7 +560,7 @@ proxies: # socks5
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
port: 443
|
||||
@ -571,7 +573,7 @@ proxies: # socks5
|
||||
udp: true
|
||||
grpc-opts:
|
||||
grpc-service-name: "example"
|
||||
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
port: 443
|
||||
@ -586,7 +588,7 @@ proxies: # socks5
|
||||
# path: /path
|
||||
# headers:
|
||||
# Host: example.com
|
||||
|
||||
|
||||
- name: "trojan-xtls"
|
||||
type: trojan
|
||||
server: server
|
||||
@ -598,7 +600,7 @@ proxies: # socks5
|
||||
# sni: example.com # aka server name
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
|
||||
|
||||
#hysteria
|
||||
- name: "hysteria"
|
||||
type: hysteria
|
||||
@ -625,7 +627,7 @@ proxies: # socks5
|
||||
# disable_mtu_discovery: false
|
||||
# fingerprint: xxxx
|
||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||
|
||||
|
||||
# wireguard
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
@ -654,13 +656,17 @@ proxies: # socks5
|
||||
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
|
||||
# allowed_ips: ['0.0.0.0/0']
|
||||
# reserved: [209,98,59]
|
||||
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
server: www.example.com
|
||||
port: 10443
|
||||
type: tuic
|
||||
# tuicV4必须填写token (不可同时填写uuid和password)
|
||||
token: TOKEN
|
||||
# tuicV5必须填写uuid和password(不可同时填写token)
|
||||
uuid: 00000000-0000-0000-0000-000000000001
|
||||
password: PASSWORD_1
|
||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||
# heartbeat-interval: 10000
|
||||
# alpn: [h3]
|
||||
@ -669,12 +675,18 @@ proxies: # socks5
|
||||
request-timeout: 8000
|
||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||
# cwnd: 10 # default: 32
|
||||
# max-udp-relay-packet-size: 1500
|
||||
# fast-open: true
|
||||
# skip-cert-verify: true
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
# sni: example.com
|
||||
|
||||
#
|
||||
# meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效
|
||||
# 警告,与原版tuic不兼容!!!
|
||||
# udp-over-stream: false
|
||||
# udp-over-stream-version: 1
|
||||
|
||||
# ShadowsocksR
|
||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||
# The supported obfses:
|
||||
@ -706,7 +718,7 @@ proxy-groups:
|
||||
- vmess
|
||||
- ss1
|
||||
- ss2
|
||||
|
||||
|
||||
# url-test 将按照 url 测试结果使用延迟最低节点
|
||||
- name: "auto"
|
||||
type: url-test
|
||||
@ -718,7 +730,7 @@ proxy-groups:
|
||||
# lazy: true
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# fallback 将按照 url 测试结果按照节点顺序选择
|
||||
- name: "fallback-auto"
|
||||
type: fallback
|
||||
@ -728,7 +740,7 @@ proxy-groups:
|
||||
- vmess1
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
|
||||
|
||||
# load-balance 将按照算法随机选择节点
|
||||
- name: "load-balance"
|
||||
type: load-balance
|
||||
@ -739,7 +751,7 @@ proxy-groups:
|
||||
url: "https://cp.cloudflare.com/generate_204"
|
||||
interval: 300
|
||||
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
||||
|
||||
|
||||
# select 用户自行选择节点
|
||||
- name: Proxy
|
||||
type: select
|
||||
@ -749,7 +761,7 @@ proxy-groups:
|
||||
- ss2
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
|
||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||
- name: en1
|
||||
type: select
|
||||
@ -757,7 +769,7 @@ proxy-groups:
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
||||
@ -770,10 +782,10 @@ proxy-groups:
|
||||
# Clash 格式的节点或支持 *ray 的分享格式
|
||||
proxy-providers:
|
||||
provider1:
|
||||
type: http
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml
|
||||
path: ./provider1.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
health-check:
|
||||
enable: true
|
||||
interval: 600
|
||||
@ -790,8 +802,8 @@ rule-providers:
|
||||
rule1:
|
||||
behavior: classical # domain ipcidr
|
||||
interval: 259200
|
||||
path: /path/to/save/file.yaml
|
||||
type: http
|
||||
path: /path/to/save/file.yaml # 默认只允许存储在 clash 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5
|
||||
url: "url"
|
||||
rule2:
|
||||
behavior: classical
|
||||
@ -841,14 +853,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: http-in-1
|
||||
type: http
|
||||
port: 10809
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
port: 10810
|
||||
@ -856,14 +868,14 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: reidr-in-1
|
||||
type: redir
|
||||
port: 10811
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
|
||||
|
||||
- name: tproxy-in-1
|
||||
type: tproxy
|
||||
port: 10812
|
||||
@ -871,7 +883,7 @@ listeners:
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# udp: false # 默认 true
|
||||
|
||||
|
||||
- name: shadowsocks-in-1
|
||||
type: shadowsocks
|
||||
port: 10813
|
||||
@ -880,7 +892,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
||||
cipher: 2022-blake3-aes-256-gcm
|
||||
|
||||
|
||||
- name: vmess-in-1
|
||||
type: vmess
|
||||
port: 10814
|
||||
@ -891,15 +903,18 @@ listeners:
|
||||
- username: 1
|
||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||
alterId: 1
|
||||
|
||||
|
||||
- name: tuic-in-1
|
||||
type: tuic
|
||||
port: 10815
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
# token:
|
||||
# - TOKEN
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
@ -908,7 +923,7 @@ listeners:
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
||||
|
||||
- name: tunnel-in-1
|
||||
type: tunnel
|
||||
port: 10816
|
||||
@ -917,7 +932,7 @@ listeners:
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||
network: [tcp, udp]
|
||||
target: target.com
|
||||
|
||||
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
@ -948,10 +963,10 @@ listeners:
|
||||
# - 1000
|
||||
# exclude_uid_range: # 排除路由的的用户范围
|
||||
# - 1000-99999
|
||||
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto_route
|
||||
|
||||
|
||||
# include_android_user: # 限制被路由的 Android 用户
|
||||
# - 0
|
||||
# - 10
|
||||
@ -959,7 +974,6 @@ listeners:
|
||||
# - com.android.chrome
|
||||
# exclude_package: # 排除被路由的 Android 应用包名
|
||||
# - com.android.captiveportallogin
|
||||
|
||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||
@ -969,8 +983,11 @@ listeners:
|
||||
# tuic-server:
|
||||
# enable: true
|
||||
# listen: 127.0.0.1:10443
|
||||
# token:
|
||||
# token: # tuicV4填写(可以同时填写users)
|
||||
# - TOKEN
|
||||
# users: # tuicV5填写(可以同时填写token)
|
||||
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# congestion-controller: bbr
|
||||
|
102
go.mod
102
go.mod
@ -1,58 +1,57 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.4
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/cilium/ebpf v0.10.0
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/dlclark/regexp2 v1.9.0
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/beevik/ntp v1.3.0
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/klauspost/cpuid/v2 v2.0.12
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/mroth/weightedrand/v2 v2.0.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3
|
||||
github.com/metacubex/sing-tun v0.1.11
|
||||
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.5-0.20230530114415-221f066dba7c
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||
github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.4
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3
|
||||
github.com/shirou/gopsutil/v3 v3.23.7
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/sync v0.2.0
|
||||
golang.org/x/sys v0.8.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.11.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.1.7
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
@ -66,46 +65,43 @@ require (
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b
|
||||
|
||||
replace github.com/sagernet/sing-vmess => github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579
|
||||
|
231
go.sum
231
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
|
||||
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
|
||||
github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||
@ -10,19 +10,21 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/beevik/ntp v1.3.0 h1:/w5VhpW5BGKS37vFm1p9oVk/t4HnnkKZAZIubHM6F7Q=
|
||||
github.com/beevik/ntp v1.3.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
|
||||
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@ -32,56 +34,54 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA=
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@ -92,40 +92,40 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c h1:D62872jiuzC6b+3aI8tqfeyc6YgbfarYKywTnnvXwEM=
|
||||
github.com/metacubex/gvisor v0.0.0-20230417114019-3c3ee672d60c/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58 h1:E/sNW9tugFjoBjAkth89MHlKHRaMdo43tGQ3MOPVayQ=
|
||||
github.com/metacubex/quic-go v0.33.3-0.20230510010206-687b537b6a58/go.mod h1:9nOiGX6kqV3+ZbkDKdTNzdFD726QQHPH6WDb36jUSpA=
|
||||
github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b h1:Bw4j3ktf5vivi5qm/ZQGtyRAgybRKSGJaMV1t3rtC+I=
|
||||
github.com/metacubex/sing v0.0.0-20230530121223-b768faae5c6b/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c h1:LpVNvlW/xE+mR8z76xJeYZlYznZXEmU4TeWeuygYdJg=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2-0.20230509230448-a5157cc00a1c/go.mod h1:4uQQReKMTU7KTfOykVBe/oGJ00pl38d+BYJ99+mx26s=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca h1:10qc50Q1hHrfGO4NjEJpIAgHX63Y256tHE0dFCTN8J4=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.0.0-20230529235701-a238874242ca/go.mod h1:jVDD4N22bDPPKA73NvB7aqdlLWiAwv8D+jx7HwhcWak=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e h1:7QlJQl4S3F3YXn48fYxjymMw8HkXg9bl++hLi4ZRyCY=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230530125750-171afb2dfd8e/go.mod h1:u9onX49LZPYuIPQ7SdM64Gnins8y5wg4Cn6ZYRSxWHU=
|
||||
github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899 h1:iRfcuztp7REfmOyasSlCL/pqNWfUDMTJ2CwbGpxpeks=
|
||||
github.com/metacubex/sing-vmess v0.1.5-0.20230520082358-78b126617899/go.mod h1:RSt9rxGHllLdc5JUebkQwaqyWLx09Lqya37DlBe8CP8=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a h1:cWKym33Qvl6HA3hj4/YuYD8hHyqQPb47wT5cJRAPgco=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230426030325-41db09ae771a/go.mod h1:Bsw2BvKMMMY0FhZPseDI50ZOalvoUPMKYyGpyqvIIqY=
|
||||
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mroth/weightedrand/v2 v2.0.1 h1:zrEVDIaau/E4QLOKu02kpg8T8myweFlMGikIgbIdrRA=
|
||||
github.com/mroth/weightedrand/v2 v2.0.1/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28 h1:ggSo4B1LDH9ZIROoUibxlrUpi7YCMri7HMXn4aNQkiM=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28/go.mod h1:SthFvvoqgrEUgIxQXRnqdUAAYQECBavkhl7iA0geVd8=
|
||||
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579 h1:dE1dBB6CTzNdSMFTE5OCHvzHLewiqiA1nhD+7egtvAc=
|
||||
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3/go.mod h1:5Mt93RlmRlIcDmvtapkhQJ8YTRGLFhHciLYopJjs7j8=
|
||||
github.com/metacubex/sing-tun v0.1.11 h1:B8meDewklvKkeUfjqR2ViuYLam0/m4IgkTi3qcJIOuc=
|
||||
github.com/metacubex/sing-tun v0.1.11/go.mod h1:vbki176Y5sxXC1DWXucrPh3q5j8cKai1D87y8m8rjQc=
|
||||
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 h1:AqqZCr9gOeKdO6oIzFh4b2puOUFcw8MdpmGHWRehyX8=
|
||||
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8/go.mod h1:tyJg7b4s8NrSztl/Y1ajA7X0sJLlIsEJWkgRVocjmgY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
|
||||
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
|
||||
github.com/openacid/low v0.1.21/go.mod h1:q+MsKI6Pz2xsCkzV4BLj7NR5M4EX0sGz5AqotpZDVh0=
|
||||
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
|
||||
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
|
||||
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
|
||||
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -136,138 +136,165 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||
github.com/quic-go/qtls-go1-19 v0.2.1/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc=
|
||||
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c/go.mod h1:NV/a66PhhWYVmUMaotlXJ8fIEFB98u+c8l/CQIEFLrU=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e h1:ur8uMsPIFG3i4Gi093BQITvwH9znsz2VUZmnmwHvpIo=
|
||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e/go.mod h1:+e5fBW3bpPyo+3uLo513gIUblc03egGjMM0+5GKbzK8=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 h1:a3Y4WVjCxwoyO4E2xdNvq577tW8lkSBgyrA8E9+2NtM=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
@ -2,11 +2,15 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
@ -92,6 +96,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateSniffer(cfg.Sniffer)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateGeneral(cfg.General)
|
||||
updateNTP(cfg.NTP)
|
||||
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateIPTables(cfg)
|
||||
@ -129,7 +134,7 @@ func GetGeneral() *config.General {
|
||||
RedirPort: ports.RedirPort,
|
||||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Tun: listener.GetTunConf(),
|
||||
Tun: listener.LastTunConf,
|
||||
TuicServer: listener.GetTuicConf(),
|
||||
ShadowSocksConfig: ports.ShadowSocksConfig,
|
||||
VmessConfig: ports.VmessConfig,
|
||||
@ -178,6 +183,13 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
func updateExperimental(c *config.Config) {
|
||||
}
|
||||
|
||||
func updateNTP(c *config.NTP) {
|
||||
if c.Enable {
|
||||
ntp.ReCreateNTPService(net.JoinHostPort(c.Server, strconv.Itoa(c.Port)),
|
||||
time.Duration(c.Interval))
|
||||
}
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
@ -360,6 +372,7 @@ func updateGeneral(general *config.General) {
|
||||
}
|
||||
|
||||
inbound.SetTfo(general.InboundTfo)
|
||||
inbound.SetMPTCP(general.InboundMPTCP)
|
||||
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
|
||||
|
@ -84,16 +84,18 @@ type tunSchema struct {
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen *string `yaml:"listen" json:"listen"`
|
||||
Token *[]string `yaml:"token" json:"token"`
|
||||
Certificate *string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen *string `yaml:"listen" json:"listen"`
|
||||
Token *[]string `yaml:"token" json:"token"`
|
||||
Users *map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate *string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
||||
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||
CWND *int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -186,6 +188,9 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
|
||||
if p.Token != nil {
|
||||
def.Token = *p.Token
|
||||
}
|
||||
if p.Users != nil {
|
||||
def.Users = *p.Users
|
||||
}
|
||||
if p.Certificate != nil {
|
||||
def.Certificate = *p.Certificate
|
||||
}
|
||||
@ -207,6 +212,9 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
|
||||
if p.MaxUdpRelayPacketSize != nil {
|
||||
def.MaxUdpRelayPacketSize = *p.MaxUdpRelayPacketSize
|
||||
}
|
||||
if p.CWND != nil {
|
||||
def.CWND = *p.CWND
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
@ -73,20 +73,16 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func closeConnection(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
if id == c.ID() {
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
if c := statistic.DefaultManager.Get(id); c != nil {
|
||||
_ = c.Close()
|
||||
}
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func closeAllConnections(w http.ResponseWriter, r *http.Request) {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
c.Close()
|
||||
}
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
_ = c.Close()
|
||||
return true
|
||||
})
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
@ -2,14 +2,17 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
func GroupRouter() http.Handler {
|
||||
@ -55,6 +58,11 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if proxy.(*adapter.Proxy).Type() == C.URLTest {
|
||||
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
|
||||
URLTestGroup.ForceSet("")
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||
@ -64,11 +72,17 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedStatus, err := utils.NewIntRanges[uint16](query.Get("expected"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url)
|
||||
|
||||
dm, err := group.URLTest(ctx, url, expectedStatus)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user