Compare commits

...

28 Commits

Author SHA1 Message Date
a0fd6cfeea fix: regexp 2023-09-04 13:31:18 +08:00
1f7a883bfc chore: new rule 2023-09-04 00:12:25 +08:00
3fd954d185 feat: regexp2 2023-09-03 22:00:37 +08:00
2092a481b3 feature: MITM 2023-09-03 21:14:27 +08:00
d6b80acfbc chore: Use xsync provided map size calculation 2023-09-02 20:17:43 +08:00
1cad615b25 chore: using xsync.MapOf replace sync.Map 2023-09-02 16:54:48 +08:00
73fa79bf3f feat: configurable TCPKeepAlive interval 2023-09-02 16:45:16 +08:00
d79c13064e chore: cleanup codes 2023-09-02 14:12:53 +08:00
427a377c2a refactor: Decouple .Cleanup from ReCreateTun
The listener.Cleanup method will be called during
executor.Shutdown and route.restart, so it should serve
all kinds of listeners rather than a single tun device.

Currently listener.ReCreateTun will call it to handle
some internal affairs, This should be decoupled.

In this way, the cleanup tasks for data outside the
process life cycle that other listeners will add here
in the future will not be accidentally triggered
by configuring tun.
2023-09-02 14:12:53 +08:00
9feb4d6668 fix: RESTful api missing TunConf.device
In commit 54fee7b, due to failure to take into account that
not all required parameters of `sing_tun.server.New` have
default values provided by `LC.Tun`, the name of the tun device
cannot be obtained when `TunConf.device` is not explicitly
configured. This commit fixed the issue.
2023-09-02 14:12:53 +08:00
a366e9a4b5 fix: ntp service panic 2023-09-02 12:37:43 +08:00
cbdf33c42c feat: ntp service 2023-09-02 02:15:46 +08:00
9ceaf20584 fix: concurrent map writes #707 2023-09-01 10:43:04 +08:00
54fee7bd3a Improve: nicer tun info for RESTful api
Let the restful api still get TunConf even when tun is off.
Otherwise the api will return the default values,
instead of the values that actually take effect after enable.

* Due to this problem, yacd changes the displayed value
back to gvisor immediately after the user selects tun stack.
2023-08-30 21:13:32 +08:00
414d8f2162 chore: use WaitGroup in dualStackDialContext 2023-08-30 17:28:36 +08:00
86cf1dd54b fix: dualStack confusing error on ipv4 failed connect 2023-08-30 17:28:36 +08:00
d099375200 chore: rename func name 2023-08-30 15:52:41 +08:00
9536372cfb fix: call shutdown before restart (#709) 2023-08-30 15:49:28 +08:00
630a17cf90 chore: cleanup codes 2023-08-26 21:20:20 +08:00
0a7b7894bd feat: proxies support direct type 2023-08-24 23:33:03 +08:00
3a9fc39cd9 chore: update quic-go to 0.38.0 2023-08-21 16:18:56 +08:00
1181fd4560 feat: add udp-over-stream for tuic
only work with meta tuic server or sing-box 1.4.0-beta.6
2023-08-21 12:37:39 +08:00
b8a60261ef chore: restore unselected
clear selected node in outboundgoup/URLtest when getGroupDelay triggered
2023-08-18 22:17:07 +08:00
db68d55a0e fix: sing-vmess panic 2023-08-17 22:33:07 +08:00
574efb4526 chore: Update dependencies 2023-08-16 21:30:12 +08:00
03b0252589 feat: bump restls to v0.1.6 (utls v1.4.3) (#692)
* feat: bump restls to v0.1.5 (utls v1.4.3)
* fix: rm dependency go-quic
2023-08-16 11:41:58 +08:00
ed09df4e13 fix: TLS ALPN support 2023-08-14 15:48:13 +08:00
f89ecd97d6 feat: Converter unofficial TUIC share link support 2023-08-14 15:11:33 +08:00
87 changed files with 4177 additions and 423 deletions

View File

@ -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
View 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)
}

View File

@ -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{

View File

@ -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
View 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,
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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
})
}

View File

@ -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{

View File

@ -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
}

View File

@ -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,

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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...)))
},

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

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

32
common/cert/storage.go Normal file
View 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](),
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -32,7 +32,7 @@ func (g GeoIPCache) Set(key string, value *router.GeoIP) {
}
func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) {
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

View File

@ -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) {

View File

@ -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](),
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
View 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
}

View File

@ -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:

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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()

View File

@ -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)

View File

@ -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()))

View File

@ -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:])

View File

@ -41,5 +41,5 @@ func upgrade(w http.ResponseWriter, r *http.Request) {
f.Flush()
}
go runRestart(execPath)
go restartExecutable(execPath)
}

View File

@ -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...)
}

View File

@ -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),

View File

@ -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
}

View File

@ -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

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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))
}

View File

@ -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)

View File

@ -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...)
}

View File

@ -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)
}
}()

View File

@ -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()

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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...)
}

View File

@ -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 {

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)

View File

@ -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
)

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
})
}

View File

@ -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)