Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
a0fd6cfeea | |||
1f7a883bfc | |||
3fd954d185 | |||
2092a481b3 | |||
d6b80acfbc | |||
1cad615b25 | |||
73fa79bf3f | |||
d79c13064e | |||
427a377c2a | |||
9feb4d6668 | |||
a366e9a4b5 | |||
cbdf33c42c | |||
9ceaf20584 | |||
54fee7bd3a | |||
414d8f2162 | |||
86cf1dd54b | |||
d099375200 | |||
9536372cfb | |||
630a17cf90 | |||
0a7b7894bd | |||
3a9fc39cd9 | |||
1181fd4560 | |||
b8a60261ef | |||
db68d55a0e | |||
574efb4526 | |||
03b0252589 | |||
ed09df4e13 | |||
f89ecd97d6 |
@ -18,6 +18,8 @@ import (
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
@ -36,7 +38,7 @@ type Proxy struct {
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
alive *atomic.Bool
|
||||
url string
|
||||
extra map[string]*extraProxyState
|
||||
extra *xsync.MapOf[string, *extraProxyState]
|
||||
}
|
||||
|
||||
// Alive implements C.Proxy
|
||||
@ -46,10 +48,8 @@ func (p *Proxy) Alive() bool {
|
||||
|
||||
// AliveForTestUrl implements C.Proxy
|
||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
return state.alive.Load()
|
||||
}
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
return state.alive.Load()
|
||||
}
|
||||
|
||||
return p.alive.Load()
|
||||
@ -88,16 +88,16 @@ func (p *Proxy) DelayHistory() []C.DelayHistory {
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
|
||||
return histories
|
||||
}
|
||||
|
||||
// DelayHistoryForTestUrl implements C.Proxy
|
||||
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
||||
var queueM []C.DelayHistory
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
queueM = state.history.Copy()
|
||||
}
|
||||
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
queueM = state.history.Copy()
|
||||
}
|
||||
|
||||
if queueM == nil {
|
||||
@ -112,19 +112,25 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
|
||||
}
|
||||
|
||||
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
|
||||
extra := map[string][]C.DelayHistory{}
|
||||
if p.extra != nil && len(p.extra) != 0 {
|
||||
for testUrl, option := range p.extra {
|
||||
histories := []C.DelayHistory{}
|
||||
queueM := option.history.Copy()
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
extraHistory := map[string][]C.DelayHistory{}
|
||||
|
||||
extra[testUrl] = histories
|
||||
p.extra.Range(func(k string, v *extraProxyState) bool {
|
||||
|
||||
testUrl := k
|
||||
state := v
|
||||
|
||||
histories := []C.DelayHistory{}
|
||||
queueM := state.history.Copy()
|
||||
|
||||
for _, item := range queueM {
|
||||
histories = append(histories, item)
|
||||
}
|
||||
}
|
||||
return extra
|
||||
|
||||
extraHistory[testUrl] = histories
|
||||
|
||||
return true
|
||||
})
|
||||
return extraHistory
|
||||
}
|
||||
|
||||
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
|
||||
@ -149,11 +155,9 @@ func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
|
||||
alive := p.alive.Load()
|
||||
history := p.history.Last()
|
||||
|
||||
if p.extra != nil {
|
||||
if state, ok := p.extra[url]; ok {
|
||||
alive = state.alive.Load()
|
||||
history = state.history.Last()
|
||||
}
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
alive = state.alive.Load()
|
||||
history = state.history.Last()
|
||||
}
|
||||
|
||||
if !alive {
|
||||
@ -214,17 +218,13 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
record.Delay = t
|
||||
}
|
||||
|
||||
if p.extra == nil {
|
||||
p.extra = map[string]*extraProxyState{}
|
||||
}
|
||||
|
||||
state, ok := p.extra[url]
|
||||
state, ok := p.extra.Load(url)
|
||||
if !ok {
|
||||
state = &extraProxyState{
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
}
|
||||
p.extra[url] = state
|
||||
p.extra.Store(url, state)
|
||||
}
|
||||
|
||||
state.alive.Store(alive)
|
||||
@ -307,7 +307,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}}
|
||||
return &Proxy{
|
||||
ProxyAdapter: adapter,
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
url: "",
|
||||
extra: xsync.NewMapOf[*extraProxyState]()}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
@ -350,14 +355,14 @@ func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url strin
|
||||
return C.OriginalHistory
|
||||
}
|
||||
|
||||
if p.extra == nil {
|
||||
store = C.ExtraHistory
|
||||
} else {
|
||||
if _, ok := p.extra[url]; ok {
|
||||
store = C.ExtraHistory
|
||||
} else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
store = C.ExtraHistory
|
||||
}
|
||||
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
_, ok := p.extra.Load(url)
|
||||
if ok {
|
||||
return C.ExtraHistory
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
22
adapter/inbound/mitm.go
Normal file
22
adapter/inbound/mitm.go
Normal file
@ -0,0 +1,22 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/context"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
// NewMitm receive mitm request and return MitmContext
|
||||
func NewMitm(target socks5.Addr, source net.Addr, userAgent string, conn net.Conn) *context.ConnContext {
|
||||
metadata := parseSocksAddr(target)
|
||||
metadata.NetWork = C.TCP
|
||||
metadata.Type = C.MITM
|
||||
metadata.UserAgent = userAgent
|
||||
if ip, port, err := parseAddr(source); err == nil {
|
||||
metadata.SrcIP = ip
|
||||
metadata.SrcPort = port
|
||||
}
|
||||
return context.NewConnContext(conn, metadata)
|
||||
}
|
@ -3,6 +3,8 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -12,6 +14,11 @@ type Direct struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
type DirectOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
||||
@ -19,7 +26,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
return NewConn(c, d), nil
|
||||
}
|
||||
|
||||
@ -40,6 +47,21 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
return newPacketConn(pc, d), nil
|
||||
}
|
||||
|
||||
func NewDirectWithOption(option DirectOption) *Direct {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Direct,
|
||||
udp: true,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDirect() *Direct {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
|
@ -7,11 +7,13 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -74,7 +76,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -111,6 +113,10 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||
tempHeaders["Proxy-Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
if metadata.Type == C.MITM {
|
||||
tempHeaders["Origin-Request-Source-Address"] = metadata.SourceAddress()
|
||||
}
|
||||
|
||||
for key, value := range tempHeaders {
|
||||
HeaderString += key + ": " + value + "\r\n"
|
||||
}
|
||||
|
50
adapter/outbound/mitm.go
Normal file
50
adapter/outbound/mitm.go
Normal file
@ -0,0 +1,50 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type Mitm struct {
|
||||
*Base
|
||||
serverAddr *net.TCPAddr
|
||||
httpProxyClient *Http
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (m *Mitm) DialContext(ctx context.Context, metadata *C.Metadata, _ ...dialer.Option) (C.Conn, error) {
|
||||
c, err := net.DialTCP("tcp", nil, m.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = c.SetKeepAlive(true)
|
||||
_ = c.SetKeepAlivePeriod(60 * time.Second)
|
||||
|
||||
metadata.Type = C.MITM
|
||||
|
||||
hc, err := m.httpProxyClient.StreamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(hc, m), nil
|
||||
}
|
||||
|
||||
func NewMitm(serverAddr string) *Mitm {
|
||||
tcpAddr, _ := net.ResolveTCPAddr("tcp", serverAddr)
|
||||
http, _ := NewHttp(HttpOption{})
|
||||
return &Mitm{
|
||||
Base: &Base{
|
||||
name: "Mitm",
|
||||
tp: C.Mitm,
|
||||
},
|
||||
serverAddr: tcpAddr,
|
||||
httpProxyClient: http,
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
|
||||
|
||||
restlsC "github.com/3andne/restls-client-go"
|
||||
"github.com/metacubex/sing-shadowsocks2"
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -294,7 +294,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
}
|
||||
|
||||
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
|
||||
restlsConfig.SessionTicketsDisabled = true
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/structure"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
@ -93,7 +94,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -121,7 +122,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
@ -207,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -80,7 +81,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -126,7 +127,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
@ -131,7 +132,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -184,7 +185,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
c, err = t.plainStream(ctx, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
@ -268,7 +269,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@ -15,12 +16,15 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/proxydialer"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/tuic"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
type Tuic struct {
|
||||
@ -59,6 +63,9 @@ type TuicOption struct {
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
|
||||
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
||||
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@ -82,6 +89,32 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if t.option.UDPOverStream {
|
||||
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
|
||||
uotMetadata := *metadata
|
||||
uotMetadata.Host = uotDestination.Fqdn
|
||||
uotMetadata.DstPort = uotDestination.Port
|
||||
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
|
||||
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
|
||||
} else {
|
||||
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
|
||||
}
|
||||
}
|
||||
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -239,6 +272,14 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
|
||||
switch option.UDPOverStreamVersion {
|
||||
case uot.Version, uot.LegacyVersion:
|
||||
case 0:
|
||||
option.UDPOverStreamVersion = uot.LegacyVersion
|
||||
default:
|
||||
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
|
||||
}
|
||||
|
||||
t := &Tuic{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -19,13 +18,6 @@ var (
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func getClientSessionCache() tls.ClientSessionCache {
|
||||
once.Do(func() {
|
||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
||||
|
@ -57,6 +57,7 @@ type VlessOption struct {
|
||||
UUID string `proxy:"uuid"`
|
||||
Flow string `proxy:"flow,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
@ -211,6 +212,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if isH2 {
|
||||
@ -261,7 +263,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -326,7 +328,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -576,7 +578,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
"github.com/Dreamacro/clash/transport/gun"
|
||||
clashVMess "github.com/Dreamacro/clash/transport/vmess"
|
||||
|
||||
@ -52,6 +53,7 @@ type VmessOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
TLS bool `proxy:"tls,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
@ -149,6 +151,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -205,6 +208,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
@ -304,7 +308,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -365,7 +369,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@ -413,6 +417,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if option.AuthenticatedLength {
|
||||
options = append(options, vmess.ClientWithAuthenticatedLength())
|
||||
}
|
||||
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
|
||||
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -464,7 +469,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
tcpKeepAlive(c)
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -302,7 +302,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
|
@ -1,17 +1,5 @@
|
||||
package outboundgroup
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func tcpKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
|
@ -106,6 +106,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewTuic(*tuicOption)
|
||||
case "direct":
|
||||
directOption := &outbound.DirectOption{}
|
||||
err = decoder.Decode(mapping, directOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy = outbound.NewDirectWithOption(*directOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
@ -14,13 +14,6 @@ var NewSize = buf.NewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
|
||||
var KeepAlive = common.KeepAlive
|
||||
|
||||
//go:norace
|
||||
func Dup[T any](obj T) T {
|
||||
return common.Dup(obj)
|
||||
}
|
||||
|
||||
var (
|
||||
Must = common.Must
|
||||
Error = common.Error
|
||||
|
11
common/cache/lrucache.go
vendored
11
common/cache/lrucache.go
vendored
@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/generics/list"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Option is part of Functional Options Pattern
|
||||
@ -87,7 +89,7 @@ func (c *LruCache[K, V]) Get(key K) (V, bool) {
|
||||
|
||||
el := c.get(key)
|
||||
if el == nil {
|
||||
return getZero[V](), false
|
||||
return lo.Empty[V](), false
|
||||
}
|
||||
value := el.value
|
||||
|
||||
@ -119,7 +121,7 @@ func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
|
||||
|
||||
el := c.get(key)
|
||||
if el == nil {
|
||||
return getZero[V](), time.Time{}, false
|
||||
return lo.Empty[V](), time.Time{}, false
|
||||
}
|
||||
|
||||
return el.value, time.Unix(el.expires, 0), true
|
||||
@ -259,8 +261,3 @@ type entry[K comparable, V any] struct {
|
||||
value V
|
||||
expires int64
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
303
common/cert/cert.go
Normal file
303
common/cert/cert.go
Normal file
@ -0,0 +1,303 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var currentSerialNumber = time.Now().Unix()
|
||||
|
||||
type Config struct {
|
||||
ca *x509.Certificate
|
||||
caPrivateKey *rsa.PrivateKey
|
||||
|
||||
roots *x509.CertPool
|
||||
|
||||
privateKey *rsa.PrivateKey
|
||||
|
||||
validity time.Duration
|
||||
keyID []byte
|
||||
organization string
|
||||
|
||||
certsStorage CertsStorage
|
||||
}
|
||||
|
||||
type CertsStorage interface {
|
||||
Get(key string) (*tls.Certificate, bool)
|
||||
|
||||
Set(key string, cert *tls.Certificate)
|
||||
}
|
||||
|
||||
func NewAuthority(name, organization string, validity time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub := privateKey.Public()
|
||||
|
||||
pkixPub, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
h := sha1.New()
|
||||
_, err = h.Write(pkixPub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyID := h.Sum(nil)
|
||||
|
||||
serial := atomic.AddInt64(¤tSerialNumber, 1)
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(serial),
|
||||
Subject: pkix.Name{
|
||||
CommonName: name,
|
||||
Organization: []string{organization},
|
||||
},
|
||||
SubjectKeyId: keyID,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
NotBefore: time.Now().Add(-validity),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
DNSNames: []string{name},
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
x509c, err := x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return x509c, privateKey, nil
|
||||
}
|
||||
|
||||
func NewConfig(ca *x509.Certificate, caPrivateKey *rsa.PrivateKey) (*Config, error) {
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert(ca)
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pub := privateKey.Public()
|
||||
|
||||
pkixPub, err := x509.MarshalPKIXPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := sha1.New()
|
||||
_, err = h.Write(pkixPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyID := h.Sum(nil)
|
||||
|
||||
return &Config{
|
||||
ca: ca,
|
||||
caPrivateKey: caPrivateKey,
|
||||
privateKey: privateKey,
|
||||
keyID: keyID,
|
||||
validity: time.Hour,
|
||||
organization: "Clash",
|
||||
certsStorage: NewDomainTrieCertsStorage(),
|
||||
roots: roots,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetCA() *x509.Certificate {
|
||||
return c.ca
|
||||
}
|
||||
|
||||
func (c *Config) SetOrganization(organization string) {
|
||||
c.organization = organization
|
||||
}
|
||||
|
||||
func (c *Config) SetValidity(validity time.Duration) {
|
||||
c.validity = validity
|
||||
}
|
||||
|
||||
func (c *Config) NewTLSConfigForHost(hostname string) *tls.Config {
|
||||
tlsConfig := &tls.Config{
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
host := clientHello.ServerName
|
||||
if host == "" {
|
||||
host = hostname
|
||||
}
|
||||
|
||||
return c.GetOrCreateCert(host)
|
||||
},
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
func (c *Config) GetOrCreateCert(hostname string, ips ...net.IP) (*tls.Certificate, error) {
|
||||
var leaf *x509.Certificate
|
||||
tlsCertificate, ok := c.certsStorage.Get(hostname)
|
||||
if ok {
|
||||
leaf = tlsCertificate.Leaf
|
||||
if _, err := leaf.Verify(x509.VerifyOptions{
|
||||
DNSName: hostname,
|
||||
Roots: c.roots,
|
||||
}); err == nil {
|
||||
return tlsCertificate, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
key = hostname
|
||||
topHost = hostname
|
||||
wildcardHost = "*." + hostname
|
||||
dnsNames []string
|
||||
)
|
||||
|
||||
if ip := net.ParseIP(hostname); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
} else {
|
||||
parts := strings.Split(hostname, ".")
|
||||
l := len(parts)
|
||||
|
||||
if leaf != nil {
|
||||
dnsNames = append(dnsNames, leaf.DNSNames...)
|
||||
}
|
||||
|
||||
if l > 2 {
|
||||
topIndex := l - 2
|
||||
topHost = strings.Join(parts[topIndex:], ".")
|
||||
|
||||
for i := topIndex; i > 0; i-- {
|
||||
wildcardHost = "*." + strings.Join(parts[i:], ".")
|
||||
|
||||
if i == topIndex && (len(dnsNames) == 0 || dnsNames[0] != topHost) {
|
||||
dnsNames = append(dnsNames, topHost, wildcardHost)
|
||||
} else if !hasDnsNames(dnsNames, wildcardHost) {
|
||||
dnsNames = append(dnsNames, wildcardHost)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dnsNames = append(dnsNames, topHost, wildcardHost)
|
||||
}
|
||||
|
||||
key = "+." + topHost
|
||||
}
|
||||
|
||||
serial := atomic.AddInt64(¤tSerialNumber, 1)
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(serial),
|
||||
Subject: pkix.Name{
|
||||
CommonName: topHost,
|
||||
Organization: []string{c.organization},
|
||||
},
|
||||
SubjectKeyId: c.keyID,
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
NotBefore: time.Now().Add(-c.validity),
|
||||
NotAfter: time.Now().Add(c.validity),
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
}
|
||||
|
||||
raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.privateKey.Public(), c.caPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x509c, err := x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsCertificate = &tls.Certificate{
|
||||
Certificate: [][]byte{raw, c.ca.Raw},
|
||||
PrivateKey: c.privateKey,
|
||||
Leaf: x509c,
|
||||
}
|
||||
|
||||
c.certsStorage.Set(key, tlsCertificate)
|
||||
return tlsCertificate, nil
|
||||
}
|
||||
|
||||
// GenerateAndSave generate CA private key and CA certificate and dump them to file
|
||||
func GenerateAndSave(caPath string, caKeyPath string) error {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkix.Name{
|
||||
Country: []string{"US"},
|
||||
CommonName: "Clash Root CA",
|
||||
Organization: []string{"Clash Trust Services"},
|
||||
},
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
NotBefore: time.Now().Add(-(time.Hour * 24 * 60)),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 25),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
caRaw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, privateKey.Public(), privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
caOut, err := os.OpenFile(caPath, os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(caOut *os.File) {
|
||||
_ = caOut.Close()
|
||||
}(caOut)
|
||||
|
||||
if err = pem.Encode(caOut, &pem.Block{Type: "CERTIFICATE", Bytes: caRaw}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
caKeyOut, err := os.OpenFile(caKeyPath, os.O_CREATE|os.O_WRONLY, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(caKeyOut *os.File) {
|
||||
_ = caKeyOut.Close()
|
||||
}(caKeyOut)
|
||||
|
||||
if err = pem.Encode(caKeyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasDnsNames(dnsNames []string, hostname string) bool {
|
||||
for _, name := range dnsNames {
|
||||
if name == hostname {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
32
common/cert/storage.go
Normal file
32
common/cert/storage.go
Normal file
@ -0,0 +1,32 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
)
|
||||
|
||||
// DomainTrieCertsStorage cache wildcard certificates
|
||||
type DomainTrieCertsStorage struct {
|
||||
certsCache *trie.DomainTrie[*tls.Certificate]
|
||||
}
|
||||
|
||||
// Get gets the certificate from the storage
|
||||
func (c *DomainTrieCertsStorage) Get(key string) (*tls.Certificate, bool) {
|
||||
ca := c.certsCache.Search(key)
|
||||
if ca == nil {
|
||||
return nil, false
|
||||
}
|
||||
return ca.Data(), true
|
||||
}
|
||||
|
||||
// Set saves the certificate to the storage
|
||||
func (c *DomainTrieCertsStorage) Set(key string, cert *tls.Certificate) {
|
||||
_ = c.certsCache.Insert(key, cert)
|
||||
}
|
||||
|
||||
func NewDomainTrieCertsStorage() *DomainTrieCertsStorage {
|
||||
return &DomainTrieCertsStorage{
|
||||
certsCache: trie.New[*tls.Certificate](),
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) {
|
||||
|
||||
func TestExecCmd(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
_, err := ExecCmd("dir")
|
||||
_, err := ExecCmd("cmd -c 'dir'")
|
||||
assert.Nil(t, err)
|
||||
return
|
||||
}
|
||||
|
@ -50,7 +50,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
hysteria["port"] = urlHysteria.Port()
|
||||
hysteria["sni"] = query.Get("peer")
|
||||
hysteria["obfs"] = query.Get("obfs")
|
||||
hysteria["alpn"] = []string{query.Get("alpn")}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
hysteria["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
hysteria["auth_str"] = query.Get("auth")
|
||||
hysteria["protocol"] = query.Get("protocol")
|
||||
up := query.Get("up")
|
||||
@ -67,6 +69,47 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
|
||||
proxies = append(proxies, hysteria)
|
||||
|
||||
case "tuic":
|
||||
// A temporary unofficial TUIC share link standard
|
||||
// Modified from https://github.com/daeuniverse/dae/discussions/182
|
||||
// Changes:
|
||||
// 1. Support TUICv4, just replace uuid:password with token
|
||||
// 2. Remove `allow_insecure` field
|
||||
urlTUIC, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
query := urlTUIC.Query()
|
||||
|
||||
tuic := make(map[string]any, 20)
|
||||
tuic["name"] = uniqueName(names, urlTUIC.Fragment)
|
||||
tuic["type"] = scheme
|
||||
tuic["server"] = urlTUIC.Hostname()
|
||||
tuic["port"] = urlTUIC.Port()
|
||||
tuic["udp"] = true
|
||||
password, v5 := urlTUIC.User.Password()
|
||||
if v5 {
|
||||
tuic["uuid"] = urlTUIC.User.Username()
|
||||
tuic["password"] = password
|
||||
} else {
|
||||
tuic["token"] = urlTUIC.User.Username()
|
||||
}
|
||||
if cc := query.Get("congestion_control"); cc != "" {
|
||||
tuic["congestion-controller"] = cc
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
tuic["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
tuic["sni"] = sni
|
||||
}
|
||||
if query.Get("disable_sni") == "1" {
|
||||
tuic["disable-sni"] = true
|
||||
}
|
||||
if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" {
|
||||
tuic["udp-relay-mode"] = udpRelayMode
|
||||
}
|
||||
|
||||
case "trojan":
|
||||
urlTrojan, err := url.Parse(line)
|
||||
if err != nil {
|
||||
@ -86,10 +129,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
trojan["udp"] = true
|
||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
||||
|
||||
sni := query.Get("sni")
|
||||
if sni != "" {
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
trojan["sni"] = sni
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
trojan["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
|
||||
network := strings.ToLower(query.Get("type"))
|
||||
if network != "" {
|
||||
@ -217,6 +262,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if strings.HasSuffix(tls, "tls") {
|
||||
vmess["tls"] = true
|
||||
}
|
||||
if alpn, ok := values["alpn"].(string); ok {
|
||||
vmess["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
@ -332,6 +380,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
}
|
||||
}
|
||||
proxies = append(proxies, ss)
|
||||
|
||||
case "ssr":
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
if err != nil {
|
||||
|
@ -24,8 +24,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
proxy["port"] = url.Port()
|
||||
proxy["uuid"] = url.User.Username()
|
||||
proxy["udp"] = true
|
||||
proxy["skip-cert-verify"] = false
|
||||
proxy["tls"] = false
|
||||
tls := strings.ToLower(query.Get("security"))
|
||||
if strings.HasSuffix(tls, "tls") || tls == "reality" {
|
||||
proxy["tls"] = true
|
||||
@ -34,6 +32,9 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
} else {
|
||||
proxy["client-fingerprint"] = fingerprint
|
||||
}
|
||||
if alpn := query.Get("alpn"); alpn != "" {
|
||||
proxy["alpn"] = strings.Split(alpn, ",")
|
||||
}
|
||||
}
|
||||
if sni := query.Get("sni"); sni != "" {
|
||||
proxy["servername"] = sni
|
||||
|
@ -4,8 +4,11 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var KeepAliveInterval time.Duration
|
||||
|
||||
func SplitNetworkType(s string) (string, string, error) {
|
||||
var (
|
||||
shecme string
|
||||
@ -44,3 +47,10 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
||||
host, port, err = net.SplitHostPort(temp)
|
||||
return
|
||||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval * time.Second)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -15,7 +16,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
|
||||
case <-timer.C:
|
||||
return input, nil
|
||||
case <-ctx.Done():
|
||||
return getZero[T](), ctx.Err()
|
||||
return lo.Empty[T](), ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,11 +36,6 @@ func TestPicker_Timeout(t *testing.T) {
|
||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||
|
||||
number := picker.Wait()
|
||||
assert.Equal(t, number, getZero[int]())
|
||||
assert.Equal(t, number, lo.Empty[int]())
|
||||
assert.NotNil(t, picker.Error())
|
||||
}
|
||||
|
||||
func getZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package queue
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Queue is a simple concurrent safe queue
|
||||
@ -24,7 +26,7 @@ func (q *Queue[T]) Put(items ...T) {
|
||||
// Pop returns the head of items.
|
||||
func (q *Queue[T]) Pop() T {
|
||||
if len(q.items) == 0 {
|
||||
return GetZero[T]()
|
||||
return lo.Empty[T]()
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
@ -37,7 +39,7 @@ func (q *Queue[T]) Pop() T {
|
||||
// Last returns the last of item.
|
||||
func (q *Queue[T]) Last() T {
|
||||
if len(q.items) == 0 {
|
||||
return GetZero[T]()
|
||||
return lo.Empty[T]()
|
||||
}
|
||||
|
||||
q.lock.RLock()
|
||||
@ -69,8 +71,3 @@ func New[T any](hint int64) *Queue[T] {
|
||||
items: make([]T, 0, hint),
|
||||
}
|
||||
}
|
||||
|
||||
func GetZero[T any]() T {
|
||||
var result T
|
||||
return result
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type Authenticator interface {
|
||||
@ -15,7 +15,7 @@ type AuthUser struct {
|
||||
}
|
||||
|
||||
type inMemoryAuthenticator struct {
|
||||
storage *sync.Map
|
||||
storage *xsync.MapOf[string, string]
|
||||
usernames []string
|
||||
}
|
||||
|
||||
@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
||||
return nil
|
||||
}
|
||||
|
||||
au := &inMemoryAuthenticator{storage: &sync.Map{}}
|
||||
au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()}
|
||||
for _, user := range users {
|
||||
au.storage.Store(user.User, user.Pass)
|
||||
}
|
||||
usernames := make([]string, 0, len(users))
|
||||
au.storage.Range(func(key, value any) bool {
|
||||
usernames = append(usernames, key.(string))
|
||||
au.storage.Range(func(key string, value string) bool {
|
||||
usernames = append(usernames, key)
|
||||
return true
|
||||
})
|
||||
au.usernames = usernames
|
||||
|
@ -162,14 +162,22 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
|
||||
|
||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
||||
preferIPVersion := opt.prefer
|
||||
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
|
||||
preferIPVersion := opt.prefer
|
||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
||||
defer fallbackTicker.Stop()
|
||||
|
||||
results := make(chan dialResult)
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
||||
defer wg.Done()
|
||||
result := dialResult{isPrimary: isPrimary}
|
||||
defer func() {
|
||||
select {
|
||||
@ -182,18 +190,36 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
||||
}()
|
||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
||||
}
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
|
||||
if len(ipv4s) != 0 {
|
||||
wg.Add(1)
|
||||
go racer(ipv4s, preferIPVersion != 6)
|
||||
}
|
||||
|
||||
if len(ipv6s) != 0 {
|
||||
wg.Add(1)
|
||||
go racer(ipv6s, preferIPVersion != 4)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
var fallback dialResult
|
||||
var errs []error
|
||||
for i := 0; i < 2; {
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-fallbackTicker.C:
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
case res := <-results:
|
||||
i++
|
||||
case res, ok := <-results:
|
||||
if !ok {
|
||||
break loop
|
||||
}
|
||||
if res.error == nil {
|
||||
if res.isPrimary {
|
||||
return res.Conn, nil
|
||||
@ -208,6 +234,7 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fallback.error == nil && fallback.Conn != nil {
|
||||
return fallback.Conn, nil
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func (g GeoIPCache) Set(key string, value *router.GeoIP) {
|
||||
}
|
||||
|
||||
func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) {
|
||||
asset := C.Path.GetAssetLocation(filename)
|
||||
asset := C.Path.Resolve(filename)
|
||||
idx := strings.ToLower(asset + ":" + code)
|
||||
if g.Has(idx) {
|
||||
return g.Get(idx), nil
|
||||
@ -97,7 +97,7 @@ func (g GeoSiteCache) Set(key string, value *router.GeoSite) {
|
||||
}
|
||||
|
||||
func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error) {
|
||||
asset := C.Path.GetAssetLocation(filename)
|
||||
asset := C.Path.Resolve(filename)
|
||||
idx := strings.ToLower(asset + ":" + code)
|
||||
if g.Has(idx) {
|
||||
return g.Get(idx), nil
|
||||
|
@ -26,7 +26,7 @@ func ReadFile(path string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func ReadAsset(file string) ([]byte, error) {
|
||||
return ReadFile(C.Path.GetAssetLocation(file))
|
||||
return ReadFile(C.Path.Resolve(file))
|
||||
}
|
||||
|
||||
func loadIP(geoipBytes []byte, country string) ([]*router.CIDR, error) {
|
||||
|
@ -5,23 +5,28 @@ import (
|
||||
"sync"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
mapping sync.Map
|
||||
mapping *xsync.MapOf[string, *Entry]
|
||||
lockMap *xsync.MapOf[string, *sync.Cond]
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
PacketConn C.PacketConn
|
||||
WriteBackProxy C.WriteBackProxy
|
||||
LocalUDPConnMap sync.Map
|
||||
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
|
||||
LocalLockMap *xsync.MapOf[string, *sync.Cond]
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||
t.mapping.Store(key, &Entry{
|
||||
PacketConn: e,
|
||||
WriteBackProxy: w,
|
||||
LocalUDPConnMap: sync.Map{},
|
||||
LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](),
|
||||
LocalLockMap: xsync.NewMapOf[*sync.Cond](),
|
||||
})
|
||||
}
|
||||
|
||||
@ -34,15 +39,19 @@ func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
|
||||
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||
return item.(*sync.Cond), loaded
|
||||
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
|
||||
return item, loaded
|
||||
}
|
||||
|
||||
func (t *Table) Delete(key string) {
|
||||
t.mapping.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
func (t *Table) DeleteLock(lockKey string) {
|
||||
t.lockMap.Delete(lockKey)
|
||||
}
|
||||
|
||||
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return nil
|
||||
@ -51,10 +60,10 @@ func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
return item.(*net.UDPConn)
|
||||
return item
|
||||
}
|
||||
|
||||
func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return false
|
||||
@ -63,7 +72,7 @@ func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
|
||||
func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
return
|
||||
@ -76,11 +85,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
|
||||
return item.(*sync.Cond), loaded
|
||||
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock)
|
||||
return item, loaded
|
||||
}
|
||||
|
||||
func (t *Table) DeleteLocalConnMap(lAddr, key string) {
|
||||
func (t *Table) DeleteForLocalConn(lAddr, key string) {
|
||||
entry, loaded := t.getEntry(lAddr)
|
||||
if !loaded {
|
||||
return
|
||||
@ -88,17 +97,26 @@ func (t *Table) DeleteLocalConnMap(lAddr, key string) {
|
||||
entry.LocalUDPConnMap.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) getEntry(key string) (*Entry, bool) {
|
||||
item, ok := t.mapping.Load(key)
|
||||
// This should not happen usually since this function called after PacketConn created
|
||||
if !ok {
|
||||
return nil, false
|
||||
func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
|
||||
entry, loaded := t.getEntry(lAddr)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
entry, ok := item.(*Entry)
|
||||
return entry, ok
|
||||
entry.LocalLockMap.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) getEntry(key string) (*Entry, bool) {
|
||||
return t.mapping.Load(key)
|
||||
}
|
||||
|
||||
func makeLock() *sync.Cond {
|
||||
return sync.NewCond(&sync.Mutex{})
|
||||
}
|
||||
|
||||
// New return *Cache
|
||||
func New() *Table {
|
||||
return &Table{}
|
||||
return &Table{
|
||||
mapping: xsync.NewMapOf[*Entry](),
|
||||
lockMap: xsync.NewMapOf[*sync.Cond](),
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
types "github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -65,7 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
var contents V
|
||||
@ -85,18 +87,18 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
// parse local file error, fallback to remote
|
||||
buf, err = f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
contents, err = f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
isLocal = false
|
||||
@ -104,7 +106,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
|
||||
if f.vehicle.Type() != types.File && !isLocal {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), err
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,7 +123,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
|
||||
func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
buf, err := f.vehicle.Read()
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@ -129,17 +131,17 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
f.UpdatedAt = &now
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||
return getZero[V](), true, nil
|
||||
return lo.Empty[V](), true, nil
|
||||
}
|
||||
|
||||
contents, err := f.parser(buf)
|
||||
if err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
return getZero[V](), false, err
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,8 +212,3 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
func getZero[V any]() V {
|
||||
var result V
|
||||
return result
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
|
||||
utls "github.com/sagernet/utls"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
@ -70,7 +71,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
rawSessionID[i] = 0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix()))
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
|
||||
|
||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
||||
hello.SessionId[0] = 1
|
||||
|
117
config/config.go
117
config/config.go
@ -16,6 +16,7 @@ import (
|
||||
"github.com/Dreamacro/clash/adapter/outbound"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -34,6 +35,7 @@ import (
|
||||
L "github.com/Dreamacro/clash/listener"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
rewrites "github.com/Dreamacro/clash/rewrite"
|
||||
R "github.com/Dreamacro/clash/rules"
|
||||
RP "github.com/Dreamacro/clash/rules/provider"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
@ -59,6 +61,7 @@ type General struct {
|
||||
Sniffing bool `json:"sniffing"`
|
||||
EBpf EBpf `json:"-"`
|
||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||
KeepAliveInterval int `json:"keep-alive-interval"`
|
||||
}
|
||||
|
||||
// Inbound config
|
||||
@ -77,6 +80,7 @@ type Inbound struct {
|
||||
BindAddress string `json:"bind-address"`
|
||||
InboundTfo bool `json:"inbound-tfo"`
|
||||
InboundMPTCP bool `json:"inbound-mptcp"`
|
||||
MitmPort int `json:"mitm-port"`
|
||||
}
|
||||
|
||||
// Controller config
|
||||
@ -87,6 +91,14 @@ type Controller struct {
|
||||
Secret string `json:"-"`
|
||||
}
|
||||
|
||||
// NTP config
|
||||
type NTP struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
Server string `yaml:"server"`
|
||||
Port int `yaml:"port"`
|
||||
Interval int `yaml:"interval"`
|
||||
}
|
||||
|
||||
// DNS config
|
||||
type DNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
@ -142,6 +154,12 @@ type Sniffer struct {
|
||||
ParsePureIp bool
|
||||
}
|
||||
|
||||
// Mitm config
|
||||
type Mitm struct {
|
||||
Port int `yaml:"port" json:"port"`
|
||||
Rules C.RewriteRule `yaml:"rules" json:"rules"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct {
|
||||
Fingerprints []string `yaml:"fingerprints"`
|
||||
@ -151,6 +169,8 @@ type Experimental struct {
|
||||
type Config struct {
|
||||
General *General
|
||||
IPTables *IPTables
|
||||
Mitm *Mitm
|
||||
NTP *NTP
|
||||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
@ -167,6 +187,13 @@ type Config struct {
|
||||
TLS *TLS
|
||||
}
|
||||
|
||||
type RawNTP struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
Server string `yaml:"server"`
|
||||
ServerPort int `yaml:"server-port"`
|
||||
Interval int `yaml:"interval"`
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3"`
|
||||
@ -235,12 +262,18 @@ type RawTuicServer struct {
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
}
|
||||
|
||||
type RawMitm struct {
|
||||
Port int `yaml:"port" json:"port"`
|
||||
Rules []rewrites.RawMitmRule `yaml:"rules" json:"rules"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
RedirPort int `yaml:"redir-port"`
|
||||
TProxyPort int `yaml:"tproxy-port"`
|
||||
MixedPort int `yaml:"mixed-port"`
|
||||
MitmPort int `yaml:"mitm-port"`
|
||||
ShadowSocksConfig string `yaml:"ss-config"`
|
||||
VmessConfig string `yaml:"vmess-config"`
|
||||
InboundTfo bool `yaml:"inbound-tfo"`
|
||||
@ -264,16 +297,19 @@ type RawConfig struct {
|
||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||
KeepAliveInterval int `yaml:"keep-alive-interval"`
|
||||
|
||||
Sniffer RawSniffer `yaml:"sniffer"`
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||
Hosts map[string]any `yaml:"hosts"`
|
||||
NTP RawNTP `yaml:"ntp"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Tun RawTun `yaml:"tun"`
|
||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||
EBpf EBpf `yaml:"ebpf"`
|
||||
IPTables IPTables `yaml:"iptables"`
|
||||
MITM RawMitm `yaml:"mitm"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
GeoXUrl GeoXUrl `yaml:"geox-url"`
|
||||
@ -418,6 +454,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
ParsePureIp: true,
|
||||
OverrideDest: true,
|
||||
},
|
||||
MITM: RawMitm{
|
||||
Port: 0,
|
||||
Rules: []rewrites.RawMitmRule{},
|
||||
},
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
@ -493,6 +533,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
config.Hosts = hosts
|
||||
|
||||
ntpCfg := paresNTP(rawCfg)
|
||||
config.NTP = ntpCfg
|
||||
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -509,6 +552,12 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mitm, err := parseMitm(rawCfg.MITM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Mitm = mitm
|
||||
|
||||
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||
|
||||
config.Tunnels = rawCfg.Tunnels
|
||||
@ -539,6 +588,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
|
||||
C.MmdbUrl = cfg.GeoXUrl.Mmdb
|
||||
C.GeodataMode = cfg.GeodataMode
|
||||
if cfg.KeepAliveInterval == 0 {
|
||||
cfg.KeepAliveInterval = 30
|
||||
}
|
||||
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
|
||||
log.Infoln("Keep Alive Interval set %+v", N.KeepAliveInterval)
|
||||
// checkout externalUI exist
|
||||
if externalUI != "" {
|
||||
externalUI = C.Path.Resolve(externalUI)
|
||||
@ -554,6 +608,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
RedirPort: cfg.RedirPort,
|
||||
TProxyPort: cfg.TProxyPort,
|
||||
MixedPort: cfg.MixedPort,
|
||||
MitmPort: cfg.MitmPort,
|
||||
ShadowSocksConfig: cfg.ShadowSocksConfig,
|
||||
VmessConfig: cfg.VmessConfig,
|
||||
AllowLan: cfg.AllowLan,
|
||||
@ -580,6 +635,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
FindProcessMode: cfg.FindProcessMode,
|
||||
EBpf: cfg.EBpf,
|
||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||
KeepAliveInterval: cfg.KeepAliveInterval,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -600,6 +656,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
proxies["PASS"] = adapter.NewProxy(outbound.NewPass())
|
||||
proxyList = append(proxyList, "DIRECT", "REJECT")
|
||||
|
||||
if cfg.MITM.Port != 0 {
|
||||
proxies["MITM"] = adapter.NewProxy(outbound.NewMitm(fmt.Sprintf("127.0.0.1:%d", cfg.MITM.Port)))
|
||||
proxyList = append(proxyList, "MITM")
|
||||
}
|
||||
|
||||
// parse proxy
|
||||
for idx, mapping := range proxiesConfig {
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
@ -880,6 +941,14 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
|
||||
_ = tree.Insert(domain, value)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.MITM.Port != 0 {
|
||||
value, _ := resolver.NewHostValue("8.8.9.9")
|
||||
if err := tree.Insert("mitm.clash", value); err != nil {
|
||||
log.Errorln("insert mitm.clash to host error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tree.Optimize()
|
||||
|
||||
return tree, nil
|
||||
@ -1132,6 +1201,29 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
func paresNTP(rawCfg *RawConfig) *NTP {
|
||||
var server = "time.apple.com"
|
||||
var port = 123
|
||||
var interval = 30
|
||||
cfg := rawCfg.NTP
|
||||
if len(cfg.Server) != 0 {
|
||||
server = cfg.Server
|
||||
}
|
||||
if cfg.ServerPort != 0 {
|
||||
port = cfg.ServerPort
|
||||
}
|
||||
if cfg.Interval != 0 {
|
||||
interval = cfg.Interval
|
||||
}
|
||||
ntpCfg := &NTP{
|
||||
Enable: cfg.Enable,
|
||||
Server: server,
|
||||
Port: port,
|
||||
Interval: interval,
|
||||
}
|
||||
return ntpCfg
|
||||
}
|
||||
|
||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
|
||||
cfg := rawCfg.DNS
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
@ -1405,3 +1497,28 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
||||
|
||||
return sniffer, nil
|
||||
}
|
||||
|
||||
func parseMitm(rawMitm RawMitm) (*Mitm, error) {
|
||||
var (
|
||||
req []C.Rewrite
|
||||
res []C.Rewrite
|
||||
)
|
||||
|
||||
for _, line := range rawMitm.Rules {
|
||||
rule, err := rewrites.ParseRewrite(line)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse rewrite rule failure: %w", err)
|
||||
}
|
||||
|
||||
if rule.RuleType() == C.MitmResponseHeader || rule.RuleType() == C.MitmResponseBody {
|
||||
res = append(res, rule)
|
||||
} else {
|
||||
req = append(req, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return &Mitm{
|
||||
Port: rawMitm.Port,
|
||||
Rules: rewrites.NewRewriteRules(req, res),
|
||||
}, nil
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ const (
|
||||
Direct AdapterType = iota
|
||||
Reject
|
||||
Compatible
|
||||
Mitm
|
||||
Pass
|
||||
|
||||
Relay
|
||||
@ -182,6 +183,8 @@ func (at AdapterType) String() string {
|
||||
return "Compatible"
|
||||
case Pass:
|
||||
return "Pass"
|
||||
case Mitm:
|
||||
return "Mitm"
|
||||
case Shadowsocks:
|
||||
return "Shadowsocks"
|
||||
case ShadowsocksR:
|
||||
@ -267,13 +270,17 @@ type NatTable interface {
|
||||
|
||||
Delete(key string)
|
||||
|
||||
GetLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||
DeleteLock(key string)
|
||||
|
||||
AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||
GetForLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||
|
||||
RangeLocalConn(lAddr string, f func(key, value any) bool)
|
||||
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||
|
||||
GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
|
||||
RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool)
|
||||
|
||||
DeleteLocalConnMap(lAddr, key string)
|
||||
GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool)
|
||||
|
||||
DeleteForLocalConn(lAddr, key string)
|
||||
|
||||
DeleteLockForLocalConn(lAddr, key string)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ const (
|
||||
TUN
|
||||
TUIC
|
||||
INNER
|
||||
MITM
|
||||
)
|
||||
|
||||
type NetWork int
|
||||
@ -80,6 +81,8 @@ func (t Type) String() string {
|
||||
return "Tuic"
|
||||
case INNER:
|
||||
return "Inner"
|
||||
case MITM:
|
||||
return "Mitm"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@ -144,6 +147,8 @@ type Metadata struct {
|
||||
RemoteDst string `json:"remoteDestination"`
|
||||
// Only domain rule
|
||||
SniffHost string `json:"sniffHost"`
|
||||
// Only Mitm rule
|
||||
UserAgent string `json:"userAgent"`
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
@ -157,6 +162,8 @@ func (m *Metadata) SourceAddress() string {
|
||||
func (m *Metadata) SourceDetail() string {
|
||||
if m.Type == INNER {
|
||||
return fmt.Sprintf("%s", ClashName)
|
||||
} else if m.Type == MITM {
|
||||
return fmt.Sprintf("%s-MITM", ClashName)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -148,8 +148,12 @@ func (p *path) GeoSite() string {
|
||||
return P.Join(p.homeDir, "GeoSite.dat")
|
||||
}
|
||||
|
||||
func (p *path) GetAssetLocation(file string) string {
|
||||
return P.Join(p.homeDir, file)
|
||||
func (p *path) RootCA() string {
|
||||
return p.Resolve("mitm_ca.crt")
|
||||
}
|
||||
|
||||
func (p *path) CAKey() string {
|
||||
return p.Resolve("mitm_ca.key")
|
||||
}
|
||||
|
||||
func (p *path) GetExecutableFullPath() string {
|
||||
|
120
constant/rewrite.go
Normal file
120
constant/rewrite.go
Normal file
@ -0,0 +1,120 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
regexp "github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
var RewriteTypeMapping = map[string]RewriteType{
|
||||
MitmReject.String(): MitmReject,
|
||||
MitmReject200.String(): MitmReject200,
|
||||
MitmRejectImg.String(): MitmRejectImg,
|
||||
MitmRejectDict.String(): MitmRejectDict,
|
||||
MitmRejectArray.String(): MitmRejectArray,
|
||||
Mitm302.String(): Mitm302,
|
||||
Mitm307.String(): Mitm307,
|
||||
MitmRequestHeader.String(): MitmRequestHeader,
|
||||
MitmRequestBody.String(): MitmRequestBody,
|
||||
MitmResponseHeader.String(): MitmResponseHeader,
|
||||
MitmResponseBody.String(): MitmResponseBody,
|
||||
}
|
||||
|
||||
const (
|
||||
MitmReject RewriteType = iota
|
||||
MitmReject200
|
||||
MitmRejectImg
|
||||
MitmRejectDict
|
||||
MitmRejectArray
|
||||
|
||||
Mitm302
|
||||
Mitm307
|
||||
|
||||
MitmRequestHeader
|
||||
MitmRequestBody
|
||||
|
||||
MitmResponseHeader
|
||||
MitmResponseBody
|
||||
)
|
||||
|
||||
type RewriteType int
|
||||
|
||||
// UnmarshalYAML unserialize RewriteType with yaml
|
||||
func (e *RewriteType) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := RewriteTypeMapping[tp]
|
||||
if !exist {
|
||||
return errors.New("invalid MITM Action")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML serialize RewriteType with yaml
|
||||
func (e RewriteType) MarshalYAML() (any, error) {
|
||||
return e.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unserialize RewriteType with json
|
||||
func (e *RewriteType) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := RewriteTypeMapping[tp]
|
||||
if !exist {
|
||||
return errors.New("invalid MITM Action")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialize RewriteType with json
|
||||
func (e RewriteType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.String())
|
||||
}
|
||||
|
||||
func (rt RewriteType) String() string {
|
||||
switch rt {
|
||||
case MitmReject:
|
||||
return "reject" // 404
|
||||
case MitmReject200:
|
||||
return "reject-200"
|
||||
case MitmRejectImg:
|
||||
return "reject-img"
|
||||
case MitmRejectDict:
|
||||
return "reject-dict"
|
||||
case MitmRejectArray:
|
||||
return "reject-array"
|
||||
case Mitm302:
|
||||
return "302"
|
||||
case Mitm307:
|
||||
return "307"
|
||||
case MitmRequestHeader:
|
||||
return "request-header"
|
||||
case MitmRequestBody:
|
||||
return "request-body"
|
||||
case MitmResponseHeader:
|
||||
return "response-header"
|
||||
case MitmResponseBody:
|
||||
return "response-body"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type Rewrite interface {
|
||||
ID() string
|
||||
URLRegx() *regexp.Regexp
|
||||
RuleType() RewriteType
|
||||
RuleRegx() *regexp.Regexp
|
||||
RulePayload() string
|
||||
ReplaceURLPayload([]string) string
|
||||
ReplaceSubPayload(string) string
|
||||
}
|
||||
|
||||
type RewriteRule interface {
|
||||
SearchInRequest(func(Rewrite) bool) bool
|
||||
SearchInResponse(func(Rewrite) bool) bool
|
||||
}
|
@ -23,6 +23,7 @@ const (
|
||||
Network
|
||||
Uid
|
||||
SubRules
|
||||
UserAgent
|
||||
MATCH
|
||||
AND
|
||||
OR
|
||||
@ -67,6 +68,8 @@ func (rt RuleType) String() string {
|
||||
return "Process"
|
||||
case ProcessPath:
|
||||
return "ProcessPath"
|
||||
case UserAgent:
|
||||
return "UserAgent"
|
||||
case MATCH:
|
||||
return "Match"
|
||||
case RuleSet:
|
||||
|
@ -50,6 +50,8 @@ external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{externa
|
||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||
global-client-fingerprint: chrome
|
||||
|
||||
keep-alive-interval: 30
|
||||
|
||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
||||
experimental:
|
||||
|
||||
@ -681,6 +683,11 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
# sni: example.com
|
||||
#
|
||||
# meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效
|
||||
# 警告,与原版tuic不兼容!!!
|
||||
# udp-over-stream: false
|
||||
# udp-over-stream-version: 1
|
||||
|
||||
# ShadowsocksR
|
||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||
|
29
go.mod
29
go.mod
@ -3,14 +3,16 @@ module github.com/Dreamacro/clash
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.4
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
|
||||
github.com/beevik/ntp v1.3.0
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c
|
||||
@ -19,35 +21,38 @@ require (
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3
|
||||
github.com/metacubex/sing-tun v0.1.11
|
||||
github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mroth/weightedrand/v2 v2.0.2
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||
github.com/sagernet/sing v0.2.9
|
||||
github.com/sagernet/sing-mux v0.1.2
|
||||
github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
|
||||
github.com/samber/lo v1.38.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.7
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/zhangyunhao116/fastrand v0.3.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
@ -64,6 +69,7 @@ require (
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
@ -72,7 +78,7 @@ require (
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
@ -82,7 +88,7 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
@ -97,9 +103,8 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579
|
||||
|
78
go.sum
78
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
|
||||
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
|
||||
github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
|
||||
@ -10,6 +10,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/beevik/ntp v1.3.0 h1:/w5VhpW5BGKS37vFm1p9oVk/t4HnnkKZAZIubHM6F7Q=
|
||||
github.com/beevik/ntp v1.3.0/go.mod h1:vD6h1um4kzXpqmLTuu0cCLcC+NfvC0IC+ltmEDA8E78=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
@ -35,6 +37,8 @@ github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtB
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
@ -46,6 +50,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
@ -74,8 +80,8 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@ -92,10 +98,10 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww=
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg=
|
||||
github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86 h1:qGExcB3lYk51LPEJh5HTdQplbZmuTn+tkcuhuas1LC8=
|
||||
github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86/go.mod h1:HhHoyskMk4kzfLPKcm7EF7pGXF89KRVwjbGrEaN6lIU=
|
||||
github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7 h1:XY3Y6nPL45XuN/k3rDXJ1TJknLo8rTo1SVuDOmOEf4E=
|
||||
github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28 h1:ggSo4B1LDH9ZIROoUibxlrUpi7YCMri7HMXn4aNQkiM=
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28/go.mod h1:SthFvvoqgrEUgIxQXRnqdUAAYQECBavkhl7iA0geVd8=
|
||||
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579 h1:dE1dBB6CTzNdSMFTE5OCHvzHLewiqiA1nhD+7egtvAc=
|
||||
github.com/metacubex/sing v0.0.0-20230817143035-28d23f152579/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y=
|
||||
@ -108,8 +114,8 @@ github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxf
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mroth/weightedrand/v2 v2.0.2 h1:A8wJRUBcfguGl6oOQHI8fy5P4ViGRT9hdQdlG/7RiXo=
|
||||
github.com/mroth/weightedrand/v2 v2.0.2/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
@ -130,27 +136,29 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g=
|
||||
github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo=
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I=
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
||||
@ -191,6 +199,7 @@ github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
||||
@ -199,26 +208,39 @@ gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiV
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -232,18 +254,30 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
@ -252,6 +286,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -2,11 +2,15 @@ package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
@ -87,11 +91,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
}
|
||||
|
||||
updateUsers(cfg.Users)
|
||||
updateProxies(cfg.Proxies, cfg.Providers)
|
||||
updateProxies(cfg.Mitm, cfg.Proxies, cfg.Providers)
|
||||
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
||||
updateSniffer(cfg.Sniffer)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateMitm(cfg.Mitm)
|
||||
updateGeneral(cfg.General)
|
||||
updateNTP(cfg.NTP)
|
||||
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
|
||||
updateListeners(cfg.General, cfg.Listeners, force)
|
||||
updateIPTables(cfg)
|
||||
@ -129,6 +135,7 @@ func GetGeneral() *config.General {
|
||||
RedirPort: ports.RedirPort,
|
||||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
MitmPort: ports.MitmPort,
|
||||
Tun: listener.GetTunConf(),
|
||||
TuicServer: listener.GetTuicConf(),
|
||||
ShadowSocksConfig: ports.ShadowSocksConfig,
|
||||
@ -178,6 +185,13 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
|
||||
func updateExperimental(c *config.Config) {
|
||||
}
|
||||
|
||||
func updateNTP(c *config.NTP) {
|
||||
if c.Enable {
|
||||
ntp.ReCreateNTPService(net.JoinHostPort(c.Server, strconv.Itoa(c.Port)),
|
||||
time.Duration(c.Interval))
|
||||
}
|
||||
}
|
||||
|
||||
func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
@ -250,7 +264,7 @@ func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
||||
resolver.DefaultHosts = resolver.NewHosts(tree)
|
||||
}
|
||||
|
||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||
func updateProxies(mitm *config.Mitm, proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||
tunnel.UpdateProxies(proxies, providers)
|
||||
}
|
||||
|
||||
@ -478,8 +492,13 @@ func updateIPTables(cfg *config.Config) {
|
||||
log.Infoln("[IPTABLES] Setting iptables completed")
|
||||
}
|
||||
|
||||
func updateMitm(mitm *config.Mitm) {
|
||||
listener.ReCreateMitm(mitm.Port, tunnel.TCPIn())
|
||||
tunnel.UpdateRewrites(mitm.Rules)
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
listener.Cleanup(false)
|
||||
listener.Cleanup()
|
||||
tproxy.CleanupTProxyIPTables()
|
||||
resolver.StoreFakePoolState()
|
||||
|
||||
|
@ -40,6 +40,7 @@ type configSchema struct {
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
MitmPort *int `json:"mitm-port"`
|
||||
Tun *tunSchema `json:"tun"`
|
||||
TuicServer *tuicServerSchema `json:"tuic-server"`
|
||||
ShadowSocksConfig *string `json:"ss-config"`
|
||||
@ -262,6 +263,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn)
|
||||
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tcpIn, udpIn)
|
||||
P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tcpIn, udpIn)
|
||||
P.ReCreateMitm(pointerOrDefault(general.MitmPort, ports.MitmPort), tcpIn)
|
||||
|
||||
if general.Mode != nil {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter"
|
||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||
"github.com/Dreamacro/clash/common/utils"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
@ -57,6 +58,11 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if proxy.(*adapter.Proxy).Type() == C.URLTest {
|
||||
URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest)
|
||||
URLTestGroup.ForceSet("")
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||
@ -77,7 +83,6 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
defer cancel()
|
||||
|
||||
dm, err := group.URLTest(ctx, url, expectedStatus)
|
||||
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@ -39,12 +39,12 @@ func restart(w http.ResponseWriter, r *http.Request) {
|
||||
// The background context is used because the underlying functions wrap it
|
||||
// with timeout and shut down the server, which handles current request. It
|
||||
// also should be done in a separate goroutine for the same reason.
|
||||
go runRestart(execPath)
|
||||
go restartExecutable(execPath)
|
||||
}
|
||||
|
||||
func runRestart(execPath string) {
|
||||
func restartExecutable(execPath string) {
|
||||
var err error
|
||||
listener.Cleanup(false)
|
||||
executor.Shutdown()
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command(execPath, os.Args[1:]...)
|
||||
log.Infoln("restarting: %q %q", execPath, os.Args[1:])
|
||||
|
@ -41,5 +41,5 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
go runRestart(execPath)
|
||||
go restartExecutable(execPath)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
@ -55,7 +56,7 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) {
|
||||
return
|
||||
}
|
||||
|
||||
_ = conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
var resp *http.Response
|
||||
|
||||
if !trusted {
|
||||
resp = authenticate(request, cache)
|
||||
resp = Authenticate(request, cache)
|
||||
|
||||
trusted = resp == nil
|
||||
}
|
||||
@ -66,19 +66,19 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
return // hijack connection
|
||||
}
|
||||
|
||||
removeHopByHopHeaders(request.Header)
|
||||
removeExtraHTTPHostPort(request)
|
||||
RemoveHopByHopHeaders(request.Header)
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
if request.URL.Scheme == "" || request.URL.Host == "" {
|
||||
resp = responseWith(request, http.StatusBadRequest)
|
||||
resp = ResponseWith(request, http.StatusBadRequest)
|
||||
} else {
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
resp = responseWith(request, http.StatusBadGateway)
|
||||
resp = ResponseWith(request, http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
|
||||
removeHopByHopHeaders(resp.Header)
|
||||
RemoveHopByHopHeaders(resp.Header)
|
||||
}
|
||||
|
||||
if keepAlive {
|
||||
@ -98,12 +98,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response {
|
||||
func Authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response {
|
||||
authenticator := authStore.Authenticator()
|
||||
if authenticator != nil {
|
||||
credential := parseBasicProxyAuthorization(request)
|
||||
if credential == "" {
|
||||
resp := responseWith(request, http.StatusProxyAuthRequired)
|
||||
resp := ResponseWith(request, http.StatusProxyAuthRequired)
|
||||
resp.Header.Set("Proxy-Authenticate", "Basic")
|
||||
return resp
|
||||
}
|
||||
@ -117,14 +117,14 @@ func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *h
|
||||
if !authed {
|
||||
log.Infoln("Auth failed from %s", request.RemoteAddr)
|
||||
|
||||
return responseWith(request, http.StatusForbidden)
|
||||
return ResponseWith(request, http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func responseWith(request *http.Request, statusCode int) *http.Response {
|
||||
func ResponseWith(request *http.Request, statusCode int) *http.Response {
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: http.StatusText(statusCode),
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
@ -29,7 +30,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext
|
||||
defer conn.Close()
|
||||
|
||||
removeProxyHeaders(request.Header)
|
||||
removeExtraHTTPHostPort(request)
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
address := request.Host
|
||||
if _, _, err := net.SplitHostPort(address); err != nil {
|
||||
@ -87,3 +88,65 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext
|
||||
N.Relay(bufferedLeft, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleUpgradeY(localConn net.Conn, serverConn *N.BufferedConn, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) {
|
||||
removeProxyHeaders(request.Header)
|
||||
RemoveExtraHTTPHostPort(request)
|
||||
|
||||
if serverConn == nil {
|
||||
address := request.Host
|
||||
if _, _, err := net.SplitHostPort(address); err != nil {
|
||||
port := "80"
|
||||
if request.TLS != nil {
|
||||
port = "443"
|
||||
}
|
||||
address = net.JoinHostPort(address, port)
|
||||
}
|
||||
|
||||
dstAddr := socks5.ParseAddr(address)
|
||||
if dstAddr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewHTTP(dstAddr, localConn.RemoteAddr(), right)
|
||||
|
||||
serverConn = N.NewBufferedConn(left)
|
||||
|
||||
defer func() {
|
||||
_ = serverConn.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
err := request.Write(serverConn)
|
||||
if err != nil {
|
||||
_ = localConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
resp, err = http.ReadResponse(serverConn.Reader(), request)
|
||||
if err != nil {
|
||||
_ = localConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusSwitchingProtocols {
|
||||
removeProxyHeaders(resp.Header)
|
||||
|
||||
err = localConn.SetReadDeadline(time.Time{}) // set to not time out
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = resp.Write(localConn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
N.Relay(serverConn, localConn) // blocking here
|
||||
_ = localConn.Close()
|
||||
resp = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ func removeProxyHeaders(header http.Header) {
|
||||
header.Del("Proxy-Authorization")
|
||||
}
|
||||
|
||||
// removeHopByHopHeaders remove hop-by-hop header
|
||||
func removeHopByHopHeaders(header http.Header) {
|
||||
// RemoveHopByHopHeaders remove hop-by-hop header
|
||||
func RemoveHopByHopHeaders(header http.Header) {
|
||||
// Strip hop-by-hop header based on RFC:
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
|
||||
@ -38,9 +38,9 @@ func removeHopByHopHeaders(header http.Header) {
|
||||
}
|
||||
}
|
||||
|
||||
// removeExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
||||
// RemoveExtraHTTPHostPort remove extra host port (example.com:80 --> example.com)
|
||||
// It resolves the behavior of some HTTP servers that do not handle host:80 (e.g. baidu.com)
|
||||
func removeExtraHTTPHostPort(req *http.Request) {
|
||||
func RemoveExtraHTTPHostPort(req *http.Request) {
|
||||
host := req.Host
|
||||
if host == "" {
|
||||
host = req.URL.Host
|
||||
|
@ -1,19 +1,26 @@
|
||||
package listener
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"golang.org/x/exp/slices"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cert"
|
||||
"github.com/Dreamacro/clash/component/ebpf"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/autoredir"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/listener/http"
|
||||
"github.com/Dreamacro/clash/listener/mitm"
|
||||
"github.com/Dreamacro/clash/listener/mixed"
|
||||
"github.com/Dreamacro/clash/listener/redir"
|
||||
embedSS "github.com/Dreamacro/clash/listener/shadowsocks"
|
||||
@ -23,9 +30,10 @@ import (
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"github.com/Dreamacro/clash/listener/tuic"
|
||||
"github.com/Dreamacro/clash/listener/tunnel"
|
||||
LT "github.com/Dreamacro/clash/listener/tunnel"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
rewrites "github.com/Dreamacro/clash/rewrite"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@ -42,8 +50,8 @@ var (
|
||||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
tunnelTCPListeners = map[string]*tunnel.Listener{}
|
||||
tunnelUDPListeners = map[string]*tunnel.PacketConn{}
|
||||
tunnelTCPListeners = map[string]*LT.Listener{}
|
||||
tunnelUDPListeners = map[string]*LT.PacketConn{}
|
||||
inboundListeners = map[string]C.InboundListener{}
|
||||
tunLister *sing_tun.Listener
|
||||
shadowSocksListener C.MultiAddrListener
|
||||
@ -52,6 +60,7 @@ var (
|
||||
autoRedirListener *autoredir.Listener
|
||||
autoRedirProgram *ebpf.TcEBpfProgram
|
||||
tcProgram *ebpf.TcEBpfProgram
|
||||
mitmListener *mitm.Listener
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
@ -67,6 +76,7 @@ var (
|
||||
tuicMux sync.Mutex
|
||||
autoRedirMux sync.Mutex
|
||||
tcMux sync.Mutex
|
||||
mitmMux sync.Mutex
|
||||
|
||||
LastTunConf LC.Tun
|
||||
LastTuicConf LC.TuicServer
|
||||
@ -80,13 +90,12 @@ type Ports struct {
|
||||
MixedPort int `json:"mixed-port"`
|
||||
ShadowSocksConfig string `json:"ss-config"`
|
||||
VmessConfig string `json:"vmess-config"`
|
||||
MitmPort int `json:"mitm-port"`
|
||||
}
|
||||
|
||||
func GetTunConf() LC.Tun {
|
||||
if tunLister == nil {
|
||||
return LC.Tun{
|
||||
Enable: false,
|
||||
}
|
||||
return LastTunConf
|
||||
}
|
||||
return tunLister.Config()
|
||||
}
|
||||
@ -516,7 +525,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("Start TUN listening error: %s", err.Error())
|
||||
Cleanup(false)
|
||||
tunConf.Enable = false
|
||||
}
|
||||
}()
|
||||
|
||||
@ -527,7 +536,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack
|
||||
return
|
||||
}
|
||||
|
||||
Cleanup(true)
|
||||
closeTunListener()
|
||||
|
||||
if !tunConf.Enable {
|
||||
return
|
||||
@ -701,7 +710,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
for _, elm := range needCreate {
|
||||
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
|
||||
if elm.network == "tcp" {
|
||||
l, err := tunnel.New(elm.addr, elm.target, elm.proxy, tcpIn)
|
||||
l, err := LT.New(elm.addr, elm.target, elm.proxy, tcpIn)
|
||||
if err != nil {
|
||||
log.Errorln("Start tunnel %s error: %s", elm.target, err.Error())
|
||||
continue
|
||||
@ -709,7 +718,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
tunnelTCPListeners[key] = l
|
||||
log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address())
|
||||
} else {
|
||||
l, err := tunnel.NewUDP(elm.addr, elm.target, elm.proxy, udpIn)
|
||||
l, err := LT.NewUDP(elm.addr, elm.target, elm.proxy, udpIn)
|
||||
if err != nil {
|
||||
log.Errorln("Start tunnel %s error: %s", elm.target, err.Error())
|
||||
continue
|
||||
@ -749,6 +758,79 @@ func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn ch
|
||||
}
|
||||
}
|
||||
|
||||
func ReCreateMitm(port int, tcpIn chan<- C.ConnContext) {
|
||||
mitmMux.Lock()
|
||||
defer mitmMux.Unlock()
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("Start MITM server error: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
addr := genAddr(bindAddress, port, allowLan)
|
||||
|
||||
if mitmListener != nil {
|
||||
if mitmListener.RawAddress() == addr {
|
||||
return
|
||||
}
|
||||
_ = mitmListener.Close()
|
||||
mitmListener = nil
|
||||
}
|
||||
|
||||
if portIsZero(addr) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = initCert(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
rootCACert tls.Certificate
|
||||
x509c *x509.Certificate
|
||||
certOption *cert.Config
|
||||
)
|
||||
|
||||
rootCACert, err = tls.LoadX509KeyPair(C.Path.RootCA(), C.Path.CAKey())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privateKey := rootCACert.PrivateKey.(*rsa.PrivateKey)
|
||||
|
||||
x509c, err = x509.ParseCertificate(rootCACert.Certificate[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
certOption, err = cert.NewConfig(
|
||||
x509c,
|
||||
privateKey,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
certOption.SetValidity(time.Hour * 24 * 365 * 2) // 2 years
|
||||
certOption.SetOrganization("Clash ManInTheMiddle Proxy Services")
|
||||
|
||||
opt := &mitm.Option{
|
||||
Addr: addr,
|
||||
ApiHost: "mitm.clash",
|
||||
CertConfig: certOption,
|
||||
Handler: &rewrites.RewriteHandler{},
|
||||
}
|
||||
|
||||
mitmListener, err = mitm.New(opt, tcpIn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infoln("Mitm proxy listening at: %s", mitmListener.Address())
|
||||
}
|
||||
|
||||
// GetPorts return the ports of proxy servers
|
||||
func GetPorts() *Ports {
|
||||
ports := &Ports{}
|
||||
@ -791,6 +873,12 @@ func GetPorts() *Ports {
|
||||
ports.VmessConfig = vmessListener.Config()
|
||||
}
|
||||
|
||||
if mitmListener != nil {
|
||||
_, portStr, _ := net.SplitHostPort(mitmListener.Address())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
ports.MitmPort = port
|
||||
}
|
||||
|
||||
return ports
|
||||
}
|
||||
|
||||
@ -897,10 +985,26 @@ func hasTunConfigChange(tunConf *LC.Tun) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func Cleanup(wait bool) {
|
||||
func closeTunListener() {
|
||||
if tunLister != nil {
|
||||
tunLister.Close()
|
||||
tunLister = nil
|
||||
}
|
||||
LastTunConf = LC.Tun{}
|
||||
}
|
||||
|
||||
func initCert() error {
|
||||
if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find mitm_ca.crt, start generate")
|
||||
err = cert.GenerateAndSave(C.Path.RootCA(), C.Path.CAKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("Generated CA private key and CA certificate finish")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Cleanup() {
|
||||
closeTunListener()
|
||||
}
|
||||
|
55
listener/mitm/client.go
Normal file
55
listener/mitm/client.go
Normal file
@ -0,0 +1,55 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
func getServerConn(serverConn *N.BufferedConn, request *http.Request, srcAddr net.Addr, in chan<- C.ConnContext) (*N.BufferedConn, error) {
|
||||
if serverConn != nil {
|
||||
return serverConn, nil
|
||||
}
|
||||
|
||||
address := request.URL.Host
|
||||
if _, _, err := net.SplitHostPort(address); err != nil {
|
||||
port := "80"
|
||||
if request.TLS != nil {
|
||||
port = "443"
|
||||
}
|
||||
address = net.JoinHostPort(address, port)
|
||||
}
|
||||
|
||||
dstAddr := socks5.ParseAddr(address)
|
||||
if dstAddr == nil {
|
||||
return nil, socks5.ErrAddressNotSupported
|
||||
}
|
||||
|
||||
left, right := net.Pipe()
|
||||
|
||||
in <- inbound.NewMitm(dstAddr, srcAddr, request.Header.Get("User-Agent"), right)
|
||||
|
||||
if request.TLS != nil {
|
||||
tlsConn := tls.Client(left, &tls.Config{
|
||||
ServerName: request.TLS.ServerName,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverConn = N.NewBufferedConn(tlsConn)
|
||||
} else {
|
||||
serverConn = N.NewBufferedConn(left)
|
||||
}
|
||||
|
||||
return serverConn, nil
|
||||
}
|
9
listener/mitm/hack.go
Normal file
9
listener/mitm/hack.go
Normal file
@ -0,0 +1,9 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
_ "net/http"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname validMethod net/http.validMethod
|
||||
func validMethod(method string) bool
|
349
listener/mitm/proxy.go
Normal file
349
listener/mitm/proxy.go
Normal file
@ -0,0 +1,349 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
H "github.com/Dreamacro/clash/listener/http"
|
||||
)
|
||||
|
||||
func HandleConn(c net.Conn, opt *Option, in chan<- C.ConnContext, cache *cache.LruCache[string, bool]) {
|
||||
var (
|
||||
clientIP = netip.MustParseAddrPort(c.RemoteAddr().String()).Addr()
|
||||
sourceAddr net.Addr
|
||||
serverConn *N.BufferedConn
|
||||
connState *tls.ConnectionState
|
||||
)
|
||||
|
||||
defer func() {
|
||||
if serverConn != nil {
|
||||
_ = serverConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
conn := N.NewBufferedConn(c)
|
||||
|
||||
trusted := cache == nil // disable authenticate if cache is nil
|
||||
if !trusted {
|
||||
trusted = clientIP.IsLoopback() || clientIP.IsUnspecified()
|
||||
}
|
||||
|
||||
readLoop:
|
||||
for {
|
||||
// use SetReadDeadline instead of Proxy-Connection keep-alive
|
||||
if err := conn.SetReadDeadline(time.Now().Add(65 * time.Second)); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
request, err := H.ReadRequest(conn.Reader())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
var response *http.Response
|
||||
|
||||
session := newSession(conn, request, response)
|
||||
|
||||
sourceAddr = parseSourceAddress(session.request, conn.RemoteAddr(), sourceAddr)
|
||||
session.request.RemoteAddr = sourceAddr.String()
|
||||
|
||||
if !trusted {
|
||||
session.response = H.Authenticate(session.request, cache)
|
||||
|
||||
trusted = session.response == nil
|
||||
}
|
||||
|
||||
if trusted {
|
||||
if session.request.Method == http.MethodConnect {
|
||||
if session.request.ProtoMajor > 1 {
|
||||
session.request.ProtoMajor = 1
|
||||
session.request.ProtoMinor = 1
|
||||
}
|
||||
|
||||
// Manual writing to support CONNECT for http 1.0 (workaround for uplay client)
|
||||
if _, err = fmt.Fprintf(session.conn, "HTTP/%d.%d %03d %s\r\n\r\n", session.request.ProtoMajor, session.request.ProtoMinor, http.StatusOK, "Connection established"); err != nil {
|
||||
handleError(opt, session, err)
|
||||
break // close connection
|
||||
}
|
||||
|
||||
if strings.HasSuffix(session.request.URL.Host, ":80") {
|
||||
goto readLoop
|
||||
}
|
||||
|
||||
b, err1 := conn.Peek(1)
|
||||
if err1 != nil {
|
||||
handleError(opt, session, err1)
|
||||
break // close connection
|
||||
}
|
||||
|
||||
// TLS handshake.
|
||||
if b[0] == 0x16 {
|
||||
tlsConn := tls.Server(conn, opt.CertConfig.NewTLSConfigForHost(session.request.URL.Hostname()))
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||
// handshake with the local client
|
||||
if err = tlsConn.HandshakeContext(ctx); err != nil {
|
||||
cancel()
|
||||
session.response = session.NewErrorResponse(fmt.Errorf("handshake failed: %w", err))
|
||||
_ = writeResponse(session, false)
|
||||
break // close connection
|
||||
}
|
||||
cancel()
|
||||
|
||||
cs := tlsConn.ConnectionState()
|
||||
connState = &cs
|
||||
|
||||
conn = N.NewBufferedConn(tlsConn)
|
||||
}
|
||||
|
||||
if strings.HasSuffix(session.request.URL.Host, ":443") {
|
||||
goto readLoop
|
||||
}
|
||||
|
||||
if conn.SetReadDeadline(time.Now().Add(time.Second)) != nil {
|
||||
break
|
||||
}
|
||||
|
||||
buf, err2 := conn.Peek(7)
|
||||
if err2 != nil {
|
||||
if err2 != bufio.ErrBufferFull && !os.IsTimeout(err2) {
|
||||
handleError(opt, session, err2)
|
||||
break // close connection
|
||||
}
|
||||
}
|
||||
|
||||
// others protocol over tcp
|
||||
if !isHTTPTraffic(buf) {
|
||||
if connState != nil {
|
||||
session.request.TLS = connState
|
||||
}
|
||||
|
||||
serverConn, err = getServerConn(serverConn, session.request, sourceAddr, in)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if conn.SetReadDeadline(time.Time{}) != nil {
|
||||
break
|
||||
}
|
||||
|
||||
N.Relay(serverConn, conn)
|
||||
return // hijack connection
|
||||
}
|
||||
|
||||
goto readLoop
|
||||
}
|
||||
|
||||
prepareRequest(connState, session.request)
|
||||
|
||||
// hijack api
|
||||
if session.request.URL.Hostname() == opt.ApiHost {
|
||||
if err = handleApiRequest(session, opt); err != nil {
|
||||
handleError(opt, session, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// forward websocket
|
||||
if isWebsocketRequest(request) {
|
||||
serverConn, err = getServerConn(serverConn, session.request, sourceAddr, in)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
session.request.RequestURI = ""
|
||||
if session.response = H.HandleUpgradeY(conn, serverConn, request, in); session.response == nil {
|
||||
return // hijack connection
|
||||
}
|
||||
}
|
||||
|
||||
if session.response == nil {
|
||||
H.RemoveHopByHopHeaders(session.request.Header)
|
||||
H.RemoveExtraHTTPHostPort(session.request)
|
||||
|
||||
// hijack custom request and write back custom response if necessary
|
||||
newReq, newRes := opt.Handler.HandleRequest(session)
|
||||
if newReq != nil {
|
||||
session.request = newReq
|
||||
}
|
||||
if newRes != nil {
|
||||
session.response = newRes
|
||||
|
||||
if err = writeResponse(session, false); err != nil {
|
||||
handleError(opt, session, err)
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
session.request.RequestURI = ""
|
||||
|
||||
if session.request.URL.Host == "" {
|
||||
session.response = session.NewErrorResponse(ErrInvalidURL)
|
||||
} else {
|
||||
serverConn, err = getServerConn(serverConn, session.request, sourceAddr, in)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
// send the request to remote server
|
||||
err = session.request.Write(serverConn)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
session.response, err = http.ReadResponse(serverConn.Reader(), request)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = writeResponseWithHandler(session, opt); err != nil {
|
||||
handleError(opt, session, err)
|
||||
break // close connection
|
||||
}
|
||||
}
|
||||
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func writeResponseWithHandler(session *Session, opt *Option) error {
|
||||
res := opt.Handler.HandleResponse(session)
|
||||
if res != nil {
|
||||
session.response = res
|
||||
}
|
||||
|
||||
return writeResponse(session, true)
|
||||
}
|
||||
|
||||
func writeResponse(session *Session, keepAlive bool) error {
|
||||
H.RemoveHopByHopHeaders(session.response.Header)
|
||||
|
||||
if keepAlive {
|
||||
session.response.Header.Set("Connection", "keep-alive")
|
||||
session.response.Header.Set("Keep-Alive", "timeout=60")
|
||||
}
|
||||
|
||||
return session.writeResponse()
|
||||
}
|
||||
|
||||
func handleApiRequest(session *Session, opt *Option) error {
|
||||
if opt.CertConfig != nil && strings.ToLower(session.request.URL.Path) == "/cert.crt" {
|
||||
b := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: opt.CertConfig.GetCA().Raw,
|
||||
})
|
||||
|
||||
session.response = session.NewResponse(http.StatusOK, bytes.NewReader(b))
|
||||
|
||||
session.response.Close = true
|
||||
session.response.Header.Set("Content-Type", "application/x-x509-ca-cert")
|
||||
session.response.ContentLength = int64(len(b))
|
||||
|
||||
return session.writeResponse()
|
||||
}
|
||||
|
||||
b := `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html><head>
|
||||
<title>Clash MITM Proxy Services - 404 Not Found</title>
|
||||
</head><body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL %s was not found on this server.</p>
|
||||
</body></html>
|
||||
`
|
||||
|
||||
if opt.Handler.HandleApiRequest(session) {
|
||||
return nil
|
||||
}
|
||||
|
||||
b = fmt.Sprintf(b, session.request.URL.Path)
|
||||
|
||||
session.response = session.NewResponse(http.StatusNotFound, bytes.NewReader([]byte(b)))
|
||||
session.response.Close = true
|
||||
session.response.Header.Set("Content-Type", "text/html;charset=utf-8")
|
||||
session.response.ContentLength = int64(len(b))
|
||||
|
||||
return session.writeResponse()
|
||||
}
|
||||
|
||||
func handleError(opt *Option, session *Session, err error) {
|
||||
if session.response != nil {
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, session.response.Body)
|
||||
_ = session.response.Body.Close()
|
||||
}()
|
||||
}
|
||||
opt.Handler.HandleError(session, err)
|
||||
}
|
||||
|
||||
func prepareRequest(connState *tls.ConnectionState, request *http.Request) {
|
||||
host := request.Header.Get("Host")
|
||||
if host != "" {
|
||||
request.Host = host
|
||||
}
|
||||
|
||||
if request.URL.Host == "" {
|
||||
request.URL.Host = request.Host
|
||||
}
|
||||
|
||||
if request.URL.Scheme == "" {
|
||||
request.URL.Scheme = "http"
|
||||
}
|
||||
|
||||
if connState != nil {
|
||||
request.TLS = connState
|
||||
request.URL.Scheme = "https"
|
||||
}
|
||||
|
||||
if request.Header.Get("Accept-Encoding") != "" {
|
||||
request.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
}
|
||||
|
||||
func parseSourceAddress(req *http.Request, connSource, source net.Addr) net.Addr {
|
||||
if source != nil {
|
||||
return source
|
||||
}
|
||||
|
||||
sourceAddress := req.Header.Get("Origin-Request-Source-Address")
|
||||
if sourceAddress == "" {
|
||||
return connSource
|
||||
}
|
||||
|
||||
req.Header.Del("Origin-Request-Source-Address")
|
||||
|
||||
addrPort, err := netip.ParseAddrPort(sourceAddress)
|
||||
if err != nil {
|
||||
return connSource
|
||||
}
|
||||
|
||||
return &net.TCPAddr{
|
||||
IP: addrPort.Addr().AsSlice(),
|
||||
Port: int(addrPort.Port()),
|
||||
}
|
||||
}
|
||||
|
||||
func isWebsocketRequest(req *http.Request) bool {
|
||||
return strings.EqualFold(req.Header.Get("Connection"), "Upgrade") && strings.EqualFold(req.Header.Get("Upgrade"), "websocket")
|
||||
}
|
||||
|
||||
func isHTTPTraffic(buf []byte) bool {
|
||||
method, _, _ := strings.Cut(string(buf), " ")
|
||||
return validMethod(method)
|
||||
}
|
88
listener/mitm/server.go
Normal file
88
listener/mitm/server.go
Normal file
@ -0,0 +1,88 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/cert"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
HandleRequest(*Session) (*http.Request, *http.Response) // Session.Response maybe nil
|
||||
HandleResponse(*Session) *http.Response
|
||||
HandleApiRequest(*Session) bool
|
||||
HandleError(*Session, error) // Session maybe nil
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Addr string
|
||||
ApiHost string
|
||||
|
||||
TLSConfig *tls.Config
|
||||
CertConfig *cert.Config
|
||||
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
*Option
|
||||
|
||||
listener net.Listener
|
||||
addr string
|
||||
closed bool
|
||||
}
|
||||
|
||||
// RawAddress implements C.Listener
|
||||
func (l *Listener) RawAddress() string {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
// Address implements C.Listener
|
||||
func (l *Listener) Address() string {
|
||||
return l.listener.Addr().String()
|
||||
}
|
||||
|
||||
// Close implements C.Listener
|
||||
func (l *Listener) Close() error {
|
||||
l.closed = true
|
||||
return l.listener.Close()
|
||||
}
|
||||
|
||||
// New the MITM proxy actually is a type of HTTP proxy
|
||||
func New(option *Option, in chan<- C.ConnContext) (*Listener, error) {
|
||||
return NewWithAuthenticate(option, in, true)
|
||||
}
|
||||
|
||||
func NewWithAuthenticate(option *Option, in chan<- C.ConnContext, authenticate bool) (*Listener, error) {
|
||||
l, err := net.Listen("tcp", option.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c *cache.LruCache[string, bool]
|
||||
if authenticate {
|
||||
c = cache.New[string, bool](cache.WithAge[string, bool](90))
|
||||
}
|
||||
|
||||
hl := &Listener{
|
||||
listener: l,
|
||||
addr: option.Addr,
|
||||
Option: option,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
conn, err1 := hl.listener.Accept()
|
||||
if err1 != nil {
|
||||
if hl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go HandleConn(conn, option, in, c)
|
||||
}
|
||||
}()
|
||||
|
||||
return hl, nil
|
||||
}
|
59
listener/mitm/session.go
Normal file
59
listener/mitm/session.go
Normal file
@ -0,0 +1,59 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
conn net.Conn
|
||||
request *http.Request
|
||||
response *http.Response
|
||||
|
||||
props map[string]any
|
||||
}
|
||||
|
||||
func (s *Session) Request() *http.Request {
|
||||
return s.request
|
||||
}
|
||||
|
||||
func (s *Session) Response() *http.Response {
|
||||
return s.response
|
||||
}
|
||||
|
||||
func (s *Session) GetProperties(key string) (any, bool) {
|
||||
v, ok := s.props[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (s *Session) SetProperties(key string, val any) {
|
||||
s.props[key] = val
|
||||
}
|
||||
|
||||
func (s *Session) NewResponse(code int, body io.Reader) *http.Response {
|
||||
return NewResponse(code, body, s.request)
|
||||
}
|
||||
|
||||
func (s *Session) NewErrorResponse(err error) *http.Response {
|
||||
return NewErrorResponse(s.request, err)
|
||||
}
|
||||
|
||||
func (s *Session) writeResponse() error {
|
||||
if s.response == nil {
|
||||
return ErrInvalidResponse
|
||||
}
|
||||
defer func(resp *http.Response) {
|
||||
_ = resp.Body.Close()
|
||||
}(s.response)
|
||||
return s.response.Write(s.conn)
|
||||
}
|
||||
|
||||
func newSession(conn net.Conn, request *http.Request, response *http.Response) *Session {
|
||||
return &Session{
|
||||
conn: conn,
|
||||
request: request,
|
||||
response: response,
|
||||
props: map[string]any{},
|
||||
}
|
||||
}
|
95
listener/mitm/utils.go
Normal file
95
listener/mitm/utils.go
Normal file
@ -0,0 +1,95 @@
|
||||
package mitm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidResponse = errors.New("invalid response")
|
||||
ErrInvalidURL = errors.New("invalid URL")
|
||||
)
|
||||
|
||||
func NewResponse(code int, body io.Reader, req *http.Request) *http.Response {
|
||||
if body == nil {
|
||||
body = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
rc, ok := body.(io.ReadCloser)
|
||||
if !ok {
|
||||
rc = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
res := &http.Response{
|
||||
StatusCode: code,
|
||||
Status: fmt.Sprintf("%d %s", code, http.StatusText(code)),
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{},
|
||||
Body: rc,
|
||||
Request: req,
|
||||
}
|
||||
|
||||
if req != nil {
|
||||
res.Close = req.Close
|
||||
res.Proto = req.Proto
|
||||
res.ProtoMajor = req.ProtoMajor
|
||||
res.ProtoMinor = req.ProtoMinor
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func NewErrorResponse(req *http.Request, err error) *http.Response {
|
||||
res := NewResponse(http.StatusBadGateway, nil, req)
|
||||
res.Close = true
|
||||
|
||||
date := res.Header.Get("Date")
|
||||
if date == "" {
|
||||
date = time.Now().Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
w := fmt.Sprintf(`199 "clash" %q %q`, err.Error(), date)
|
||||
res.Header.Add("Warning", w)
|
||||
return res
|
||||
}
|
||||
|
||||
func ReadDecompressedBody(res *http.Response) ([]byte, error) {
|
||||
rBody := res.Body
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
gzReader, err := gzip.NewReader(rBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rBody = gzReader
|
||||
|
||||
defer func(gzReader *gzip.Reader) {
|
||||
_ = gzReader.Close()
|
||||
}(gzReader)
|
||||
}
|
||||
return ioutil.ReadAll(rBody)
|
||||
}
|
||||
|
||||
func DecodeLatin1(reader io.Reader) (string, error) {
|
||||
r := transform.NewReader(reader, charmap.ISO8859_1.NewDecoder())
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func EncodeLatin1(str string) ([]byte, error) {
|
||||
return charmap.ISO8859_1.NewEncoder().Bytes([]byte(str))
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package mixed
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@ -70,7 +70,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) {
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
@ -66,6 +67,6 @@ func handleRedir(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Ad
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
in <- inbound.NewSocket(target, conn, C.REDIR, additions...)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = c.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(c)
|
||||
go sl.HandleConn(c, tcpIn)
|
||||
}
|
||||
}()
|
||||
|
@ -72,7 +72,27 @@ func UpstreamMetadata(metadata M.Metadata) M.Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
func ConvertMetadata(metadata *C.Metadata) M.Metadata {
|
||||
return M.Metadata{
|
||||
Protocol: metadata.Type.String(),
|
||||
Source: M.SocksaddrFrom(metadata.SrcIP, metadata.SrcPort),
|
||||
Destination: M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) IsSpecialFqdn(fqdn string) bool {
|
||||
switch fqdn {
|
||||
case mux.Destination.Fqdn:
|
||||
case vmess.MuxDestination.Fqdn:
|
||||
case uot.MagicAddress:
|
||||
case uot.LegacyMagicAddress:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) ParseSpecialFqdn(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
switch metadata.Destination.Fqdn {
|
||||
case mux.Destination.Fqdn:
|
||||
return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata))
|
||||
@ -89,6 +109,13 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
|
||||
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
|
||||
return h.NewPacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata)
|
||||
}
|
||||
return errors.New("not special fqdn")
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
if h.IsSpecialFqdn(metadata.Destination.Fqdn) {
|
||||
return h.ParseSpecialFqdn(ctx, conn, metadata)
|
||||
}
|
||||
target := socks5.ParseAddr(metadata.Destination.String())
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait() // this goroutine must exit after conn.Close()
|
||||
|
@ -5,15 +5,16 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
embedSS "github.com/Dreamacro/clash/listener/shadowsocks"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
|
||||
shadowsocks "github.com/metacubex/sing-shadowsocks"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowaead"
|
||||
@ -64,7 +65,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
case common.Contains(shadowaead.List, config.Cipher):
|
||||
sl.service, err = shadowaead.NewService(config.Cipher, nil, config.Password, udpTimeout, h)
|
||||
case common.Contains(shadowaead_2022.List, config.Cipher):
|
||||
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, time.Now)
|
||||
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, ntp.Now)
|
||||
default:
|
||||
err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher)
|
||||
return embedSS.New(config, tcpIn, udpIn)
|
||||
@ -145,7 +146,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = c.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
go sl.HandleConn(c, tcpIn)
|
||||
}
|
||||
|
@ -7,9 +7,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/ntp"
|
||||
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
@ -42,7 +44,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe
|
||||
Additions: additions,
|
||||
}
|
||||
|
||||
service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection())
|
||||
service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection(), vmess.ServiceWithTimeFunc(ntp.Now))
|
||||
err = service.UpdateUsers(
|
||||
common.Map(config.Users, func(it LC.VmessUser) string {
|
||||
return it.Username
|
||||
@ -83,7 +85,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = c.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
go sl.HandleConn(c, tcpIn)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (*
|
||||
}
|
||||
|
||||
func handleSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) {
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
if err != nil {
|
||||
|
@ -55,16 +55,15 @@ func (c *packet) InAddr() net.Addr {
|
||||
func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natTable C.NatTable) (*net.UDPConn, error) {
|
||||
remote := rAddr.String()
|
||||
local := lAddr.String()
|
||||
localConn := natTable.GetLocalConn(local, remote)
|
||||
localConn := natTable.GetForLocalConn(local, remote)
|
||||
// localConn not exist
|
||||
if localConn == nil {
|
||||
lockKey := remote + "-lock"
|
||||
cond, loaded := natTable.GetOrCreateLockForLocalConn(local, lockKey)
|
||||
cond, loaded := natTable.GetOrCreateLockForLocalConn(local, remote)
|
||||
if loaded {
|
||||
cond.L.Lock()
|
||||
cond.Wait()
|
||||
// we should get localConn here
|
||||
localConn = natTable.GetLocalConn(local, remote)
|
||||
localConn = natTable.GetForLocalConn(local, remote)
|
||||
if localConn == nil {
|
||||
return nil, fmt.Errorf("localConn is nil, nat entry not exist")
|
||||
}
|
||||
@ -74,7 +73,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT
|
||||
return nil, fmt.Errorf("cond is nil, nat entry not exist")
|
||||
}
|
||||
defer func() {
|
||||
natTable.DeleteLocalConnMap(local, lockKey)
|
||||
natTable.DeleteLockForLocalConn(local, remote)
|
||||
cond.Broadcast()
|
||||
}()
|
||||
conn, err := listenLocalConn(rAddr, lAddr, in, natTable)
|
||||
@ -82,7 +81,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT
|
||||
log.Errorln("listenLocalConn failed with error: %s, packet loss (rAddr[%T]=%s lAddr[%T]=%s)", err.Error(), rAddr, remote, lAddr, local)
|
||||
return nil, err
|
||||
}
|
||||
natTable.AddLocalConn(local, remote, conn)
|
||||
natTable.AddForLocalConn(local, remote, conn)
|
||||
localConn = conn
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
@ -32,7 +33,7 @@ func (l *Listener) Close() error {
|
||||
|
||||
func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) {
|
||||
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
in <- inbound.NewSocket(target, conn, C.TPROXY, additions...)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tuic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/sockopt"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
LC "github.com/Dreamacro/clash/listener/config"
|
||||
"github.com/Dreamacro/clash/listener/sing"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
"github.com/Dreamacro/clash/transport/tuic"
|
||||
@ -36,6 +38,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
h := &sing.ListenerHandler{
|
||||
TcpIn: tcpIn,
|
||||
UdpIn: udpIn,
|
||||
Type: C.TUIC,
|
||||
Additions: additions,
|
||||
}
|
||||
cert, err := CN.ParseCert(config.Certificate, config.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -86,7 +94,19 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||
newAdditions = slices.Clone(additions)
|
||||
newAdditions = append(newAdditions, _additions...)
|
||||
}
|
||||
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||
connCtx := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||
metadata := sing.ConvertMetadata(connCtx.Metadata())
|
||||
if h.IsSpecialFqdn(metadata.Destination.Fqdn) {
|
||||
go func() { // ParseSpecialFqdn will block, so open a new goroutine
|
||||
_ = h.ParseSpecialFqdn(
|
||||
sing.WithAdditions(context.Background(), newAdditions...),
|
||||
conn,
|
||||
metadata,
|
||||
)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
tcpIn <- connCtx
|
||||
return nil
|
||||
}
|
||||
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
N "github.com/Dreamacro/clash/common/net"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
@ -34,7 +35,7 @@ func (l *Listener) Close() error {
|
||||
}
|
||||
|
||||
func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) {
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
N.TCPKeepAlive(conn)
|
||||
ctx := inbound.NewSocket(l.target, conn, C.TUNNEL, additions...)
|
||||
ctx.Metadata().SpecialProxy = l.proxy
|
||||
in <- ctx
|
||||
|
95
ntp/service.go
Normal file
95
ntp/service.go
Normal file
@ -0,0 +1,95 @@
|
||||
package ntp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/beevik/ntp"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var offset time.Duration
|
||||
var service *Service
|
||||
|
||||
type Service struct {
|
||||
addr string
|
||||
interval time.Duration
|
||||
ticker *time.Ticker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
}
|
||||
|
||||
func ReCreateNTPService(addr string, interval time.Duration) {
|
||||
if service != nil {
|
||||
service.Stop()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
service = &Service{addr: addr, interval: interval, ctx: ctx, cancel: cancel}
|
||||
service.Start()
|
||||
}
|
||||
|
||||
func (srv *Service) Start() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
log.Infoln("NTP service start")
|
||||
srv.ticker = time.NewTicker(srv.interval * time.Minute)
|
||||
service.running = true
|
||||
go func() {
|
||||
for {
|
||||
err := srv.updateTime(srv.addr)
|
||||
if err != nil {
|
||||
log.Warnln("updateTime failed: %s", err)
|
||||
}
|
||||
select {
|
||||
case <-srv.ticker.C:
|
||||
case <-srv.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (srv *Service) Stop() {
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
if service.running {
|
||||
srv.ticker.Stop()
|
||||
srv.cancel()
|
||||
service.running = false
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Service) Running() bool {
|
||||
if srv == nil {
|
||||
return false
|
||||
}
|
||||
srv.mu.Lock()
|
||||
defer srv.mu.Unlock()
|
||||
return srv.running
|
||||
}
|
||||
|
||||
func (srv *Service) updateTime(addr string) error {
|
||||
response, err := ntp.Query(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localTime := time.Now()
|
||||
ntpTime := response.Time
|
||||
offset = localTime.Sub(ntpTime)
|
||||
if offset > time.Duration(0) {
|
||||
log.Warnln("System clock is ahead of NTP time by %s", offset)
|
||||
} else if offset < time.Duration(0) {
|
||||
log.Warnln("System clock is behind NTP time by %s", -offset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Now() time.Time {
|
||||
now := time.Now()
|
||||
if service.Running() && offset.Abs() > 0 {
|
||||
now = now.Add(offset)
|
||||
}
|
||||
return now
|
||||
}
|
72
rewrite/base.go
Normal file
72
rewrite/base.go
Normal file
@ -0,0 +1,72 @@
|
||||
package rewrites
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyDict = NewResponseBody([]byte("{}"))
|
||||
EmptyArray = NewResponseBody([]byte("[]"))
|
||||
OnePixelPNG = NewResponseBody([]byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x11, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x60, 0x60, 0x60, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x03, 0xfe, 0x8f, 0xeb, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82})
|
||||
)
|
||||
|
||||
type Body interface {
|
||||
Body() io.ReadCloser
|
||||
ContentLength() int64
|
||||
}
|
||||
|
||||
type ResponseBody struct {
|
||||
data []byte
|
||||
length int64
|
||||
}
|
||||
|
||||
func (r *ResponseBody) Body() io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader(r.data))
|
||||
}
|
||||
|
||||
func (r *ResponseBody) ContentLength() int64 {
|
||||
return r.length
|
||||
}
|
||||
|
||||
func NewResponseBody(data []byte) *ResponseBody {
|
||||
return &ResponseBody{
|
||||
data: data,
|
||||
length: int64(len(data)),
|
||||
}
|
||||
}
|
||||
|
||||
type RewriteRules struct {
|
||||
request []C.Rewrite
|
||||
response []C.Rewrite
|
||||
}
|
||||
|
||||
func (rr *RewriteRules) SearchInRequest(do func(C.Rewrite) bool) bool {
|
||||
for _, v := range rr.request {
|
||||
if do(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (rr *RewriteRules) SearchInResponse(do func(C.Rewrite) bool) bool {
|
||||
for _, v := range rr.response {
|
||||
if do(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewRewriteRules(req []C.Rewrite, res []C.Rewrite) *RewriteRules {
|
||||
return &RewriteRules{
|
||||
request: req,
|
||||
response: res,
|
||||
}
|
||||
}
|
||||
|
||||
var _ C.RewriteRule = (*RewriteRules)(nil)
|
212
rewrite/handler.go
Normal file
212
rewrite/handler.go
Normal file
@ -0,0 +1,212 @@
|
||||
package rewrites
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/mitm"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
var _ mitm.Handler = (*RewriteHandler)(nil)
|
||||
|
||||
type RewriteHandler struct{}
|
||||
|
||||
func (*RewriteHandler) HandleRequest(session *mitm.Session) (*http.Request, *http.Response) {
|
||||
var (
|
||||
request = session.Request()
|
||||
response *http.Response
|
||||
)
|
||||
|
||||
rule, sub, found := matchRewriteRule(request.URL.String(), true)
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Infof("[MITM] %s <- request %s", rule.RuleType().String(), request.URL.String())
|
||||
|
||||
switch rule.RuleType() {
|
||||
case C.MitmReject:
|
||||
response = session.NewResponse(http.StatusNotFound, nil)
|
||||
response.Header.Set("Content-Type", "text/html; charset=utf-8")
|
||||
case C.MitmReject200:
|
||||
response = session.NewResponse(http.StatusOK, nil)
|
||||
response.Header.Set("Content-Type", "text/html; charset=utf-8")
|
||||
case C.MitmRejectImg:
|
||||
response = session.NewResponse(http.StatusOK, OnePixelPNG.Body())
|
||||
response.Header.Set("Content-Type", "image/png")
|
||||
response.ContentLength = OnePixelPNG.ContentLength()
|
||||
case C.MitmRejectDict:
|
||||
response = session.NewResponse(http.StatusOK, EmptyDict.Body())
|
||||
response.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
response.ContentLength = EmptyDict.ContentLength()
|
||||
case C.MitmRejectArray:
|
||||
response = session.NewResponse(http.StatusOK, EmptyArray.Body())
|
||||
response.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
response.ContentLength = EmptyArray.ContentLength()
|
||||
case C.Mitm302:
|
||||
response = session.NewResponse(http.StatusFound, nil)
|
||||
response.Header.Set("Location", rule.ReplaceURLPayload(sub))
|
||||
case C.Mitm307:
|
||||
response = session.NewResponse(http.StatusTemporaryRedirect, nil)
|
||||
response.Header.Set("Location", rule.ReplaceURLPayload(sub))
|
||||
case C.MitmRequestHeader:
|
||||
if len(request.Header) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawHeader := &bytes.Buffer{}
|
||||
oldHeader := request.Header
|
||||
if err := oldHeader.Write(rawHeader); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newRawHeader := rule.ReplaceSubPayload(rawHeader.String())
|
||||
tb := textproto.NewReader(bufio.NewReader(strings.NewReader(newRawHeader)))
|
||||
newHeader, err := tb.ReadMIMEHeader()
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, nil
|
||||
}
|
||||
request.Header = http.Header(newHeader)
|
||||
case C.MitmRequestBody:
|
||||
if !CanRewriteBody(request.ContentLength, request.Header.Get("Content-Type")) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, request.ContentLength)
|
||||
_, err := io.ReadFull(request.Body, buf)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newBody := rule.ReplaceSubPayload(string(buf))
|
||||
request.Body = io.NopCloser(strings.NewReader(newBody))
|
||||
request.ContentLength = int64(len(newBody))
|
||||
default:
|
||||
found = false
|
||||
}
|
||||
|
||||
if found {
|
||||
if response != nil {
|
||||
response.Close = true
|
||||
}
|
||||
return request, response
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (*RewriteHandler) HandleResponse(session *mitm.Session) *http.Response {
|
||||
var (
|
||||
request = session.Request()
|
||||
response = session.Response()
|
||||
)
|
||||
|
||||
rule, _, found := matchRewriteRule(request.URL.String(), false)
|
||||
found = found && rule.RuleRegx() != nil
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("[MITM] %s <- response %s", rule.RuleType().String(), request.URL.String())
|
||||
|
||||
switch rule.RuleType() {
|
||||
case C.MitmResponseHeader:
|
||||
if len(response.Header) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawHeader := &bytes.Buffer{}
|
||||
oldHeader := response.Header
|
||||
if err := oldHeader.Write(rawHeader); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
newRawHeader := rule.ReplaceSubPayload(rawHeader.String())
|
||||
tb := textproto.NewReader(bufio.NewReader(strings.NewReader(newRawHeader)))
|
||||
newHeader, err := tb.ReadMIMEHeader()
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
|
||||
response.Header = http.Header(newHeader)
|
||||
response.Header.Set("Content-Length", strconv.FormatInt(response.ContentLength, 10))
|
||||
case C.MitmResponseBody:
|
||||
if !CanRewriteBody(response.ContentLength, response.Header.Get("Content-Type")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := mitm.ReadDecompressedBody(response)
|
||||
_ = response.Body.Close()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
//body, err := mitm.DecodeLatin1(bytes.NewReader(b))
|
||||
//if err != nil {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
newBody := rule.ReplaceSubPayload(string(b))
|
||||
|
||||
//modifiedBody, err := mitm.EncodeLatin1(newBody)
|
||||
//if err != nil {
|
||||
// return nil
|
||||
//}
|
||||
modifiedBody := []byte(newBody)
|
||||
|
||||
response.Body = io.NopCloser(bytes.NewReader(modifiedBody))
|
||||
response.Header.Del("Content-Encoding")
|
||||
response.ContentLength = int64(len(modifiedBody))
|
||||
default:
|
||||
found = false
|
||||
}
|
||||
|
||||
if found {
|
||||
return response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *RewriteHandler) HandleApiRequest(*mitm.Session) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// HandleError session maybe nil
|
||||
func (h *RewriteHandler) HandleError(*mitm.Session, error) {}
|
||||
|
||||
func matchRewriteRule(url string, isRequest bool) (rr C.Rewrite, sub []string, found bool) {
|
||||
rewrites := tunnel.Rewrites()
|
||||
if isRequest {
|
||||
found = rewrites.SearchInRequest(func(r C.Rewrite) bool {
|
||||
sub, err := r.URLRegx().FindStringMatch(url)
|
||||
if err != nil || sub == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
rr = r
|
||||
var groups []string
|
||||
for _, fg := range sub.Groups() {
|
||||
groups = append(groups, fg.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
found = rewrites.SearchInResponse(func(r C.Rewrite) bool {
|
||||
if b, err := r.URLRegx().MatchString(url); b && err == nil {
|
||||
rr = r
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
53
rewrite/parser.go
Normal file
53
rewrite/parser.go
Normal file
@ -0,0 +1,53 @@
|
||||
package rewrites
|
||||
|
||||
import (
|
||||
regexp "github.com/dlclark/regexp2"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
func ParseRewrite(line RawMitmRule) (C.Rewrite, error) {
|
||||
var (
|
||||
urlRegx *regexp.Regexp
|
||||
ruleType *C.RewriteType
|
||||
ruleRegx *regexp.Regexp
|
||||
rulePayload string
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
url := line.Url
|
||||
urlRegx, err = regexp.Compile(strings.Trim(url, " "), regexp.None)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ruleType = &line.Action
|
||||
switch *ruleType {
|
||||
case C.Mitm302, C.Mitm307:
|
||||
{
|
||||
rulePayload = line.New
|
||||
break
|
||||
}
|
||||
case C.MitmRequestHeader, C.MitmRequestBody, C.MitmResponseHeader, C.MitmResponseBody:
|
||||
{
|
||||
var old string
|
||||
if line.Old == nil {
|
||||
old = ".*"
|
||||
} else {
|
||||
old = *line.Old
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(old, regexp.Singleline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ruleRegx = re
|
||||
|
||||
rulePayload = line.New
|
||||
}
|
||||
}
|
||||
|
||||
return NewRewriteRule(urlRegx, *ruleType, ruleRegx, rulePayload), nil
|
||||
}
|
105
rewrite/rewrite.go
Normal file
105
rewrite/rewrite.go
Normal file
@ -0,0 +1,105 @@
|
||||
package rewrites
|
||||
|
||||
import (
|
||||
regexp "github.com/dlclark/regexp2"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type RawMitmRule struct {
|
||||
Url string `yaml:"url" json:"url"`
|
||||
Action C.RewriteType `yaml:"action" json:"action"`
|
||||
Old *string `yaml:"old" json:"old"`
|
||||
New string `yaml:"new" json:"new"`
|
||||
}
|
||||
|
||||
type RewriteRule struct {
|
||||
id string
|
||||
urlRegx *regexp.Regexp
|
||||
ruleType C.RewriteType
|
||||
ruleRegx *regexp.Regexp
|
||||
rulePayload string
|
||||
}
|
||||
|
||||
func (r *RewriteRule) ID() string {
|
||||
return r.id
|
||||
}
|
||||
|
||||
func (r *RewriteRule) URLRegx() *regexp.Regexp {
|
||||
return r.urlRegx
|
||||
}
|
||||
|
||||
func (r *RewriteRule) RuleType() C.RewriteType {
|
||||
return r.ruleType
|
||||
}
|
||||
|
||||
func (r *RewriteRule) RuleRegx() *regexp.Regexp {
|
||||
return r.ruleRegx
|
||||
}
|
||||
|
||||
func (r *RewriteRule) RulePayload() string {
|
||||
return r.rulePayload
|
||||
}
|
||||
|
||||
func (r *RewriteRule) ReplaceURLPayload(matchSub []string) string {
|
||||
url := r.rulePayload
|
||||
|
||||
l := len(matchSub)
|
||||
if l < 2 {
|
||||
return url
|
||||
}
|
||||
|
||||
for i := 1; i < l; i++ {
|
||||
url = strings.ReplaceAll(url, "$"+strconv.Itoa(i), matchSub[i])
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
func (r *RewriteRule) ReplaceSubPayload(oldData string) string {
|
||||
payload := r.rulePayload
|
||||
|
||||
if r.ruleRegx == nil {
|
||||
return oldData
|
||||
}
|
||||
|
||||
sub, err := r.ruleRegx.FindStringMatch(oldData)
|
||||
|
||||
for err == nil && sub != nil {
|
||||
var (
|
||||
groups []string
|
||||
sPayload = payload
|
||||
)
|
||||
for _, fg := range sub.Groups() {
|
||||
groups = append(groups, fg.String())
|
||||
}
|
||||
|
||||
l := len(groups)
|
||||
|
||||
for i := 1; i < l; i++ {
|
||||
sPayload = strings.Replace(payload, "$"+strconv.Itoa(i), groups[i], 1)
|
||||
}
|
||||
|
||||
oldData = strings.Replace(oldData, groups[0], sPayload, 1)
|
||||
|
||||
sub, err = r.ruleRegx.FindNextMatch(sub)
|
||||
}
|
||||
|
||||
return oldData
|
||||
}
|
||||
|
||||
func NewRewriteRule(urlRegx *regexp.Regexp, ruleType C.RewriteType, ruleRegx *regexp.Regexp, rulePayload string) *RewriteRule {
|
||||
id, _ := uuid.NewV4()
|
||||
return &RewriteRule{
|
||||
id: id.String(),
|
||||
urlRegx: urlRegx,
|
||||
ruleType: ruleType,
|
||||
ruleRegx: ruleRegx,
|
||||
rulePayload: rulePayload,
|
||||
}
|
||||
}
|
||||
|
||||
var _ C.Rewrite = (*RewriteRule)(nil)
|
28
rewrite/util.go
Normal file
28
rewrite/util.go
Normal file
@ -0,0 +1,28 @@
|
||||
package rewrites
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var allowContentType = []string{
|
||||
"text/",
|
||||
"application/xhtml",
|
||||
"application/xml",
|
||||
"application/atom+xml",
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
}
|
||||
|
||||
func CanRewriteBody(contentLength int64, contentType string) bool {
|
||||
if contentLength <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range allowContentType {
|
||||
if strings.HasPrefix(contentType, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
52
rules/common/user_gent.go
Normal file
52
rules/common/user_gent.go
Normal file
@ -0,0 +1,52 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
)
|
||||
|
||||
type UserAgent struct {
|
||||
*Base
|
||||
ua string
|
||||
adapter string
|
||||
}
|
||||
|
||||
func (d *UserAgent) RuleType() C.RuleType {
|
||||
return C.UserAgent
|
||||
}
|
||||
|
||||
func (d *UserAgent) Match(metadata *C.Metadata) (bool, string) {
|
||||
if metadata.Type != C.MITM || metadata.UserAgent == "" {
|
||||
return false, d.adapter
|
||||
}
|
||||
|
||||
return strings.Contains(metadata.UserAgent, d.ua), d.adapter
|
||||
}
|
||||
|
||||
func (d *UserAgent) Adapter() string {
|
||||
return d.adapter
|
||||
}
|
||||
|
||||
func (d *UserAgent) Payload() string {
|
||||
return d.ua
|
||||
}
|
||||
|
||||
func (d *UserAgent) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewUserAgent(ua string, adapter string) (*UserAgent, error) {
|
||||
ua = strings.Trim(ua, "*")
|
||||
if ua == "" {
|
||||
return nil, errPayload
|
||||
}
|
||||
|
||||
return &UserAgent{
|
||||
Base: &Base{},
|
||||
ua: ua,
|
||||
adapter: adapter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ C.Rule = (*UserAgent)(nil)
|
34
test/go.mod
34
test/go.mod
@ -14,12 +14,13 @@ require (
|
||||
replace github.com/Dreamacro/clash => ../
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.4 // indirect
|
||||
github.com/3andne/restls-client-go v0.1.6 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/RyuaNerin/go-krypto v1.0.2 // indirect
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/beevik/ntp v1.3.0 // indirect
|
||||
github.com/cilium/ebpf v0.11.0 // indirect
|
||||
github.com/coreos/go-iptables v0.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@ -31,6 +32,7 @@ require (
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.0.0 // indirect
|
||||
@ -44,7 +46,7 @@ require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
@ -52,7 +54,7 @@ require (
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect
|
||||
github.com/metacubex/quic-go v0.37.4-0.20230806014204-ef9b221eec12 // indirect
|
||||
github.com/metacubex/quic-go v0.38.1-0.20230821081539-517fdb17fb28 // indirect
|
||||
github.com/metacubex/sing-shadowsocks v0.2.4 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.1.3 // indirect
|
||||
github.com/metacubex/sing-tun v0.1.11 // indirect
|
||||
@ -60,7 +62,7 @@ require (
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.0.2 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/openacid/low v0.1.21 // indirect
|
||||
@ -71,17 +73,18 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||
github.com/sagernet/sing v0.2.9 // indirect
|
||||
github.com/sagernet/sing-mux v0.1.2 // indirect
|
||||
github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a // indirect
|
||||
github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
|
||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
|
||||
@ -94,19 +97,30 @@ require (
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
github.com/zhangyunhao116/fastrand v0.3.0 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
|
||||
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.53.0-dev.0.20230123225046-4075ef07c5d5 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220326024801-5d1f3d24cb84 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
1542
test/go.sum
1542
test/go.sum
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,26 @@
|
||||
package gun
|
||||
|
||||
func UVarintLen(x uint64) int {
|
||||
i := 0
|
||||
for x >= 0x80 {
|
||||
x >>= 7
|
||||
i++
|
||||
switch {
|
||||
case x < 1<<(7*1):
|
||||
return 1
|
||||
case x < 1<<(7*2):
|
||||
return 2
|
||||
case x < 1<<(7*3):
|
||||
return 3
|
||||
case x < 1<<(7*4):
|
||||
return 4
|
||||
case x < 1<<(7*5):
|
||||
return 5
|
||||
case x < 1<<(7*6):
|
||||
return 6
|
||||
case x < 1<<(7*7):
|
||||
return 7
|
||||
case x < 1<<(7*8):
|
||||
return 8
|
||||
case x < 1<<(7*9):
|
||||
return 9
|
||||
default:
|
||||
return 10
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
@ -49,7 +50,7 @@ type clientImpl struct {
|
||||
openStreams atomic.Int64
|
||||
closed atomic.Bool
|
||||
|
||||
udpInputMap sync.Map
|
||||
udpInputMap *xsync.MapOf[uint32, net.Conn]
|
||||
|
||||
// only ready for PoolClient
|
||||
dialerRef C.Dialer
|
||||
@ -263,11 +264,10 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||
if quicConn != nil {
|
||||
_ = quicConn.CloseWithError(ProtocolError, errStr)
|
||||
}
|
||||
udpInputMap := &t.udpInputMap
|
||||
udpInputMap.Range(func(key, value any) bool {
|
||||
if conn, ok := value.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
udpInputMap := t.udpInputMap
|
||||
udpInputMap.Range(func(key uint32, value net.Conn) bool {
|
||||
conn := value
|
||||
_ = conn.Close()
|
||||
udpInputMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
@ -469,6 +469,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client
|
||||
ClientOption: clientOption,
|
||||
udp: udp,
|
||||
dialerRef: dialerRef,
|
||||
udpInputMap: xsync.NewIntegerMapOf[uint32, net.Conn](),
|
||||
}
|
||||
c := &Client{ci}
|
||||
runtime.SetFinalizer(c, closeClient)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
@ -33,6 +34,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid
|
||||
quicConn: quicConn,
|
||||
uuid: uuid,
|
||||
authCh: make(chan struct{}),
|
||||
udpInputMap: xsync.NewIntegerMapOf[uint32, *atomic.Bool](),
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +47,7 @@ type serverHandler struct {
|
||||
authOk atomic.Bool
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
udpInputMap *xsync.MapOf[uint32, *atomic.Bool]
|
||||
}
|
||||
|
||||
func (s *serverHandler) AuthOk() bool {
|
||||
@ -78,8 +80,7 @@ func (s *serverHandler) parsePacket(packet *Packet, udpRelayMode common.UdpRelay
|
||||
|
||||
assocId = packet.ASSOC_ID
|
||||
|
||||
v, _ := s.udpInputMap.LoadOrStore(assocId, &atomic.Bool{})
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
writeClosed, _ := s.udpInputMap.LoadOrCompute(assocId, func() *atomic.Bool { return &atomic.Bool{} })
|
||||
if writeClosed.Load() {
|
||||
return nil
|
||||
}
|
||||
@ -173,8 +174,7 @@ func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
writeClosed := v.(*atomic.Bool)
|
||||
if writeClosed, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
writeClosed.Store(true)
|
||||
}
|
||||
case HeartbeatType:
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
@ -46,7 +47,7 @@ type clientImpl struct {
|
||||
openStreams atomic.Int64
|
||||
closed atomic.Bool
|
||||
|
||||
udpInputMap sync.Map
|
||||
udpInputMap xsync.MapOf[uint16, net.Conn]
|
||||
|
||||
// only ready for PoolClient
|
||||
dialerRef C.Dialer
|
||||
@ -270,10 +271,9 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||
_ = quicConn.CloseWithError(ProtocolError, errStr)
|
||||
}
|
||||
udpInputMap := &t.udpInputMap
|
||||
udpInputMap.Range(func(key, value any) bool {
|
||||
if conn, ok := value.(net.Conn); ok {
|
||||
_ = conn.Close()
|
||||
}
|
||||
udpInputMap.Range(func(key uint16, value net.Conn) bool {
|
||||
conn := value
|
||||
_ = conn.Close()
|
||||
udpInputMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
@ -406,6 +406,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client
|
||||
ClientOption: clientOption,
|
||||
udp: udp,
|
||||
dialerRef: dialerRef,
|
||||
udpInputMap: *xsync.NewIntegerMapOf[uint16, net.Conn](),
|
||||
}
|
||||
c := &Client{ci}
|
||||
runtime.SetFinalizer(c, closeClient)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
@ -32,6 +33,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid
|
||||
quicConn: quicConn,
|
||||
uuid: uuid,
|
||||
authCh: make(chan struct{}),
|
||||
udpInputMap: xsync.NewIntegerMapOf[uint16, *serverUDPInput](),
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +47,7 @@ type serverHandler struct {
|
||||
authUUID atomic.TypedValue[string]
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
udpInputMap *xsync.MapOf[uint16, *serverUDPInput]
|
||||
}
|
||||
|
||||
func (s *serverHandler) AuthOk() bool {
|
||||
@ -94,8 +96,7 @@ func (s *serverHandler) parsePacket(packet *Packet, udpRelayMode common.UdpRelay
|
||||
|
||||
assocId = packet.ASSOC_ID
|
||||
|
||||
v, _ := s.udpInputMap.LoadOrStore(assocId, &serverUDPInput{})
|
||||
input := v.(*serverUDPInput)
|
||||
input, _ := s.udpInputMap.LoadOrCompute(assocId, func() *serverUDPInput { return &serverUDPInput{} })
|
||||
if input.writeClosed.Load() {
|
||||
return nil
|
||||
}
|
||||
@ -186,8 +187,7 @@ func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
input := v.(*serverUDPInput)
|
||||
if input, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||
input.writeClosed.Store(true)
|
||||
}
|
||||
}
|
||||
|
@ -73,12 +73,9 @@ func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, key string,
|
||||
}
|
||||
|
||||
func closeAllLocalCoon(lAddr string) {
|
||||
natTable.RangeLocalConn(lAddr, func(key, value any) bool {
|
||||
conn, ok := value.(*net.UDPConn)
|
||||
if !ok || conn == nil {
|
||||
log.Debugln("Value %#v unknown value when closing TProxy local conn...", conn)
|
||||
return true
|
||||
}
|
||||
natTable.RangeForLocalConn(lAddr, func(key string, value *net.UDPConn) bool {
|
||||
conn := value
|
||||
|
||||
conn.Close()
|
||||
log.Debugln("Closing TProxy local conn... lAddr=%s rAddr=%s", lAddr, key)
|
||||
return true
|
||||
|
@ -2,11 +2,11 @@ package statistic
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v2"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
@ -14,6 +14,7 @@ var DefaultManager *Manager
|
||||
|
||||
func init() {
|
||||
DefaultManager = &Manager{
|
||||
connections: xsync.NewMapOf[Tracker](),
|
||||
uploadTemp: atomic.NewInt64(0),
|
||||
downloadTemp: atomic.NewInt64(0),
|
||||
uploadBlip: atomic.NewInt64(0),
|
||||
@ -27,7 +28,7 @@ func init() {
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
connections sync.Map
|
||||
connections *xsync.MapOf[string, Tracker]
|
||||
uploadTemp *atomic.Int64
|
||||
downloadTemp *atomic.Int64
|
||||
uploadBlip *atomic.Int64
|
||||
@ -48,14 +49,14 @@ func (m *Manager) Leave(c Tracker) {
|
||||
|
||||
func (m *Manager) Get(id string) (c Tracker) {
|
||||
if value, ok := m.connections.Load(id); ok {
|
||||
c = value.(Tracker)
|
||||
c = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Manager) Range(f func(c Tracker) bool) {
|
||||
m.connections.Range(func(key, value any) bool {
|
||||
return f(value.(Tracker))
|
||||
m.connections.Range(func(key string, value Tracker) bool {
|
||||
return f(value)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ var (
|
||||
udpQueue = make(chan C.PacketAdapter, 200)
|
||||
natTable = nat.New()
|
||||
rules []C.Rule
|
||||
rewrites C.RewriteRule
|
||||
listeners = make(map[string]C.InboundListener)
|
||||
subRules map[string][]C.Rule
|
||||
proxies = make(map[string]C.Proxy)
|
||||
@ -179,6 +180,18 @@ func isHandle(t C.Type) bool {
|
||||
return status == Running || (status == Inner && t == C.INNER)
|
||||
}
|
||||
|
||||
// Rewrites return all rewrites
|
||||
func Rewrites() C.RewriteRule {
|
||||
return rewrites
|
||||
}
|
||||
|
||||
// UpdateRewrites handle update rewrites
|
||||
func UpdateRewrites(rules C.RewriteRule) {
|
||||
configMux.Lock()
|
||||
rewrites = rules
|
||||
configMux.Unlock()
|
||||
}
|
||||
|
||||
// processUDP starts a loop to handle udp packet
|
||||
func processUDP() {
|
||||
queue := udpQueue
|
||||
@ -318,8 +331,7 @@ func handleUDPConn(packet C.PacketAdapter) {
|
||||
return
|
||||
}
|
||||
|
||||
lockKey := key + "-lock"
|
||||
cond, loaded := natTable.GetOrCreateLock(lockKey)
|
||||
cond, loaded := natTable.GetOrCreateLock(key)
|
||||
|
||||
go func() {
|
||||
defer packet.Drop()
|
||||
@ -333,7 +345,7 @@ func handleUDPConn(packet C.PacketAdapter) {
|
||||
}
|
||||
|
||||
defer func() {
|
||||
natTable.Delete(lockKey)
|
||||
natTable.DeleteLock(key)
|
||||
cond.Broadcast()
|
||||
}()
|
||||
|
||||
@ -434,8 +446,9 @@ func handleTCPConn(connCtx C.ConnContext) {
|
||||
return
|
||||
}
|
||||
|
||||
isMitmProxy := metadata.Type == C.MITM
|
||||
dialMetadata := metadata
|
||||
if len(metadata.Host) > 0 {
|
||||
if len(metadata.Host) > 0 && !isMitmProxy {
|
||||
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
||||
if dstIp, _ := node.RandIP(); !FakeIPRange().Contains(dstIp) {
|
||||
dialMetadata.DstIP = dstIp
|
||||
@ -548,6 +561,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
||||
}
|
||||
|
||||
for _, rule := range getRules(metadata) {
|
||||
if metadata.Type == C.MITM && rule.Adapter() == "MITM" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !resolved && shouldResolveIP(rule, metadata) {
|
||||
func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
|
||||
|
Reference in New Issue
Block a user