Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
53f9e1ee71 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -123,7 +123,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
go-version: "1.20"
|
||||
check-latest: true
|
||||
|
||||
- name: Test
|
||||
@ -142,7 +142,7 @@ jobs:
|
||||
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r26
|
||||
ndk-version: r25b
|
||||
add-to-path: false
|
||||
local-cache: true
|
||||
|
||||
@ -204,7 +204,7 @@ jobs:
|
||||
|
||||
Upload-Prerelease:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }}
|
||||
if: ${{ github.ref_type=='branch' }}
|
||||
needs: [Build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -4,9 +4,9 @@ RUN echo "I'm building for $TARGETPLATFORM"
|
||||
|
||||
RUN apk add --no-cache gzip && \
|
||||
mkdir /clash-config && \
|
||||
wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \
|
||||
wget -O /clash-config/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
|
||||
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
|
||||
|
||||
COPY docker/file-name.sh /clash/file-name.sh
|
||||
WORKDIR /clash
|
||||
|
36
README.md
36
README.md
@ -24,22 +24,13 @@
|
||||
- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
|
||||
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
|
||||
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
|
||||
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
|
||||
based off latency
|
||||
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config
|
||||
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
|
||||
- Remote providers, allowing users to get node lists remotely instead of hardcoding in config
|
||||
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
|
||||
- Comprehensive HTTP RESTful API controller
|
||||
|
||||
## Dashboard
|
||||
|
||||
We made an official web dashboard providing first class support for this project, check it out
|
||||
at [metacubexd](https://github.com/MetaCubeX/metacubexd)
|
||||
|
||||
## Wiki
|
||||
|
||||
Configuration examples can be found
|
||||
at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be
|
||||
found [Clash.Meta Wiki](https://clash-meta.wiki).
|
||||
Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki).
|
||||
|
||||
## Build
|
||||
|
||||
@ -52,7 +43,7 @@ git clone https://github.com/MetaCubeX/Clash.Meta.git
|
||||
cd Clash.Meta && go mod download
|
||||
```
|
||||
|
||||
If you can't visit GitHub, you should set proxy first:
|
||||
If you can't visit github,you should set proxy first:
|
||||
|
||||
```shell
|
||||
go env -w GOPROXY=https://goproxy.io,direct
|
||||
@ -333,27 +324,36 @@ ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Launch clash-meta daemon on system startup with:
|
||||
Launch clashd on system startup with:
|
||||
|
||||
```shell
|
||||
$ systemctl enable Clash-Meta
|
||||
```
|
||||
|
||||
Launch clash-meta daemon immediately with:
|
||||
Launch clashd immediately with:
|
||||
|
||||
```shell
|
||||
$ systemctl start Clash-Meta
|
||||
```
|
||||
|
||||
### Display Process name
|
||||
|
||||
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
|
||||
|
||||
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
|
||||
|
||||
### Dashboard
|
||||
|
||||
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
|
||||
|
||||
## Development
|
||||
|
||||
If you want to build an application that uses clash as a library, check out
|
||||
If you want to build an application that uses clash as a library, check out the
|
||||
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
|
||||
|
||||
## Debugging
|
||||
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
|
||||
|
||||
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug
|
||||
API.
|
||||
|
||||
## Credits
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
@ -18,8 +17,6 @@ import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
@ -38,7 +35,7 @@ type Proxy struct {
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
url string
|
||||
extra *xsync.MapOf[string, *extraProxyState]
|
||||
extra map[string]*extraProxyState
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
@ -48,8 +45,10 @@ func (p *Proxy) Alive() bool {
|
||||
|
||||
// AliveForTestUrl implements C.Proxy
|
||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
return state.alive.Load()
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
return state.alive.Load()
|
||||
}
|
||||
}
|
||||
|
||||
return p.alive.Load()
|
||||
@ -88,16 +87,16 @@ 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.history.Copy()
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
queueM = state.history.Copy()
|
||||
}
|
||||
}
|
||||
|
||||
if queueM == nil {
|
||||
@ -112,25 +111,19 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
||||
}
|
||||
|
||||
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
|
||||
extraHistory := map[string][]C.DelayHistory{}
|
||||
extra := map[string][]C.DelayHistory{}
|
||||
if p.extra != nil && len(p.extra) != 0 {
|
||||
for testUrl, option := range p.extra {
|
||||
histories := []C.DelayHistory{}
|
||||
queueM := option.history.Copy()
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
|
||||
p.extra.Range(func(k string, v *extraProxyState) bool {
|
||||
|
||||
testUrl := k
|
||||
state := v
|
||||
|
||||
histories := []C.DelayHistory{}
|
||||
queueM := state.history.Copy()
|
||||
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
extra[testUrl] = histories
|
||||
}
|
||||
|
||||
extraHistory[testUrl] = histories
|
||||
|
||||
return true
|
||||
})
|
||||
return extraHistory
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
@ -155,9 +148,11 @@ func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
|
||||
alive := p.alive.Load()
|
||||
history := p.history.Last()
|
||||
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
alive = state.alive.Load()
|
||||
history = state.history.Last()
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
alive = state.alive.Load()
|
||||
history = state.history.Last()
|
||||
}
|
||||
}
|
||||
|
||||
if !alive {
|
||||
@ -181,7 +176,6 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
_ = 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()
|
||||
@ -217,18 +211,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
if alive {
|
||||
record.Delay = t
|
||||
}
|
||||
p.history.Put(record)
|
||||
if p.history.Len() > defaultHistoriesNum {
|
||||
p.history.Pop()
|
||||
|
||||
if p.extra == nil {
|
||||
p.extra = map[string]*extraProxyState{}
|
||||
}
|
||||
|
||||
state, ok := p.extra.Load(url)
|
||||
state, ok := p.extra[url]
|
||||
if !ok {
|
||||
state = &extraProxyState{
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
}
|
||||
p.extra.Store(url, state)
|
||||
p.extra[url] = state
|
||||
}
|
||||
|
||||
state.alive.Store(alive)
|
||||
@ -311,12 +305,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{
|
||||
ProxyAdapter: adapter,
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
url: "",
|
||||
extra: xsync.NewMapOf[*extraProxyState]()}
|
||||
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
@ -337,15 +326,11 @@ 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: uint16(uintPort),
|
||||
DstPort: port,
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -359,14 +344,14 @@ func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url strin
|
||||
return C.OriginalHistory
|
||||
}
|
||||
|
||||
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
return C.ExtraHistory
|
||||
if p.extra == nil {
|
||||
store = C.ExtraHistory
|
||||
} else {
|
||||
if _, ok := p.extra[url]; ok {
|
||||
store = C.ExtraHistory
|
||||
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
store = C.ExtraHistory
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := p.extra.Load(url)
|
||||
if ok {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
@ -17,10 +17,6 @@ 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)
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package inbound
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = false
|
||||
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
//go:build go1.21
|
||||
|
||||
package inbound
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = true
|
||||
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
listenConfig.SetMultipathTCP(open)
|
||||
}
|
@ -3,7 +3,6 @@ package inbound
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
@ -38,9 +37,7 @@ 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 {
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
metadata.DstPort = uint16(port)
|
||||
}
|
||||
metadata.DstPort = 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 = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
case socks5.AtypIPv4:
|
||||
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
|
||||
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||
case socks5.AtypIPv6:
|
||||
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
||||
metadata.DstIP = ip6.Unmap()
|
||||
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||
}
|
||||
|
||||
return metadata
|
||||
@ -43,16 +43,11 @@ 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: uint16Port,
|
||||
DstPort: port,
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
@ -63,10 +58,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
return metadata
|
||||
}
|
||||
|
||||
func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
|
||||
func parseAddr(addr net.Addr) (netip.Addr, string, error) {
|
||||
// Filter when net.Addr interface is nil
|
||||
if addr == nil {
|
||||
return netip.Addr{}, 0, errors.New("nil addr")
|
||||
return netip.Addr{}, "", errors.New("nil addr")
|
||||
}
|
||||
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
|
||||
ip, port, err := parseAddr(rawAddr.RawAddr())
|
||||
@ -77,14 +72,9 @@ func parseAddr(addr net.Addr) (netip.Addr, uint16, error) {
|
||||
addrStr := addr.String()
|
||||
host, port, err := net.SplitHostPort(addrStr)
|
||||
if err != nil {
|
||||
return netip.Addr{}, 0, err
|
||||
}
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
return netip.Addr{}, "", err
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
return ip, uint16Port, err
|
||||
return ip, port, err
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ type Base struct {
|
||||
udp bool
|
||||
xudp bool
|
||||
tfo bool
|
||||
mpTcp bool
|
||||
rmark int
|
||||
id string
|
||||
prefer C.DNSPrefer
|
||||
@ -144,16 +143,11 @@ 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"`
|
||||
@ -167,7 +161,6 @@ type BaseOption struct {
|
||||
UDP bool
|
||||
XUDP bool
|
||||
TFO bool
|
||||
MPTCP bool
|
||||
Interface string
|
||||
RoutingMark int
|
||||
Prefer C.DNSPrefer
|
||||
@ -181,7 +174,6 @@ 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,
|
||||
|
@ -3,9 +3,6 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -15,11 +12,6 @@ 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))
|
||||
@ -27,7 +19,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
return NewConn(c, d), nil
|
||||
}
|
||||
|
||||
@ -41,28 +33,13 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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{
|
||||
|
@ -7,16 +7,14 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -76,7 +74,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -157,13 +155,19 @@ func NewHttp(option HttpOption) (*Http, error) {
|
||||
if option.SNI != "" {
|
||||
sni = option.SNI
|
||||
}
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
}, option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(option.Fingerprint) == 0 {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
})
|
||||
} else {
|
||||
var err error
|
||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: sni,
|
||||
}, option.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +177,6 @@ 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),
|
||||
|
@ -2,11 +2,16 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@ -14,9 +19,9 @@ import (
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
|
||||
@ -38,6 +43,8 @@ const (
|
||||
DefaultHopInterval = 10
|
||||
)
|
||||
|
||||
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
||||
|
||||
type Hysteria struct {
|
||||
*Base
|
||||
|
||||
@ -113,12 +120,12 @@ type HysteriaOption struct {
|
||||
|
||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||
var up, down uint64
|
||||
up = StringToBps(c.Up)
|
||||
up = stringToBps(c.Up)
|
||||
if up == 0 {
|
||||
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
||||
}
|
||||
|
||||
down = StringToBps(c.Down)
|
||||
down = stringToBps(c.Down)
|
||||
if down == 0 {
|
||||
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
|
||||
}
|
||||
@ -146,10 +153,37 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(option.CustomCA) > 0 {
|
||||
bs, err = os.ReadFile(option.CustomCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err)
|
||||
}
|
||||
} else if option.CustomCAString != "" {
|
||||
bs = []byte(option.CustomCAString)
|
||||
}
|
||||
|
||||
if len(bs) > 0 {
|
||||
block, _ := pem.Decode(bs)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("CA cert is not PEM")
|
||||
}
|
||||
|
||||
fpBytes := sha256.Sum256(block.Bytes)
|
||||
if len(option.Fingerprint) == 0 {
|
||||
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
||||
}
|
||||
}
|
||||
|
||||
if len(option.Fingerprint) != 0 {
|
||||
var err error
|
||||
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
@ -234,6 +268,42 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func stringToBps(s string) uint64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// when have not unit, use Mbps
|
||||
if v, err := strconv.Atoi(s); err == nil {
|
||||
return stringToBps(fmt.Sprintf("%d Mbps", v))
|
||||
}
|
||||
|
||||
m := rateStringRegexp.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var n uint64
|
||||
switch m[2] {
|
||||
case "K":
|
||||
n = 1 << 10
|
||||
case "M":
|
||||
n = 1 << 20
|
||||
case "G":
|
||||
n = 1 << 30
|
||||
case "T":
|
||||
n = 1 << 40
|
||||
default:
|
||||
n = 1
|
||||
}
|
||||
v, _ := strconv.ParseUint(m[1], 10, 64)
|
||||
n = v * n
|
||||
if m[3] == "b" {
|
||||
// Bits, need to convert to bytes
|
||||
n = n >> 3
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type hyPacketConn struct {
|
||||
core.UDPConn
|
||||
}
|
||||
|
@ -1,157 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
tuicCommon "github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
|
||||
}
|
||||
|
||||
type Hysteria2 struct {
|
||||
*Base
|
||||
|
||||
option *Hysteria2Option
|
||||
client *hysteria2.Client
|
||||
dialer proxydialer.SingDialer
|
||||
}
|
||||
|
||||
type Hysteria2Option struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Up string `proxy:"up,omitempty"`
|
||||
Down string `proxy:"down,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := h.Base.DialOptions(opts...)
|
||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(CN.NewRefConn(c, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
options := h.Base.DialOptions(opts...)
|
||||
h.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
pc, err := h.client.ListenPacket(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pc == nil {
|
||||
return nil, errors.New("packetConn is nil")
|
||||
}
|
||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
|
||||
}
|
||||
|
||||
func closeHysteria2(h *Hysteria2) {
|
||||
if h.client != nil {
|
||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
}
|
||||
|
||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
var salamanderPassword string
|
||||
if len(option.Obfs) > 0 {
|
||||
if option.ObfsPassword == "" {
|
||||
return nil, errors.New("missing obfs password")
|
||||
}
|
||||
switch option.Obfs {
|
||||
case hysteria2.ObfsTypeSalamander:
|
||||
salamanderPassword = option.ObfsPassword
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
|
||||
}
|
||||
}
|
||||
|
||||
serverName := option.Server
|
||||
if option.SNI != "" {
|
||||
serverName = option.SNI
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
}
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
clientOptions := hysteria2.ClientOptions{
|
||||
Context: context.TODO(),
|
||||
Dialer: singDialer,
|
||||
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
||||
SendBPS: StringToBps(option.Up),
|
||||
ReceiveBPS: StringToBps(option.Down),
|
||||
SalamanderPassword: salamanderPassword,
|
||||
Password: option.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
UDPDisabled: false,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
||||
client, err := hysteria2.NewClient(clientOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outbound := &Hysteria2{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.Hysteria2,
|
||||
udp: true,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
dialer: singDialer,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria2)
|
||||
|
||||
return outbound, nil
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
restlsC "github.com/3andne/restls-client-go"
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks2"
|
||||
"github.com/metacubex/sing-shadowsocks2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -294,6 +294,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
|
||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
||||
restlsConfig.SessionTicketsDisabled = true
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||
}
|
||||
@ -314,7 +315,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
tp: C.Shadowsocks,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
|
@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -181,7 +181,6 @@ 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),
|
||||
|
@ -3,6 +3,7 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
CN "github.com/Dreamacro/clash/common/net"
|
||||
@ -14,13 +15,14 @@ import (
|
||||
mux "github.com/sagernet/sing-mux"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type SingMux struct {
|
||||
C.ProxyAdapter
|
||||
base ProxyBase
|
||||
client *mux.Client
|
||||
dialer proxydialer.SingDialer
|
||||
dialer *muxSingDialer
|
||||
onlyTcp bool
|
||||
}
|
||||
|
||||
@ -39,9 +41,27 @@ type ProxyBase interface {
|
||||
DialOptions(opts ...dialer.Option) []dialer.Option
|
||||
}
|
||||
|
||||
type muxSingDialer struct {
|
||||
dialer dialer.Dialer
|
||||
proxy C.ProxyAdapter
|
||||
statistic bool
|
||||
}
|
||||
|
||||
var _ N.Dialer = (*muxSingDialer)(nil)
|
||||
|
||||
func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
||||
return cDialer.DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic)
|
||||
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||
}
|
||||
|
||||
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := s.base.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
s.dialer.dialer = dialer.NewDialer(options...)
|
||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -54,7 +74,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
}
|
||||
options := s.base.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
s.dialer.dialer = dialer.NewDialer(options...)
|
||||
|
||||
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
@ -94,7 +114,7 @@ func closeSingMux(s *SingMux) {
|
||||
}
|
||||
|
||||
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
|
||||
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
|
||||
singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic}
|
||||
client, err := mux.NewClient(mux.Options{
|
||||
Dialer: singDialer,
|
||||
Protocol: option.Protocol,
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
@ -60,7 +59,8 @@ func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
err := snell.WriteUDPHeader(c, s.version)
|
||||
return c, err
|
||||
}
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(port), s.version)
|
||||
return c, err
|
||||
}
|
||||
|
||||
@ -72,7 +72,8 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
@ -94,7 +95,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -122,7 +123,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
@ -182,7 +183,6 @@ 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),
|
||||
@ -208,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
@ -9,10 +9,9 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
@ -81,7 +80,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -127,7 +126,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
@ -156,7 +155,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
bindUDPAddr.IP = serverAddr.IP
|
||||
}
|
||||
|
||||
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
|
||||
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -180,10 +179,13 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
ServerName: option.Server,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(option.Fingerprint) == 0 {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
var err error
|
||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +196,6 @@ 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),
|
||||
|
@ -8,14 +8,13 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
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 {
|
||||
@ -46,6 +45,8 @@ 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"`
|
||||
}
|
||||
|
||||
@ -94,6 +95,11 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
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
|
||||
@ -111,6 +117,12 @@ 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
|
||||
@ -133,7 +145,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -186,7 +198,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
c, err = t.plainStream(ctx, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
@ -225,10 +237,24 @@ 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
|
||||
}
|
||||
@ -240,7 +266,6 @@ 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),
|
||||
@ -270,7 +295,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@ -281,10 +306,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
ServerName: tOption.ServerName,
|
||||
}
|
||||
|
||||
var err error
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(option.Fingerprint) == 0 {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
var err error
|
||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
||||
|
@ -2,25 +2,25 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"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 {
|
||||
@ -59,9 +59,6 @@ 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
|
||||
@ -85,32 +82,6 @@ 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
|
||||
@ -158,13 +129,40 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig.ServerName = option.SNI
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
var err error
|
||||
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(option.CustomCA) > 0 {
|
||||
bs, err = os.ReadFile(option.CustomCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err)
|
||||
}
|
||||
} else if option.CustomCAString != "" {
|
||||
bs = []byte(option.CustomCAString)
|
||||
}
|
||||
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
if len(bs) > 0 {
|
||||
block, _ := pem.Decode(bs)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("CA cert is not PEM")
|
||||
}
|
||||
|
||||
fpBytes := sha256.Sum256(block.Bytes)
|
||||
if len(option.Fingerprint) == 0 {
|
||||
option.Fingerprint = hex.EncodeToString(fpBytes[:])
|
||||
}
|
||||
}
|
||||
|
||||
if len(option.Fingerprint) != 0 {
|
||||
var err error
|
||||
tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{"h3"}
|
||||
@ -241,14 +239,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
|
||||
switch option.UDPOverStreamVersion {
|
||||
case uot.Version, uot.LegacyVersion:
|
||||
case 0:
|
||||
option.UDPOverStreamVersion = uot.LegacyVersion
|
||||
default:
|
||||
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
|
||||
}
|
||||
|
||||
t := &Tuic{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
@ -292,10 +282,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
|
||||
t.client = tuic.NewPoolClientV4(clientOption)
|
||||
} else {
|
||||
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
|
||||
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
|
||||
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
||||
}
|
||||
clientOption := &tuic.ClientOptionV5{
|
||||
TlsConfig: tlsConfig,
|
||||
QuicConfig: quicConfig,
|
||||
@ -304,7 +290,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
UdpRelayMode: udpRelayMode,
|
||||
CongestionController: option.CongestionController,
|
||||
ReduceRtt: option.ReduceRtt,
|
||||
MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
|
||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||
MaxOpenStreams: clientMaxOpenStreams,
|
||||
CWND: option.CWND,
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
xtls "github.com/xtls/go"
|
||||
"net"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -17,10 +17,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
once sync.Once
|
||||
globalClientSessionCache tls.ClientSessionCache
|
||||
globalClientXSessionCache xtls.ClientSessionCache
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func getClientSessionCache() tls.ClientSessionCache {
|
||||
once.Do(func() {
|
||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||
@ -28,11 +36,18 @@ 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 := uint(metadata.DstPort)
|
||||
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||
switch addrType {
|
||||
case socks5.AtypDomainName:
|
||||
@ -123,41 +138,3 @@ func safeConnClose(c net.Conn, err error) {
|
||||
_ = c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
||||
|
||||
func StringToBps(s string) uint64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// when have not unit, use Mbps
|
||||
if v, err := strconv.Atoi(s); err == nil {
|
||||
return StringToBps(fmt.Sprintf("%d Mbps", v))
|
||||
}
|
||||
|
||||
m := rateStringRegexp.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var n uint64
|
||||
switch m[2] {
|
||||
case "K":
|
||||
n = 1 << 10
|
||||
case "M":
|
||||
n = 1 << 20
|
||||
case "G":
|
||||
n = 1 << 30
|
||||
case "T":
|
||||
n = 1 << 40
|
||||
default:
|
||||
n = 1
|
||||
}
|
||||
v, _ := strconv.ParseUint(m[1], 10, 64)
|
||||
n = v * n
|
||||
if m[3] == "b" {
|
||||
// Bits, need to convert to bytes
|
||||
n = n >> 3
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
@ -57,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"`
|
||||
@ -111,9 +110,13 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(v.option.Fingerprint) == 0 {
|
||||
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -130,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.streamTLSConn(ctx, c, false)
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -145,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.streamTLSConn(ctx, c, true)
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -160,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
|
||||
c, err = v.streamTLSConn(ctx, c, false)
|
||||
// handle TLS And XTLS
|
||||
c, err = v.streamTLSOrXTLSConn(ctx, c, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -177,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
|
||||
@ -199,17 +202,29 @@ func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
func (v *Vless) streamTLSOrXTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
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 {
|
||||
@ -226,6 +241,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
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
|
||||
@ -260,7 +279,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -325,7 +344,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -398,11 +417,12 @@ 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: metadata.DstPort,
|
||||
Port: uint16(port),
|
||||
Mux: metadata.NetWork == C.UDP && xudp,
|
||||
}
|
||||
}
|
||||
@ -506,11 +526,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)
|
||||
}
|
||||
@ -529,7 +549,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option.PacketAddr = false
|
||||
}
|
||||
|
||||
client, err := vless.NewClient(option.UUID, addons)
|
||||
client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -542,7 +562,6 @@ 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),
|
||||
@ -575,7 +594,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@ -589,7 +608,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
})
|
||||
|
@ -13,13 +13,11 @@ import (
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"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"
|
||||
|
||||
@ -54,7 +52,6 @@ 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"`
|
||||
@ -128,9 +125,12 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(v.option.Fingerprint) == 0 {
|
||||
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||
} else {
|
||||
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -149,7 +149,6 @@ 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 != "" {
|
||||
@ -206,7 +205,6 @@ 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 != "" {
|
||||
@ -306,7 +304,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -367,7 +365,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -415,7 +413,6 @@ 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
|
||||
@ -439,7 +436,6 @@ 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),
|
||||
@ -467,7 +463,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
tcpKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@ -481,7 +477,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
||||
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
})
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/wireguard-go/device"
|
||||
)
|
||||
|
||||
@ -35,7 +36,7 @@ type WireGuard struct {
|
||||
bind *wireguard.ClientBind
|
||||
device *device.Device
|
||||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
dialer *wgSingDialer
|
||||
startOnce sync.Once
|
||||
startErr error
|
||||
resolver *dns.Resolver
|
||||
@ -69,6 +70,37 @@ type WireGuardPeerOption struct {
|
||||
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
|
||||
}
|
||||
|
||||
type wgSingDialer struct {
|
||||
dialer dialer.Dialer
|
||||
proxyName string
|
||||
}
|
||||
|
||||
var _ N.Dialer = (*wgSingDialer)(nil)
|
||||
|
||||
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
var cDialer C.Dialer = d.dialer
|
||||
if len(d.proxyName) > 0 {
|
||||
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cDialer = pd
|
||||
}
|
||||
return cDialer.DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
var cDialer C.Dialer = d.dialer
|
||||
if len(d.proxyName) > 0 {
|
||||
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cDialer = pd
|
||||
}
|
||||
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||
}
|
||||
|
||||
type wgSingErrorHandler struct {
|
||||
name string
|
||||
}
|
||||
@ -136,7 +168,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()),
|
||||
dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||
|
||||
@ -270,7 +302,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
@ -323,7 +355,7 @@ func closeWireGuard(w *WireGuard) {
|
||||
|
||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := w.Base.DialOptions(opts...)
|
||||
w.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
w.dialer.dialer = dialer.NewDialer(options...)
|
||||
var conn net.Conn
|
||||
w.startOnce.Do(func() {
|
||||
w.startErr = w.tunDevice.Start()
|
||||
@ -342,7 +374,8 @@ 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 {
|
||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -355,7 +388,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||
|
||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
options := w.Base.DialOptions(opts...)
|
||||
w.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
w.dialer.dialer = dialer.NewDialer(options...)
|
||||
var pc net.PacketConn
|
||||
w.startOnce.Do(func() {
|
||||
w.startErr = w.tunDevice.Start()
|
||||
@ -379,7 +412,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
port, _ := strconv.Atoi(metadata.DstPort)
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,5 +1,17 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
|
@ -92,13 +92,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewHysteria(*hyOption)
|
||||
case "hysteria2":
|
||||
hyOption := &outbound.Hysteria2Option{}
|
||||
err = decoder.Decode(mapping, hyOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewHysteria2(*hyOption)
|
||||
case "wireguard":
|
||||
wgOption := &outbound.WireGuardOption{}
|
||||
err = decoder.Decode(mapping, wgOption)
|
||||
@ -113,13 +106,6 @@ 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)
|
||||
}
|
||||
|
@ -299,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("%w, %w", err, err1)
|
||||
return nil, fmt.Errorf("%s, %w", err.Error(), err1)
|
||||
}
|
||||
schema.Proxies = proxies
|
||||
}
|
||||
|
@ -11,9 +11,18 @@ 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,8 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/generics/list"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Option is part of Functional Options Pattern
|
||||
@ -84,27 +82,9 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
// Get returns the any representation of a cached response and a bool
|
||||
// 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 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
|
||||
return getZero[V](), false
|
||||
}
|
||||
value := el.value
|
||||
|
||||
@ -116,12 +96,9 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
|
||||
// and a bool set to true if the key was found.
|
||||
// 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 lo.Empty[V](), time.Time{}, false
|
||||
return getZero[V](), time.Time{}, false
|
||||
}
|
||||
|
||||
return el.value, time.Unix(el.expires, 0), true
|
||||
@ -138,18 +115,11 @@ func (c *LruCache[K, V]) Exist(key K) bool {
|
||||
|
||||
// Set stores the any representation of a response for a given key.
|
||||
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.
|
||||
@ -158,10 +128,6 @@ 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
|
||||
@ -199,6 +165,9 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
le, ok := c.cache[key]
|
||||
if !ok {
|
||||
return nil
|
||||
@ -222,11 +191,12 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] {
|
||||
// Delete removes the value associated with a key.
|
||||
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() {
|
||||
@ -249,10 +219,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
|
||||
}
|
||||
|
||||
@ -261,3 +231,8 @@ type entry[K comparable, V any] struct {
|
||||
value V
|
||||
expires int64
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
|
||||
|
||||
func TestExecCmd(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
_, err := ExecCmd("cmd -c 'dir'")
|
||||
_, err := ExecCmd("dir")
|
||||
assert.Nil(t, err)
|
||||
return
|
||||
}
|
||||
|
@ -50,9 +50,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["port"] = urlHysteria.Port()
|
||||
hysteria["sni"] = query.Get("peer")
|
||||
hysteria["obfs"] = query.Get("obfs")
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
hysteria["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
hysteria["alpn"] = []string{query.Get("alpn")}
|
||||
hysteria["auth_str"] = query.Get("auth")
|
||||
hysteria["protocol"] = query.Get("protocol")
|
||||
up := query.Get("up")
|
||||
@ -68,79 +66,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
||||
|
||||
proxies = append(proxies, hysteria)
|
||||
case "hysteria2":
|
||||
urlHysteria2, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
query := urlHysteria2.Query()
|
||||
name := uniqueName(names, urlHysteria2.Fragment)
|
||||
hysteria2 := make(map[string]any, 20)
|
||||
|
||||
hysteria2["name"] = name
|
||||
hysteria2["type"] = scheme
|
||||
hysteria2["server"] = urlHysteria2.Hostname()
|
||||
if port := urlHysteria2.Port(); port != "" {
|
||||
hysteria2["port"] = port
|
||||
} else {
|
||||
hysteria2["port"] = "443"
|
||||
}
|
||||
hysteria2["obfs"] = query.Get("obfs")
|
||||
hysteria2["obfs-password"] = query.Get("obfs-password")
|
||||
hysteria2["sni"] = query.Get("sni")
|
||||
hysteria2["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
hysteria2["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if auth := urlHysteria2.User.String(); auth != "" {
|
||||
hysteria2["password"] = auth
|
||||
}
|
||||
hysteria2["fingerprint"] = query.Get("pinSHA256")
|
||||
hysteria2["down"] = query.Get("down")
|
||||
hysteria2["up"] = query.Get("up")
|
||||
|
||||
proxies = append(proxies, hysteria2)
|
||||
case "tuic":
|
||||
// A temporary unofficial TUIC share link standard
|
||||
// Modified from https://github.com/daeuniverse/dae/discussions/182
|
||||
// Changes:
|
||||
// 1. Support TUICv4, just replace uuid:password with token
|
||||
// 2. Remove `allow_insecure` field
|
||||
urlTUIC, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
query := urlTUIC.Query()
|
||||
|
||||
tuic := make(map[string]any, 20)
|
||||
tuic["name"] = uniqueName(names, urlTUIC.Fragment)
|
||||
tuic["type"] = scheme
|
||||
tuic["server"] = urlTUIC.Hostname()
|
||||
tuic["port"] = urlTUIC.Port()
|
||||
tuic["udp"] = true
|
||||
password, v5 := urlTUIC.User.Password()
|
||||
if v5 {
|
||||
tuic["uuid"] = urlTUIC.User.Username()
|
||||
tuic["password"] = password
|
||||
} else {
|
||||
tuic["token"] = urlTUIC.User.Username()
|
||||
}
|
||||
if cc := query.Get("congestion_control"); cc != "" {
|
||||
tuic["congestion-controller"] = cc
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
tuic["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
tuic["sni"] = sni
|
||||
}
|
||||
if query.Get("disable_sni") == "1" {
|
||||
tuic["disable-sni"] = true
|
||||
}
|
||||
if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" {
|
||||
tuic["udp-relay-mode"] = udpRelayMode
|
||||
}
|
||||
|
||||
case "trojan":
|
||||
urlTrojan, err := url.Parse(line)
|
||||
@ -161,12 +86,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
trojan["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
@ -294,9 +217,6 @@ 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 {
|
||||
@ -412,7 +332,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, ss)
|
||||
|
||||
case "ssr":
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
if err != nil {
|
||||
|
@ -1,35 +0,0 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// https://v2.hysteria.network/zh/docs/developers/URI-Scheme/
|
||||
func TestConvertsV2Ray_normal(t *testing.T) {
|
||||
hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test"
|
||||
|
||||
expected := []map[string]interface{}{
|
||||
{
|
||||
"name": "hy2test",
|
||||
"type": "hysteria2",
|
||||
"server": "example.com",
|
||||
"port": "8443",
|
||||
"sni": "real.example.com",
|
||||
"obfs": "salamander",
|
||||
"obfs-password": "gawrgura",
|
||||
"alpn": []string{"h3", "h4"},
|
||||
"password": "letmein",
|
||||
"up": "114",
|
||||
"down": "514",
|
||||
"skip-cert-verify": true,
|
||||
"fingerprint": "deadbeef",
|
||||
},
|
||||
}
|
||||
|
||||
proxies, err := ConvertsV2Ray([]byte(hy2test))
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, proxies)
|
||||
}
|
@ -24,6 +24,8 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
proxy["port"] = url.Port()
|
||||
proxy["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
|
||||
@ -32,9 +34,6 @@ 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
|
||||
|
@ -4,11 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var KeepAliveInterval = 15 * time.Second
|
||||
|
||||
func SplitNetworkType(s string) (string, string, error) {
|
||||
var (
|
||||
shecme string
|
||||
@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
||||
host, port, err = net.SplitHostPort(temp)
|
||||
return
|
||||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,6 @@ func (p *Picker[T]) Wait() T {
|
||||
p.wg.Wait()
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
p.cancel = nil
|
||||
}
|
||||
return p.result
|
||||
}
|
||||
@ -70,7 +69,6 @@ func (p *Picker[T]) Go(f func() (T, error)) {
|
||||
p.result = ret
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
p.cancel = nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -80,13 +78,3 @@ func (p *Picker[T]) Go(f func() (T, error)) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Close cancels the picker context and releases resources associated with it.
|
||||
// If Wait has been called, then there is no need to call Close.
|
||||
func (p *Picker[T]) Close() error {
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
p.cancel = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -16,7 +15,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
|
||||
case <-timer.C:
|
||||
return input, nil
|
||||
case <-ctx.Done():
|
||||
return lo.Empty[T](), ctx.Err()
|
||||
return getZero[T](), ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -36,6 +35,11 @@ func TestPicker_Timeout(t *testing.T) {
|
||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||
|
||||
number := picker.Wait()
|
||||
assert.Equal(t, number, lo.Empty[int]())
|
||||
assert.Equal(t, number, getZero[int]())
|
||||
assert.NotNil(t, picker.Error())
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -32,32 +32,23 @@ func NewAllocator() *Allocator {
|
||||
|
||||
// Get a []byte from pool with most appropriate cap
|
||||
func (alloc *Allocator) Get(size int) []byte {
|
||||
switch {
|
||||
case size < 0:
|
||||
panic("alloc.Get: len out of range")
|
||||
case size == 0:
|
||||
if size <= 0 || size > 65536 {
|
||||
return nil
|
||||
case size > 65536:
|
||||
return make([]byte, size)
|
||||
default:
|
||||
bits := msb(size)
|
||||
if size == 1<<bits {
|
||||
return alloc.buffers[bits].Get().([]byte)[:size]
|
||||
}
|
||||
|
||||
return alloc.buffers[bits+1].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]
|
||||
}
|
||||
|
||||
// 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) != 1<<bits {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 || 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.Equal(t, 65537, len(alloc.Get(65537)))
|
||||
assert.Nil(t, alloc.Get(65537))
|
||||
}
|
||||
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
assert.Nil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.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.Nil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 65537)), "put elem:65537 []bytes misbehavior")
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
|
@ -2,8 +2,6 @@ package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Queue is a simple concurrent safe queue
|
||||
@ -26,7 +24,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 lo.Empty[T]()
|
||||
return GetZero[T]()
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
@ -39,7 +37,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 lo.Empty[T]()
|
||||
return GetZero[T]()
|
||||
}
|
||||
|
||||
q.lock.RLock()
|
||||
@ -71,3 +69,8 @@ func New[T any](hint int64) *Queue[T] {
|
||||
items: make([]T, 0, hint),
|
||||
}
|
||||
}
|
||||
|
||||
func GetZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -96,11 +96,6 @@ 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:
|
||||
@ -287,9 +282,6 @@ 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 {
|
||||
|
@ -2,20 +2,48 @@ package utils
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// sliceHeader is equivalent to reflect.SliceHeader, but represents the pointer
|
||||
// to the underlying array as unsafe.Pointer rather than uintptr, allowing
|
||||
// sliceHeaders to be directly converted to slice objects.
|
||||
type sliceHeader struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
|
||||
// slice returns a slice whose underlying array starts at ptr an which length
|
||||
// and capacity are len.
|
||||
func slice[T any](ptr *T, length int) []T {
|
||||
var s []T
|
||||
hdr := (*sliceHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = unsafe.Pointer(ptr)
|
||||
hdr.Len = length
|
||||
hdr.Cap = length
|
||||
return s
|
||||
}
|
||||
|
||||
// stringHeader is equivalent to reflect.StringHeader, but represents the
|
||||
// pointer to the underlying array as unsafe.Pointer rather than uintptr,
|
||||
// allowing StringHeaders to be directly converted to strings.
|
||||
type stringHeader struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
}
|
||||
|
||||
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
|
||||
// same memory backing s instead of making a heap-allocated copy. This is only
|
||||
// valid if the returned slice is never mutated.
|
||||
func ImmutableBytesFromString(s string) []byte {
|
||||
b := unsafe.StringData(s)
|
||||
return unsafe.Slice(b, len(s))
|
||||
shdr := (*stringHeader)(unsafe.Pointer(&s))
|
||||
return slice((*byte)(shdr.Data), shdr.Len)
|
||||
}
|
||||
|
||||
// StringFromImmutableBytes is equivalent to string(bs), except that it uses
|
||||
// the same memory backing bs instead of making a heap-allocated copy. This is
|
||||
// only valid if bs is never mutated after StringFromImmutableBytes returns.
|
||||
func StringFromImmutableBytes(bs []byte) string {
|
||||
if len(bs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return unsafe.String(&bs[0], len(bs))
|
||||
// This is cheaper than messing with StringHeader and SliceHeader, which as
|
||||
// of this writing produces many dead stores of zeroes. Compare
|
||||
// strings.Builder.String().
|
||||
return *(*string)(unsafe.Pointer(&bs))
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Authenticator interface {
|
||||
@ -15,7 +15,7 @@ type AuthUser struct {
|
||||
}
|
||||
|
||||
type inMemoryAuthenticator struct {
|
||||
storage *xsync.MapOf[string, string]
|
||||
storage *sync.Map
|
||||
usernames []string
|
||||
}
|
||||
|
||||
@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
||||
return nil
|
||||
}
|
||||
|
||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
||||
au := &inMemoryAuthenticator{storage: &sync.Map{}}
|
||||
for _, user := range users {
|
||||
au.storage.Store(user.User, user.Pass)
|
||||
}
|
||||
usernames := make([]string, 0, len(users))
|
||||
au.storage.Range(func(key string, value string) bool {
|
||||
usernames = append(usernames, key)
|
||||
au.storage.Range(func(key, value any) bool {
|
||||
usernames = append(usernames, key.(string))
|
||||
return true
|
||||
})
|
||||
au.usernames = usernames
|
||||
|
@ -20,20 +20,3 @@ 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)
|
||||
}
|
||||
}
|
||||
|
22
component/dialer/control_go119.go
Normal file
22
component/dialer/control_go119.go
Normal file
@ -0,0 +1,22 @@
|
||||
//go:build !go1.20
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func addControlToDialer(d *net.Dialer, fn controlFn) {
|
||||
ld := *d
|
||||
d.Control = func(network, address string, c syscall.RawConn) (err error) {
|
||||
switch {
|
||||
case ld.Control != nil:
|
||||
if err = ld.Control(network, address, c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return fn(context.Background(), network, address, c)
|
||||
}
|
||||
}
|
26
component/dialer/control_go120.go
Normal file
26
component/dialer/control_go120.go
Normal file
@ -0,0 +1,26 @@
|
||||
//go:build go1.20
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func addControlToDialer(d *net.Dialer, fn controlFn) {
|
||||
ld := *d
|
||||
d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
||||
switch {
|
||||
case ld.ControlContext != nil:
|
||||
if err = ld.ControlContext(ctx, network, address, c); err != nil {
|
||||
return
|
||||
}
|
||||
case ld.Control != nil:
|
||||
if err = ld.Control(network, address, c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return fn(ctx, network, address, c)
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@ -132,9 +131,6 @@ 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)
|
||||
}
|
||||
@ -162,22 +158,14 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
|
||||
|
||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
||||
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 {
|
||||
@ -190,36 +178,18 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
||||
}()
|
||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
||||
}
|
||||
|
||||
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)
|
||||
}()
|
||||
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
var fallback dialResult
|
||||
var errs []error
|
||||
|
||||
loop:
|
||||
for {
|
||||
for i := 0; i < 2; {
|
||||
select {
|
||||
case <-fallbackTicker.C:
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
case res, ok := <-results:
|
||||
if !ok {
|
||||
break loop
|
||||
}
|
||||
case res := <-results:
|
||||
i++
|
||||
if res.error == nil {
|
||||
if res.isPrimary {
|
||||
return res.Conn, nil
|
||||
@ -234,11 +204,10 @@ loop:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
return nil, errors.Join(errs...)
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
|
||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
@ -275,7 +244,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
@ -292,7 +261,7 @@ func serialDialContext(ctx context.Context, network string, ips []netip.Addr, po
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil, errors.Join(errs...)
|
||||
return nil, errorsJoin(errs...)
|
||||
}
|
||||
|
||||
type dialResult struct {
|
||||
|
@ -2,9 +2,17 @@ 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...)
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
//go:build !go1.21
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
const multipathTCPAvailable = false
|
||||
|
||||
func setMultiPathTCP(dialer *net.Dialer) {
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
//go:build go1.21
|
||||
|
||||
package dialer
|
||||
|
||||
import "net"
|
||||
|
||||
const multipathTCPAvailable = true
|
||||
|
||||
func setMultiPathTCP(dialer *net.Dialer) {
|
||||
dialer.SetMultipathTCP(true)
|
||||
}
|
@ -25,7 +25,6 @@ type option struct {
|
||||
network int
|
||||
prefer int
|
||||
tfo bool
|
||||
mpTcp bool
|
||||
resolver resolver.Resolver
|
||||
netDialer NetDialer
|
||||
}
|
||||
@ -84,12 +83,6 @@ func WithTFO(tfo bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMPTCP(mpTcp bool) Option {
|
||||
return func(opt *option) {
|
||||
opt.mpTcp = mpTcp
|
||||
}
|
||||
}
|
||||
|
||||
func WithNetDialer(netDialer NetDialer) Option {
|
||||
return func(opt *option) {
|
||||
opt.netDialer = netDialer
|
||||
|
@ -2,7 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -10,13 +9,15 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
)
|
||||
|
||||
const (
|
||||
UA = "clash.meta"
|
||||
)
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
UA := C.UA
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
@ -59,7 +60,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
|
||||
return d.DialContext(ctx, network, address)
|
||||
}
|
||||
},
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
TLSClientConfig: tls.GetDefaultTLSConfig(),
|
||||
}
|
||||
|
||||
client := http.Client{Transport: transport}
|
||||
|
@ -24,6 +24,8 @@ var (
|
||||
|
||||
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
|
||||
|
||||
const FlagRunning = 32 // interface is in running state, compatibility with golang<1.20
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
@ -39,7 +41,7 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
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) {
|
||||
if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&FlagRunning == 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -12,68 +12,42 @@ import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
type databaseType = uint8
|
||||
|
||||
const (
|
||||
typeMaxmind databaseType = iota
|
||||
typeSing
|
||||
typeMetaV0
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
var (
|
||||
reader Reader
|
||||
once sync.Once
|
||||
mmdb *geoip2.Reader
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func LoadFromBytes(buffer []byte) {
|
||||
once.Do(func() {
|
||||
mmdb, err := maxminddb.FromBytes(buffer)
|
||||
var err error
|
||||
mmdb, err = geoip2.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 := maxminddb.Open(C.Path.MMDB())
|
||||
instance, err := geoip2.Open(C.Path.MMDB())
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func Instance() Reader {
|
||||
func Instance() *geoip2.Reader {
|
||||
once.Do(func() {
|
||||
mmdbPath := C.Path.MMDB()
|
||||
log.Debugln("Load MMDB file: %s", mmdbPath)
|
||||
mmdb, err := maxminddb.Open(mmdbPath)
|
||||
var err error
|
||||
mmdb, err = geoip2.Open(C.Path.MMDB())
|
||||
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
|
||||
log.Fatalln("Can't load mmdb: %s", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
return reader
|
||||
return mmdb
|
||||
}
|
||||
|
||||
func DownloadMMDB(path string) (err error) {
|
||||
|
@ -1,56 +0,0 @@
|
||||
package mmdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
type geoip2Country struct {
|
||||
Country struct {
|
||||
IsoCode string `maxminddb:"iso_code"`
|
||||
} `maxminddb:"country"`
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
*maxminddb.Reader
|
||||
databaseType
|
||||
}
|
||||
|
||||
func (r Reader) LookupCode(ipAddress net.IP) []string {
|
||||
switch r.databaseType {
|
||||
case typeMaxmind:
|
||||
var country geoip2Country
|
||||
_ = r.Lookup(ipAddress, &country)
|
||||
if country.Country.IsoCode == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{country.Country.IsoCode}
|
||||
|
||||
case typeSing:
|
||||
var code string
|
||||
_ = r.Lookup(ipAddress, &code)
|
||||
if code == "" {
|
||||
return []string{}
|
||||
}
|
||||
return []string{code}
|
||||
|
||||
case typeMetaV0:
|
||||
var record any
|
||||
_ = r.Lookup(ipAddress, &record)
|
||||
switch record := record.(type) {
|
||||
case string:
|
||||
return []string{record}
|
||||
case []any: // lookup returned type of slice is []any
|
||||
return common.Map(record, func(it any) string {
|
||||
return it.(string)
|
||||
})
|
||||
}
|
||||
return []string{}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint("unknown geoip database type:", r.databaseType))
|
||||
}
|
||||
}
|
@ -5,28 +5,23 @@ import (
|
||||
"sync"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
mapping *xsync.MapOf[string, *Entry]
|
||||
lockMap *xsync.MapOf[string, *sync.Cond]
|
||||
mapping sync.Map
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
PacketConn C.PacketConn
|
||||
WriteBackProxy C.WriteBackProxy
|
||||
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
|
||||
LocalLockMap *xsync.MapOf[string, *sync.Cond]
|
||||
LocalUDPConnMap sync.Map
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||
t.mapping.Store(key, &Entry{
|
||||
PacketConn: e,
|
||||
WriteBackProxy: w,
|
||||
LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](),
|
||||
LocalLockMap: xsync.NewMapOf[*sync.Cond](),
|
||||
LocalUDPConnMap: sync.Map{},
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,19 +34,15 @@ func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
|
||||
return item, loaded
|
||||
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||
return item.(*sync.Cond), loaded
|
||||
}
|
||||
|
||||
func (t *Table) Delete(key string) {
|
||||
t.mapping.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) DeleteLock(lockKey string) {
|
||||
t.lockMap.Delete(lockKey)
|
||||
}
|
||||
|
||||
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return nil
|
||||
@ -60,10 +51,10 @@ func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
return item
|
||||
return item.(*net.UDPConn)
|
||||
}
|
||||
|
||||
func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return false
|
||||
@ -72,7 +63,7 @@ func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) {
|
||||
func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return
|
||||
@ -85,11 +76,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock)
|
||||
return item, loaded
|
||||
item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||
return item.(*sync.Cond), loaded
|
||||
}
|
||||
|
||||
func (t *Table) DeleteForLocalConn(lAddr, key string) {
|
||||
func (t *Table) DeleteLocalConnMap(lAddr, key string) {
|
||||
entry, loaded := t.getEntry(lAddr)
|
||||
if !loaded {
|
||||
return
|
||||
@ -97,26 +88,17 @@ func (t *Table) DeleteForLocalConn(lAddr, key string) {
|
||||
entry.LocalUDPConnMap.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
|
||||
entry, loaded := t.getEntry(lAddr)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
entry.LocalLockMap.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) getEntry(key string) (*Entry, bool) {
|
||||
return t.mapping.Load(key)
|
||||
}
|
||||
|
||||
func makeLock() *sync.Cond {
|
||||
return sync.NewCond(&sync.Mutex{})
|
||||
item, ok := t.mapping.Load(key)
|
||||
// This should not happen usually since this function called after PacketConn created
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
entry, ok := item.(*Entry)
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
// New return *Cache
|
||||
func New() *Table {
|
||||
return &Table{
|
||||
mapping: xsync.NewMapOf[*Entry](),
|
||||
lockMap: xsync.NewMapOf[*sync.Cond](),
|
||||
}
|
||||
return &Table{}
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
package proxydialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type SingDialer interface {
|
||||
N.Dialer
|
||||
SetDialer(dialer C.Dialer)
|
||||
}
|
||||
|
||||
type singDialer proxyDialer
|
||||
|
||||
var _ N.Dialer = (*singDialer)(nil)
|
||||
|
||||
func (d *singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return (*proxyDialer)(d).DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (d *singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return (*proxyDialer)(d).ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||
}
|
||||
|
||||
func (d *singDialer) SetDialer(dialer C.Dialer) {
|
||||
(*proxyDialer)(d).dialer = dialer
|
||||
}
|
||||
|
||||
func NewSingDialer(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) SingDialer {
|
||||
return (*singDialer)(&proxyDialer{
|
||||
proxy: proxy,
|
||||
dialer: dialer,
|
||||
statistic: statistic,
|
||||
})
|
||||
}
|
||||
|
||||
type byNameSingDialer struct {
|
||||
dialer C.Dialer
|
||||
proxyName string
|
||||
}
|
||||
|
||||
var _ N.Dialer = (*byNameSingDialer)(nil)
|
||||
|
||||
func (d *byNameSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
var cDialer C.Dialer = d.dialer
|
||||
if len(d.proxyName) > 0 {
|
||||
pd, err := NewByName(d.proxyName, d.dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cDialer = pd
|
||||
}
|
||||
return cDialer.DialContext(ctx, network, destination.String())
|
||||
}
|
||||
|
||||
func (d *byNameSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
var cDialer C.Dialer = d.dialer
|
||||
if len(d.proxyName) > 0 {
|
||||
pd, err := NewByName(d.proxyName, d.dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cDialer = pd
|
||||
}
|
||||
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
||||
}
|
||||
|
||||
func (d *byNameSingDialer) SetDialer(dialer C.Dialer) {
|
||||
d.dialer = dialer
|
||||
}
|
||||
|
||||
func NewByNameSingDialer(proxyName string, dialer C.Dialer) SingDialer {
|
||||
return &byNameSingDialer{
|
||||
dialer: dialer,
|
||||
proxyName: proxyName,
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
@ -21,39 +20,28 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
|
||||
}
|
||||
}
|
||||
|
||||
// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts.
|
||||
//
|
||||
//go:linkname lookupStaticHost net.lookupStaticHost
|
||||
func lookupStaticHost(host string) ([]string, string)
|
||||
|
||||
// Return the search result and whether to match the parameter `isDomain`
|
||||
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
||||
if value := h.DomainTrie.Search(domain); value != nil {
|
||||
hostValue := value.Data()
|
||||
for {
|
||||
if isDomain && hostValue.IsDomain {
|
||||
return &hostValue, true
|
||||
value := h.DomainTrie.Search(domain)
|
||||
if value == nil {
|
||||
return nil, false
|
||||
}
|
||||
hostValue := value.Data()
|
||||
for {
|
||||
if isDomain && hostValue.IsDomain {
|
||||
return &hostValue, true
|
||||
} else {
|
||||
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
|
||||
hostValue = node.Data()
|
||||
} else {
|
||||
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
|
||||
hostValue = node.Data()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if isDomain == hostValue.IsDomain {
|
||||
return &hostValue, true
|
||||
}
|
||||
|
||||
return &hostValue, false
|
||||
}
|
||||
if !isDomain {
|
||||
addr, _ := lookupStaticHost(domain)
|
||||
if hostValue, err := NewHostValue(addr); err == nil {
|
||||
return &hostValue, true
|
||||
}
|
||||
if isDomain == hostValue.IsDomain {
|
||||
return &hostValue, true
|
||||
}
|
||||
return nil, false
|
||||
return &hostValue, false
|
||||
}
|
||||
|
||||
type HostValue struct {
|
||||
|
@ -1,19 +0,0 @@
|
||||
//go:build !go1.22
|
||||
|
||||
// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
|
||||
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname testHookHostsPath net.testHookHostsPath
|
||||
var testHookHostsPath string
|
||||
|
||||
func init() {
|
||||
if dir, err := windows.GetSystemDirectory(); err == nil {
|
||||
testHookHostsPath = dir + "/Drivers/etc/hosts"
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@ import (
|
||||
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -67,7 +65,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return lo.Empty[V](), err
|
||||
return getZero[V](), err
|
||||
}
|
||||
|
||||
var contents V
|
||||
@ -87,18 +85,18 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return lo.Empty[V](), err
|
||||
return getZero[V](), err
|
||||
}
|
||||
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = f.vehicle.Read()
|
||||
if err != nil {
|
||||
return lo.Empty[V](), err
|
||||
return getZero[V](), err
|
||||
}
|
||||
|
||||
contents, err = f.parser(buf)
|
||||
if err != nil {
|
||||
return lo.Empty[V](), err
|
||||
return getZero[V](), err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
@ -106,7 +104,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 lo.Empty[V](), err
|
||||
return getZero[V](), err
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +121,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
buf, err := f.vehicle.Read()
|
||||
if err != nil {
|
||||
return lo.Empty[V](), false, err
|
||||
return getZero[V](), false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -131,17 +129,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 lo.Empty[V](), true, nil
|
||||
return getZero[V](), true, nil
|
||||
}
|
||||
|
||||
contents, err := f.parser(buf)
|
||||
if err != nil {
|
||||
return lo.Empty[V](), false, err
|
||||
return getZero[V](), false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return lo.Empty[V](), false, err
|
||||
return getZero[V](), false, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,3 +210,8 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
func getZero[V any]() V {
|
||||
var result V
|
||||
return result
|
||||
}
|
||||
|
@ -2,14 +2,12 @@ package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
)
|
||||
|
||||
type FileVehicle struct {
|
||||
@ -56,10 +54,8 @@ 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
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -25,24 +26,29 @@ 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
|
||||
}
|
||||
|
||||
// TCPSniff returns true if the connection is sniffed to have a domain
|
||||
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) 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(metadata.DstPort)
|
||||
inWhitelist = sniffer.SupportPort(uint16(port))
|
||||
if inWhitelist {
|
||||
overrideDest = config.OverrideDest
|
||||
break
|
||||
@ -51,26 +57,26 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
}
|
||||
|
||||
if !inWhitelist {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
dst := fmt.Sprintf("%s:%d", metadata.DstIP, metadata.DstPort)
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||
defer sd.rwMux.RUnlock()
|
||||
return false
|
||||
return
|
||||
}
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return false
|
||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||
return
|
||||
} else {
|
||||
if sd.skipSNI.Has(host) {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
sd.rwMux.RLock()
|
||||
@ -78,23 +84,20 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
||||
sd.rwMux.RUnlock()
|
||||
|
||||
sd.replaceDomain(metadata, host, overrideDest)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
// show log early, since the following code may mutate `metadata.Host`
|
||||
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.SourceDetail(),
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
metadata.SniffHost = host
|
||||
if overrideDest {
|
||||
metadata.Host = host
|
||||
}
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
metadata.SourceDetail(),
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
}
|
||||
|
||||
func (sd *SnifferDispatcher) Enable() bool {
|
||||
@ -146,7 +149,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:%d", metadata.DstIP, metadata.DstPort)
|
||||
dst := fmt.Sprintf("%s:%s", metadata.DstIP, metadata.DstPort)
|
||||
count, _ := sd.skipList.Get(dst)
|
||||
if count <= 5 {
|
||||
count++
|
||||
|
@ -1,4 +1,4 @@
|
||||
package ca
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -8,13 +8,14 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
xtls "github.com/xtls/go"
|
||||
)
|
||||
|
||||
var trustCerts []*x509.Certificate
|
||||
var globalCertPool *x509.CertPool
|
||||
var certPool *x509.CertPool
|
||||
var mutex sync.RWMutex
|
||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||
|
||||
@ -34,12 +35,12 @@ func AddCertificate(certificate string) error {
|
||||
|
||||
func initializeCertPool() {
|
||||
var err error
|
||||
globalCertPool, err = x509.SystemCertPool()
|
||||
certPool, err = x509.SystemCertPool()
|
||||
if err != nil {
|
||||
globalCertPool = x509.NewCertPool()
|
||||
certPool = x509.NewCertPool()
|
||||
}
|
||||
for _, cert := range trustCerts {
|
||||
globalCertPool.AddCert(cert)
|
||||
certPool.AddCert(cert)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,15 +55,15 @@ func getCertPool() *x509.CertPool {
|
||||
if len(trustCerts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if globalCertPool == nil {
|
||||
if certPool == nil {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
if globalCertPool != nil {
|
||||
return globalCertPool
|
||||
if certPool != nil {
|
||||
return certPool
|
||||
}
|
||||
initializeCertPool()
|
||||
}
|
||||
return globalCertPool
|
||||
return certPool
|
||||
}
|
||||
|
||||
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
@ -95,49 +96,53 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) {
|
||||
return (*[32]byte)(fpByte), nil
|
||||
}
|
||||
|
||||
// GetTLSConfig specified fingerprint, customCA and customCAString
|
||||
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) {
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
var certificate []byte
|
||||
var err error
|
||||
if len(customCA) > 0 {
|
||||
certificate, err = os.ReadFile(customCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load ca error: %w", err)
|
||||
}
|
||||
} else if customCAString != "" {
|
||||
certificate = []byte(customCAString)
|
||||
}
|
||||
if len(certificate) > 0 {
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certificate) {
|
||||
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
} else {
|
||||
tlsConfig.RootCAs = getCertPool()
|
||||
}
|
||||
if len(fingerprint) > 0 {
|
||||
var fingerprintBytes *[32]byte
|
||||
fingerprintBytes, err = convertFingerprint(fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
||||
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
return tlsConfig, nil
|
||||
func GetDefaultTLSConfig() *tls.Config {
|
||||
return GetGlobalTLSConfig(nil)
|
||||
}
|
||||
|
||||
// GetSpecifiedFingerprintTLSConfig specified fingerprint
|
||||
func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) {
|
||||
return GetTLSConfig(tlsConfig, fingerprint, "", "")
|
||||
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
||||
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
return tlsConfig, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||
tlsConfig, _ = GetTLSConfig(tlsConfig, "", "", "")
|
||||
certPool := getCertPool()
|
||||
if tlsConfig == nil {
|
||||
return &tls.Config{
|
||||
RootCAs: certPool,
|
||||
}
|
||||
}
|
||||
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,7 +22,6 @@ 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"
|
||||
@ -71,7 +70,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
rawSessionID[i] = 0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||||
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[0] = 1
|
||||
|
114
config/config.go
114
config/config.go
@ -8,7 +8,6 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -17,7 +16,6 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -53,7 +51,6 @@ 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"`
|
||||
@ -61,7 +58,6 @@ type General struct {
|
||||
Sniffing bool `json:"sniffing"`
|
||||
EBpf EBpf `json:"-"`
|
||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||
GlobalUA string `json:"global-ua"`
|
||||
}
|
||||
|
||||
// Inbound config
|
||||
@ -79,7 +75,6 @@ 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
|
||||
@ -90,16 +85,6 @@ 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"`
|
||||
DialerProxy string `yaml:"dialer-proxy"`
|
||||
WriteToSystem bool `yaml:"write-to-system"`
|
||||
}
|
||||
|
||||
// DNS config
|
||||
type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
@ -157,15 +142,13 @@ type Sniffer struct {
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct {
|
||||
Fingerprints []string `yaml:"fingerprints"`
|
||||
QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"`
|
||||
Fingerprints []string `yaml:"fingerprints"`
|
||||
}
|
||||
|
||||
// Config is clash config manager
|
||||
type Config struct {
|
||||
General *General
|
||||
IPTables *IPTables
|
||||
NTP *NTP
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
@ -182,15 +165,6 @@ 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"`
|
||||
DialerProxy string `yaml:"dialer-proxy"`
|
||||
WriteToSystem bool `yaml:"write-to-system"`
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
@ -268,7 +242,6 @@ 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"`
|
||||
@ -279,8 +252,6 @@ type RawConfig struct {
|
||||
ExternalController string `yaml:"external-controller"`
|
||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||
ExternalUI string `yaml:"external-ui"`
|
||||
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
|
||||
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
|
||||
Secret string `yaml:"secret"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
RoutingMark int `yaml:"routing-mark"`
|
||||
@ -290,14 +261,11 @@ type RawConfig struct {
|
||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||
GlobalUA string `yaml:"global-ua"`
|
||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
||||
|
||||
Sniffer RawSniffer `yaml:"sniffer"`
|
||||
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"`
|
||||
@ -305,7 +273,7 @@ type RawConfig struct {
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
GeoXUrl GeoXUrl `yaml:"geox-url"`
|
||||
GeoXUrl RawGeoXUrl `yaml:"geox-url"`
|
||||
Proxy []map[string]any `yaml:"proxies"`
|
||||
ProxyGroup []map[string]any `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
@ -314,7 +282,7 @@ type RawConfig struct {
|
||||
Listeners []map[string]any `yaml:"listeners"`
|
||||
}
|
||||
|
||||
type GeoXUrl struct {
|
||||
type RawGeoXUrl struct {
|
||||
GeoIp string `yaml:"geoip" json:"geoip"`
|
||||
Mmdb string `yaml:"mmdb" json:"mmdb"`
|
||||
GeoSite string `yaml:"geosite" json:"geosite"`
|
||||
@ -377,7 +345,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
ProxyGroup: []map[string]any{},
|
||||
TCPConcurrent: false,
|
||||
FindProcessMode: P.FindProcessStrict,
|
||||
GlobalUA: "clash.meta",
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
@ -409,13 +376,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
InboundInterface: "lo",
|
||||
Bypass: []string{},
|
||||
},
|
||||
NTP: RawNTP{
|
||||
Enable: false,
|
||||
WriteToSystem: false,
|
||||
Server: "time.apple.com",
|
||||
ServerPort: 123,
|
||||
Interval: 30,
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
@ -458,12 +418,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
GeoXUrl: GeoXUrl{
|
||||
Mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb",
|
||||
GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
||||
GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
||||
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",
|
||||
},
|
||||
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
|
||||
@ -489,7 +448,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)
|
||||
}
|
||||
|
||||
@ -531,9 +490,6 @@ 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
|
||||
@ -574,40 +530,15 @@ 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
|
||||
C.UA = cfg.GlobalUA
|
||||
if cfg.KeepAliveInterval != 0 {
|
||||
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
|
||||
}
|
||||
|
||||
ExternalUIPath = cfg.ExternalUI
|
||||
// checkout externalUI exist
|
||||
if ExternalUIPath != "" {
|
||||
ExternalUIPath = C.Path.Resolve(ExternalUIPath)
|
||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
||||
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
|
||||
log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath)
|
||||
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ExternalUIPath = defaultUIpath
|
||||
cfg.ExternalUI = defaultUIpath
|
||||
if externalUI != "" {
|
||||
externalUI = C.Path.Resolve(externalUI)
|
||||
if _, err := os.Stat(externalUI); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("external-ui: %s not exist", externalUI)
|
||||
}
|
||||
}
|
||||
// checkout UIpath/name exist
|
||||
if cfg.ExternalUIName != "" {
|
||||
ExternalUIName = cfg.ExternalUIName
|
||||
} else {
|
||||
ExternalUIFolder = ExternalUIPath
|
||||
}
|
||||
if cfg.ExternalUIURL != "" {
|
||||
ExternalUIURL = cfg.ExternalUIURL
|
||||
}
|
||||
|
||||
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
|
||||
return &General{
|
||||
Inbound: Inbound{
|
||||
@ -621,7 +552,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
AllowLan: cfg.AllowLan,
|
||||
BindAddress: cfg.BindAddress,
|
||||
InboundTfo: cfg.InboundTfo,
|
||||
InboundMPTCP: cfg.InboundMPTCP,
|
||||
},
|
||||
Controller: Controller{
|
||||
ExternalController: cfg.ExternalController,
|
||||
@ -635,14 +565,12 @@ 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,
|
||||
FindProcessMode: cfg.FindProcessMode,
|
||||
EBpf: cfg.EBpf,
|
||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||
GlobalUA: cfg.GlobalUA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -784,9 +712,6 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.
|
||||
|
||||
func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) {
|
||||
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")
|
||||
@ -1195,19 +1120,6 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func paresNTP(rawCfg *RawConfig) *NTP {
|
||||
cfg := rawCfg.NTP
|
||||
ntpCfg := &NTP{
|
||||
Enable: cfg.Enable,
|
||||
Server: cfg.Server,
|
||||
Port: cfg.ServerPort,
|
||||
Interval: cfg.Interval,
|
||||
DialerProxy: cfg.DialerProxy,
|
||||
WriteToSystem: cfg.WriteToSystem,
|
||||
}
|
||||
return ntpCfg
|
||||
}
|
||||
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
|
||||
cfg := rawCfg.DNS
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
|
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"os"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -27,6 +28,23 @@ 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
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
_ "github.com/Dreamacro/clash/component/geodata/standard"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
func UpdateGeoDatabases() error {
|
||||
@ -38,7 +44,7 @@ func UpdateGeoDatabases() error {
|
||||
return fmt.Errorf("can't download MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
instance, err := geoip2.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MMDB database file: %s", err)
|
||||
}
|
||||
@ -66,3 +72,19 @@ func UpdateGeoDatabases() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return os.WriteFile(path, bytes, 0o644)
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
ExternalUIURL string
|
||||
ExternalUIPath string
|
||||
ExternalUIFolder string
|
||||
ExternalUIName string
|
||||
)
|
||||
var (
|
||||
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
|
||||
)
|
||||
var xdMutex sync.Mutex
|
||||
|
||||
func UpdateUI() error {
|
||||
xdMutex.Lock()
|
||||
defer xdMutex.Unlock()
|
||||
|
||||
err := prepare()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := downloadForBytes(ExternalUIURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download file: %w", err)
|
||||
}
|
||||
|
||||
saved := path.Join(C.Path.HomeDir(), "download.zip")
|
||||
if saveFile(data, saved) != nil {
|
||||
return fmt.Errorf("can't save zip file: %w", err)
|
||||
}
|
||||
defer os.Remove(saved)
|
||||
|
||||
err = cleanup(ExternalUIFolder)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("cleanup exist file error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
unzipFolder, err := unzip(saved, C.Path.HomeDir())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't extract zip file: %w", err)
|
||||
}
|
||||
|
||||
err = os.Rename(unzipFolder, ExternalUIFolder)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't rename folder: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepare() error {
|
||||
if ExternalUIPath == "" || ExternalUIURL == "" {
|
||||
return ErrIncompleteConf
|
||||
}
|
||||
|
||||
if ExternalUIName != "" {
|
||||
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
|
||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ExternalUIFolder = ExternalUIPath
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzip(src, dest string) (string, error) {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer r.Close()
|
||||
var extractedFolder string
|
||||
for _, f := range r.File {
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if extractedFolder == "" {
|
||||
extractedFolder = filepath.Dir(fpath)
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
}
|
||||
|
||||
func cleanup(root string) error {
|
||||
if _, err := os.Stat(root); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,37 +1,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
clashHttp "github.com/Dreamacro/clash/component/http"
|
||||
)
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return os.WriteFile(path, bytes, 0o644)
|
||||
}
|
||||
|
||||
func trimArr(arr []string) (r []string) {
|
||||
for _, e := range arr {
|
||||
r = append(r, strings.Trim(e, " "))
|
||||
|
@ -36,7 +36,6 @@ const (
|
||||
Vless
|
||||
Trojan
|
||||
Hysteria
|
||||
Hysteria2
|
||||
WireGuard
|
||||
Tuic
|
||||
)
|
||||
@ -201,8 +200,6 @@ func (at AdapterType) String() string {
|
||||
return "Trojan"
|
||||
case Hysteria:
|
||||
return "Hysteria"
|
||||
case Hysteria2:
|
||||
return "Hysteria2"
|
||||
case WireGuard:
|
||||
return "WireGuard"
|
||||
case Tuic:
|
||||
@ -270,17 +267,13 @@ type NatTable interface {
|
||||
|
||||
Delete(key string)
|
||||
|
||||
DeleteLock(key string)
|
||||
GetLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||
|
||||
GetForLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||
AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||
|
||||
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||
RangeLocalConn(lAddr string, f func(key, value any) bool)
|
||||
|
||||
RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool)
|
||||
GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
|
||||
|
||||
GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool)
|
||||
|
||||
DeleteForLocalConn(lAddr, key string)
|
||||
|
||||
DeleteLockForLocalConn(lAddr, key string)
|
||||
DeleteLocalConnMap(lAddr, key string)
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
UA string
|
||||
)
|
@ -30,7 +30,6 @@ const (
|
||||
TUNNEL
|
||||
TUN
|
||||
TUIC
|
||||
HYSTERIA2
|
||||
INNER
|
||||
)
|
||||
|
||||
@ -79,8 +78,6 @@ func (t Type) String() string {
|
||||
return "Tun"
|
||||
case TUIC:
|
||||
return "Tuic"
|
||||
case HYSTERIA2:
|
||||
return "Hysteria2"
|
||||
case INNER:
|
||||
return "Inner"
|
||||
default:
|
||||
@ -113,8 +110,6 @@ func ParseType(t string) (*Type, error) {
|
||||
res = TUN
|
||||
case "TUIC":
|
||||
res = TUIC
|
||||
case "HYSTERIA2":
|
||||
res = HYSTERIA2
|
||||
case "INNER":
|
||||
res = INNER
|
||||
default:
|
||||
@ -133,10 +128,10 @@ type Metadata struct {
|
||||
Type Type `json:"type"`
|
||||
SrcIP netip.Addr `json:"sourceIP"`
|
||||
DstIP netip.Addr `json:"destinationIP"`
|
||||
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
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
InIP netip.Addr `json:"inboundIP"`
|
||||
InPort uint16 `json:"inboundPort,string"` // `,string` is used to compatible with old version json output
|
||||
InPort string `json:"inboundPort"`
|
||||
InName string `json:"inboundName"`
|
||||
InUser string `json:"inboundUser"`
|
||||
Host string `json:"host"`
|
||||
@ -152,11 +147,11 @@ type Metadata struct {
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
return net.JoinHostPort(m.String(), strconv.FormatUint(uint64(m.DstPort), 10))
|
||||
return net.JoinHostPort(m.String(), m.DstPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceAddress() string {
|
||||
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10))
|
||||
return net.JoinHostPort(m.SrcIP.String(), m.SrcPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceDetail() string {
|
||||
@ -177,7 +172,7 @@ func (m *Metadata) SourceDetail() string {
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceValid() bool {
|
||||
return m.SrcPort != 0 && m.SrcIP.IsValid()
|
||||
return m.SrcPort != "" && m.SrcIP.IsValid()
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrType() int {
|
||||
@ -216,7 +211,8 @@ func (m *Metadata) Pure() *Metadata {
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrPort() netip.AddrPort {
|
||||
return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort)
|
||||
port, _ := strconv.ParseUint(m.DstPort, 10, 16)
|
||||
return netip.AddrPortFrom(m.DstIP.Unmap(), uint16(port))
|
||||
}
|
||||
|
||||
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||
@ -246,11 +242,6 @@ 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{}
|
||||
@ -258,7 +249,7 @@ func (m *Metadata) SetRemoteAddress(rawAddress string) error {
|
||||
m.Host = ""
|
||||
m.DstIP = ip.Unmap()
|
||||
}
|
||||
m.DstPort = uint16Port
|
||||
m.DstPort = port
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -23,7 +22,7 @@ var Path = func() *path {
|
||||
if err != nil {
|
||||
homeDir, _ = os.Getwd()
|
||||
}
|
||||
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
|
||||
allowUnsafePath := strings.TrimSpace(os.Getenv("SKIP_SAFE_PATH_CHECK")) == "1"
|
||||
homeDir = P.Join(homeDir, ".config", Name)
|
||||
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
|
||||
}()
|
||||
@ -91,15 +90,13 @@ func (p *path) MMDB() string {
|
||||
// 目录则直接跳过
|
||||
continue
|
||||
} else {
|
||||
if strings.EqualFold(fi.Name(), "Country.mmdb") ||
|
||||
strings.EqualFold(fi.Name(), "geoip.db") ||
|
||||
strings.EqualFold(fi.Name(), "geoip.metadb") {
|
||||
if strings.EqualFold(fi.Name(), "Country.mmdb") {
|
||||
GeoipName = fi.Name()
|
||||
return P.Join(p.homeDir, fi.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
return P.Join(p.homeDir, "geoip.metadb")
|
||||
return P.Join(p.homeDir, "Country.mmdb")
|
||||
}
|
||||
|
||||
func (p *path) OldCache() string {
|
||||
|
@ -10,14 +10,12 @@ 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
|
||||
@ -66,8 +64,6 @@ func (e TUNStack) String() string {
|
||||
return "System"
|
||||
case TunLWIP:
|
||||
return "LWIP"
|
||||
case TunMixed:
|
||||
return "Mixed"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
@ -99,7 +99,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
if strings.HasSuffix(c.Client.Net, "tls") {
|
||||
conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
|
||||
conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig))
|
||||
}
|
||||
|
||||
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/metacubex/quic-go"
|
||||
@ -382,7 +382,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error)
|
||||
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
||||
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
||||
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/metacubex/quic-go"
|
||||
@ -330,7 +330,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
tlsConfig := tlsC.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
|
@ -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))
|
||||
mapping = cache.New(cache.WithSize[netip.Addr, string](4096), cache.WithStale[netip.Addr, string](true))
|
||||
}
|
||||
|
||||
return &ResolverEnhancer{
|
||||
|
@ -2,7 +2,6 @@ package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
@ -10,6 +9,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fallbackIPFilter interface {
|
||||
@ -24,13 +24,8 @@ var geoIPMatcher *router.GeoIPMatcher
|
||||
|
||||
func (gf *geoipFilter) Match(ip netip.Addr) bool {
|
||||
if !C.GeodataMode {
|
||||
codes := mmdb.Instance().LookupCode(ip.AsSlice())
|
||||
for _, code := range codes {
|
||||
if !strings.EqualFold(code, gf.code) && !ip.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
record, _ := mmdb.Instance().Country(ip.AsSlice())
|
||||
return !strings.EqualFold(record.Country.IsoCode, gf.code) && !ip.IsPrivate()
|
||||
}
|
||||
|
||||
if geoIPMatcher == nil {
|
||||
|
@ -129,10 +129,6 @@ 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)))
|
||||
}
|
||||
|
||||
|
@ -200,7 +200,6 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
|
||||
isIPReq := isIPRequest(q)
|
||||
if isIPReq {
|
||||
cache=true
|
||||
return r.ipExchange(ctx, m)
|
||||
}
|
||||
|
||||
|
79
dns/util.go
79
dns/util.go
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -30,13 +29,9 @@ const (
|
||||
)
|
||||
|
||||
func minimalTTL(records []D.RR) uint32 {
|
||||
minObj := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
||||
return r1.Header().Ttl < r2.Header().Ttl
|
||||
})
|
||||
if minObj != nil {
|
||||
return minObj.Header().Ttl
|
||||
}
|
||||
return 0
|
||||
}).Header().Ttl
|
||||
}
|
||||
|
||||
func updateTTL(records []D.RR, ttl uint32) {
|
||||
@ -50,27 +45,27 @@ func updateTTL(records []D.RR, ttl uint32) {
|
||||
}
|
||||
|
||||
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
|
||||
putMsgToCacheWithExpire(c, key, msg, 0)
|
||||
}
|
||||
|
||||
func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) {
|
||||
if sec == 0 {
|
||||
if sec = minimalTTL(msg.Answer); sec == 0 {
|
||||
if sec = minimalTTL(msg.Ns); sec == 0 {
|
||||
sec = minimalTTL(msg.Extra)
|
||||
}
|
||||
}
|
||||
if sec == 0 {
|
||||
// skip dns cache for acme challenge
|
||||
if len(msg.Question) != 0 {
|
||||
if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") {
|
||||
log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if sec > 120 {
|
||||
sec = 120 // at least 2 minutes to cache
|
||||
}
|
||||
|
||||
}
|
||||
var ttl uint32
|
||||
switch {
|
||||
case len(msg.Answer) != 0:
|
||||
ttl = minimalTTL(msg.Answer)
|
||||
case len(msg.Ns) != 0:
|
||||
ttl = minimalTTL(msg.Ns)
|
||||
case len(msg.Extra) != 0:
|
||||
ttl = minimalTTL(msg.Extra)
|
||||
default:
|
||||
log.Debugln("[DNS] response msg empty: %#v", msg)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(sec)*time.Second))
|
||||
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
|
||||
}
|
||||
|
||||
func setMsgTTL(msg *D.Msg, ttl uint32) {
|
||||
@ -198,10 +193,6 @@ 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]
|
||||
@ -215,7 +206,7 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstPort: uint16(uintPort),
|
||||
DstPort: port,
|
||||
}
|
||||
if proxyAdapter != nil {
|
||||
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
|
||||
@ -240,7 +231,7 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: uint16(uintPort),
|
||||
DstPort: port,
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.DialContext(ctx, network, addr, opts...)
|
||||
@ -266,10 +257,6 @@ 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]
|
||||
@ -287,10 +274,10 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
NetWork: C.UDP,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: uint16(uintPort),
|
||||
DstPort: port,
|
||||
}
|
||||
if proxyAdapter == nil {
|
||||
return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
|
||||
}
|
||||
|
||||
if !proxyAdapter.SupportUDP() {
|
||||
@ -301,16 +288,12 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
_, cache = client.(rcodeClient)
|
||||
cache = !cache
|
||||
fast.Go(func() (*D.Msg, error) {
|
||||
log.Debugln("[DNS] resolve %s from %s", domain, client.Address())
|
||||
m, err := client.ExchangeContext(ctx, m)
|
||||
@ -319,19 +302,21 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
} 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])
|
||||
return nil, errors.New("server failure")
|
||||
}
|
||||
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), client.Address())
|
||||
return m, nil
|
||||
})
|
||||
}
|
||||
|
||||
msg = fast.Wait()
|
||||
if msg == nil {
|
||||
err = errors.New("all DNS requests failed")
|
||||
elm := fast.Wait()
|
||||
if elm == nil {
|
||||
err := errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
|
||||
}
|
||||
return nil, true, err
|
||||
}
|
||||
msg = elm
|
||||
return
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ mode: rule
|
||||
|
||||
#自定义 geodata url
|
||||
geox-url:
|
||||
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"
|
||||
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"
|
||||
|
||||
log-level: debug # 日志等级 silent/error/warning/info/debug
|
||||
|
||||
@ -41,11 +41,7 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
|
||||
# secret: "123456" # `Authorization:Bearer ${secret}`
|
||||
|
||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||
|
||||
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||
external-ui: /path/to/ui/folder/
|
||||
external-ui-name: xd
|
||||
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
||||
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||
|
||||
# interface-name: en0 # 设置出口网卡
|
||||
|
||||
@ -54,18 +50,8 @@ external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-
|
||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||
global-client-fingerprint: chrome
|
||||
|
||||
# TCP keep alive interval
|
||||
keep-alive-interval: 15
|
||||
|
||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||
experimental:
|
||||
# Disable quic-go GSO support. This may result in reduced performance on Linux.
|
||||
# This is not recommended for most users.
|
||||
# Only users encountering issues with quic-go's internal implementation should enable this,
|
||||
# and they should disable it as soon as the issue is resolved.
|
||||
# This field will be removed when quic-go fixes all their issues in GSO.
|
||||
# This equivalent to the environment variable QUIC_GO_DISABLE_GSO=1.
|
||||
#quic-go-disable-gso: true
|
||||
|
||||
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
||||
hosts:
|
||||
@ -642,25 +628,6 @@ proxies: # socks5
|
||||
# fingerprint: xxxx
|
||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||
|
||||
#hysteria2
|
||||
- name: "hysteria2"
|
||||
type: hysteria2
|
||||
server: server.com
|
||||
port: 443
|
||||
# up和down均不写或为0则使用BBR流控
|
||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
password: yourpassword
|
||||
# obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander
|
||||
# obfs-password: yourpassword
|
||||
# sni: server.com
|
||||
# skip-cert-verify: false
|
||||
# fingerprint: xxxx
|
||||
# alpn:
|
||||
# - h3
|
||||
# ca: "./my.ca"
|
||||
# ca-str: "xyz"
|
||||
|
||||
# wireguard
|
||||
- name: "wg"
|
||||
type: wireguard
|
||||
@ -714,11 +681,6 @@ proxies: # socks5
|
||||
# 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
|
||||
|
89
go.mod
89
go.mod
@ -1,56 +1,56 @@
|
||||
module github.com/Dreamacro/clash
|
||||
|
||||
go 1.20
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/3andne/restls-client-go v0.1.4
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/cilium/ebpf v0.10.0
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
|
||||
github.com/jpillora/backoff v1.0.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4
|
||||
github.com/metacubex/sing-tun v0.1.12
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018
|
||||
github.com/metacubex/sing-vmess v0.1.5
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/miekg/dns v1.1.54
|
||||
github.com/mroth/weightedrand/v2 v2.0.1
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.11
|
||||
github.com/sagernet/sing-mux v0.1.3
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||
github.com/sagernet/sing v0.2.5
|
||||
github.com/sagernet/sing-mux v0.1.0
|
||||
github.com/sagernet/sing-shadowtls v0.1.2
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.8
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/shirou/gopsutil/v3 v3.23.5
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.12.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sync v0.2.0
|
||||
golang.org/x/sys v0.9.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
)
|
||||
@ -66,26 +66,27 @@ 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.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // 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/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.16.7 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
@ -93,16 +94,16 @@ require (
|
||||
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.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b
|
||||
|
182
go.sum
182
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
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/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=
|
||||
@ -14,10 +14,10 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
|
||||
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
|
||||
github.com/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/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
|
||||
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
|
||||
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
|
||||
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -32,30 +32,26 @@ 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.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/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/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/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.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-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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/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/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
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=
|
||||
@ -67,18 +63,20 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
|
||||
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-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
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.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=
|
||||
@ -95,38 +93,38 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8=
|
||||
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4=
|
||||
github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg=
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 h1:6g+ohVa8FQLXz/ATmped/4kWuK0HKvhy1hwzQXyF0EI=
|
||||
github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
|
||||
github.com/metacubex/sing-tun v0.1.12 h1:Jgmz0k3ddRiJ8zfS4X7j6B/iSy6GnOdDEU0nhqiZcK4=
|
||||
github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb h1:92YTNmYXCSycERjKn/zPbeK5DiW3dd80j3+oVTEWTE8=
|
||||
github.com/metacubex/quic-go v0.35.2-0.20230603072621-ea2663348ebb/go.mod h1:6pg8+Tje9KOltnj1whuvB2i5KFUMPp1TAF3oPhc5axM=
|
||||
github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b h1:mVd3v+zMQq61rJe/pJJSh0/Iin9UnkQaZTH2NOg/2Vg=
|
||||
github.com/metacubex/sing v0.0.0-20230618234508-ce8816d0274b/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2 h1:prciO78IwtR4Sp+/CnP+aZSzpBRfL7zKaYez1S4EOnI=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.2/go.mod h1:haolI+8Yc8MhNDqNuoRP4X5vaquXWNYeL1YxrQZ5kCU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0 h1:ZxPEToY1RaRtG6ljz2n13ASMVqyAM7Bh11TmWoExYu4=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.0/go.mod h1:6C4EkvqMz5h7jECKrQeIByoLDHxiepsgPajIrxqxj/s=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018 h1:M7vBGA4RL4BBLSYfi15u/9QdVSqPkhuL4KRCuRhxuQY=
|
||||
github.com/metacubex/sing-tun v0.1.5-0.20230618235243-65051e73b018/go.mod h1:DSVNjWT0rkkg8zn2+wpDvxgXuXRmMiNFDnVmnUctbAc=
|
||||
github.com/metacubex/sing-vmess v0.1.5 h1:wODu17P27aGw0GhSIb/rIZWNh3/F5ghF/1PDDt95CQY=
|
||||
github.com/metacubex/sing-vmess v0.1.5/go.mod h1:s00xTd3c/zOMQHyPec0G/pbUklndleiH0QaHZRd4Ykg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
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/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.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/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/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/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
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/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=
|
||||
@ -135,35 +133,35 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-mux v0.1.0 h1:xihlDRNs1J+hYwmvW9/ZmaghjDx7O0Y5dty0pOLQGB4=
|
||||
github.com/sagernet/sing-mux v0.1.0/go.mod h1:i3jKjV4pRTFTV/ly5V3oa2JMPy0SAZ5X8X4tDU9Hw94=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2 h1:wkPf4gF+cmaP0cIbArpyq+mc6GcwbMx60CssmmhEQ0s=
|
||||
github.com/sagernet/sing-shadowtls v0.1.2/go.mod h1:rTxhbSY8jGWZOWjdeOe1vP3E+hkgen8aRA2p7YccM88=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
|
||||
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/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-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
||||
github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE=
|
||||
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@ -174,22 +172,23 @@ github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6y
|
||||
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.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
@ -197,6 +196,8 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3 h1:a3Y4WVjCxwoyO4E2xdNvq577tW8lkSBgyrA8E9+2NtM=
|
||||
github.com/xtls/go v0.0.0-20230107031059-4610f88d00f3/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
@ -206,28 +207,28 @@ gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiV
|
||||
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.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -242,34 +243,35 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-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=
|
||||
|
@ -2,22 +2,16 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/ca"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
G "github.com/Dreamacro/clash/component/geodata"
|
||||
"github.com/Dreamacro/clash/component/iface"
|
||||
@ -25,6 +19,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
SNI "github.com/Dreamacro/clash/component/sniffer"
|
||||
CTLS "github.com/Dreamacro/clash/component/tls"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -84,9 +79,9 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
|
||||
tunnel.OnSuspend()
|
||||
|
||||
ca.ResetCertificate()
|
||||
CTLS.ResetCertificate()
|
||||
for _, c := range cfg.TLS.CustomTrustCert {
|
||||
if err := ca.AddCertificate(c); err != nil {
|
||||
if err := CTLS.AddCertificate(c); err != nil {
|
||||
log.Warnln("%s\nadd error: %s", c, err.Error())
|
||||
}
|
||||
}
|
||||
@ -97,7 +92,6 @@ 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)
|
||||
@ -143,7 +137,6 @@ func GetGeneral() *config.General {
|
||||
AllowLan: listener.AllowLan(),
|
||||
BindAddress: listener.BindAddress(),
|
||||
},
|
||||
Controller: config.Controller{},
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
IPv6: !resolver.DisableIPv6,
|
||||
@ -183,20 +176,6 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Config) {
|
||||
if c.Experimental.QUICGoDisableGSO {
|
||||
_ = os.Setenv("QUIC_GO_DISABLE_GSO", "1")
|
||||
}
|
||||
}
|
||||
|
||||
func updateNTP(c *config.NTP) {
|
||||
if c.Enable {
|
||||
ntp.ReCreateNTPService(
|
||||
net.JoinHostPort(c.Server, strconv.Itoa(c.Port)),
|
||||
time.Duration(c.Interval),
|
||||
c.DialerProxy,
|
||||
c.WriteToSystem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
||||
@ -381,7 +360,6 @@ func updateGeneral(general *config.General) {
|
||||
}
|
||||
|
||||
inbound.SetTfo(general.InboundTfo)
|
||||
inbound.SetMPTCP(general.InboundMPTCP)
|
||||
|
||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||
|
||||
@ -500,7 +478,7 @@ func updateIPTables(cfg *config.Config) {
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
listener.Cleanup()
|
||||
listener.Cleanup(false)
|
||||
tproxy.CleanupTProxyIPTables()
|
||||
resolver.StoreFakePoolState()
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"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"
|
||||
@ -58,11 +57,6 @@ 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)
|
||||
@ -83,6 +77,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url, expectedStatus)
|
||||
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
|
@ -4,35 +4,22 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func proxyProviderRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getProviders)
|
||||
|
||||
r.Route("/{providerName}", func(r chi.Router) {
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProviderName, findProviderByName)
|
||||
r.Get("/", getProvider)
|
||||
r.Put("/", updateProvider)
|
||||
r.Get("/healthcheck", healthCheckProvider)
|
||||
r.Mount("/", proxyProviderProxyRouter())
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func proxyProviderProxyRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProxyName, findProviderProxyByName)
|
||||
r.Get("/", getProxy)
|
||||
r.Get("/healthcheck", getProxyDelay)
|
||||
})
|
||||
return r
|
||||
}
|
||||
@ -67,7 +54,7 @@ func healthCheckProvider(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func parseProviderName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := getEscapeParam(r, "providerName")
|
||||
name := getEscapeParam(r, "name")
|
||||
ctx := context.WithValue(r.Context(), CtxKeyProviderName, name)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
@ -89,27 +76,6 @@ func findProviderByName(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func findProviderProxyByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
name = r.Context().Value(CtxKeyProxyName).(string)
|
||||
pd = r.Context().Value(CtxKeyProvider).(provider.ProxyProvider)
|
||||
)
|
||||
proxy, exist := lo.Find(pd.Proxies(), func(proxy C.Proxy) bool {
|
||||
return proxy.Name() == name
|
||||
})
|
||||
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func ruleProviderRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getRuleProviders)
|
||||
|
@ -46,7 +46,7 @@ func parseProxyName(next http.Handler) http.Handler {
|
||||
func findProxyByName(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||
proxies := tunnel.ProxiesWithProviders()
|
||||
proxies := tunnel.Proxies()
|
||||
proxy, exist := proxies[name]
|
||||
if !exist {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
@ -60,7 +60,7 @@ func findProxyByName(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func getProxies(w http.ResponseWriter, r *http.Request) {
|
||||
proxies := tunnel.ProxiesWithProviders()
|
||||
proxies := tunnel.Proxies()
|
||||
render.JSON(w, r, render.M{
|
||||
"proxies": proxies,
|
||||
})
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@ -39,12 +39,12 @@ func restart(w http.ResponseWriter, r *http.Request) {
|
||||
// The background context is used because the underlying functions wrap it
|
||||
// with timeout and shut down the server, which handles current request. It
|
||||
// also should be done in a separate goroutine for the same reason.
|
||||
go restartExecutable(execPath)
|
||||
go runRestart(execPath)
|
||||
}
|
||||
|
||||
func restartExecutable(execPath string) {
|
||||
func runRestart(execPath string) {
|
||||
var err error
|
||||
executor.Shutdown()
|
||||
listener.Cleanup(false)
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command(execPath, os.Args[1:]...)
|
||||
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
|
||||
|
@ -1,12 +1,10 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/hub/updater"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
@ -16,12 +14,11 @@ import (
|
||||
|
||||
func upgradeRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/", upgradeCore)
|
||||
r.Post("/ui", updateUI)
|
||||
r.Post("/", upgrade)
|
||||
return r
|
||||
}
|
||||
|
||||
func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
||||
func upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/home/controlupdate.go#L108
|
||||
log.Infoln("start update")
|
||||
execPath, err := os.Executable()
|
||||
@ -44,26 +41,5 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
go restartExecutable(execPath)
|
||||
}
|
||||
|
||||
func updateUI(w http.ResponseWriter, r *http.Request) {
|
||||
err := config.UpdateUI()
|
||||
if err != nil {
|
||||
if errors.Is(err, config.ErrIncompleteConf) {
|
||||
log.Warnln("%s", err)
|
||||
render.Status(r, http.StatusNotImplemented)
|
||||
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
|
||||
} else {
|
||||
log.Warnln("%s", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, r, render.M{"status": "ok"})
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
go runRestart(execPath)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ var (
|
||||
workDir string
|
||||
|
||||
// mu protects all fields below.
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
|
||||
currentExeName string // 当前可执行文件
|
||||
updateDir string // 更新目录
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
@ -56,7 +55,7 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||
return
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(conn)
|
||||
_ = conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...)
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
package config
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type Hysteria2Server struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Obfs string `yaml:"obfs" json:"obfs,omitempty"`
|
||||
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
Up string `yaml:"up" json:"up,omitempty"`
|
||||
Down string `yaml:"down" json:"down,omitempty"`
|
||||
IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"`
|
||||
Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"`
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (h Hysteria2Server) String() string {
|
||||
b, _ := json.Marshal(h)
|
||||
return string(b)
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/listener/sing_hysteria2"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
type Hysteria2Option struct {
|
||||
BaseOption
|
||||
Users map[string]string `inbound:"users,omitempty"`
|
||||
Obfs string `inbound:"obfs,omitempty"`
|
||||
ObfsPassword string `inbound:"obfs-password,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||
ALPN []string `inbound:"alpn,omitempty"`
|
||||
Up string `inbound:"up,omitempty"`
|
||||
Down string `inbound:"down,omitempty"`
|
||||
IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"`
|
||||
Masquerade string `inbound:"masquerade,omitempty"`
|
||||
CWND int `inbound:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
func (o Hysteria2Option) Equal(config C.InboundConfig) bool {
|
||||
return optionToString(o) == optionToString(config)
|
||||
}
|
||||
|
||||
type Hysteria2 struct {
|
||||
*Base
|
||||
config *Hysteria2Option
|
||||
l *sing_hysteria2.Listener
|
||||
ts LC.Hysteria2Server
|
||||
}
|
||||
|
||||
func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
|
||||
base, err := NewBase(&options.BaseOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Hysteria2{
|
||||
Base: base,
|
||||
config: options,
|
||||
ts: LC.Hysteria2Server{
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Users: options.Users,
|
||||
Obfs: options.Obfs,
|
||||
ObfsPassword: options.ObfsPassword,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
MaxIdleTime: options.MaxIdleTime,
|
||||
ALPN: options.ALPN,
|
||||
Up: options.Up,
|
||||
Down: options.Down,
|
||||
IgnoreClientBandwidth: options.IgnoreClientBandwidth,
|
||||
Masquerade: options.Masquerade,
|
||||
CWND: options.CWND,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Config implements constant.InboundListener
|
||||
func (t *Hysteria2) Config() C.InboundConfig {
|
||||
return t.config
|
||||
}
|
||||
|
||||
// Address implements constant.InboundListener
|
||||
func (t *Hysteria2) Address() string {
|
||||
if t.l != nil {
|
||||
for _, addr := range t.l.AddrList() {
|
||||
return addr.String()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Listen implements constant.InboundListener
|
||||
func (t *Hysteria2) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error {
|
||||
var err error
|
||||
t.l, err = sing_hysteria2.New(t.ts, tcpIn, udpIn, t.Additions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("Hysteria2[%s] proxy listening at: %s", t.Name(), t.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements constant.InboundListener
|
||||
func (t *Hysteria2) Close() error {
|
||||
return t.l.Close()
|
||||
}
|
||||
|
||||
var _ C.InboundListener = (*Hysteria2)(nil)
|
@ -84,7 +84,9 @@ type Ports struct {
|
||||
|
||||
func GetTunConf() LC.Tun {
|
||||
if tunLister == nil {
|
||||
return LastTunConf
|
||||
return LC.Tun{
|
||||
Enable: false,
|
||||
}
|
||||
}
|
||||
return tunLister.Config()
|
||||
}
|
||||
@ -514,7 +516,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||
tunConf.Enable = false
|
||||
Cleanup(false)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -525,7 +527,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack
|
||||
return
|
||||
}
|
||||
|
||||
closeTunListener()
|
||||
Cleanup(true)
|
||||
|
||||
if !tunConf.Enable {
|
||||
return
|
||||
@ -895,13 +897,10 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func closeTunListener() {
|
||||
func Cleanup(wait bool) {
|
||||
if tunLister != nil {
|
||||
tunLister.Close()
|
||||
tunLister = nil
|
||||
}
|
||||
}
|
||||
|
||||
func Cleanup() {
|
||||
closeTunListener()
|
||||
LastTunConf = LC.Tun{}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package mixed
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -70,7 +70,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
|
||||
N.TCPKeepAlive(conn)
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
|
@ -86,13 +86,6 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
|
||||
return nil, err
|
||||
}
|
||||
listener, err = IN.NewVmess(vmessOption)
|
||||
case "hysteria2":
|
||||
hysteria2Option := &IN.Hysteria2Option{}
|
||||
err = decoder.Decode(mapping, hysteria2Option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listener, err = IN.NewHysteria2(hysteria2Option)
|
||||
case "tuic":
|
||||
tuicOption := &IN.TuicOption{
|
||||
MaxIdleTime: 15000,
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -67,6 +66,6 @@ func handleRedir(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Ad
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
in <- inbound.NewSocket(target, conn, C.REDIR, additions...)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
}
|
||||
continue
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
_ = c.(*net.TCPConn).SetKeepAlive(true)
|
||||
go sl.HandleConn(c, tcpIn)
|
||||
}
|
||||
}()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user