Compare commits
72 Commits
dev-refact
...
v1.12.0
Author | SHA1 | Date | |
---|---|---|---|
10d2d14938 | |||
8ce9737f3d | |||
6b44178108 | |||
6664547f43 | |||
10383e2701 | |||
f4b9f2965f | |||
2ba933d16a | |||
669961e496 | |||
f979491013 | |||
0d55b28805 | |||
9c70e649ca | |||
8c079bf5bc | |||
2cdf4a0532 | |||
4ba34ce672 | |||
637f1b5aed | |||
56a87125e0 | |||
6fedc8d942 | |||
dbb834d964 | |||
449946cc15 | |||
c3671a154d | |||
6874fb785b | |||
5141ddc96e | |||
6a03371731 | |||
b658bb415b | |||
85405a54c7 | |||
30a0834e72 | |||
109a76e1fc | |||
c1a99b9be4 | |||
bf55428954 | |||
5e55d6b08f | |||
21098d2627 | |||
8da67ba61c | |||
54a0947bb4 | |||
a562b249a2 | |||
5af17f70b4 | |||
ca5bb91977 | |||
bbac54433e | |||
b6a5ec6490 | |||
aaf700f0b5 | |||
2ce89aca1e | |||
efdf69022a | |||
d4d1d4cc2a | |||
a8c4900891 | |||
930a7af8e7 | |||
77acd4ba8d | |||
691cf1d8d6 | |||
d1decb8e58 | |||
625c4a1079 | |||
341ef19099 | |||
2563b20019 | |||
1b3b5b4dfe | |||
2e6bdc5636 | |||
be298cfa16 | |||
277e71b26a | |||
f7c903a586 | |||
ff4a5bef9b | |||
d8dc44e786 | |||
c968104a19 | |||
f7481ecadf | |||
85c37b473a | |||
23bc231df3 | |||
2146b605f7 | |||
8853e97b40 | |||
7d04904109 | |||
a5acd3aa97 | |||
099aa1e3c2 | |||
63fdb348db | |||
81ee44f6c0 | |||
130a3a261d | |||
94368f43eb | |||
eea9a12560 | |||
0a4570b55c |
@ -251,8 +251,8 @@ User=clash-meta
|
||||
Group=clash-meta
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=cap_net_admin
|
||||
AmbientCapabilities=cap_net_admin
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 1s
|
||||
ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
|
||||
@ -274,7 +274,7 @@ $ systemctl start Clash-Meta
|
||||
|
||||
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 [Dashboard For Meta](https://github.com/Clash-Mini/Dashboard).
|
||||
To display process name in GUI please use [Dashboard For Meta](https://github.com/MetaCubeX/clash-dashboard).
|
||||
|
||||

|
||||
|
||||
|
@ -151,25 +151,32 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: transport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = resp.Body.Close()
|
||||
|
||||
if unifiedDelay {
|
||||
start = time.Now()
|
||||
second := time.Now()
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
if err == nil {
|
||||
_ = resp.Body.Close()
|
||||
start = second
|
||||
}
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
|
||||
t = uint16(time.Since(start) / time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -43,24 +41,32 @@ var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
|
||||
type Hysteria struct {
|
||||
*Base
|
||||
|
||||
client *core.Client
|
||||
clientTransport *transport.ClientTransport
|
||||
client *core.Client
|
||||
}
|
||||
|
||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), hyDialer(func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||
}))
|
||||
hdc := hyDialerWithContext{
|
||||
ctx: ctx,
|
||||
hyDialer: func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||
},
|
||||
}
|
||||
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(tcpConn, h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
udpConn, err := h.client.DialUDP(hyDialer(func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||
}))
|
||||
hdc := hyDialerWithContext{
|
||||
ctx: ctx,
|
||||
hyDialer: func() (net.PacketConn, error) {
|
||||
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...)
|
||||
},
|
||||
}
|
||||
udpConn, err := h.client.DialUDP(&hdc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -73,11 +79,8 @@ type HysteriaOption struct {
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Protocol string `proxy:"protocol,omitempty"`
|
||||
Up string `proxy:"up,omitempty"`
|
||||
UpMbps int `proxy:"up_mbps,omitempty"`
|
||||
Down string `proxy:"down,omitempty"`
|
||||
DownMbps int `proxy:"down_mbps,omitempty"`
|
||||
Auth string `proxy:"auth,omitempty"`
|
||||
Up string `proxy:"up"`
|
||||
Down string `proxy:"down"`
|
||||
AuthString string `proxy:"auth_str,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
@ -92,22 +95,16 @@ type HysteriaOption struct {
|
||||
|
||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||
var up, down uint64
|
||||
if len(c.Up) > 0 {
|
||||
up = stringToBps(c.Up)
|
||||
if up == 0 {
|
||||
return 0, 0, errors.New("invalid speed format")
|
||||
}
|
||||
} else {
|
||||
up = uint64(c.UpMbps) * mbpsToBps
|
||||
up = stringToBps(c.Up)
|
||||
if up == 0 {
|
||||
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
|
||||
}
|
||||
if len(c.Down) > 0 {
|
||||
down = stringToBps(c.Down)
|
||||
if down == 0 {
|
||||
return 0, 0, errors.New("invalid speed format")
|
||||
}
|
||||
} else {
|
||||
down = uint64(c.DownMbps) * mbpsToBps
|
||||
|
||||
down = stringToBps(c.Down)
|
||||
if down == 0 {
|
||||
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
|
||||
}
|
||||
|
||||
return up, down, nil
|
||||
}
|
||||
|
||||
@ -121,7 +118,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
serverName := option.Server
|
||||
if option.SNI != "" {
|
||||
serverName = option.Server
|
||||
serverName = option.SNI
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: serverName,
|
||||
@ -173,21 +170,18 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
|
||||
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
|
||||
}
|
||||
var auth []byte
|
||||
if option.Auth != "" {
|
||||
authBytes, err := base64.StdEncoding.DecodeString(option.Auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hysteria %s parse auth error: %w", addr, err)
|
||||
}
|
||||
auth = authBytes
|
||||
} else {
|
||||
auth = []byte(option.AuthString)
|
||||
}
|
||||
|
||||
var auth = []byte(option.AuthString)
|
||||
var obfuscator obfs.Obfuscator
|
||||
if len(option.Obfs) > 0 {
|
||||
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
|
||||
}
|
||||
up, down, _ := option.Speed()
|
||||
|
||||
up, down, err := option.Speed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := core.NewClient(
|
||||
addr, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
@ -205,8 +199,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
},
|
||||
client: client,
|
||||
clientTransport: clientTransport,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -214,6 +207,12 @@ 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
|
||||
@ -263,8 +262,15 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
type hyDialer func() (net.PacketConn, error)
|
||||
|
||||
func (h hyDialer) ListenPacket() (net.PacketConn, error) {
|
||||
return h()
|
||||
type hyDialerWithContext struct {
|
||||
hyDialer func() (net.PacketConn, error)
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, error) {
|
||||
return h.hyDialer()
|
||||
}
|
||||
|
||||
func (h *hyDialerWithContext) Context() context.Context {
|
||||
return h.ctx
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -29,6 +30,7 @@ type ShadowSocks struct {
|
||||
*Base
|
||||
method shadowsocks.Method
|
||||
|
||||
option *ShadowSocksOption
|
||||
// obfs
|
||||
obfsMode string
|
||||
obfsOption *simpleObfsOption
|
||||
@ -45,6 +47,7 @@ type ShadowSocksOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Plugin string `proxy:"plugin,omitempty"`
|
||||
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
|
||||
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
|
||||
}
|
||||
|
||||
type simpleObfsOption struct {
|
||||
@ -77,6 +80,10 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
}
|
||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
||||
metadata.Host = uot.UOTMagicAddress
|
||||
metadata.DstPort = "443"
|
||||
}
|
||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
|
||||
@ -96,6 +103,13 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, op
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
tcpConn, err := ss.DialContext(ctx, metadata, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(uot.NewClientConn(tcpConn), ss), nil
|
||||
}
|
||||
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -110,6 +124,19 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
||||
return newPacketConn(pc, ss), nil
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
return newPacketConn(uot.NewClientConn(c), ss), nil
|
||||
}
|
||||
return nil, errors.New("no support")
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) SupportUOT() bool {
|
||||
return ss.option.UDPOverTCP
|
||||
}
|
||||
|
||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
|
||||
@ -167,6 +194,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
},
|
||||
method: method,
|
||||
|
||||
option: &option,
|
||||
obfsMode: obfsMode,
|
||||
v2rayOption: v2rayOption,
|
||||
obfsOption: obfsOption,
|
||||
|
@ -70,30 +70,32 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
Path: v.option.WSOpts.Path,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
if len(v.option.WSOpts.Headers) != 0 {
|
||||
header := http.Header{}
|
||||
for key, value := range v.option.WSOpts.Headers {
|
||||
header.Add(key, value)
|
||||
wsOpts.Headers.Add(key, value)
|
||||
}
|
||||
wsOpts.Headers = header
|
||||
}
|
||||
|
||||
wsOpts.TLS = true
|
||||
wsOpts.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
if v.option.ServerName != "" {
|
||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||
wsOpts.TLSConfig.ServerName = host
|
||||
if v.option.TLS {
|
||||
wsOpts.TLS = true
|
||||
wsOpts.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
if v.option.ServerName != "" {
|
||||
wsOpts.TLSConfig.ServerName = v.option.ServerName
|
||||
} else if host := wsOpts.Headers.Get("Host"); host != "" {
|
||||
wsOpts.TLSConfig.ServerName = host
|
||||
}
|
||||
} else {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
if host := wsOpts.Headers.Get("Host"); host == "" {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
}
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
|
@ -5,17 +5,21 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/common/convert"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
"github.com/Dreamacro/clash/transport/vmess"
|
||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing-vmess/packetaddr"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type Vmess struct {
|
||||
@ -31,25 +35,23 @@ type Vmess struct {
|
||||
|
||||
type VmessOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
AlterID int `proxy:"alterId"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
|
||||
// TODO: compatible with VMESS WS older version configurations
|
||||
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
|
||||
WSPath string `proxy:"ws-path,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
UUID string `proxy:"uuid"`
|
||||
AlterID int `proxy:"alterId"`
|
||||
Cipher string `proxy:"cipher"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPOptions struct {
|
||||
@ -81,13 +83,13 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
case "ws":
|
||||
|
||||
host, port, _ := net.SplitHostPort(v.addr)
|
||||
wsOpts := &vmess.WebsocketConfig{
|
||||
wsOpts := &clashVMess.WebsocketConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: v.option.WSOpts.Path,
|
||||
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
|
||||
Headers: make(http.Header),
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
if len(v.option.WSOpts.Headers) != 0 {
|
||||
@ -109,15 +111,17 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
wsOpts.TLSConfig.ServerName = host
|
||||
}
|
||||
} else {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
if host := wsOpts.Headers.Get("Host"); host == "" {
|
||||
wsOpts.Headers.Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(wsOpts.Headers)
|
||||
}
|
||||
}
|
||||
c, err = vmess.StreamWebsocketConn(c, wsOpts)
|
||||
c, err = clashVMess.StreamWebsocketConn(c, wsOpts)
|
||||
case "http":
|
||||
// readability first, so just copy default TLS logic
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
tlsOpts := &clashVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
}
|
||||
@ -126,27 +130,24 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
http.Header(v.option.HTTPOpts.Headers).Set("Host", convert.RandHost())
|
||||
convert.SetUserAgent(v.option.HTTPOpts.Headers)
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
httpOpts := &vmess.HTTPConfig{
|
||||
httpOpts := &clashVMess.HTTPConfig{
|
||||
Host: host,
|
||||
Method: v.option.HTTPOpts.Method,
|
||||
Path: v.option.HTTPOpts.Path,
|
||||
Headers: v.option.HTTPOpts.Headers,
|
||||
}
|
||||
|
||||
c = vmess.StreamHTTPConn(c, httpOpts)
|
||||
c = clashVMess.StreamHTTPConn(c, httpOpts)
|
||||
case "h2":
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := vmess.TLSConfig{
|
||||
tlsOpts := clashVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"h2"},
|
||||
@ -156,24 +157,24 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, &tlsOpts)
|
||||
c, err = clashVMess.StreamTLSConn(c, &tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h2Opts := &vmess.H2Config{
|
||||
h2Opts := &clashVMess.H2Config{
|
||||
Hosts: v.option.HTTP2Opts.Host,
|
||||
Path: v.option.HTTP2Opts.Path,
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := &vmess.TLSConfig{
|
||||
tlsOpts := &clashVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
}
|
||||
@ -182,15 +183,18 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = vmess.StreamTLSConn(c, tlsOpts)
|
||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
if metadata.NetWork == C.UDP {
|
||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
}
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -203,7 +207,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
c, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -233,6 +237,11 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
if v.option.PacketAddr {
|
||||
metadata.Host = packetaddr.SeqPacketMagicAddress
|
||||
metadata.DstPort = "443"
|
||||
}
|
||||
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
@ -242,7 +251,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
}
|
||||
defer safeConnClose(c, err)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
|
||||
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||
} else {
|
||||
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
@ -258,11 +267,21 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
||||
return v.ListenPacketOnStreamConn(c, metadata)
|
||||
if v.option.PacketAddr {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil
|
||||
} else if pc, ok := c.(net.PacketConn); ok {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
|
||||
}
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if v.option.PacketAddr {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindClient(c)}, v), nil
|
||||
} else if pc, ok := c.(net.PacketConn); ok {
|
||||
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
|
||||
}
|
||||
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
|
||||
}
|
||||
|
||||
@ -273,14 +292,11 @@ func (v *Vmess) SupportUOT() bool {
|
||||
|
||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
security := strings.ToLower(option.Cipher)
|
||||
client, err := vmess.NewClient(vmess.Config{
|
||||
UUID: option.UUID,
|
||||
AlterID: uint16(option.AlterID),
|
||||
Security: security,
|
||||
HostName: option.Server,
|
||||
Port: strconv.Itoa(option.Port),
|
||||
IsAead: option.AlterID == 0,
|
||||
})
|
||||
var options []vmess.ClientOption
|
||||
if option.AuthenticatedLength {
|
||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
||||
}
|
||||
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -339,44 +355,29 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
v.gunConfig = gunConfig
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
|
||||
var addrType byte
|
||||
var addr []byte
|
||||
switch metadata.AddrType {
|
||||
case C.AtypIPv4:
|
||||
addrType = byte(vmess.AtypIPv4)
|
||||
addr = make([]byte, net.IPv4len)
|
||||
copy(addr[:], metadata.DstIP.AsSlice())
|
||||
case C.AtypIPv6:
|
||||
addrType = byte(vmess.AtypIPv6)
|
||||
addr = make([]byte, net.IPv6len)
|
||||
copy(addr[:], metadata.DstIP.AsSlice())
|
||||
case C.AtypDomainName:
|
||||
addrType = byte(vmess.AtypDomainName)
|
||||
addr = make([]byte, len(metadata.Host)+1)
|
||||
addr[0] = byte(len(metadata.Host))
|
||||
copy(addr[1:], []byte(metadata.Host))
|
||||
}
|
||||
type threadSafePacketConn struct {
|
||||
net.PacketConn
|
||||
access sync.Mutex
|
||||
}
|
||||
|
||||
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||
return &vmess.DstAddr{
|
||||
UDP: metadata.NetWork == C.UDP,
|
||||
AddrType: addrType,
|
||||
Addr: addr,
|
||||
Port: uint(port),
|
||||
}
|
||||
func (c *threadSafePacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
return c.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
type vmessPacketConn struct {
|
||||
net.Conn
|
||||
rAddr net.Addr
|
||||
rAddr net.Addr
|
||||
access sync.Mutex
|
||||
}
|
||||
|
||||
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
uc.access.Lock()
|
||||
defer uc.access.Unlock()
|
||||
return uc.Conn.Write(b)
|
||||
}
|
||||
|
||||
|
@ -111,11 +111,11 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
delay, err := proxy.URLTest(ctx, url)
|
||||
lock.Lock()
|
||||
if err == nil {
|
||||
lock.Lock()
|
||||
mp[proxy.Name()] = delay
|
||||
lock.Unlock()
|
||||
}
|
||||
lock.Unlock()
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
|
@ -40,9 +40,10 @@ func (f *fetcher[V]) VehicleType() types.VehicleType {
|
||||
|
||||
func (f *fetcher[V]) Initial() (V, error) {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
isLocal bool
|
||||
buf []byte
|
||||
err error
|
||||
isLocal bool
|
||||
forceUpdate bool
|
||||
)
|
||||
|
||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||
@ -51,10 +52,8 @@ func (f *fetcher[V]) Initial() (V, error) {
|
||||
f.updatedAt = &modTime
|
||||
isLocal = true
|
||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||
defer func() {
|
||||
log.Infoln("[Provider] %s's proxies not updated for a long time, force refresh", f.Name())
|
||||
go f.Update()
|
||||
}()
|
||||
log.Infoln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
||||
forceUpdate = true
|
||||
}
|
||||
} else {
|
||||
buf, err = f.vehicle.Read()
|
||||
@ -64,7 +63,21 @@ func (f *fetcher[V]) Initial() (V, error) {
|
||||
return getZero[V](), err
|
||||
}
|
||||
|
||||
proxies, err := f.parser(buf)
|
||||
var proxies V
|
||||
if forceUpdate {
|
||||
var forceBuf []byte
|
||||
if forceBuf, err = f.vehicle.Read(); err == nil {
|
||||
if proxies, err = f.parser(forceBuf); err == nil {
|
||||
isLocal = false
|
||||
buf = forceBuf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil || !forceUpdate {
|
||||
proxies, err = f.parser(buf)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return getZero[V](), err
|
||||
@ -189,6 +202,7 @@ func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
|
||||
parser: parser,
|
||||
done: make(chan struct{}, 1),
|
||||
onUpdate: onUpdate,
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/common/singledo"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/batch"
|
||||
@ -26,6 +27,7 @@ type HealthCheck struct {
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
@ -63,17 +65,21 @@ func (hc *HealthCheck) touch() {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
_, _ = p.URLTest(ctx, hc.url)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
b.Wait()
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
for _, proxy := range hc.proxies {
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
defer cancel()
|
||||
_, _ = p.URLTest(ctx, hc.url)
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
b.Wait()
|
||||
return struct{}{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
||||
@ -88,5 +94,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
}
|
||||
}
|
||||
|
@ -10,34 +10,24 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var encRaw = base64.RawStdEncoding
|
||||
var enc = base64.StdEncoding
|
||||
|
||||
func DecodeBase64(buf []byte) ([]byte, error) {
|
||||
dBuf := make([]byte, enc.DecodedLen(len(buf)))
|
||||
n, err := enc.Decode(dBuf, buf)
|
||||
func DecodeBase64(buf []byte) []byte {
|
||||
dBuf := make([]byte, encRaw.DecodedLen(len(buf)))
|
||||
n, err := encRaw.Decode(dBuf, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
n, err = enc.Decode(dBuf, buf)
|
||||
if err != nil {
|
||||
return buf
|
||||
}
|
||||
}
|
||||
|
||||
return dBuf[:n], nil
|
||||
}
|
||||
|
||||
// DecodeBase64StringToString decode base64 string to string
|
||||
func DecodeBase64StringToString(s string) (string, error) {
|
||||
dBuf, err := enc.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(dBuf), nil
|
||||
return dBuf[:n]
|
||||
}
|
||||
|
||||
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
|
||||
func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
data, err := DecodeBase64(buf)
|
||||
if err != nil {
|
||||
data = buf
|
||||
}
|
||||
data := DecodeBase64(buf)
|
||||
|
||||
arr := strings.Split(string(data), "\n")
|
||||
|
||||
@ -76,8 +66,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["alpn"] = query.Get("alpn")
|
||||
hysteria["auth_str"] = query.Get("auth")
|
||||
hysteria["protocol"] = query.Get("protocol")
|
||||
hysteria["down_mbps"], _ = strconv.Atoi(query.Get("downmbps"))
|
||||
hysteria["up_mbps"], _ = strconv.Atoi(query.Get("upmbps"))
|
||||
up := query.Get("up")
|
||||
down := query.Get("down")
|
||||
if up == "" {
|
||||
up = query.Get("upmbps")
|
||||
}
|
||||
if down == "" {
|
||||
down = query.Get("downmbps")
|
||||
}
|
||||
hysteria["down"] = down
|
||||
hysteria["up"] = up
|
||||
hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure"))
|
||||
|
||||
proxies = append(proxies, hysteria)
|
||||
@ -116,7 +114,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
//headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
wsOpts["path"] = query.Get("path")
|
||||
@ -150,7 +147,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
vless["uuid"] = urlVless.User.Username()
|
||||
vless["udp"] = true
|
||||
vless["skip-cert-verify"] = false
|
||||
|
||||
vless["tls"] = false
|
||||
tls := strings.ToLower(query.Get("security"))
|
||||
if strings.Contains(tls, "tls") {
|
||||
vless["tls"] = true
|
||||
}
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
vless["servername"] = sni
|
||||
@ -162,49 +163,53 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
fakeType := strings.ToLower(query.Get("headerType"))
|
||||
if network == "tcp" && fakeType == "http" {
|
||||
network = "http"
|
||||
}
|
||||
if network == "http" {
|
||||
network = "h2"
|
||||
}
|
||||
vless["network"] = network
|
||||
fakeType := strings.ToLower(query.Get("headerType"))
|
||||
if fakeType == "http" {
|
||||
network = "http"
|
||||
} else if network == "http" {
|
||||
network = "h2"
|
||||
}
|
||||
|
||||
vless["network"] = network
|
||||
switch network {
|
||||
case "tcp":
|
||||
if fakeType != "none" {
|
||||
headers := make(map[string]any)
|
||||
httpOpts := make(map[string]any)
|
||||
httpOpts["path"] = []string{"/"}
|
||||
|
||||
if query.Get("host") != "" {
|
||||
headers["Host"] = []string{query.Get("host")}
|
||||
}
|
||||
|
||||
if query.Get("method") != "" {
|
||||
httpOpts["method"] = query.Get("method")
|
||||
}
|
||||
|
||||
if query.Get("path") != "" {
|
||||
httpOpts["path"] = []string{query.Get("path")}
|
||||
}
|
||||
httpOpts["headers"] = headers
|
||||
vless["http-opts"] = httpOpts
|
||||
}
|
||||
|
||||
case "http":
|
||||
headers := make(map[string]any)
|
||||
httpOpts := make(map[string]any)
|
||||
|
||||
if query.Get("method") != "" {
|
||||
httpOpts["method"] = query.Get("method")
|
||||
}
|
||||
if query.Get("path") != "" {
|
||||
httpOpts["path"] = query.Get("path")
|
||||
}
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
httpOpts["headers"] = headers
|
||||
|
||||
vless["http-opts"] = httpOpts
|
||||
|
||||
case "h2":
|
||||
headers := make(map[string]any)
|
||||
h2Opts := make(map[string]any)
|
||||
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
h2Opts["path"] = query.Get("path")
|
||||
h2Opts["path"] = []string{"/"}
|
||||
if query.Get("path") != "" {
|
||||
h2Opts["path"] = []string{query.Get("path")}
|
||||
}
|
||||
if query.Get("host") != "" {
|
||||
h2Opts["host"] = []string{query.Get("host")}
|
||||
}
|
||||
h2Opts["headers"] = headers
|
||||
|
||||
vless["h2-opts"] = h2Opts
|
||||
|
||||
case "ws":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
//headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
headers["Host"] = query.Get("host")
|
||||
wsOpts["path"] = query.Get("path")
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
@ -219,7 +224,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
proxies = append(proxies, vless)
|
||||
|
||||
case "vmess":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -242,23 +247,28 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
vmess["alterId"] = values["aid"]
|
||||
vmess["cipher"] = "auto"
|
||||
vmess["udp"] = true
|
||||
vmess["tls"] = false
|
||||
vmess["skip-cert-verify"] = false
|
||||
|
||||
if values["cipher"] != nil && values["cipher"] != "" {
|
||||
vmess["cipher"] = values["cipher"]
|
||||
}
|
||||
|
||||
sni := values["sni"]
|
||||
if sni != "" {
|
||||
vmess["sni"] = sni
|
||||
vmess["servername"] = sni
|
||||
}
|
||||
|
||||
host := values["host"]
|
||||
network := strings.ToLower(values["net"].(string))
|
||||
|
||||
if values["type"] == "http" {
|
||||
network = "http"
|
||||
} else if network == "http" {
|
||||
network = "h2"
|
||||
}
|
||||
vmess["network"] = network
|
||||
|
||||
tls := strings.ToLower(values["tls"].(string))
|
||||
if tls != "" && tls != "0" && tls != "null" {
|
||||
if host != nil {
|
||||
vmess["servername"] = host
|
||||
}
|
||||
if strings.Contains(tls, "tls") {
|
||||
vmess["tls"] = true
|
||||
}
|
||||
|
||||
@ -266,11 +276,13 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
case "http":
|
||||
headers := make(map[string]any)
|
||||
httpOpts := make(map[string]any)
|
||||
|
||||
//headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
httpOpts["method"] = values["method"]
|
||||
httpOpts["path"] = values["path"]
|
||||
if values["host"] != "" && values["host"] != nil {
|
||||
headers["Host"] = []string{values["host"].(string)}
|
||||
}
|
||||
httpOpts["path"] = []string{"/"}
|
||||
if values["path"] != "" && values["path"] != nil {
|
||||
httpOpts["path"] = []string{values["path"].(string)}
|
||||
}
|
||||
httpOpts["headers"] = headers
|
||||
|
||||
vmess["http-opts"] = httpOpts
|
||||
@ -278,9 +290,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
case "h2":
|
||||
headers := make(map[string]any)
|
||||
h2Opts := make(map[string]any)
|
||||
if values["host"] != "" && values["host"] != nil {
|
||||
headers["Host"] = []string{values["host"].(string)}
|
||||
}
|
||||
|
||||
//headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
h2Opts["path"] = values["path"]
|
||||
h2Opts["headers"] = headers
|
||||
|
||||
@ -289,15 +302,14 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
case "ws":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
|
||||
headers["Host"] = RandHost()
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
|
||||
if values["path"] != nil {
|
||||
wsOpts["path"] = values["path"]
|
||||
wsOpts["path"] = []string{"/"}
|
||||
if values["host"] != "" && values["host"] != nil {
|
||||
headers["Host"] = values["host"].(string)
|
||||
}
|
||||
if values["path"] != "" && values["path"] != nil {
|
||||
wsOpts["path"] = values["path"].(string)
|
||||
}
|
||||
wsOpts["headers"] = headers
|
||||
|
||||
vmess["ws-opts"] = wsOpts
|
||||
|
||||
case "grpc":
|
||||
@ -318,7 +330,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
port := urlSS.Port()
|
||||
|
||||
if port == "" {
|
||||
dcBuf, err := enc.DecodeString(urlSS.Host)
|
||||
dcBuf, err := encRaw.DecodeString(urlSS.Host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -335,11 +347,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
)
|
||||
|
||||
if password, found = urlSS.User.Password(); !found {
|
||||
dcBuf, err := enc.DecodeString(cipher)
|
||||
if err != nil {
|
||||
continue
|
||||
dcBuf, _ := enc.DecodeString(cipher)
|
||||
if !strings.Contains(string(dcBuf), "2022-blake3") {
|
||||
dcBuf, _ = encRaw.DecodeString(cipher)
|
||||
}
|
||||
|
||||
cipher, password, found = strings.Cut(string(dcBuf), ":")
|
||||
if !found {
|
||||
continue
|
||||
@ -354,11 +365,19 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
ss["port"] = urlSS.Port()
|
||||
ss["cipher"] = cipher
|
||||
ss["password"] = password
|
||||
query := urlSS.Query()
|
||||
ss["udp"] = true
|
||||
|
||||
if strings.Contains(query.Get("plugin"), "obfs") {
|
||||
obfsParams := strings.Split(query.Get("plugin"), ";")
|
||||
ss["plugin"] = "obfs"
|
||||
ss["plugin-opts"] = map[string]any{
|
||||
"host": obfsParams[2][10:],
|
||||
"mode": obfsParams[1][5:],
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, ss)
|
||||
case "ssr":
|
||||
dcBuf, err := enc.DecodeString(body)
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -430,7 +449,7 @@ func urlSafe(data string) string {
|
||||
}
|
||||
|
||||
func decodeUrlSafe(data string) string {
|
||||
dcBuf, err := base64.URLEncoding.DecodeString(data)
|
||||
dcBuf, err := base64.RawURLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -57,6 +56,10 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
if DisableIPv6 {
|
||||
network = "udp4"
|
||||
}
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
|
||||
@ -167,25 +170,31 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
|
||||
go startRacer(ctx, network+"4", host, opt.direct, false)
|
||||
go startRacer(ctx, network+"6", host, opt.direct, true)
|
||||
|
||||
for res := range results {
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
return nil, primary.error
|
||||
count := 2
|
||||
for i := 0; i < count; i++ {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if !res.ipv6 {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
|
||||
if primary.done && fallback.done {
|
||||
if primary.resolved {
|
||||
return nil, primary.error
|
||||
} else if fallback.resolved {
|
||||
return nil, fallback.error
|
||||
} else {
|
||||
return nil, primary.error
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +230,6 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
|
||||
}
|
||||
|
||||
results := make(chan dialResult)
|
||||
|
||||
tcpRacer := func(ctx context.Context, ip netip.Addr) {
|
||||
result := dialResult{ip: ip}
|
||||
|
||||
@ -248,13 +256,13 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
|
||||
}
|
||||
|
||||
connCount := len(ips)
|
||||
for res := range results {
|
||||
connCount--
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
|
||||
if connCount == 0 {
|
||||
for i := 0; i < connCount; i++ {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
break
|
||||
}
|
||||
}
|
||||
|
52
component/geodata/init.go
Normal file
52
component/geodata/init.go
Normal file
@ -0,0 +1,52 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var initFlag bool
|
||||
|
||||
func InitGeoSite() error {
|
||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find GeoSite.dat, start download")
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
log.Infoln("Download GeoSite.dat finish")
|
||||
}
|
||||
if !initFlag {
|
||||
if err := Verify(C.GeositeName); err != nil {
|
||||
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
}
|
||||
initFlag = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
resp, err := http.Get(C.GeoSiteUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
|
||||
return err
|
||||
}
|
@ -173,7 +173,7 @@ func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
if ip.Is4() {
|
||||
if ip.Is4() || ip.Is4In6() {
|
||||
return []netip.Addr{ip}, nil
|
||||
}
|
||||
return []netip.Addr{}, ErrIPVersion
|
||||
|
@ -254,6 +254,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
rawCfg := &RawConfig{
|
||||
AllowLan: false,
|
||||
BindAddress: "*",
|
||||
IPv6: true,
|
||||
Mode: T.Rule,
|
||||
GeodataMode: C.GeodataMode,
|
||||
GeodataLoader: "memconservative",
|
||||
@ -265,7 +266,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
Proxy: []map[string]any{},
|
||||
ProxyGroup: []map[string]any{},
|
||||
TCPConcurrent: false,
|
||||
EnableProcess: true,
|
||||
EnableProcess: false,
|
||||
Tun: RawTun{
|
||||
Enable: false,
|
||||
Device: "",
|
||||
@ -281,6 +282,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
},
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
UseHosts: true,
|
||||
EnhancedMode: C.DNSMapping,
|
||||
FakeIPRange: "198.18.0.1/16",
|
||||
@ -533,7 +535,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin
|
||||
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
||||
// parse rule provider
|
||||
for name, mapping := range cfg.RuleProvider {
|
||||
rp, err := RP.ParseRuleProvider(name, mapping)
|
||||
rp, err := RP.ParseRuleProvider(name, mapping, R.ParseRule)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -666,7 +668,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||
addr = u.Host
|
||||
dnsNetType = "dhcp" // UDP from DHCP
|
||||
case "quic":
|
||||
addr, err = hostWithDefaultPort(u.Host, "784")
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
dnsNetType = "quic" // DNS over QUIC
|
||||
default:
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme)
|
||||
@ -723,7 +725,7 @@ func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) {
|
||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
||||
var sites []*router.DomainMatcher
|
||||
if len(countries) > 0 {
|
||||
if err := initGeoSite(); err != nil {
|
||||
if err := geodata.InitGeoSite(); err != nil {
|
||||
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var initFlag bool
|
||||
|
||||
func downloadMMDB(path string) (err error) {
|
||||
resp, err := http.Get(C.MmdbUrl)
|
||||
if err != nil {
|
||||
@ -48,46 +46,6 @@ func downloadGeoIP(path string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
resp, err := http.Get(C.GeoSiteUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func initGeoSite() error {
|
||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find GeoSite.dat, start download")
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
log.Infoln("Download GeoSite.dat finish")
|
||||
}
|
||||
if !initFlag {
|
||||
if err := geodata.Verify(C.GeositeName); err != nil {
|
||||
log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
|
||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
}
|
||||
initFlag = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initGeoIP() error {
|
||||
if C.GeodataMode {
|
||||
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
||||
|
23
dns/doq.go
23
dns/doq.go
@ -17,7 +17,7 @@ import (
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const NextProtoDQ = "doq-i00"
|
||||
const NextProtoDQ = "doq"
|
||||
|
||||
var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
|
||||
|
||||
@ -93,7 +93,7 @@ func isActive(s quic.Connection) bool {
|
||||
// getSession - opens or returns an existing quic.Connection
|
||||
// useCached - if true and cached session exists, return it right away
|
||||
// otherwise - forcibly creates a new session
|
||||
func (dc *quicClient) getSession() (quic.Connection, error) {
|
||||
func (dc *quicClient) getSession(ctx context.Context) (quic.Connection, error) {
|
||||
var session quic.Connection
|
||||
dc.RLock()
|
||||
session = dc.session
|
||||
@ -111,14 +111,14 @@ func (dc *quicClient) getSession() (quic.Connection, error) {
|
||||
defer dc.Unlock()
|
||||
|
||||
var err error
|
||||
session, err = dc.openSession()
|
||||
session, err = dc.openSession(ctx)
|
||||
if err != nil {
|
||||
// This does not look too nice, but QUIC (or maybe quic-go)
|
||||
// doesn't seem stable enough.
|
||||
// Maybe retransmissions aren't fully implemented in quic-go?
|
||||
// Anyways, the simple solution is to make a second try when
|
||||
// it fails to open the QUIC session.
|
||||
session, err = dc.openSession()
|
||||
session, err = dc.openSession(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -127,11 +127,11 @@ func (dc *quicClient) getSession() (quic.Connection, error) {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
func (dc *quicClient) openSession(ctx context.Context) (quic.Connection, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: []string{
|
||||
"http/1.1", "h2", NextProtoDQ,
|
||||
NextProtoDQ,
|
||||
},
|
||||
SessionTicketsDisabled: false,
|
||||
}
|
||||
@ -149,6 +149,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
)
|
||||
|
||||
host, port, err := net.SplitHostPort(dc.addr)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -162,12 +163,12 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
udpAddr := net.UDPAddr{IP: ip.AsSlice(), Port: p}
|
||||
|
||||
if dc.proxyAdapter == "" {
|
||||
udp, err = dialer.ListenPacket(context.Background(), "udp", "")
|
||||
udp, err = dialer.ListenPacket(ctx, "udp", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
conn, err := dialContextExtra(context.Background(), dc.proxyAdapter, "udp", ip, port)
|
||||
conn, err := dialContextExtra(ctx, dc.proxyAdapter, "udp", ip, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -180,7 +181,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
udp = wrapConn
|
||||
}
|
||||
|
||||
session, err := quic.Dial(udp, &udpAddr, host, tlsConfig, quicConfig)
|
||||
session, err := quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, quicConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open QUIC session: %w", err)
|
||||
}
|
||||
@ -189,7 +190,7 @@ func (dc *quicClient) openSession() (quic.Connection, error) {
|
||||
}
|
||||
|
||||
func (dc *quicClient) openStream(ctx context.Context) (quic.Stream, error) {
|
||||
session, err := dc.getSession()
|
||||
session, err := dc.getSession(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -46,11 +46,11 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
|
||||
rr.A = ip.AsSlice()
|
||||
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else if ip.Is6() && q.Qtype == D.TypeAAAA {
|
||||
} else if q.Qtype == D.TypeAAAA {
|
||||
rr := &D.AAAA{}
|
||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||
rr.AAAA = ip.AsSlice()
|
||||
|
||||
ip := ip.As16()
|
||||
rr.AAAA = ip[:]
|
||||
msg.Answer = []D.RR{rr}
|
||||
} else {
|
||||
return next(ctx, r)
|
||||
|
33
dns/util.go
33
dns/util.go
@ -152,17 +152,9 @@ func (wpc *wrapPacketConn) LocalAddr() net.Addr {
|
||||
}
|
||||
|
||||
func dialContextExtra(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
|
||||
adapter, ok := tunnel.Proxies()[adapterName]
|
||||
if !ok {
|
||||
opts = append(opts, dialer.WithInterface(adapterName))
|
||||
adapter, _ = tunnel.Proxies()[tunnel.Direct.String()]
|
||||
}
|
||||
|
||||
networkType := C.TCP
|
||||
if network == "udp" {
|
||||
if !adapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName)
|
||||
}
|
||||
|
||||
networkType = C.UDP
|
||||
}
|
||||
|
||||
@ -179,6 +171,29 @@ func dialContextExtra(ctx context.Context, adapterName string, network string, d
|
||||
DstPort: port,
|
||||
}
|
||||
|
||||
adapter, ok := tunnel.Proxies()[adapterName]
|
||||
if !ok {
|
||||
opts = append(opts, dialer.WithInterface(adapterName))
|
||||
if C.TCP == networkType {
|
||||
return dialer.DialContext(ctx, network, dstIP.String()+":"+port, opts...)
|
||||
} else {
|
||||
packetConn, err := dialer.ListenPacket(ctx, network, dstIP.String()+":"+port, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wrapPacketConn{
|
||||
PacketConn: packetConn,
|
||||
rAddr: metadata.UDPAddr(),
|
||||
}, nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if networkType == C.UDP && !adapter.SupportUDP() {
|
||||
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName)
|
||||
}
|
||||
|
||||
if networkType == C.UDP {
|
||||
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
if err != nil {
|
||||
|
9
go.mod
9
go.mod
@ -13,8 +13,9 @@ require (
|
||||
github.com/lucas-clemente/quic-go v0.27.2
|
||||
github.com/miekg/dns v1.1.49
|
||||
github.com/oschwald/geoip2-golang v1.7.0
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/tobyxdd/hysteria v1.0.4
|
||||
@ -27,7 +28,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220601130007-6a08d81f6bc4
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||
@ -38,7 +39,7 @@ require (
|
||||
|
||||
replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
|
||||
|
||||
replace github.com/tobyxdd/hysteria => github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15
|
||||
replace github.com/tobyxdd/hysteria => github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.27.1-0.20220512040129-ed2a645d9218
|
||||
|
||||
|
18
go.sum
18
go.sum
@ -40,8 +40,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15 h1:SraqLzYEGfrV8ETkVYc5evvCrn95hMFdCtcXXP8bA9Y=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15/go.mod h1:bXVjOca4Xf3JRenwuPKu02XaOiZwejrMSlgsu/U88J4=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256 h1:wm5RrQfwJS63pe5G15AKdXfrwlIYFciwCs3MrVxzxSU=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256/go.mod h1:bXVjOca4Xf3JRenwuPKu02XaOiZwejrMSlgsu/U88J4=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@ -305,10 +305,12 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7 h1:Q+uNKLNSKqpx+p96qcBTVFh8RUKiQFr4IrNVi5Q5yl0=
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7/go.mod h1:w2HnJzXKHpD6F5Z/9XlSD4qbcpHY2RSZuQnFzqgELMg=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13 h1:bQN0hjTHdB7SyaD9yjEYAl+bDl/kXW9zC0xNa+LMTrA=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13/go.mod h1:Fp/9+odJhtgDmiHbZClMLnxaVvmDRJxwA7u/+uXWDiQ=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c h1:Jhgjyb2jXL4GtwJec6/kgeTqaQXsvMiNX2wAkGOSD3I=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c/go.mod h1:ng5pxdNnKZWlxzZTXRqWeY0ftzhScPZmjgJGJeRuPYY=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec h1:jUSfKmyL6K9O2TvIvcVacZ4eNXHYbNSfdph+DRPyVlU=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec/go.mod h1:jDZ8fJgOea7Y7MMHWgfqwLBVLnhtW2zuxS5wjtDaB84=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@ -564,8 +566,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -2,7 +2,6 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/component/process"
|
||||
"github.com/Dreamacro/clash/listener/inner"
|
||||
"net/netip"
|
||||
"os"
|
||||
@ -89,6 +88,10 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
updateTun(cfg.Tun)
|
||||
updateExperimental(cfg)
|
||||
|
||||
// DON'T Delete
|
||||
// ClashX will use this line to determine if the 'Meta' has finished booting
|
||||
log.Infoln("Apply all configs finished.")
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
}
|
||||
|
||||
@ -127,7 +130,9 @@ func GetGeneral() *config.General {
|
||||
return general
|
||||
}
|
||||
|
||||
func updateExperimental(c *config.Config) {}
|
||||
func updateExperimental(c *config.Config) {
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
if !c.Enable {
|
||||
@ -274,8 +279,8 @@ func updateSniffer(sniffer *config.Sniffer) {
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
log.SetLevel(general.LogLevel)
|
||||
process.EnableFindProcess(general.EnableProcess)
|
||||
tunnel.SetMode(general.Mode)
|
||||
tunnel.SetAlwaysFindProcess(general.EnableProcess)
|
||||
dialer.DisableIPv6 = !general.IPv6
|
||||
if !dialer.DisableIPv6 {
|
||||
log.Infoln("Use IPv6")
|
||||
|
@ -46,6 +46,7 @@ type configSchema struct {
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Sniffing *bool `json:"sniffing"`
|
||||
TcpConcurrent *bool `json:"tcp-concurrent"`
|
||||
InterfaceName *string `json:"interface-name"`
|
||||
}
|
||||
|
||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
@ -85,6 +86,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
dialer.SetDial(*general.TcpConcurrent)
|
||||
}
|
||||
|
||||
if general.InterfaceName != nil {
|
||||
dialer.DefaultInterface.Store(*general.InterfaceName)
|
||||
}
|
||||
|
||||
ports := P.GetPorts()
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
|
@ -64,7 +64,7 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"golang.org/x/net/idna"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -8,8 +9,9 @@ import (
|
||||
|
||||
type Domain struct {
|
||||
*Base
|
||||
domain string
|
||||
adapter string
|
||||
domain string
|
||||
rawDomain string
|
||||
adapter string
|
||||
}
|
||||
|
||||
func (d *Domain) RuleType() C.RuleType {
|
||||
@ -28,14 +30,16 @@ func (d *Domain) Adapter() string {
|
||||
}
|
||||
|
||||
func (d *Domain) Payload() string {
|
||||
return d.domain
|
||||
return d.rawDomain
|
||||
}
|
||||
|
||||
func NewDomain(domain string, adapter string) *Domain {
|
||||
actualDomain, _ := idna.ToASCII(domain)
|
||||
return &Domain{
|
||||
Base: &Base{},
|
||||
domain: strings.ToLower(domain),
|
||||
adapter: adapter,
|
||||
Base: &Base{},
|
||||
domain: strings.ToLower(actualDomain),
|
||||
adapter: adapter,
|
||||
rawDomain: domain,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"golang.org/x/net/idna"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -8,8 +9,9 @@ import (
|
||||
|
||||
type DomainKeyword struct {
|
||||
*Base
|
||||
keyword string
|
||||
adapter string
|
||||
keyword string
|
||||
adapter string
|
||||
rawKeyword string
|
||||
}
|
||||
|
||||
func (dk *DomainKeyword) RuleType() C.RuleType {
|
||||
@ -29,14 +31,16 @@ func (dk *DomainKeyword) Adapter() string {
|
||||
}
|
||||
|
||||
func (dk *DomainKeyword) Payload() string {
|
||||
return dk.keyword
|
||||
return dk.rawKeyword
|
||||
}
|
||||
|
||||
func NewDomainKeyword(keyword string, adapter string) *DomainKeyword {
|
||||
actualDomainKeyword, _ := idna.ToASCII(keyword)
|
||||
return &DomainKeyword{
|
||||
Base: &Base{},
|
||||
keyword: strings.ToLower(keyword),
|
||||
adapter: adapter,
|
||||
Base: &Base{},
|
||||
keyword: strings.ToLower(actualDomainKeyword),
|
||||
adapter: adapter,
|
||||
rawKeyword: keyword,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"golang.org/x/net/idna"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -8,8 +9,9 @@ import (
|
||||
|
||||
type DomainSuffix struct {
|
||||
*Base
|
||||
suffix string
|
||||
adapter string
|
||||
suffix string
|
||||
adapter string
|
||||
rawSuffix string
|
||||
}
|
||||
|
||||
func (ds *DomainSuffix) RuleType() C.RuleType {
|
||||
@ -29,14 +31,16 @@ func (ds *DomainSuffix) Adapter() string {
|
||||
}
|
||||
|
||||
func (ds *DomainSuffix) Payload() string {
|
||||
return ds.suffix
|
||||
return ds.rawSuffix
|
||||
}
|
||||
|
||||
func NewDomainSuffix(suffix string, adapter string) *DomainSuffix {
|
||||
actualDomainKeyword, _ := idna.ToASCII(suffix)
|
||||
return &DomainSuffix{
|
||||
Base: &Base{},
|
||||
suffix: strings.ToLower(suffix),
|
||||
adapter: adapter,
|
||||
Base: &Base{},
|
||||
suffix: strings.ToLower(actualDomainKeyword),
|
||||
adapter: adapter,
|
||||
rawSuffix: suffix,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,6 @@ import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname initGeoSite github.com/Dreamacro/clash/config.initGeoSite
|
||||
func initGeoSite() error
|
||||
|
||||
type GEOSITE struct {
|
||||
*Base
|
||||
country string
|
||||
@ -53,7 +50,7 @@ func (gs *GEOSITE) GetRecodeSize() int {
|
||||
|
||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||
if !initFlag {
|
||||
if err := initGeoSite(); err != nil {
|
||||
if err := geodata.InitGeoSite(); err != nil {
|
||||
log.Errorln("can't initial GeoSite: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ func (ps *Process) Match(metadata *C.Metadata) bool {
|
||||
if ps.nameOnly {
|
||||
return strings.EqualFold(metadata.Process, ps.process)
|
||||
}
|
||||
|
||||
return strings.EqualFold(metadata.ProcessPath, ps.process)
|
||||
}
|
||||
|
||||
@ -32,6 +33,10 @@ func (ps *Process) Payload() string {
|
||||
return ps.process
|
||||
}
|
||||
|
||||
func (ps *Process) ShouldFindProcess() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewProcess(process string, adapter string, nameOnly bool) (*Process, error) {
|
||||
return &Process{
|
||||
Base: &Base{},
|
||||
|
@ -19,9 +19,10 @@ func (A *AND) ShouldFindProcess() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewAND(payload string, adapter string) (*AND, error) {
|
||||
func NewAND(payload string, adapter string,
|
||||
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*AND, error) {
|
||||
and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter}
|
||||
rules, err := parseRuleByPayload(payload)
|
||||
rules, err := parseRuleByPayload(payload, parse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -9,10 +9,7 @@ import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname parseRule github.com/Dreamacro/clash/rules.ParseRule
|
||||
func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
|
||||
|
||||
func parseRuleByPayload(payload string) ([]C.Rule, error) {
|
||||
func parseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ([]C.Rule, error) {
|
||||
regex, err := regexp.Compile("\\(.*\\)")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -29,7 +26,7 @@ func parseRuleByPayload(payload string) ([]C.Rule, error) {
|
||||
for _, subRange := range subRanges {
|
||||
subPayload := payload[subRange.start+1 : subRange.end]
|
||||
|
||||
rule, err := payloadToRule(subPayload)
|
||||
rule, err := payloadToRule(subPayload, parseLogicSubRule(parseRule))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -47,7 +44,7 @@ func containRange(r Range, preStart, preEnd int) bool {
|
||||
return preStart < r.start && preEnd > r.end
|
||||
}
|
||||
|
||||
func payloadToRule(subPayload string) (C.Rule, error) {
|
||||
func payloadToRule(subPayload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (C.Rule, error) {
|
||||
splitStr := strings.SplitN(subPayload, ",", 2)
|
||||
if len(splitStr) < 2 {
|
||||
return nil, fmt.Errorf("[%s] format is error", subPayload)
|
||||
@ -62,6 +59,17 @@ func payloadToRule(subPayload string) (C.Rule, error) {
|
||||
return parseRule(tp, param[0], "", param[1:])
|
||||
}
|
||||
|
||||
func parseLogicSubRule(parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
return func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "MATCH":
|
||||
return nil, fmt.Errorf("unsupported rule type on logic rule")
|
||||
default:
|
||||
return parseRule(tp, payload, target, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Range struct {
|
||||
start int
|
||||
end int
|
||||
|
@ -1,13 +1,72 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
RC "github.com/Dreamacro/clash/rules/common"
|
||||
RP "github.com/Dreamacro/clash/rules/provider"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ParseRule(tp, payload, target string, params []string) (parsed constant.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "DOMAIN":
|
||||
parsed = RC.NewDomain(payload, target)
|
||||
case "DOMAIN-SUFFIX":
|
||||
parsed = RC.NewDomainSuffix(payload, target)
|
||||
case "DOMAIN-KEYWORD":
|
||||
parsed = RC.NewDomainKeyword(payload, target)
|
||||
case "GEOSITE":
|
||||
parsed, parseErr = RC.NewGEOSITE(payload, target)
|
||||
case "GEOIP":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve)
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve))
|
||||
case "SRC-IP-CIDR":
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||
case "IP-SUFFIX":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve)
|
||||
case "SRC-IP-SUFFIX":
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
|
||||
case "SRC-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, true)
|
||||
case "DST-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, false)
|
||||
case "PROCESS-NAME":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, true)
|
||||
case "PROCESS-PATH":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, false)
|
||||
case "NETWORK":
|
||||
parsed, parseErr = RC.NewNetworkType(payload, target)
|
||||
case "UID":
|
||||
parsed, parseErr = RC.NewUid(payload, target)
|
||||
case "IN-TYPE":
|
||||
parsed, parseErr = RC.NewInType(payload, target)
|
||||
case "AND":
|
||||
parsed, parseErr = NewAND(payload, target, ParseRule)
|
||||
case "OR":
|
||||
parsed, parseErr = NewOR(payload, target, ParseRule)
|
||||
case "NOT":
|
||||
parsed, parseErr = NewNOT(payload, target, ParseRule)
|
||||
case "RULE-SET":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule)
|
||||
case "MATCH":
|
||||
parsed = RC.NewMatch(target)
|
||||
parseErr = nil
|
||||
default:
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestAND(t *testing.T) {
|
||||
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
||||
and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "DIRECT", and.adapter)
|
||||
assert.Equal(t, false, and.ShouldResolveIP())
|
||||
@ -18,29 +77,29 @@ func TestAND(t *testing.T) {
|
||||
DstPort: "20000",
|
||||
}))
|
||||
|
||||
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
||||
and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
||||
and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
func TestNOT(t *testing.T) {
|
||||
not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT")
|
||||
not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT", ParseRule)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, false, not.Match(&constant.Metadata{
|
||||
DstPort: "6100",
|
||||
}))
|
||||
|
||||
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT")
|
||||
_, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
_, err = NewNOT("(())", "DIRECT")
|
||||
_, err = NewNOT("(())", "DIRECT", ParseRule)
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestOR(t *testing.T) {
|
||||
or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT")
|
||||
or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, true, or.Match(&constant.Metadata{
|
||||
NetWork: constant.TCP,
|
||||
|
@ -17,9 +17,9 @@ func (not *NOT) ShouldFindProcess() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewNOT(payload string, adapter string) (*NOT, error) {
|
||||
func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*NOT, error) {
|
||||
not := &NOT{Base: &common.Base{}, adapter: adapter}
|
||||
rule, err := parseRuleByPayload(payload)
|
||||
rule, err := parseRuleByPayload(payload, parse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -45,9 +45,9 @@ func (or *OR) ShouldResolveIP() bool {
|
||||
return or.needIP
|
||||
}
|
||||
|
||||
func NewOR(payload string, adapter string) (*OR, error) {
|
||||
func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*OR, error) {
|
||||
or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter}
|
||||
rules, err := parseRuleByPayload(payload)
|
||||
rules, err := parseRuleByPayload(payload, parse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,29 +1,64 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
RC "github.com/Dreamacro/clash/rules/common"
|
||||
"github.com/Dreamacro/clash/rules/logic"
|
||||
RP "github.com/Dreamacro/clash/rules/provider"
|
||||
"github.com/Dreamacro/clash/rules/ruleparser"
|
||||
)
|
||||
|
||||
func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "DOMAIN":
|
||||
parsed = RC.NewDomain(payload, target)
|
||||
case "DOMAIN-SUFFIX":
|
||||
parsed = RC.NewDomainSuffix(payload, target)
|
||||
case "DOMAIN-KEYWORD":
|
||||
parsed = RC.NewDomainKeyword(payload, target)
|
||||
case "GEOSITE":
|
||||
parsed, parseErr = RC.NewGEOSITE(payload, target)
|
||||
case "GEOIP":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve)
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve))
|
||||
case "SRC-IP-CIDR":
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||
case "IP-SUFFIX":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve)
|
||||
case "SRC-IP-SUFFIX":
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
|
||||
case "SRC-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, true)
|
||||
case "DST-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, false)
|
||||
case "PROCESS-NAME":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, true)
|
||||
case "PROCESS-PATH":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, false)
|
||||
case "NETWORK":
|
||||
parsed, parseErr = RC.NewNetworkType(payload, target)
|
||||
case "UID":
|
||||
parsed, parseErr = RC.NewUid(payload, target)
|
||||
case "IN-TYPE":
|
||||
parsed, parseErr = RC.NewInType(payload, target)
|
||||
case "AND":
|
||||
parsed, parseErr = logic.NewAND(payload, target)
|
||||
parsed, parseErr = logic.NewAND(payload, target, ParseRule)
|
||||
case "OR":
|
||||
parsed, parseErr = logic.NewOR(payload, target)
|
||||
parsed, parseErr = logic.NewOR(payload, target, ParseRule)
|
||||
case "NOT":
|
||||
parsed, parseErr = logic.NewNOT(payload, target)
|
||||
parsed, parseErr = logic.NewNOT(payload, target, ParseRule)
|
||||
case "RULE-SET":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve)
|
||||
parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule)
|
||||
case "MATCH":
|
||||
parsed = RC.NewMatch(target)
|
||||
parseErr = nil
|
||||
default:
|
||||
parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params)
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||
}
|
||||
|
||||
if parseErr != nil {
|
||||
|
@ -1,14 +1,17 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type classicalStrategy struct {
|
||||
rules []C.Rule
|
||||
count int
|
||||
shouldResolveIP bool
|
||||
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
|
||||
}
|
||||
|
||||
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
|
||||
@ -34,7 +37,7 @@ func (c *classicalStrategy) OnUpdate(rules []string) {
|
||||
shouldResolveIP := false
|
||||
for _, rawRule := range rules {
|
||||
ruleType, rule, params := ruleParse(rawRule)
|
||||
r, err := parseRule(ruleType, rule, "", params)
|
||||
r, err := c.parse(ruleType, rule, "", params)
|
||||
if err != nil {
|
||||
log.Warnln("parse rule error:[%s]", err.Error())
|
||||
} else {
|
||||
@ -50,6 +53,26 @@ func (c *classicalStrategy) OnUpdate(rules []string) {
|
||||
c.count = len(classicalRules)
|
||||
}
|
||||
|
||||
func NewClassicalStrategy() *classicalStrategy {
|
||||
return &classicalStrategy{rules: []C.Rule{}}
|
||||
func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
item := strings.Split(ruleRaw, ",")
|
||||
if len(item) == 1 {
|
||||
return "", item[0], nil
|
||||
} else if len(item) == 2 {
|
||||
return item[0], item[1], nil
|
||||
} else if len(item) > 2 {
|
||||
return item[0], item[1], item[2:]
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func NewClassicalStrategy(parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) *classicalStrategy {
|
||||
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "MATCH":
|
||||
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
||||
default:
|
||||
return parse(tp, payload, target, params)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"strings"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
type domainStrategy struct {
|
||||
@ -28,7 +28,8 @@ func (d *domainStrategy) OnUpdate(rules []string) {
|
||||
domainTrie := trie.New[bool]()
|
||||
count := 0
|
||||
for _, rule := range rules {
|
||||
err := domainTrie.Insert(rule, true)
|
||||
actualDomain, _ := idna.ToASCII(rule)
|
||||
err := domainTrie.Insert(actualDomain, true)
|
||||
if err != nil {
|
||||
log.Warnln("invalid domain:[%s]", rule)
|
||||
} else {
|
||||
@ -40,19 +41,6 @@ func (d *domainStrategy) OnUpdate(rules []string) {
|
||||
d.count = count
|
||||
}
|
||||
|
||||
func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
item := strings.Split(ruleRaw, ",")
|
||||
if len(item) == 1 {
|
||||
return "", item[0], nil
|
||||
} else if len(item) == 2 {
|
||||
return item[0], item[1], nil
|
||||
} else if len(item) > 2 {
|
||||
return item[0], item[1], item[2:]
|
||||
}
|
||||
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func NewDomainStrategy() *domainStrategy {
|
||||
return &domainStrategy{}
|
||||
}
|
||||
|
@ -40,9 +40,10 @@ func (f *fetcher) VehicleType() P.VehicleType {
|
||||
|
||||
func (f *fetcher) Initial() (interface{}, error) {
|
||||
var (
|
||||
buf []byte
|
||||
hasLocal bool
|
||||
err error
|
||||
buf []byte
|
||||
hasLocal bool
|
||||
err error
|
||||
forceUpdate bool
|
||||
)
|
||||
|
||||
defer func() {
|
||||
@ -57,10 +58,8 @@ func (f *fetcher) Initial() (interface{}, error) {
|
||||
f.updatedAt = &modTime
|
||||
hasLocal = true
|
||||
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
|
||||
defer func() {
|
||||
log.Infoln("[Provider] %s's rules not updated for a long time, force refresh", f.Name())
|
||||
go f.update()
|
||||
}()
|
||||
forceUpdate = true
|
||||
log.Infoln("[Provider] %s not updated for a long time, force refresh", f.Name())
|
||||
}
|
||||
} else {
|
||||
buf, err = f.vehicle.Read()
|
||||
@ -70,7 +69,21 @@ func (f *fetcher) Initial() (interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules, err := f.parser(buf)
|
||||
var rules interface{}
|
||||
if forceUpdate {
|
||||
var forceBuf []byte
|
||||
if forceBuf, err = f.vehicle.Read(); err == nil {
|
||||
if rules, err = f.parser(forceBuf); err == nil {
|
||||
hasLocal = false
|
||||
buf = forceBuf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil || !forceUpdate {
|
||||
rules, err = f.parser(buf)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !hasLocal {
|
||||
return nil, err
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
P "github.com/Dreamacro/clash/constant/provider"
|
||||
RC "github.com/Dreamacro/clash/rules/common"
|
||||
"github.com/Dreamacro/clash/rules/ruleparser"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -19,7 +17,7 @@ type ruleProviderSchema struct {
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
}
|
||||
|
||||
func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) {
|
||||
func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) {
|
||||
schema := &ruleProviderSchema{}
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
@ -49,19 +47,5 @@ func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvi
|
||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||
}
|
||||
|
||||
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil
|
||||
}
|
||||
|
||||
func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params)
|
||||
|
||||
if parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
ruleExtra := &C.RuleExtra{
|
||||
Network: RC.FindNetwork(params),
|
||||
SourceIPs: RC.FindSourceIPs(params),
|
||||
}
|
||||
parsed.SetRuleExtra(ruleExtra)
|
||||
return parsed, parseErr
|
||||
return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil
|
||||
}
|
||||
|
@ -99,7 +99,8 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle) P.RuleProvider {
|
||||
func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle,
|
||||
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) P.RuleProvider {
|
||||
rp := &ruleSetProvider{
|
||||
behavior: behavior,
|
||||
}
|
||||
@ -112,7 +113,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration
|
||||
|
||||
fetcher := newFetcher(name, interval, vehicle, rulesParse, onUpdate)
|
||||
rp.fetcher = fetcher
|
||||
rp.strategy = newStrategy(behavior)
|
||||
rp.strategy = newStrategy(behavior, parse)
|
||||
|
||||
wrapper := &RuleSetProvider{
|
||||
rp,
|
||||
@ -123,7 +124,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func newStrategy(behavior P.RuleType) ruleStrategy {
|
||||
func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ruleStrategy {
|
||||
switch behavior {
|
||||
case P.Domain:
|
||||
strategy := NewDomainStrategy()
|
||||
@ -132,7 +133,7 @@ func newStrategy(behavior P.RuleType) ruleStrategy {
|
||||
strategy := NewIPCidrStrategy()
|
||||
return strategy
|
||||
case P.Classical:
|
||||
strategy := NewClassicalStrategy()
|
||||
strategy := NewClassicalStrategy(parse)
|
||||
return strategy
|
||||
default:
|
||||
return nil
|
||||
|
@ -47,7 +47,7 @@ func (rs *RuleSet) getProviders() P.RuleProvider {
|
||||
return rs.ruleProvider
|
||||
}
|
||||
|
||||
func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) {
|
||||
func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*RuleSet, error) {
|
||||
rp, ok := RuleProviders()[ruleProviderName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("rule set %s not found", ruleProviderName)
|
||||
|
@ -1,50 +0,0 @@
|
||||
package ruleparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
RC "github.com/Dreamacro/clash/rules/common"
|
||||
)
|
||||
|
||||
func ParseSameRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "DOMAIN":
|
||||
parsed = RC.NewDomain(payload, target)
|
||||
case "DOMAIN-SUFFIX":
|
||||
parsed = RC.NewDomainSuffix(payload, target)
|
||||
case "DOMAIN-KEYWORD":
|
||||
parsed = RC.NewDomainKeyword(payload, target)
|
||||
case "GEOSITE":
|
||||
parsed, parseErr = RC.NewGEOSITE(payload, target)
|
||||
case "GEOIP":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewGEOIP(payload, target, noResolve)
|
||||
case "IP-CIDR", "IP-CIDR6":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve))
|
||||
case "SRC-IP-CIDR":
|
||||
parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true))
|
||||
case "IP-SUFFIX":
|
||||
noResolve := RC.HasNoResolve(params)
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve)
|
||||
case "SRC-IP-SUFFIX":
|
||||
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
|
||||
case "SRC-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, true)
|
||||
case "DST-PORT":
|
||||
parsed, parseErr = RC.NewPort(payload, target, false)
|
||||
case "PROCESS-NAME":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, true)
|
||||
case "PROCESS-PATH":
|
||||
parsed, parseErr = RC.NewProcess(payload, target, false)
|
||||
case "NETWORK":
|
||||
parsed, parseErr = RC.NewNetworkType(payload, target)
|
||||
case "UID":
|
||||
parsed, parseErr = RC.NewUid(payload, target)
|
||||
case "IN-TYPE":
|
||||
parsed, parseErr = RC.NewInType(payload, target)
|
||||
default:
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||
}
|
||||
return
|
||||
}
|
@ -31,6 +31,7 @@ const (
|
||||
ImageShadowsocks = "mritd/shadowsocks:latest"
|
||||
ImageShadowsocksRust = "ghcr.io/shadowsocks/ssserver-rust:latest"
|
||||
ImageVmess = "v2fly/v2fly-core:latest"
|
||||
ImageVmessLatest = "sagernet/v2fly-core:latest"
|
||||
ImageVless = "teddysun/xray:latest"
|
||||
ImageTrojan = "trojangfw/trojan:latest"
|
||||
ImageTrojanGo = "p4gefau1t/trojan-go:latest"
|
||||
@ -450,26 +451,26 @@ func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
||||
writeRandData := func(pc net.PacketConn, addr net.Addr) (map[int][]byte, error) {
|
||||
hashMap := map[int][]byte{}
|
||||
mux := sync.Mutex{}
|
||||
for i := 0; i < times; i++ {
|
||||
go func(idx int) {
|
||||
go func() {
|
||||
for i := 0; i < times; i++ {
|
||||
buf := make([]byte, chunkSize)
|
||||
if _, err := rand.Read(buf[1:]); err != nil {
|
||||
t.Log(err.Error())
|
||||
return
|
||||
}
|
||||
buf[0] = byte(idx)
|
||||
buf[0] = byte(i)
|
||||
|
||||
hash := md5.Sum(buf)
|
||||
mux.Lock()
|
||||
hashMap[idx] = hash[:]
|
||||
hashMap[i] = hash[:]
|
||||
mux.Unlock()
|
||||
|
||||
if _, err := pc.WriteTo(buf, addr); err != nil {
|
||||
t.Log(err.Error())
|
||||
return
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return hashMap, nil
|
||||
}
|
||||
|
27
test/config/xray-shadowsocks.json
Normal file
27
test/config/xray-shadowsocks.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10002,
|
||||
"listen": "0.0.0.0",
|
||||
"protocol": "shadowsocks",
|
||||
"settings": {
|
||||
"network": "tcp,udp",
|
||||
"clients": [
|
||||
{
|
||||
"method": "aes-128-gcm",
|
||||
"level": 0,
|
||||
"password": "FzcLbKs2dY9mhL"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ replace github.com/Dreamacro/clash => ../
|
||||
|
||||
replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
|
||||
|
||||
replace github.com/tobyxdd/hysteria => github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15
|
||||
replace github.com/tobyxdd/hysteria => github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256
|
||||
|
||||
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.27.1-0.20220512040129-ed2a645d9218
|
||||
|
||||
@ -61,8 +61,9 @@ require (
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13 // indirect
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c // indirect
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/tobyxdd/hysteria v1.0.4 // indirect
|
||||
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
|
||||
@ -78,7 +79,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20220608143224-64259d1afd70 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
|
18
test/go.sum
18
test/go.sum
@ -42,8 +42,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15 h1:SraqLzYEGfrV8ETkVYc5evvCrn95hMFdCtcXXP8bA9Y=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220607074613-210c46c75b15/go.mod h1:bXVjOca4Xf3JRenwuPKu02XaOiZwejrMSlgsu/U88J4=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256 h1:wm5RrQfwJS63pe5G15AKdXfrwlIYFciwCs3MrVxzxSU=
|
||||
github.com/MetaCubeX/hysteria v1.0.5-0.20220626134949-6fa84cd3e256/go.mod h1:bXVjOca4Xf3JRenwuPKu02XaOiZwejrMSlgsu/U88J4=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
|
||||
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
@ -323,10 +323,12 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7 h1:Q+uNKLNSKqpx+p96qcBTVFh8RUKiQFr4IrNVi5Q5yl0=
|
||||
github.com/sagernet/sing v0.0.0-20220609091055-86d0144940e7/go.mod h1:w2HnJzXKHpD6F5Z/9XlSD4qbcpHY2RSZuQnFzqgELMg=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13 h1:bQN0hjTHdB7SyaD9yjEYAl+bDl/kXW9zC0xNa+LMTrA=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220609092835-699292971c13/go.mod h1:Fp/9+odJhtgDmiHbZClMLnxaVvmDRJxwA7u/+uXWDiQ=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c h1:98QC0wtaD648MFPw82KaT1O9LloQgR4ZyIDtNtsno8Y=
|
||||
github.com/sagernet/sing v0.0.0-20220627234642-a817f7084d9c/go.mod h1:I67R/q5f67xDExL2kL3RLIP7kGJBOPkYXkpRAykgC+E=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c h1:Jhgjyb2jXL4GtwJec6/kgeTqaQXsvMiNX2wAkGOSD3I=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220627234717-689e0165ef2c/go.mod h1:ng5pxdNnKZWlxzZTXRqWeY0ftzhScPZmjgJGJeRuPYY=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec h1:jUSfKmyL6K9O2TvIvcVacZ4eNXHYbNSfdph+DRPyVlU=
|
||||
github.com/sagernet/sing-vmess v0.0.0-20220616051646-3d3fc5d01eec/go.mod h1:jDZ8fJgOea7Y7MMHWgfqwLBVLnhtW2zuxS5wjtDaB84=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@ -584,8 +586,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -40,8 +40,8 @@ func TestClash_Hysteria(t *testing.T) {
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
Obfs: "fuck me till the daylight",
|
||||
UpMbps: 100,
|
||||
DownMbps: 100,
|
||||
Up: "100",
|
||||
Down: "100",
|
||||
SkipCertVerify: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -3,11 +3,13 @@ package main
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -277,3 +279,37 @@ func Benchmark_Shadowsocks(b *testing.B) {
|
||||
require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
|
||||
benchmarkProxy(b, proxy)
|
||||
}
|
||||
|
||||
func TestClash_ShadowsocksUoT(t *testing.T) {
|
||||
configPath := C.Path.Resolve("xray-shadowsocks.json")
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: ImageVless,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{fmt.Sprintf("%s:/etc/xray/config.json", configPath)},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "xray-ss")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewShadowSocks(outbound.ShadowSocksOption{
|
||||
Name: "ss",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
Password: "FzcLbKs2dY9mhL",
|
||||
Cipher: "aes-128-gcm",
|
||||
UDP: true,
|
||||
UDPOverTCP: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
@ -44,6 +44,74 @@ func TestClash_Vmess(t *testing.T) {
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VmessAuthenticatedLength(t *testing.T) {
|
||||
configPath := C.Path.Resolve("vmess.json")
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vmess")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||
Name: "vmess",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
UDP: true,
|
||||
AuthenticatedLength: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VmessPacketAddr(t *testing.T) {
|
||||
configPath := C.Path.Resolve("vmess.json")
|
||||
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmessLatest,
|
||||
ExposedPorts: defaultExposedPorts,
|
||||
}
|
||||
hostCfg := &container.HostConfig{
|
||||
PortBindings: defaultPortBindings,
|
||||
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||
}
|
||||
|
||||
id, err := startContainer(cfg, hostCfg, "vmess")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
cleanContainer(id)
|
||||
})
|
||||
|
||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||
Name: "vmess",
|
||||
Server: localIP.String(),
|
||||
Port: 10002,
|
||||
UUID: "b831381d-6324-4d53-ad4f-8cda48b30811",
|
||||
Cipher: "auto",
|
||||
UDP: true,
|
||||
PacketAddr: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(waitTime)
|
||||
testSuit(t, proxy)
|
||||
}
|
||||
|
||||
func TestClash_VmessTLS(t *testing.T) {
|
||||
cfg := &container.Config{
|
||||
Image: ImageVmess,
|
||||
|
@ -38,9 +38,9 @@ var (
|
||||
mode = Rule
|
||||
|
||||
// default timeout for UDP session
|
||||
udpTimeout = 60 * time.Second
|
||||
procesCache string
|
||||
failTotal int
|
||||
udpTimeout = 60 * time.Second
|
||||
|
||||
alwaysFindProcess = false
|
||||
)
|
||||
|
||||
func SetSniffing(b bool) {
|
||||
@ -122,6 +122,11 @@ func SetMode(m TunnelMode) {
|
||||
mode = m
|
||||
}
|
||||
|
||||
// SetAlwaysFindProcess set always find process info, may be increase many memory
|
||||
func SetAlwaysFindProcess(findProcess bool) {
|
||||
alwaysFindProcess = findProcess
|
||||
}
|
||||
|
||||
// processUDP starts a loop to handle udp packet
|
||||
func processUDP() {
|
||||
queue := udpQueue
|
||||
@ -180,27 +185,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
||||
}
|
||||
}
|
||||
|
||||
// pre resolve process name
|
||||
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||
if err == nil && P.ShouldFindProcess(metadata) {
|
||||
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
||||
if err != nil {
|
||||
if failTotal < 20 {
|
||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||
failTotal++
|
||||
}
|
||||
} else {
|
||||
metadata.Process = filepath.Base(path)
|
||||
metadata.ProcessPath = path
|
||||
if uid != -1 {
|
||||
metadata.Uid = &uid
|
||||
}
|
||||
if procesCache != metadata.Process {
|
||||
log.Debugln("[Process] %s from process %s", metadata.String(), path)
|
||||
}
|
||||
procesCache = metadata.Process
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -386,6 +370,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
var processUid int32
|
||||
process := ""
|
||||
processPath := ""
|
||||
foundProcess := false
|
||||
for _, rule := range rules {
|
||||
if !resolved && shouldResolveIP(rule, metadata) {
|
||||
ip, err := resolver.ResolveIP(metadata.Host)
|
||||
@ -398,6 +386,25 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if !foundProcess && (alwaysFindProcess || rule.ShouldFindProcess()) {
|
||||
srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16)
|
||||
uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort))
|
||||
if err != nil {
|
||||
log.Debugln("[Process] find process %s: %v", metadata.String(), err)
|
||||
} else {
|
||||
process = filepath.Base(path)
|
||||
processPath = path
|
||||
processUid = uid
|
||||
foundProcess = true
|
||||
}
|
||||
}
|
||||
|
||||
if foundProcess {
|
||||
metadata.Uid = &processUid
|
||||
metadata.Process = process
|
||||
metadata.ProcessPath = processPath
|
||||
}
|
||||
|
||||
if rule.Match(metadata) {
|
||||
adapter, ok := proxies[rule.Adapter()]
|
||||
if !ok {
|
||||
|
Reference in New Issue
Block a user