Compare commits

..

51 Commits

Author SHA1 Message Date
wwqgtxx
fc9d5cfee9 feat: add external-controller-cors can config allow-origins and allow-private-network 2024-09-29 17:13:43 +08:00
wwqgtxx
264713571d chore: set 0o666 to unix socket file 2024-09-27 22:36:19 +08:00
wwqgtxx
a67c379884 chore: code cleanup 2024-09-27 21:42:06 +08:00
xishang0128
af5ad3254b chore: Use DELETE to clear the proxy group fixed 2024-09-27 21:14:04 +08:00
wwqgtxx
acfc9f8baa chore: reset resolver's connection after default interface changed 2024-09-27 20:36:00 +08:00
wwqgtxx
1633885794 chore: update dependencies 2024-09-27 20:36:00 +08:00
wwqgtxx
2afa2798b1 chore: allow set security descriptor of namedpipe by environment variable LISTEN_NAMEDPIPE_SDDL 2024-09-27 18:31:50 +08:00
wwqgtxx
cd2d1c6bb0 fix: skip-auth-prefixes not apply on listeners when users is unset 2024-09-27 18:10:05 +08:00
wwqgtxx
88bfe7cffe feat: add external-controller-pipe for windows
maybe useful for electron and tauri client, node.js and rust still not support AF_UNIX on windows
2024-09-27 16:09:03 +08:00
wwqgtxx
43cb48231a cache: add dns cache in udp packet sender
reduce the cost of re-resolving DNS for each packet received and prevent the target IP from jumping between multiple resolution results
2024-09-26 22:21:59 +08:00
wwqgtxx
4fa15c6334 chore: ensures packets can be sent without blocking the tunnel 2024-09-26 11:21:07 +08:00
wwqgtxx
5812a7bdeb chore: simplify the code 2024-09-25 21:37:15 +08:00
HamsterReserved
3922b17067 chore: deliver UDP packets from same connection in receiving order (#1540)
All UDP packets are queued into a single channel, and multiple
workers are launched to poll the channel in current design.

This introduces a problem where UDP packets from a single connection
are delivered to different workers, thus forwarded in a random order
if workers are on different CPU cores. Though UDP peers normally
have their own logic to handle out-of-order packets, this behavior will
inevitably cause significant variance in delay and harm connection quality.
Furthermore, this out-of-order behavior is noticeable even if the underlying
transport could provide guaranteed orderly delivery -  this is unacceptable.

This commit takes the idea of RSS in terms of NICs: it creates a distinct
queue for each worker, hashes incoming packets, and distribute the packet
to a worker by hash result. The tuple (SrcIP, SrcPort, DstIP, DstPort, Proto)
is used for hashing (Proto is always UDP so it's dropped from final
implementation), thus packets from the same connection can be sent to
the same worker, keeping the receiving order. Different connections can be
hashed to different workers to maintain performance.

Performance for single UDP connection is not affected, as there is already
a lock in natTable that prevents multiple packets being processed in different
workers, limiting single connection forwarding performance to 1 worker.
The only performance penalty is the hashing code, which should be neglectable
given the footprint of en/decryption work.

Co-authored-by: Hamster Tian <haotia@gmail.com>
2024-09-25 21:28:30 +08:00
wwqgtxx
a4e84f0479 chore: better apply tcp keepalive to listeners 2024-09-25 15:10:53 +08:00
wwqgtxx
6c0383026e fix: AmneziaWG not working 2024-09-24 13:25:13 +08:00
wwqgtxx
59a2b24593 chore: save etag in bbolt by msgpack 2024-09-23 19:25:35 +08:00
wwqgtxx
966eeae41b chore: rewrite bbolt cachefile implements
never use returned byte slices outside the transaction, ref:
https://pkg.go.dev/go.etcd.io/bbolt#hdr-Caveats
2024-09-23 09:35:48 +08:00
wwqgtxx
150c6ccd25 chore: skip duplicates nameserver when parse 2024-09-23 08:54:07 +08:00
wwqgtxx
33823f1728 chore: sync internal interface 2024-09-22 22:45:55 +08:00
wwqgtxx
781b783346 feat: add amnezia-wg-option to wireguard outbound 2024-09-22 22:07:14 +08:00
wwqgtxx
ddfa9e8671 feat: add etag-support to let user can disable this feature manually 2024-09-22 14:41:45 +08:00
wwqgtxx
b7cb6774bf chore: support ETag for update geo 2024-09-22 13:57:57 +08:00
wwqgtxx
5d242510c8 chore: support ETag for providers 2024-09-22 11:42:29 +08:00
wwqgtxx
223eae0e06 chore: force refresh provider in background 2024-09-22 00:24:49 +08:00
wwqgtxx
7dafe7889e chore: disallow space at begin or end in DomainTrie 2024-09-21 21:03:59 +08:00
wwqgtxx
d80e8bb0c2 chore: remove some confusing restrictions on comma separation in NameServerPolicy configuration 2024-09-21 20:03:17 +08:00
wwqgtxx
f52fe6aa74 fix: tun.device not shown in restful api 2024-09-21 19:46:39 +08:00
wwqgtxx
a08aa10630 chore: some internal types support encoding.TextUnmarshaler 2024-09-19 18:36:24 +08:00
wwqgtxx
794645b7f8 chore: direct using structure package decode proxy-name 2024-09-19 18:26:05 +08:00
wwqgtxx
f020b20ab9 chore: structure support encoding.TextUnmarshaler 2024-09-19 18:26:05 +08:00
Chun
3676d1b79f feat: add proxy name replacement functionality for override (#1481)
* feat: add proxy name replacement functionality for override

* style: modify `override schema` info and provider parse error message

---------

Co-authored-by: chun <pujichun@outlook.com>
2024-09-18 22:41:06 +08:00
落心
58c973ee2b fix: NewRejectWithOption has wrong type (#1518)
Co-authored-by: nico <nico@starpay.com>
2024-09-18 22:36:20 +08:00
Larvan2
fb4d3c41c8 chore: simplify VlessFlow Option 2024-09-17 12:03:24 +08:00
wwqgtxx
e33d4a4769 chore: cleanup the patch code 2024-09-12 11:19:54 +08:00
xishang0128
4c3fe98ebd chore: modify the default download address of ASN 2024-09-11 19:24:53 +08:00
wwqgtxx
0a2f606e1b chore: cleanup the patch code 2024-09-11 16:10:49 +08:00
Larvan2
8230bc8e7d chore: parse float in subscription info 2024-09-11 13:34:59 +08:00
wwqgtxx
ecbbf9d220 feat: doh client support ecs and ecs-override 2024-09-11 10:56:51 +08:00
wwqgtxx
f305e440ef fix: new tun with fd unneeded calculate interface name 2024-09-11 10:04:50 +08:00
wwqgtxx
910f236696 fix: UpdateMonitor should start when AutoDetectInterface enabled 2024-09-11 09:52:21 +08:00
wwqgtxx
417d709d60 fix: RawSrcAddr and RawSrcAddr in metadata 2024-09-10 21:46:56 +08:00
wwqgtxx
f8557f5fd8 chore: don't start UpdateMonitor when AutoRoute not enabled 2024-09-10 17:13:13 +08:00
wwqgtxx
89b9438fc0 fix: restful server restart 2024-09-10 16:43:00 +08:00
wwqgtxx
7c8f451892 chore: cleanup geo internal code 2024-09-09 16:08:48 +08:00
wwqgtxx
ef244b896a chore: update geo in a batch 2024-09-09 10:07:50 +08:00
wwqgtxx
595a575cde chore: add Count for ProxyProvider 2024-09-09 10:07:50 +08:00
wwqgtxx
b1301b1b41 chore: update quic-go to 0.47.0 2024-09-09 10:07:50 +08:00
wwqgtxx
dc29514fb6 chore: typo 2024-09-09 10:07:50 +08:00
wwqgtxx
8940bdd56f chore: better pool_test 2024-09-09 10:07:50 +08:00
H1JK
ade4234615 chore: mrs use best compression 2024-09-07 20:22:21 +08:00
xishang0128
faaa90f8a6 feat: Allows passing in base64-encoded configuration strings 2024-09-03 17:55:11 +08:00
121 changed files with 2403 additions and 1354 deletions

View File

@@ -39,6 +39,11 @@ type Proxy struct {
extra *xsync.MapOf[string, *internalProxyState] extra *xsync.MapOf[string, *internalProxyState]
} }
// Adapter implements C.Proxy
func (p *Proxy) Adapter() C.ProxyAdapter {
return p.ProxyAdapter
}
// AliveForTestUrl implements C.Proxy // AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool { func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok { if state, ok := p.extra.Load(url); ok {

View File

@@ -11,6 +11,8 @@ import (
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) { func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseHTTPAddr(request) metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPS metadata.Type = C.HTTPS
metadata.RawSrcAddr = conn.RemoteAddr()
metadata.RawDstAddr = conn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr())) ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...) ApplyAdditions(metadata, additions...)
return conn, metadata return conn, metadata

View File

@@ -3,6 +3,9 @@ package inbound
import ( import (
"context" "context"
"net" "net"
"sync"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/tfo-go" "github.com/metacubex/tfo-go"
) )
@@ -11,28 +14,47 @@ var (
lc = tfo.ListenConfig{ lc = tfo.ListenConfig{
DisableTFO: true, DisableTFO: true,
} }
mutex sync.RWMutex
) )
func SetTfo(open bool) { func SetTfo(open bool) {
mutex.Lock()
defer mutex.Unlock()
lc.DisableTFO = !open lc.DisableTFO = !open
} }
func Tfo() bool { func Tfo() bool {
mutex.RLock()
defer mutex.RUnlock()
return !lc.DisableTFO return !lc.DisableTFO
} }
func SetMPTCP(open bool) { func SetMPTCP(open bool) {
mutex.Lock()
defer mutex.Unlock()
setMultiPathTCP(&lc.ListenConfig, open) setMultiPathTCP(&lc.ListenConfig, open)
} }
func MPTCP() bool { func MPTCP() bool {
mutex.RLock()
defer mutex.RUnlock()
return getMultiPathTCP(&lc.ListenConfig) return getMultiPathTCP(&lc.ListenConfig)
} }
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) { func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
mutex.RLock()
defer mutex.RUnlock()
return lc.Listen(ctx, network, address) return lc.Listen(ctx, network, address)
} }
func Listen(network, address string) (net.Listener, error) { func Listen(network, address string) (net.Listener, error) {
return ListenContext(context.Background(), network, address) return ListenContext(context.Background(), network, address)
} }
func init() {
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
mutex.Lock()
defer mutex.Unlock()
keepalive.SetNetListenConfig(&lc.ListenConfig)
})
}

View File

@@ -0,0 +1,14 @@
//go:build !windows
package inbound
import (
"net"
"os"
)
const SupportNamedPipe = false
func ListenNamedPipe(path string) (net.Listener, error) {
return nil, os.ErrInvalid
}

View File

@@ -0,0 +1,32 @@
package inbound
import (
"net"
"os"
"github.com/metacubex/wireguard-go/ipc/namedpipe"
"golang.org/x/sys/windows"
)
const SupportNamedPipe = true
// windowsSDDL is the Security Descriptor set on the namedpipe.
// It provides read/write access to all users and the local system.
const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)"
func ListenNamedPipe(path string) (net.Listener, error) {
sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL")
if sddl == "" {
sddl = windowsSDDL
}
securityDescriptor, err := windows.SecurityDescriptorFromString(sddl)
if err != nil {
return nil, err
}
namedpipeLC := namedpipe.ListenConfig{
SecurityDescriptor: securityDescriptor,
InputBufferSize: 256 * 1024,
OutputBufferSize: 256 * 1024,
}
return namedpipeLC.Listen(path)
}

View File

@@ -6,7 +6,6 @@ import (
"os" "os"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -38,7 +37,6 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
return d.loopBack.NewConn(NewConn(c, d)), nil return d.loopBack.NewConn(NewConn(c, d)), nil
} }

View File

@@ -7,13 +7,11 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -76,7 +74,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -37,7 +37,7 @@ func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
tp: C.Direct, tp: C.Reject,
udp: true, udp: true,
}, },
} }

View File

@@ -149,7 +149,6 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -80,7 +80,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -6,7 +6,6 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -94,7 +93,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err) return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -122,7 +120,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version) err = snell.WriteUDPHeader(c, s.version)
@@ -207,8 +204,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
}) })
} }

View File

@@ -10,7 +10,6 @@ import (
"net/netip" "net/netip"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -82,7 +81,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -128,7 +126,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c)
var user *socks5.User var user *socks5.User
if ss.user != "" { if ss.user != "" {
user = &socks5.User{ user = &socks5.User{

View File

@@ -77,7 +77,6 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)

View File

@@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -154,7 +153,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
@@ -212,7 +210,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
N.TCPKeepAlive(c)
c, err = t.plainStream(ctx, c) c, err = t.plainStream(ctx, c)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -314,7 +311,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -262,7 +262,6 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -327,7 +326,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -505,17 +503,14 @@ func NewVless(option VlessOption) (*Vless, error) {
var addons *vless.Addons var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 { if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16] option.Flow = option.Flow[:16]
switch option.Flow { if option.Flow != vless.XRV {
case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
addons = &vless.Addons{
Flow: option.Flow,
}
case vless.XRO, vless.XRD, vless.XRS:
log.Fatalln("Legacy XTLS protocol %s is deprecated and no longer supported", option.Flow)
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
} }
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
addons = &vless.Addons{
Flow: option.Flow,
}
} }
switch option.PacketEncoding { switch option.PacketEncoding {
@@ -577,7 +572,6 @@ func NewVless(option VlessOption) (*Vless, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -312,7 +312,6 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -373,7 +372,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
@@ -473,7 +471,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
N.TCPKeepAlive(c)
return c, nil return c, nil
} }

View File

@@ -24,18 +24,24 @@ import (
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/dns"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
amnezia "github.com/metacubex/amneziawg-go/device"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"
"github.com/metacubex/wireguard-go/device"
"github.com/sagernet/sing/common/debug" "github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/wireguard-go/device"
) )
type wireguardGoDevice interface {
Close()
IpcSet(uapiConf string) error
}
type WireGuard struct { type WireGuard struct {
*Base *Base
bind *wireguard.ClientBind bind *wireguard.ClientBind
device *device.Device device wireguardGoDevice
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
resolver *dns.Resolver resolver *dns.Resolver
@@ -67,6 +73,8 @@ type WireGuardOption struct {
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
AmneziaWGOption *AmneziaWGOption `proxy:"amnezia-wg-option,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"` Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
@@ -84,6 +92,18 @@ type WireGuardPeerOption struct {
AllowedIPs []string `proxy:"allowed-ips,omitempty"` AllowedIPs []string `proxy:"allowed-ips,omitempty"`
} }
type AmneziaWGOption struct {
JC int `proxy:"jc"`
JMin int `proxy:"jmin"`
JMax int `proxy:"jmax"`
S1 int `proxy:"s1"`
S2 int `proxy:"s2"`
H1 uint32 `proxy:"h1"`
H2 uint32 `proxy:"h2"`
H3 uint32 `proxy:"h3"`
H4 uint32 `proxy:"h4"`
}
type wgSingErrorHandler struct { type wgSingErrorHandler struct {
name string name string
} }
@@ -243,14 +263,20 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create WireGuard device") return nil, E.Cause(err, "create WireGuard device")
} }
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{ logger := &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
Errorf: func(format string, args ...interface{}) { Errorf: func(format string, args ...interface{}) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
}, },
}, option.Workers) }
if option.AmneziaWGOption != nil {
outbound.bind.SetParseReserved(false) // AmneziaWG don't need parse reserved
outbound.device = amnezia.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
} else {
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
}
var has6 bool var has6 bool
for _, address := range outbound.localPrefixes { for _, address := range outbound.localPrefixes {
@@ -270,7 +296,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
for i := range nss { for i := range nss {
nss[i].ProxyAdapter = refP nss[i].ProxyAdapter = refP
} }
outbound.resolver = dns.NewResolver(dns.Config{ outbound.resolver, _ = dns.NewResolver(dns.Config{
Main: nss, Main: nss,
IPv6: has6, IPv6: has6,
}) })
@@ -367,6 +393,17 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
ipcConf := "" ipcConf := ""
if !updateOnly { if !updateOnly {
ipcConf += "private_key=" + w.option.PrivateKey + "\n" ipcConf += "private_key=" + w.option.PrivateKey + "\n"
if w.option.AmneziaWGOption != nil {
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
}
} }
if len(w.option.Peers) > 0 { if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers { for i, peer := range w.option.Peers {

View File

@@ -4,3 +4,7 @@ type SelectAble interface {
Set(string) error Set(string) error
ForceSet(name string) ForceSet(name string)
} }
var _ SelectAble = (*Fallback)(nil)
var _ SelectAble = (*URLTest)(nil)
var _ SelectAble = (*Selector)(nil)

View File

@@ -1,6 +1,7 @@
package provider package provider
import ( import (
"encoding"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@@ -9,8 +10,9 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/dlclark/regexp2"
) )
var ( var (
@@ -27,6 +29,15 @@ type healthCheckSchema struct {
ExpectedStatus string `provider:"expected-status,omitempty"` ExpectedStatus string `provider:"expected-status,omitempty"`
} }
type OverrideProxyNameSchema struct {
// matching expression for regex replacement
Pattern *regexp2.Regexp `provider:"pattern"`
// the new content after regex matching
Target string `provider:"target"`
}
var _ encoding.TextUnmarshaler = (*regexp2.Regexp)(nil) // ensure *regexp2.Regexp can decode direct by structure package
type OverrideSchema struct { type OverrideSchema struct {
TFO *bool `provider:"tfo,omitempty"` TFO *bool `provider:"tfo,omitempty"`
MPTcp *bool `provider:"mptcp,omitempty"` MPTcp *bool `provider:"mptcp,omitempty"`
@@ -41,6 +52,8 @@ type OverrideSchema struct {
IPVersion *string `provider:"ip-version,omitempty"` IPVersion *string `provider:"ip-version,omitempty"`
AdditionalPrefix *string `provider:"additional-prefix,omitempty"` AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
AdditionalSuffix *string `provider:"additional-suffix,omitempty"` AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
ProxyName []OverrideProxyNameSchema `provider:"proxy-name,omitempty"`
} }
type proxyProviderSchema struct { type proxyProviderSchema struct {
@@ -94,11 +107,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
path := C.Path.GetPathByHash("proxies", schema.URL) path := C.Path.GetPathByHash("proxies", schema.URL)
if schema.Path != "" { if schema.Path != "" {
path = C.Path.Resolve(schema.Path) path = C.Path.Resolve(schema.Path)
if !features.CMFA && !C.Path.IsSafePath(path) { if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, fmt.Errorf("%w: %s", errSubPath, path)
} }
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header) vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout)
default: default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
} }

View File

@@ -1,19 +0,0 @@
//go:build android && cmfa
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func Suspend(s bool) {
suspended = s
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/metacubex/mihomo/component/resource" "github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
@@ -72,19 +71,15 @@ func (pp *proxySetProvider) HealthCheck() {
} }
func (pp *proxySetProvider) Update() error { func (pp *proxySetProvider) Update() error {
elm, same, err := pp.Fetcher.Update() _, _, err := pp.Fetcher.Update()
if err == nil && !same {
pp.OnUpdate(elm)
}
return err return err
} }
func (pp *proxySetProvider) Initial() error { func (pp *proxySetProvider) Initial() error {
elm, err := pp.Fetcher.Initial() _, err := pp.Fetcher.Initial()
if err != nil { if err != nil {
return err return err
} }
pp.OnUpdate(elm)
pp.getSubscriptionInfo() pp.getSubscriptionInfo()
pp.closeAllConnections() pp.closeAllConnections()
return nil return nil
@@ -98,6 +93,10 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies return pp.proxies
} }
func (pp *proxySetProvider) Count() int {
return len(pp.proxies)
}
func (pp *proxySetProvider) Touch() { func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch() pp.healthCheck.touch()
} }
@@ -125,8 +124,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
go func() { go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy()) http.MethodGet, nil, nil, pp.Vehicle().Proxy())
if err != nil { if err != nil {
return return
} }
@@ -134,7 +133,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo")) userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" { if userInfoStr == "" {
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(), resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy()) http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
if err != nil { if err != nil {
return return
@@ -145,10 +144,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
return return
} }
} }
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr) pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
}
}() }()
} }
@@ -267,6 +263,10 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies return cp.proxies
} }
func (cp *compatibleProvider) Count() int {
return len(cp.proxies)
}
func (cp *compatibleProvider) Touch() { func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch() cp.healthCheck.touch()
} }
@@ -399,6 +399,16 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
case "additional-suffix": case "additional-suffix":
name := mapping["name"].(string) name := mapping["name"].(string)
mapping["name"] = name + *field.Interface().(*string) mapping["name"] = name + *field.Interface().(*string)
case "proxy-name":
// Iterate through all naming replacement rules and perform the replacements.
for _, expr := range override.ProxyName {
name := mapping["name"].(string)
newName, err := expr.Pattern.Replace(name, expr.Target, 0, -1)
if err != nil {
return nil, fmt.Errorf("proxy name replace error: %w", err)
}
mapping["name"] = newName
}
default: default:
mapping[fieldName] = field.Elem().Interface() mapping[fieldName] = field.Elem().Interface()
} }

View File

@@ -1,8 +1,11 @@
package provider package provider
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"github.com/metacubex/mihomo/log"
) )
type SubscriptionInfo struct { type SubscriptionInfo struct {
@@ -12,28 +15,46 @@ type SubscriptionInfo struct {
Expire int64 Expire int64
} }
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) { func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
userinfo = strings.ToLower(userinfo) userinfo = strings.ToLower(userinfo)
userinfo = strings.ReplaceAll(userinfo, " ", "") userinfo = strings.ReplaceAll(userinfo, " ", "")
si = new(SubscriptionInfo) si = new(SubscriptionInfo)
for _, field := range strings.Split(userinfo, ";") { for _, field := range strings.Split(userinfo, ";") {
switch name, value, _ := strings.Cut(field, "="); name { name, value, ok := strings.Cut(field, "=")
case "upload": if !ok {
si.Upload, err = strconv.ParseInt(value, 10, 64) continue
case "download":
si.Download, err = strconv.ParseInt(value, 10, 64)
case "total":
si.Total, err = strconv.ParseInt(value, 10, 64)
case "expire":
if value == "" {
si.Expire = 0
} else {
si.Expire, err = strconv.ParseInt(value, 10, 64)
}
} }
intValue, err := parseValue(value)
if err != nil { if err != nil {
return log.Warnln("[Provider] get subscription-userinfo: %e", err)
continue
}
switch name {
case "upload":
si.Upload = intValue
case "download":
si.Download = intValue
case "total":
si.Total = intValue
case "expire":
si.Expire = intValue
} }
} }
return
return si
}
func parseValue(value string) (int64, error) {
if intValue, err := strconv.ParseInt(value, 10, 64); err == nil {
return intValue, nil
}
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
return int64(floatValue), nil
}
return 0, fmt.Errorf("failed to parse value '%s'", value)
} }

View File

@@ -33,15 +33,8 @@ type ARC[K comparable, V any] struct {
// New returns a new Adaptive Replacement Cache (ARC). // New returns a new Adaptive Replacement Cache (ARC).
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] { func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
arc := &ARC[K, V]{ arc := &ARC[K, V]{}
p: 0, arc.Clear()
t1: list.New[*entry[K, V]](),
b1: list.New[*entry[K, V]](),
t2: list.New[*entry[K, V]](),
b2: list.New[*entry[K, V]](),
len: 0,
cache: make(map[K]*entry[K, V]),
}
for _, option := range options { for _, option := range options {
option(arc) option(arc)
@@ -49,6 +42,19 @@ func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
return arc return arc
} }
func (a *ARC[K, V]) Clear() {
a.mutex.Lock()
defer a.mutex.Unlock()
a.p = 0
a.t1 = list.New[*entry[K, V]]()
a.b1 = list.New[*entry[K, V]]()
a.t2 = list.New[*entry[K, V]]()
a.b2 = list.New[*entry[K, V]]()
a.len = 0
a.cache = make(map[K]*entry[K, V])
}
// Set inserts a new key-value pair into the cache. // Set inserts a new key-value pair into the cache.
// This optimizes future access to this entry (side effect). // This optimizes future access to this entry (side effect).
func (a *ARC[K, V]) Set(key K, value V) { func (a *ARC[K, V]) Set(key K, value V) {

View File

@@ -68,10 +68,8 @@ type LruCache[K comparable, V any] struct {
// New creates an LruCache // New creates an LruCache
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{ lc := &LruCache[K, V]{}
lru: list.New[*entry[K, V]](), lc.Clear()
cache: make(map[K]*list.Element[*entry[K, V]]),
}
for _, option := range options { for _, option := range options {
option(lc) option(lc)
@@ -80,6 +78,14 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
return lc return lc
} }
func (c *LruCache[K, V]) Clear() {
c.mu.Lock()
defer c.mu.Unlock()
c.lru = list.New[*entry[K, V]]()
c.cache = make(map[K]*list.Element[*entry[K, V]])
}
// Get returns any representation of a cached response and a bool // Get returns any representation of a cached response and a bool
// set to true if the key was found. // set to true if the key was found.
func (c *LruCache[K, V]) Get(key K) (V, bool) { func (c *LruCache[K, V]) Get(key K) (V, bool) {
@@ -250,15 +256,6 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
} }
} }
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[K]*list.Element[*entry[K, V]])
return nil
}
// Compute either sets the computed new value for the key or deletes // Compute either sets the computed new value for the key or deletes
// the value for the key. When the delete result of the valueFn function // the value for the key. When the delete result of the valueFn function
// is set to true, the value will be deleted, if it exists. When delete // is set to true, the value will be deleted, if it exists. When delete

View File

@@ -1,23 +0,0 @@
package net
import (
"net"
"runtime"
"time"
)
var (
KeepAliveIdle = 0 * time.Second
KeepAliveInterval = 0 * time.Second
DisableKeepAlive = false
)
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
if runtime.GOOS == "android" || DisableKeepAlive {
_ = tcp.SetKeepAlive(false)
} else {
tcpKeepAlive(tcp)
}
}
}

View File

@@ -1,10 +0,0 @@
//go:build !go1.23
package net
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
}

View File

@@ -1,19 +0,0 @@
//go:build go1.23
package net
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
config := net.KeepAliveConfig{
Enable: true,
Idle: KeepAliveIdle,
Interval: KeepAliveInterval,
}
if !SupportTCPKeepAliveCount() {
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
config.Count = -1
}
_ = tcp.SetKeepAliveConfig(config)
}

View File

@@ -3,6 +3,7 @@ package structure
// references: https://github.com/mitchellh/mapstructure // references: https://github.com/mitchellh/mapstructure
import ( import (
"encoding"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"reflect" "reflect"
@@ -86,35 +87,41 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
} }
func (d *Decoder) decode(name string, data any, val reflect.Value) error { func (d *Decoder) decode(name string, data any, val reflect.Value) error {
kind := val.Kind() for {
switch { kind := val.Kind()
case isInt(kind): if kind == reflect.Pointer && val.IsNil() {
return d.decodeInt(name, data, val)
case isUint(kind):
return d.decodeUint(name, data, val)
case isFloat(kind):
return d.decodeFloat(name, data, val)
}
switch kind {
case reflect.Pointer:
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem())) val.Set(reflect.New(val.Type().Elem()))
} }
return d.decode(name, data, val.Elem()) if ok, err := d.decodeTextUnmarshaller(name, data, val); ok {
case reflect.String: return err
return d.decodeString(name, data, val) }
case reflect.Bool: switch {
return d.decodeBool(name, data, val) case isInt(kind):
case reflect.Slice: return d.decodeInt(name, data, val)
return d.decodeSlice(name, data, val) case isUint(kind):
case reflect.Map: return d.decodeUint(name, data, val)
return d.decodeMap(name, data, val) case isFloat(kind):
case reflect.Interface: return d.decodeFloat(name, data, val)
return d.setInterface(name, data, val) }
case reflect.Struct: switch kind {
return d.decodeStruct(name, data, val) case reflect.Pointer:
default: val = val.Elem()
return fmt.Errorf("type %s not support", val.Kind().String()) continue
case reflect.String:
return d.decodeString(name, data, val)
case reflect.Bool:
return d.decodeBool(name, data, val)
case reflect.Slice:
return d.decodeSlice(name, data, val)
case reflect.Map:
return d.decodeMap(name, data, val)
case reflect.Interface:
return d.setInterface(name, data, val)
case reflect.Struct:
return d.decodeStruct(name, data, val)
default:
return fmt.Errorf("type %s not support", val.Kind().String())
}
} }
} }
@@ -553,3 +560,25 @@ func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err er
val.Set(dataVal) val.Set(dataVal)
return nil return nil
} }
func (d *Decoder) decodeTextUnmarshaller(name string, data any, val reflect.Value) (bool, error) {
if !val.CanAddr() {
return false, nil
}
valAddr := val.Addr()
if !valAddr.CanInterface() {
return false, nil
}
unmarshaller, ok := valAddr.Interface().(encoding.TextUnmarshaler)
if !ok {
return false, nil
}
var str string
if err := d.decodeString(name, data, reflect.Indirect(reflect.ValueOf(&str))); err != nil {
return false, err
}
if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
return true, fmt.Errorf("cannot parse '%s' as %s: %s", name, val.Type(), err)
}
return true, nil
}

View File

@@ -1,6 +1,7 @@
package structure package structure
import ( import (
"strconv"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -179,3 +180,90 @@ func TestStructure_SliceNilValueComplex(t *testing.T) {
err = decoder.Decode(rawMap, ss) err = decoder.Decode(rawMap, ss)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestStructure_SliceCap(t *testing.T) {
rawMap := map[string]any{
"foo": []string{},
}
s := &struct {
Foo []string `test:"foo,omitempty"`
Bar []string `test:"bar,omitempty"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.NotNil(t, s.Foo) // structure's Decode will ensure value not nil when input has value even it was set an empty array
assert.Nil(t, s.Bar)
}
func TestStructure_Base64(t *testing.T) {
rawMap := map[string]any{
"foo": "AQID",
}
s := &struct {
Foo []byte `test:"foo"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, []byte{1, 2, 3}, s.Foo)
}
func TestStructure_Pointer(t *testing.T) {
rawMap := map[string]any{
"foo": "foo",
}
s := &struct {
Foo *string `test:"foo,omitempty"`
Bar *string `test:"bar,omitempty"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.NotNil(t, s.Foo)
assert.Equal(t, "foo", *s.Foo)
assert.Nil(t, s.Bar)
}
type num struct {
a int
}
func (n *num) UnmarshalText(text []byte) (err error) {
n.a, err = strconv.Atoi(string(text))
return
}
func TestStructure_TextUnmarshaller(t *testing.T) {
rawMap := map[string]any{
"num": "255",
"num_p": "127",
}
s := &struct {
Num num `test:"num"`
NumP *num `test:"num_p"`
}{}
err := decoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, 255, s.Num.a)
assert.NotNil(t, s.NumP)
assert.Equal(t, s.NumP.a, 127)
// test WeaklyTypedInput
rawMap["num"] = 256
err = decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s)
err = weakTypeDecoder.Decode(rawMap, s)
assert.Nil(t, err)
assert.Equal(t, 256, s.Num.a)
// test invalid input
rawMap["num_p"] = "abc"
err = decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s)
}

62
common/utils/hash.go Normal file
View File

@@ -0,0 +1,62 @@
package utils
import (
"crypto/md5"
"encoding/hex"
"errors"
)
// HashType warps hash array inside struct
// someday can change to other hash algorithm simply
type HashType struct {
md5 [md5.Size]byte // MD5
}
func MakeHash(data []byte) HashType {
return HashType{md5.Sum(data)}
}
func (h HashType) Equal(hash HashType) bool {
return h.md5 == hash.md5
}
func (h HashType) Bytes() []byte {
return h.md5[:]
}
func (h HashType) String() string {
return hex.EncodeToString(h.Bytes())
}
func (h HashType) MarshalText() ([]byte, error) {
return []byte(h.String()), nil
}
func (h *HashType) UnmarshalText(data []byte) error {
if hex.DecodedLen(len(data)) != md5.Size {
return errors.New("invalid hash length")
}
_, err := hex.Decode(h.md5[:], data)
return err
}
func (h HashType) MarshalBinary() ([]byte, error) {
return h.md5[:], nil
}
func (h *HashType) UnmarshalBinary(data []byte) error {
if len(data) != md5.Size {
return errors.New("invalid hash length")
}
copy(h.md5[:], data)
return nil
}
func (h HashType) Len() int {
return len(h.md5)
}
func (h HashType) IsValid() bool {
var zero HashType
return h != zero
}

View File

@@ -5,6 +5,11 @@ type Authenticator interface {
Users() []string Users() []string
} }
type AuthStore interface {
Authenticator() Authenticator
SetAuthenticator(Authenticator)
}
type AuthUser struct { type AuthUser struct {
User string User string
Pass string Pass string

View File

@@ -12,6 +12,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -84,7 +85,7 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
if cfg.addrReuse { if cfg.addrReuse {
addrReuseToListenConfig(lc) addrReuseToListenConfig(lc)
} }
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CFMA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
socketHookToListenConfig(lc) socketHookToListenConfig(lc)
} else { } else {
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
@@ -144,11 +145,12 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
} }
dialer := netDialer.(*net.Dialer) dialer := netDialer.(*net.Dialer)
keepalive.SetNetDialer(dialer)
if opt.mpTcp { if opt.mpTcp {
setMultiPathTCP(dialer) setMultiPathTCP(dialer)
} }
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CFMA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer) socketHookToToDialer(dialer)
} else { } else {
if opt.interfaceName != "" { if opt.interfaceName != "" {

View File

@@ -7,11 +7,11 @@ import (
) )
// SocketControl // SocketControl
// never change type traits because it's used in CFMA // never change type traits because it's used in CMFA
type SocketControl func(network, address string, conn syscall.RawConn) error type SocketControl func(network, address string, conn syscall.RawConn) error
// DefaultSocketHook // DefaultSocketHook
// never change type traits because it's used in CFMA // never change type traits because it's used in CMFA
var DefaultSocketHook SocketControl var DefaultSocketHook SocketControl
func socketHookToToDialer(dialer *net.Dialer) { func socketHookToToDialer(dialer *net.Dialer) {

View File

@@ -7,46 +7,32 @@ import (
) )
type cachefileStore struct { type cachefileStore struct {
cache *cachefile.CacheFile cache *cachefile.FakeIpStore
} }
// GetByHost implements store.GetByHost // GetByHost implements store.GetByHost
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) { func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
elm := c.cache.GetFakeip([]byte(host)) return c.cache.GetByHost(host)
if elm == nil {
return netip.Addr{}, false
}
if len(elm) == 4 {
return netip.AddrFrom4(*(*[4]byte)(elm)), true
} else {
return netip.AddrFrom16(*(*[16]byte)(elm)), true
}
} }
// PutByHost implements store.PutByHost // PutByHost implements store.PutByHost
func (c *cachefileStore) PutByHost(host string, ip netip.Addr) { func (c *cachefileStore) PutByHost(host string, ip netip.Addr) {
c.cache.PutFakeip([]byte(host), ip.AsSlice()) c.cache.PutByHost(host, ip)
} }
// GetByIP implements store.GetByIP // GetByIP implements store.GetByIP
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) { func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
elm := c.cache.GetFakeip(ip.AsSlice()) return c.cache.GetByIP(ip)
if elm == nil {
return "", false
}
return string(elm), true
} }
// PutByIP implements store.PutByIP // PutByIP implements store.PutByIP
func (c *cachefileStore) PutByIP(ip netip.Addr, host string) { func (c *cachefileStore) PutByIP(ip netip.Addr, host string) {
c.cache.PutFakeip(ip.AsSlice(), []byte(host)) c.cache.PutByIP(ip, host)
} }
// DelByIP implements store.DelByIP // DelByIP implements store.DelByIP
func (c *cachefileStore) DelByIP(ip netip.Addr) { func (c *cachefileStore) DelByIP(ip netip.Addr) {
addr := ip.AsSlice() c.cache.DelByIP(ip)
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
} }
// Exist implements store.Exist // Exist implements store.Exist
@@ -63,3 +49,7 @@ func (c *cachefileStore) CloneTo(store store) {}
func (c *cachefileStore) FlushFakeIP() error { func (c *cachefileStore) FlushFakeIP() error {
return c.cache.FlushFakeIP() return c.cache.FlushFakeIP()
} }
func newCachefileStore(cache *cachefile.CacheFile) *cachefileStore {
return &cachefileStore{cache.FakeIpStore()}
}

View File

@@ -67,8 +67,9 @@ func (m *memoryStore) CloneTo(store store) {
// FlushFakeIP implements store.FlushFakeIP // FlushFakeIP implements store.FlushFakeIP
func (m *memoryStore) FlushFakeIP() error { func (m *memoryStore) FlushFakeIP() error {
_ = m.cacheIP.Clear() m.cacheIP.Clear()
return m.cacheHost.Clear() m.cacheHost.Clear()
return nil
} }
func newMemoryStore(size int) *memoryStore { func newMemoryStore(size int) *memoryStore {

View File

@@ -201,9 +201,7 @@ func New(options Options) (*Pool, error) {
ipnet: options.IPNet, ipnet: options.IPNet,
} }
if options.Persistence { if options.Persistence {
pool.store = &cachefileStore{ pool.store = newCachefileStore(cachefile.Cache())
cache: cachefile.Cache(),
}
} else { } else {
pool.store = newMemoryStore(options.Size) pool.store = newMemoryStore(options.Size)
} }

View File

@@ -43,9 +43,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
return nil, "", err return nil, "", err
} }
pool.store = &cachefileStore{ pool.store = newCachefileStore(&cachefile.CacheFile{DB: db})
cache: &cachefile.CacheFile{DB: db},
}
return pool, f.Name(), nil return pool, f.Name(), nil
} }
@@ -63,13 +61,13 @@ func TestPool_Basic(t *testing.T) {
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last) bar, exist := pool.LookBack(last)
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.True(t, pool.Lookup("foo.com") == netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, pool.Lookup("foo.com"), netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5}))
assert.True(t, exist) assert.True(t, exist)
assert.Equal(t, bar, "bar.com") assert.Equal(t, bar, "bar.com")
assert.True(t, pool.Gateway() == netip.AddrFrom4([4]byte{192, 168, 0, 1})) assert.Equal(t, pool.Gateway(), netip.AddrFrom4([4]byte{192, 168, 0, 1}))
assert.True(t, pool.Broadcast() == netip.AddrFrom4([4]byte{192, 168, 0, 15})) assert.Equal(t, pool.Broadcast(), netip.AddrFrom4([4]byte{192, 168, 0, 15}))
assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5}))) assert.True(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 5})))
assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6}))) assert.False(t, pool.Exist(netip.AddrFrom4([4]byte{192, 168, 0, 6})))
@@ -91,13 +89,13 @@ func TestPool_BasicV6(t *testing.T) {
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
bar, exist := pool.LookBack(last) bar, exist := pool.LookBack(last)
assert.True(t, first == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.Equal(t, first, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.True(t, pool.Lookup("foo.com") == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804")) assert.Equal(t, pool.Lookup("foo.com"), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8804"))
assert.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")) assert.Equal(t, last, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))
assert.True(t, exist) assert.True(t, exist)
assert.Equal(t, bar, "bar.com") assert.Equal(t, bar, "bar.com")
assert.True(t, pool.Gateway() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801")) assert.Equal(t, pool.Gateway(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
assert.True(t, pool.Broadcast() == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff")) assert.Equal(t, pool.Broadcast(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
assert.Equal(t, pool.IPNet().String(), ipnet.String()) assert.Equal(t, pool.IPNet().String(), ipnet.String())
assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))) assert.True(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805")))
assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806"))) assert.False(t, pool.Exist(netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8806")))
@@ -143,8 +141,8 @@ func TestPool_CycleUsed(t *testing.T) {
} }
baz := pool.Lookup("baz.com") baz := pool.Lookup("baz.com")
next := pool.Lookup("foo.com") next := pool.Lookup("foo.com")
assert.True(t, foo == baz) assert.Equal(t, foo, baz)
assert.True(t, next == bar) assert.Equal(t, next, bar)
} }
} }
@@ -201,7 +199,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
pool.Lookup("baz.com") pool.Lookup("baz.com")
next := pool.Lookup("foo.com") next := pool.Lookup("foo.com")
assert.False(t, first == next) assert.NotEqual(t, first, next)
} }
func TestPool_DoubleMapping(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) {
@@ -231,7 +229,7 @@ func TestPool_DoubleMapping(t *testing.T) {
assert.False(t, bazExist) assert.False(t, bazExist)
assert.True(t, barExist) assert.True(t, barExist)
assert.False(t, bazIP == newBazIP) assert.NotEqual(t, bazIP, newBazIP)
} }
func TestPool_Clone(t *testing.T) { func TestPool_Clone(t *testing.T) {
@@ -243,8 +241,8 @@ func TestPool_Clone(t *testing.T) {
first := pool.Lookup("foo.com") first := pool.Lookup("foo.com")
last := pool.Lookup("bar.com") last := pool.Lookup("bar.com")
assert.True(t, first == netip.AddrFrom4([4]byte{192, 168, 0, 4})) assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4}))
assert.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5})) assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5}))
newPool, _ := New(Options{ newPool, _ := New(Options{
IPNet: ipnet, IPNet: ipnet,
@@ -289,13 +287,13 @@ func TestPool_FlushFileCache(t *testing.T) {
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.True(t, foo == fox) assert.Equal(t, foo, fox)
assert.True(t, foo == next) assert.Equal(t, foo, next)
assert.False(t, foo == baz) assert.NotEqual(t, foo, baz)
assert.True(t, bar == bax) assert.Equal(t, bar, bax)
assert.True(t, bar == baz) assert.Equal(t, bar, baz)
assert.False(t, bar == next) assert.NotEqual(t, bar, next)
assert.True(t, baz == nero) assert.Equal(t, baz, nero)
} }
} }
@@ -318,11 +316,11 @@ func TestPool_FlushMemoryCache(t *testing.T) {
baz := pool.Lookup("foo.com") baz := pool.Lookup("foo.com")
nero := pool.Lookup("foo.com") nero := pool.Lookup("foo.com")
assert.True(t, foo == fox) assert.Equal(t, foo, fox)
assert.True(t, foo == next) assert.Equal(t, foo, next)
assert.False(t, foo == baz) assert.NotEqual(t, foo, baz)
assert.True(t, bar == bax) assert.Equal(t, bar, bax)
assert.True(t, bar == baz) assert.Equal(t, bar, baz)
assert.False(t, bar == next) assert.NotEqual(t, bar, next)
assert.True(t, baz == nero) assert.Equal(t, baz, nero)
} }

View File

@@ -6,8 +6,10 @@ import (
"io" "io"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
"github.com/metacubex/mihomo/common/atomic"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -18,12 +20,79 @@ var (
initGeoSite bool initGeoSite bool
initGeoIP int initGeoIP int
initASN bool initASN bool
initGeoSiteMutex sync.Mutex
initGeoIPMutex sync.Mutex
initASNMutex sync.Mutex
geoIpEnable atomic.Bool
geoSiteEnable atomic.Bool
asnEnable atomic.Bool
geoIpUrl string
mmdbUrl string
geoSiteUrl string
asnUrl string
) )
func GeoIpUrl() string {
return geoIpUrl
}
func SetGeoIpUrl(url string) {
geoIpUrl = url
}
func MmdbUrl() string {
return mmdbUrl
}
func SetMmdbUrl(url string) {
mmdbUrl = url
}
func GeoSiteUrl() string {
return geoSiteUrl
}
func SetGeoSiteUrl(url string) {
geoSiteUrl = url
}
func ASNUrl() string {
return asnUrl
}
func SetASNUrl(url string) {
asnUrl = url
}
func downloadToPath(url string, path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, nil, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func InitGeoSite() error { func InitGeoSite() error {
geoSiteEnable.Store(true)
initGeoSiteMutex.Lock()
defer initGeoSiteMutex.Unlock()
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download") log.Infoln("Can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil { if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
} }
log.Infoln("Download GeoSite.dat finish") log.Infoln("Download GeoSite.dat finish")
@@ -35,7 +104,7 @@ func InitGeoSite() error {
if err := os.Remove(C.Path.GeoSite()); err != nil { if err := os.Remove(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
} }
if err := downloadGeoSite(C.Path.GeoSite()); err != nil { if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
} }
} }
@@ -44,49 +113,14 @@ func InitGeoSite() error {
return nil return nil
} }
func downloadGeoSite(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func downloadGeoIP(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func InitGeoIP() error { func InitGeoIP() error {
if C.GeodataMode { geoIpEnable.Store(true)
initGeoIPMutex.Lock()
defer initGeoIPMutex.Unlock()
if GeodataMode() {
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
log.Infoln("Can't find GeoIP.dat, start download") log.Infoln("Can't find GeoIP.dat, start download")
if err := downloadGeoIP(C.Path.GeoIP()); err != nil { if err := downloadToPath(GeoIpUrl(), C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
} }
log.Infoln("Download GeoIP.dat finish") log.Infoln("Download GeoIP.dat finish")
@@ -99,7 +133,7 @@ func InitGeoIP() error {
if err := os.Remove(C.Path.GeoIP()); err != nil { if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
} }
if err := downloadGeoIP(C.Path.GeoIP()); err != nil { if err := downloadToPath(GeoIpUrl(), C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
} }
} }
@@ -110,7 +144,7 @@ func InitGeoIP() error {
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download") log.Infoln("Can't find MMDB, start download")
if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { if err := downloadToPath(MmdbUrl(), C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error()) return fmt.Errorf("can't download MMDB: %s", err.Error())
} }
} }
@@ -121,7 +155,7 @@ func InitGeoIP() error {
if err := os.Remove(C.Path.MMDB()); err != nil { if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
} }
if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { if err := downloadToPath(MmdbUrl(), C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error()) return fmt.Errorf("can't download MMDB: %s", err.Error())
} }
} }
@@ -131,9 +165,12 @@ func InitGeoIP() error {
} }
func InitASN() error { func InitASN() error {
asnEnable.Store(true)
initASNMutex.Lock()
defer initASNMutex.Unlock()
if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) {
log.Infoln("Can't find ASN.mmdb, start download") log.Infoln("Can't find ASN.mmdb, start download")
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil { if err := downloadToPath(ASNUrl(), C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN.mmdb: %s", err.Error()) return fmt.Errorf("can't download ASN.mmdb: %s", err.Error())
} }
log.Infoln("Download ASN.mmdb finish") log.Infoln("Download ASN.mmdb finish")
@@ -145,7 +182,7 @@ func InitASN() error {
if err := os.Remove(C.Path.ASN()); err != nil { if err := os.Remove(C.Path.ASN()); err != nil {
return fmt.Errorf("can't remove invalid ASN: %s", err.Error()) return fmt.Errorf("can't remove invalid ASN: %s", err.Error())
} }
if err := mmdb.DownloadASN(C.Path.ASN()); err != nil { if err := downloadToPath(ASNUrl(), C.Path.ASN()); err != nil {
return fmt.Errorf("can't download ASN: %s", err.Error()) return fmt.Errorf("can't download ASN: %s", err.Error())
} }
} }
@@ -153,3 +190,15 @@ func InitASN() error {
} }
return nil return nil
} }
func GeoIpEnable() bool {
return geoIpEnable.Load()
}
func GeoSiteEnable() bool {
return geoSiteEnable.Load()
}
func ASNEnable() bool {
return asnEnable.Load()
}

View File

@@ -13,8 +13,6 @@ import (
var ( var (
geoMode bool geoMode bool
AutoUpdate bool
UpdateInterval int
geoLoaderName = "memconservative" geoLoaderName = "memconservative"
geoSiteMatcher = "succinct" geoSiteMatcher = "succinct"
) )
@@ -25,14 +23,6 @@ func GeodataMode() bool {
return geoMode return geoMode
} }
func GeoAutoUpdate() bool {
return AutoUpdate
}
func GeoUpdateInterval() int {
return UpdateInterval
}
func LoaderName() string { func LoaderName() string {
return geoLoaderName return geoLoaderName
} }
@@ -44,12 +34,6 @@ func SiteMatcherName() string {
func SetGeodataMode(newGeodataMode bool) { func SetGeodataMode(newGeodataMode bool) {
geoMode = newGeodataMode geoMode = newGeodataMode
} }
func SetGeoAutoUpdate(newAutoUpdate bool) {
AutoUpdate = newAutoUpdate
}
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
UpdateInterval = newGeoUpdateInterval
}
func SetLoader(newLoader string) { func SetLoader(newLoader string) {
if newLoader == "memc" { if newLoader == "memc" {
@@ -209,8 +193,11 @@ func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
return matcher, nil return matcher, nil
} }
func ClearCache() { func ClearGeoSiteCache() {
loadGeoSiteMatcherListSF.Reset() loadGeoSiteMatcherListSF.Reset()
loadGeoSiteMatcherSF.Reset() loadGeoSiteMatcherSF.Reset()
}
func ClearGeoIPCache() {
loadGeoIPMatcherSF.Reset() loadGeoIPMatcherSF.Reset()
} }

View File

@@ -12,10 +12,21 @@ import (
"time" "time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/listener/inner"
) )
var (
ua string
)
func UA() string {
return ua
}
func SetUA(UA string) {
ua = UA
}
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
return HttpRequestWithProxy(ctx, url, method, header, body, "") return HttpRequestWithProxy(ctx, url, method, header, body, "")
} }
@@ -35,7 +46,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
} }
if _, ok := header["User-Agent"]; !ok { if _, ok := header["User-Agent"]; !ok {
req.Header.Set("User-Agent", C.UA) req.Header.Set("User-Agent", UA())
} }
if err != nil { if err != nil {

View File

@@ -0,0 +1,65 @@
package keepalive
import (
"net"
"runtime"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/utils"
)
var (
keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second)
keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second)
disableKeepAlive = atomic.NewBool(false)
SetDisableKeepAliveCallback = utils.NewCallback[bool]()
)
func SetKeepAliveIdle(t time.Duration) {
keepAliveIdle.Store(t)
}
func SetKeepAliveInterval(t time.Duration) {
keepAliveInterval.Store(t)
}
func KeepAliveIdle() time.Duration {
return keepAliveIdle.Load()
}
func KeepAliveInterval() time.Duration {
return keepAliveInterval.Load()
}
func SetDisableKeepAlive(disable bool) {
if runtime.GOOS == "android" {
setDisableKeepAlive(false)
} else {
setDisableKeepAlive(disable)
}
}
func setDisableKeepAlive(disable bool) {
disableKeepAlive.Store(disable)
SetDisableKeepAliveCallback.Emit(disable)
}
func DisableKeepAlive() bool {
return disableKeepAlive.Load()
}
func SetNetDialer(dialer *net.Dialer) {
setNetDialer(dialer)
}
func SetNetListenConfig(lc *net.ListenConfig) {
setNetListenConfig(lc)
}
func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil {
tcpKeepAlive(tcp)
}
}

View File

@@ -0,0 +1,30 @@
//go:build !go1.23
package keepalive
import "net"
func tcpKeepAlive(tcp *net.TCPConn) {
if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false)
} else {
_ = tcp.SetKeepAlive(true)
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval())
}
}
func setNetDialer(dialer *net.Dialer) {
if DisableKeepAlive() {
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
} else {
dialer.KeepAlive = KeepAliveInterval()
}
}
func setNetListenConfig(lc *net.ListenConfig) {
if DisableKeepAlive() {
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
} else {
lc.KeepAlive = KeepAliveInterval()
}
}

View File

@@ -0,0 +1,45 @@
//go:build go1.23
package keepalive
import "net"
func keepAliveConfig() net.KeepAliveConfig {
config := net.KeepAliveConfig{
Enable: true,
Idle: KeepAliveIdle(),
Interval: KeepAliveInterval(),
}
if !SupportTCPKeepAliveCount() {
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
config.Count = -1
}
return config
}
func tcpKeepAlive(tcp *net.TCPConn) {
if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false)
} else {
_ = tcp.SetKeepAliveConfig(keepAliveConfig())
}
}
func setNetDialer(dialer *net.Dialer) {
if DisableKeepAlive() {
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
dialer.KeepAliveConfig.Enable = false
} else {
dialer.KeepAliveConfig = keepAliveConfig()
}
}
func setNetListenConfig(lc *net.ListenConfig) {
if DisableKeepAlive() {
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
lc.KeepAliveConfig.Enable = false
} else {
lc.KeepAliveConfig = keepAliveConfig()
}
}

View File

@@ -1,6 +1,6 @@
//go:build go1.23 && unix //go:build go1.23 && unix
package net package keepalive
func SupportTCPKeepAliveIdle() bool { func SupportTCPKeepAliveIdle() bool {
return true return true

View File

@@ -2,7 +2,7 @@
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go // copy and modify from golang1.23's internal/syscall/windows/version_windows.go
package net package keepalive
import ( import (
"errors" "errors"

View File

@@ -1,15 +1,9 @@
package mmdb package mmdb
import ( import (
"context"
"io"
"net/http"
"os"
"sync" "sync"
"time"
mihomoOnce "github.com/metacubex/mihomo/common/once" mihomoOnce "github.com/metacubex/mihomo/common/once"
mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -25,26 +19,26 @@ const (
) )
var ( var (
IPreader IPReader ipReader IPReader
ASNreader ASNReader asnReader ASNReader
IPonce sync.Once ipOnce sync.Once
ASNonce sync.Once asnOnce sync.Once
) )
func LoadFromBytes(buffer []byte) { func LoadFromBytes(buffer []byte) {
IPonce.Do(func() { ipOnce.Do(func() {
mmdb, err := maxminddb.FromBytes(buffer) mmdb, err := maxminddb.FromBytes(buffer)
if err != nil { if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error()) log.Fatalln("Can't load mmdb: %s", err.Error())
} }
IPreader = IPReader{Reader: mmdb} ipReader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
IPreader.databaseType = typeSing ipReader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
IPreader.databaseType = typeMetaV0 ipReader.databaseType = typeMetaV0
default: default:
IPreader.databaseType = typeMaxmind ipReader.databaseType = typeMaxmind
} }
}) })
} }
@@ -58,83 +52,45 @@ func Verify(path string) bool {
} }
func IPInstance() IPReader { func IPInstance() IPReader {
IPonce.Do(func() { ipOnce.Do(func() {
mmdbPath := C.Path.MMDB() mmdbPath := C.Path.MMDB()
log.Infoln("Load MMDB file: %s", mmdbPath) log.Infoln("Load MMDB file: %s", mmdbPath)
mmdb, err := maxminddb.Open(mmdbPath) mmdb, err := maxminddb.Open(mmdbPath)
if err != nil { if err != nil {
log.Fatalln("Can't load MMDB: %s", err.Error()) log.Fatalln("Can't load MMDB: %s", err.Error())
} }
IPreader = IPReader{Reader: mmdb} ipReader = IPReader{Reader: mmdb}
switch mmdb.Metadata.DatabaseType { switch mmdb.Metadata.DatabaseType {
case "sing-geoip": case "sing-geoip":
IPreader.databaseType = typeSing ipReader.databaseType = typeSing
case "Meta-geoip0": case "Meta-geoip0":
IPreader.databaseType = typeMetaV0 ipReader.databaseType = typeMetaV0
default: default:
IPreader.databaseType = typeMaxmind ipReader.databaseType = typeMaxmind
} }
}) })
return IPreader return ipReader
}
func DownloadMMDB(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
} }
func ASNInstance() ASNReader { func ASNInstance() ASNReader {
ASNonce.Do(func() { asnOnce.Do(func() {
ASNPath := C.Path.ASN() ASNPath := C.Path.ASN()
log.Infoln("Load ASN file: %s", ASNPath) log.Infoln("Load ASN file: %s", ASNPath)
asn, err := maxminddb.Open(ASNPath) asn, err := maxminddb.Open(ASNPath)
if err != nil { if err != nil {
log.Fatalln("Can't load ASN: %s", err.Error()) log.Fatalln("Can't load ASN: %s", err.Error())
} }
ASNreader = ASNReader{Reader: asn} asnReader = ASNReader{Reader: asn}
}) })
return ASNreader return asnReader
}
func DownloadASN(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
} }
func ReloadIP() { func ReloadIP() {
mihomoOnce.Reset(&IPonce) mihomoOnce.Reset(&ipOnce)
} }
func ReloadASN() { func ReloadASN() {
mihomoOnce.Reset(&ASNonce) mihomoOnce.Reset(&asnOnce)
} }

View File

@@ -1,18 +0,0 @@
//go:build android && cmfa
package mmdb
import "github.com/oschwald/maxminddb-golang"
func InstallOverride(override *maxminddb.Reader) {
newReader := IPReader{Reader: override}
switch override.Metadata.DatabaseType {
case "sing-geoip":
IPreader.databaseType = typeSing
case "Meta-geoip0":
IPreader.databaseType = typeMetaV0
default:
IPreader.databaseType = typeMaxmind
}
IPreader = newReader
}

View File

@@ -10,47 +10,30 @@ import (
) )
type Table struct { type Table struct {
mapping *xsync.MapOf[string, *Entry] mapping *xsync.MapOf[string, *entry]
lockMap *xsync.MapOf[string, *sync.Cond]
} }
type Entry struct { type entry struct {
PacketConn C.PacketConn PacketSender C.PacketSender
WriteBackProxy C.WriteBackProxy
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn] LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
LocalLockMap *xsync.MapOf[string, *sync.Cond] LocalLockMap *xsync.MapOf[string, *sync.Cond]
} }
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) { func (t *Table) GetOrCreate(key string, maker func() C.PacketSender) (C.PacketSender, bool) {
t.mapping.Store(key, &Entry{ item, loaded := t.mapping.LoadOrCompute(key, func() *entry {
PacketConn: e, return &entry{
WriteBackProxy: w, PacketSender: maker(),
LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](), LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](),
LocalLockMap: xsync.NewMapOf[string, *sync.Cond](), LocalLockMap: xsync.NewMapOf[string, *sync.Cond](),
}
}) })
} return item.PacketSender, loaded
func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) {
entry, exist := t.getEntry(key)
if !exist {
return nil, nil
}
return entry.PacketConn, entry.WriteBackProxy
}
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.lockMap.LoadOrCompute(key, makeLock)
return item, loaded
} }
func (t *Table) Delete(key string) { func (t *Table) Delete(key string) {
t.mapping.Delete(key) t.mapping.Delete(key)
} }
func (t *Table) DeleteLock(lockKey string) {
t.lockMap.Delete(lockKey)
}
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn { func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
entry, exist := t.getEntry(lAddr) entry, exist := t.getEntry(lAddr)
if !exist { if !exist {
@@ -105,7 +88,7 @@ func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
entry.LocalLockMap.Delete(key) entry.LocalLockMap.Delete(key)
} }
func (t *Table) getEntry(key string) (*Entry, bool) { func (t *Table) getEntry(key string) (*entry, bool) {
return t.mapping.Load(key) return t.mapping.Load(key)
} }
@@ -116,7 +99,6 @@ func makeLock() *sync.Cond {
// New return *Cache // New return *Cache
func New() *Table { func New() *Table {
return &Table{ return &Table{
mapping: xsync.NewMapOf[string, *Entry](), mapping: xsync.NewMapOf[string, *entry](),
lockMap: xsync.NewMapOf[string, *sync.Cond](),
} }
} }

View File

@@ -23,11 +23,11 @@ func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, str
} }
// PackageNameResolver // PackageNameResolver
// never change type traits because it's used in CFMA // never change type traits because it's used in CMFA
type PackageNameResolver func(metadata *C.Metadata) (string, error) type PackageNameResolver func(metadata *C.Metadata) (string, error)
// DefaultPackageNameResolver // DefaultPackageNameResolver
// never change type traits because it's used in CFMA // never change type traits because it's used in CMFA
var DefaultPackageNameResolver PackageNameResolver var DefaultPackageNameResolver PackageNameResolver
func FindPackageName(metadata *C.Metadata) (string, error) { func FindPackageName(metadata *C.Metadata) (string, error) {

View File

@@ -19,6 +19,7 @@ var (
bucketSelected = []byte("selected") bucketSelected = []byte("selected")
bucketFakeip = []byte("fakeip") bucketFakeip = []byte("fakeip")
bucketETag = []byte("etag")
) )
// CacheFile store and update the cache file // CacheFile store and update the cache file
@@ -69,80 +70,6 @@ func (c *CacheFile) SelectedMap() map[string]string {
return mapping return mapping
} }
func (c *CacheFile) PutFakeip(key, value []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put(key, value)
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) DelFakeipPair(ip, host []byte) error {
if c.DB == nil {
return nil
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
err = bucket.Delete(ip)
if len(host) > 0 {
if err := bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
return err
}
func (c *CacheFile) GetFakeip(key []byte) []byte {
if c.DB == nil {
return nil
}
tx, err := c.DB.Begin(false)
if err != nil {
return nil
}
defer tx.Rollback()
bucket := tx.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return bucket.Get(key)
}
func (c *CacheFile) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}
func (c *CacheFile) Close() error { func (c *CacheFile) Close() error {
return c.DB.Close() return c.DB.Close()
} }

View File

@@ -0,0 +1,58 @@
package cachefile
import (
"time"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/bbolt"
"github.com/vmihailenco/msgpack/v5"
)
type EtagWithHash struct {
Hash utils.HashType
ETag string
Time time.Time
}
func (c *CacheFile) SetETagWithHash(url string, etagWithHash EtagWithHash) {
if c.DB == nil {
return
}
data, err := msgpack.Marshal(etagWithHash)
if err != nil {
return // maybe panic is better
}
err = c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketETag)
if err != nil {
return err
}
return bucket.Put([]byte(url), data)
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
return
}
}
func (c *CacheFile) GetETagWithHash(key string) (etagWithHash EtagWithHash) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketETag); bucket != nil {
if v := bucket.Get([]byte(key)); v != nil {
if err := msgpack.Unmarshal(v, &etagWithHash); err != nil {
return err
}
}
}
return nil
})
return
}

View File

@@ -0,0 +1,115 @@
package cachefile
import (
"net/netip"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/bbolt"
)
type FakeIpStore struct {
*CacheFile
}
func (c *CacheFile) FakeIpStore() *FakeIpStore {
return &FakeIpStore{c}
}
func (c *FakeIpStore) GetByHost(host string) (ip netip.Addr, exist bool) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketFakeip); bucket != nil {
if v := bucket.Get([]byte(host)); v != nil {
ip, exist = netip.AddrFromSlice(v)
}
}
return nil
})
return
}
func (c *FakeIpStore) PutByHost(host string, ip netip.Addr) {
if c.DB == nil {
return
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put([]byte(host), ip.AsSlice())
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) GetByIP(ip netip.Addr) (host string, exist bool) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketFakeip); bucket != nil {
if v := bucket.Get(ip.AsSlice()); v != nil {
host, exist = string(v), true
}
}
return nil
})
return
}
func (c *FakeIpStore) PutByIP(ip netip.Addr, host string) {
if c.DB == nil {
return
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
return bucket.Put(ip.AsSlice(), []byte(host))
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) DelByIP(ip netip.Addr) {
if c.DB == nil {
return
}
addr := ip.AsSlice()
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
if err != nil {
return err
}
host := bucket.Get(addr)
err = bucket.Delete(addr)
if len(host) > 0 {
if err = bucket.Delete(host); err != nil {
return err
}
}
return err
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
}
}
func (c *FakeIpStore) FlushFakeIP() error {
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketFakeip)
if bucket == nil {
return nil
}
return t.DeleteBucket(bucketFakeip)
})
return err
}

View File

@@ -46,6 +46,8 @@ type Resolver interface {
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
Invalid() bool Invalid() bool
ClearCache()
ResetConnection()
} }
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver // LookupIPv4WithResolver same as LookupIPv4, but with a resolver
@@ -255,6 +257,15 @@ func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, er
return LookupIP(ctx, host) return LookupIP(ctx, host)
} }
func ResetConnection() {
if DefaultResolver != nil {
go DefaultResolver.ResetConnection()
}
if ProxyServerHostResolver != nil {
go ProxyServerHostResolver.ResetConnection()
}
}
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) { func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
for _, v := range ips { for _, v := range ips {
if v.Unmap().Is4() { if v.Unmap().Is4() {

View File

@@ -1,13 +1,11 @@
package resource package resource
import ( import (
"bytes"
"context" "context"
"crypto/md5"
"os" "os"
"path/filepath"
"time" "time"
"github.com/metacubex/mihomo/common/utils"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -15,11 +13,6 @@ import (
"github.com/samber/lo" "github.com/samber/lo"
) )
var (
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
)
type Parser[V any] func([]byte) (V, error) type Parser[V any] func([]byte) (V, error)
type Fetcher[V any] struct { type Fetcher[V any] struct {
@@ -29,10 +22,10 @@ type Fetcher[V any] struct {
name string name string
vehicle types.Vehicle vehicle types.Vehicle
updatedAt time.Time updatedAt time.Time
hash [16]byte hash utils.HashType
parser Parser[V] parser Parser[V]
interval time.Duration interval time.Duration
OnUpdate func(V) onUpdate func(V)
watcher *fswatch.Watcher watcher *fswatch.Watcher
} }
@@ -54,107 +47,63 @@ func (f *Fetcher[V]) UpdatedAt() time.Time {
func (f *Fetcher[V]) Initial() (V, error) { func (f *Fetcher[V]) Initial() (V, error) {
var ( var (
buf []byte buf []byte
err error contents V
isLocal bool err error
forceUpdate bool
) )
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
// local file exists, use it first
buf, err = os.ReadFile(f.vehicle.Path()) buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime() modTime := stat.ModTime()
f.updatedAt = modTime contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false)
isLocal = true f.updatedAt = modTime // reset updatedAt to file's modTime
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) if err == nil {
forceUpdate = true err = f.startPullLoop(time.Since(modTime) > f.interval)
if err != nil {
return lo.Empty[V](), err
}
return contents, nil
} }
} else {
buf, err = f.vehicle.Read(f.ctx)
f.updatedAt = time.Now()
} }
// parse local file error, fallback to remote
contents, _, err = f.Update()
if err != nil { if err != nil {
return lo.Empty[V](), err return lo.Empty[V](), err
} }
err = f.startPullLoop(false)
var contents V
if forceUpdate {
var forceBuf []byte
if forceBuf, err = f.vehicle.Read(f.ctx); err == nil {
if contents, err = f.parser(forceBuf); err == nil {
isLocal = false
buf = forceBuf
}
}
}
if err != nil || !forceUpdate {
contents, err = f.parser(buf)
}
if err != nil { if err != nil {
if !isLocal { return lo.Empty[V](), err
return lo.Empty[V](), err
}
// parse local file error, fallback to remote
buf, err = f.vehicle.Read(f.ctx)
if err != nil {
return lo.Empty[V](), err
}
contents, err = f.parser(buf)
if err != nil {
return lo.Empty[V](), err
}
isLocal = false
} }
if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return lo.Empty[V](), err
}
}
f.hash = md5.Sum(buf)
// pull contents automatically
if f.vehicle.Type() == types.File {
f.watcher, err = fswatch.NewWatcher(fswatch.Options{
Path: []string{f.vehicle.Path()},
Direct: true,
Callback: f.update,
})
if err != nil {
return lo.Empty[V](), err
}
err = f.watcher.Start()
if err != nil {
return lo.Empty[V](), err
}
} else if f.interval > 0 {
go f.pullLoop()
}
return contents, nil return contents, nil
} }
func (f *Fetcher[V]) Update() (V, bool, error) { func (f *Fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read(f.ctx) buf, hash, err := f.vehicle.Read(f.ctx, f.hash)
if err != nil { if err != nil {
return lo.Empty[V](), false, err return lo.Empty[V](), false, err
} }
return f.SideUpdate(buf) return f.loadBuf(buf, hash, f.vehicle.Type() != types.File)
} }
func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) { func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) {
return f.loadBuf(buf, utils.MakeHash(buf), true)
}
func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (V, bool, error) {
now := time.Now() now := time.Now()
hash := md5.Sum(buf) if f.hash.Equal(hash) {
if bytes.Equal(f.hash[:], hash[:]) { if updateFile {
_ = os.Chtimes(f.vehicle.Path(), now, now)
}
f.updatedAt = now f.updatedAt = now
_ = os.Chtimes(f.vehicle.Path(), now, now) return lo.Empty[V](), true, nil
}
if buf == nil { // f.hash has been changed between f.vehicle.Read but should not happen (cause by concurrent)
return lo.Empty[V](), true, nil return lo.Empty[V](), true, nil
} }
@@ -163,15 +112,18 @@ func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) {
return lo.Empty[V](), false, err return lo.Empty[V](), false, err
} }
if f.vehicle.Type() != types.File { if updateFile {
if err := safeWrite(f.vehicle.Path(), buf); err != nil { if err = f.vehicle.Write(buf); err != nil {
return lo.Empty[V](), false, err return lo.Empty[V](), false, err
} }
} }
f.updatedAt = now f.updatedAt = now
f.hash = hash f.hash = hash
if f.onUpdate != nil {
f.onUpdate(contents)
}
return contents, false, nil return contents, false, nil
} }
@@ -183,27 +135,57 @@ func (f *Fetcher[V]) Close() error {
return nil return nil
} }
func (f *Fetcher[V]) pullLoop() { func (f *Fetcher[V]) pullLoop(forceUpdate bool) {
initialInterval := f.interval - time.Since(f.updatedAt) initialInterval := f.interval - time.Since(f.updatedAt)
if initialInterval > f.interval { if initialInterval > f.interval {
initialInterval = f.interval initialInterval = f.interval
} }
if forceUpdate {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
f.updateWithLog()
}
timer := time.NewTimer(initialInterval) timer := time.NewTimer(initialInterval)
defer timer.Stop() defer timer.Stop()
for { for {
select { select {
case <-timer.C: case <-timer.C:
timer.Reset(f.interval) timer.Reset(f.interval)
f.update(f.vehicle.Path()) f.updateWithLog()
case <-f.ctx.Done(): case <-f.ctx.Done():
return return
} }
} }
} }
func (f *Fetcher[V]) update(path string) { func (f *Fetcher[V]) startPullLoop(forceUpdate bool) (err error) {
elm, same, err := f.Update() // pull contents automatically
if f.vehicle.Type() == types.File {
f.watcher, err = fswatch.NewWatcher(fswatch.Options{
Path: []string{f.vehicle.Path()},
Direct: true,
Callback: f.updateCallback,
})
if err != nil {
return err
}
err = f.watcher.Start()
if err != nil {
return err
}
} else if f.interval > 0 {
go f.pullLoop(forceUpdate)
}
return
}
func (f *Fetcher[V]) updateCallback(path string) {
f.updateWithLog()
}
func (f *Fetcher[V]) updateWithLog() {
_, same, err := f.Update()
if err != nil { if err != nil {
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
return return
@@ -215,21 +197,7 @@ func (f *Fetcher[V]) update(path string) {
} }
log.Infoln("[Provider] %s's content update", f.Name()) log.Infoln("[Provider] %s's content update", f.Name())
if f.OnUpdate != nil { return
f.OnUpdate(elm)
}
}
func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, dirMode); err != nil {
return err
}
}
return os.WriteFile(path, buf, fileMode)
} }
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] { func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
@@ -240,7 +208,7 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
name: name, name: name,
vehicle: vehicle, vehicle: vehicle,
parser: parser, parser: parser,
OnUpdate: onUpdate, onUpdate: onUpdate,
interval: interval, interval: interval,
} }
} }

View File

@@ -6,12 +6,46 @@ import (
"io" "io"
"net/http" "net/http"
"os" "os"
"path/filepath"
"time" "time"
"github.com/metacubex/mihomo/common/utils"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/profile/cachefile"
types "github.com/metacubex/mihomo/constant/provider" types "github.com/metacubex/mihomo/constant/provider"
) )
const (
DefaultHttpTimeout = time.Second * 20
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
)
var (
etag = false
)
func ETag() bool {
return etag
}
func SetETag(b bool) {
etag = b
}
func safeWrite(path string, buf []byte) error {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, dirMode); err != nil {
return err
}
}
return os.WriteFile(path, buf, fileMode)
}
type FileVehicle struct { type FileVehicle struct {
path string path string
} }
@@ -24,23 +58,37 @@ func (f *FileVehicle) Path() string {
return f.path return f.path
} }
func (f *FileVehicle) Read(ctx context.Context) ([]byte, error) { func (f *FileVehicle) Url() string {
return os.ReadFile(f.path) return "file://" + f.path
}
func (f *FileVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
buf, err = os.ReadFile(f.path)
if err != nil {
return
}
hash = utils.MakeHash(buf)
return
} }
func (f *FileVehicle) Proxy() string { func (f *FileVehicle) Proxy() string {
return "" return ""
} }
func (f *FileVehicle) Write(buf []byte) error {
return safeWrite(f.path, buf)
}
func NewFileVehicle(path string) *FileVehicle { func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path} return &FileVehicle{path: path}
} }
type HTTPVehicle struct { type HTTPVehicle struct {
url string url string
path string path string
proxy string proxy string
header http.Header header http.Header
timeout time.Duration
} }
func (h *HTTPVehicle) Url() string { func (h *HTTPVehicle) Url() string {
@@ -59,24 +107,60 @@ func (h *HTTPVehicle) Proxy() string {
return h.proxy return h.proxy
} }
func (h *HTTPVehicle) Read(ctx context.Context) ([]byte, error) { func (h *HTTPVehicle) Write(buf []byte) error {
ctx, cancel := context.WithTimeout(ctx, time.Second*20) return safeWrite(h.path, buf)
}
func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
ctx, cancel := context.WithTimeout(ctx, h.timeout)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy) header := h.header
setIfNoneMatch := false
if etag && oldHash.IsValid() {
etagWithHash := cachefile.Cache().GetETagWithHash(h.url)
if oldHash.Equal(etagWithHash.Hash) && etagWithHash.ETag != "" {
if header == nil {
header = http.Header{}
} else {
header = header.Clone()
}
header.Set("If-None-Match", etagWithHash.ETag)
setIfNoneMatch = true
}
}
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, header, nil, h.proxy)
if err != nil { if err != nil {
return nil, err return
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, errors.New(resp.Status) if setIfNoneMatch && resp.StatusCode == http.StatusNotModified {
return nil, oldHash, nil
}
err = errors.New(resp.Status)
return
} }
buf, err := io.ReadAll(resp.Body) buf, err = io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return
} }
return buf, nil hash = utils.MakeHash(buf)
if etag {
cachefile.Cache().SetETagWithHash(h.url, cachefile.EtagWithHash{
Hash: hash,
ETag: resp.Header.Get("ETag"),
Time: time.Now(),
})
}
return
} }
func NewHTTPVehicle(url string, path string, proxy string, header http.Header) *HTTPVehicle { func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration) *HTTPVehicle {
return &HTTPVehicle{url, path, proxy, header} return &HTTPVehicle{
url: url,
path: path,
proxy: proxy,
header: header,
timeout: timeout,
}
} }

View File

@@ -3,6 +3,8 @@ package trie
import ( import (
"errors" "errors"
"strings" "strings"
"unicode"
"unicode/utf8"
) )
const ( const (
@@ -25,6 +27,14 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' { if domain != "" && domain[len(domain)-1] == '.' {
return nil, false return nil, false
} }
if domain != "" {
if r, _ := utf8.DecodeRuneInString(domain); unicode.IsSpace(r) {
return nil, false
}
if r, _ := utf8.DecodeLastRuneInString(domain); unicode.IsSpace(r) {
return nil, false
}
}
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
parts := strings.Split(domain, domainStep) parts := strings.Split(domain, domainStep)
if len(parts) == 1 { if len(parts) == 1 {

View File

@@ -127,3 +127,14 @@ func TestTrie_Foreach(t *testing.T) {
}) })
assert.Equal(t, 7, count) assert.Equal(t, 7, count)
} }
func TestTrie_Space(t *testing.T) {
validDomain := func(domain string) bool {
_, ok := trie.ValidAndSplitDomain(domain)
return ok
}
assert.True(t, validDomain("google.com"))
assert.False(t, validDomain(" google.com"))
assert.False(t, validDomain(" google.com "))
assert.True(t, validDomain("Mijia Cloud"))
}

View File

@@ -237,7 +237,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024
func downloadPackageFile() (err error) { func downloadPackageFile() (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, nil, nil)
if err != nil { if err != nil {
return fmt.Errorf("http request failed: %w", err) return fmt.Errorf("http request failed: %w", err)
} }
@@ -418,7 +418,7 @@ func copyFile(src, dst string) error {
func getLatestVersion() (version string, err error) { func getLatestVersion() (version string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, nil, nil)
if err != nil { if err != nil {
return "", fmt.Errorf("get Latest Version fail: %w", err) return "", fmt.Errorf("get Latest Version fail: %w", err)
} }

View File

@@ -1,6 +1,7 @@
package updater package updater
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -8,9 +9,12 @@ import (
"time" "time"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/batch"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard" _ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb" "github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -18,82 +22,186 @@ import (
) )
var ( var (
UpdatingGeo atomic.Bool autoUpdate bool
updateInterval int
updatingGeo atomic.Bool
) )
func updateGeoDatabases() error { func GeoAutoUpdate() bool {
defer runtime.GC() return autoUpdate
geoLoader, err := geodata.GetGeoDataLoader("standard") }
func GeoUpdateInterval() int {
return updateInterval
}
func SetGeoAutoUpdate(newAutoUpdate bool) {
autoUpdate = newAutoUpdate
}
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
updateInterval = newGeoUpdateInterval
}
func UpdateMMDB() (err error) {
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
return err return fmt.Errorf("can't download MMDB database file: %w", err)
}
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download MMDB database file: no data")
} }
if C.GeodataMode { instance, err := maxminddb.FromBytes(data)
data, err := downloadForBytes(C.GeoIpUrl) if err != nil {
if err != nil { return fmt.Errorf("invalid MMDB database file: %s", err)
return fmt.Errorf("can't download GeoIP database file: %w", err) }
} _ = instance.Close()
if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil { defer mmdb.ReloadIP()
return fmt.Errorf("invalid GeoIP database file: %s", err) mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
} if err = vehicle.Write(data); err != nil {
return fmt.Errorf("can't save MMDB database file: %w", err)
}
return nil
}
if err = saveFile(data, C.Path.GeoIP()); err != nil { func UpdateASN() (err error) {
return fmt.Errorf("can't save GeoIP database file: %w", err) vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
} var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
} else { oldHash = utils.MakeHash(buf)
defer mmdb.ReloadIP() }
data, err := downloadForBytes(C.MmdbUrl) data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err) return fmt.Errorf("can't download ASN database file: %w", err)
} }
if oldHash.Equal(hash) { // same hash, ignored
instance, err := maxminddb.FromBytes(data) return nil
if err != nil { }
return fmt.Errorf("invalid MMDB database file: %s", err) if len(data) == 0 {
} return fmt.Errorf("can't download ASN database file: no data")
_ = instance.Close()
mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
if err = saveFile(data, C.Path.MMDB()); err != nil {
return fmt.Errorf("can't save MMDB database file: %w", err)
}
} }
if C.ASNEnable { instance, err := maxminddb.FromBytes(data)
defer mmdb.ReloadASN() if err != nil {
data, err := downloadForBytes(C.ASNUrl) return fmt.Errorf("invalid ASN database file: %s", err)
if err != nil { }
return fmt.Errorf("can't download ASN database file: %w", err) _ = instance.Close()
}
instance, err := maxminddb.FromBytes(data) defer mmdb.ReloadASN()
if err != nil { mmdb.ASNInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
return fmt.Errorf("invalid ASN database file: %s", err) if err = vehicle.Write(data); err != nil {
} return fmt.Errorf("can't save ASN database file: %w", err)
_ = instance.Close() }
return nil
}
mmdb.ASNInstance().Reader.Close() func UpdateGeoIp() (err error) {
if err = saveFile(data, C.Path.ASN()); err != nil { geoLoader, err := geodata.GetGeoDataLoader("standard")
return fmt.Errorf("can't save ASN database file: %w", err)
} vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil {
return fmt.Errorf("can't download GeoIP database file: %w", err)
}
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download GeoIP database file: no data")
} }
data, err := downloadForBytes(C.GeoSiteUrl) if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoIP database file: %s", err)
}
defer geodata.ClearGeoIPCache()
if err = vehicle.Write(data); err != nil {
return fmt.Errorf("can't save GeoIP database file: %w", err)
}
return nil
}
func UpdateGeoSite() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard")
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil { if err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err) return fmt.Errorf("can't download GeoSite database file: %w", err)
} }
if oldHash.Equal(hash) { // same hash, ignored
return nil
}
if len(data) == 0 {
return fmt.Errorf("can't download GeoSite database file: no data")
}
if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil { if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoSite database file: %s", err) return fmt.Errorf("invalid GeoSite database file: %s", err)
} }
if err = saveFile(data, C.Path.GeoSite()); err != nil { defer geodata.ClearGeoSiteCache()
if err = vehicle.Write(data); err != nil {
return fmt.Errorf("can't save GeoSite database file: %w", err) return fmt.Errorf("can't save GeoSite database file: %w", err)
} }
return nil
}
geodata.ClearCache() func updateGeoDatabases() error {
defer runtime.GC()
b, _ := batch.New[interface{}](context.Background())
if geodata.GeoIpEnable() {
if geodata.GeodataMode() {
b.Go("UpdateGeoIp", func() (_ interface{}, err error) {
err = UpdateGeoIp()
return
})
} else {
b.Go("UpdateMMDB", func() (_ interface{}, err error) {
err = UpdateMMDB()
return
})
}
}
if geodata.ASNEnable() {
b.Go("UpdateASN", func() (_ interface{}, err error) {
err = UpdateASN()
return
})
}
if geodata.GeoSiteEnable() {
b.Go("UpdateGeoSite", func() (_ interface{}, err error) {
err = UpdateGeoSite()
return
})
}
if e := b.Wait(); e != nil {
return e.Err
}
return nil return nil
} }
@@ -103,12 +211,12 @@ var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")
func UpdateGeoDatabases() error { func UpdateGeoDatabases() error {
log.Infoln("[GEO] Start updating GEO database") log.Infoln("[GEO] Start updating GEO database")
if UpdatingGeo.Load() { if updatingGeo.Load() {
return ErrGetDatabaseUpdateSkip return ErrGetDatabaseUpdateSkip
} }
UpdatingGeo.Store(true) updatingGeo.Store(true)
defer UpdatingGeo.Store(false) defer updatingGeo.Store(false)
log.Infoln("[GEO] Updating GEO database") log.Infoln("[GEO] Updating GEO database")
@@ -122,7 +230,7 @@ func UpdateGeoDatabases() error {
func getUpdateTime() (err error, time time.Time) { func getUpdateTime() (err error, time time.Time) {
var fileInfo os.FileInfo var fileInfo os.FileInfo
if C.GeodataMode { if geodata.GeodataMode() {
fileInfo, err = os.Stat(C.Path.GeoIP()) fileInfo, err = os.Stat(C.Path.GeoIP())
if err != nil { if err != nil {
return err, time return err, time
@@ -138,13 +246,13 @@ func getUpdateTime() (err error, time time.Time) {
} }
func RegisterGeoUpdater() { func RegisterGeoUpdater() {
if C.GeoUpdateInterval <= 0 { if updateInterval <= 0 {
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval) log.Errorln("[GEO] Invalid update interval: %d", updateInterval)
return return
} }
go func() { go func() {
ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) ticker := time.NewTicker(time.Duration(updateInterval) * time.Hour)
defer ticker.Stop() defer ticker.Stop()
err, lastUpdate := getUpdateTime() err, lastUpdate := getUpdateTime()
@@ -154,8 +262,8 @@ func RegisterGeoUpdater() {
} }
log.Infoln("[GEO] last update time %s", lastUpdate) log.Infoln("[GEO] last update time %s", lastUpdate)
if lastUpdate.Add(time.Duration(C.GeoUpdateInterval) * time.Hour).Before(time.Now()) { if lastUpdate.Add(time.Duration(updateInterval) * time.Hour).Before(time.Now()) {
log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(C.GeoUpdateInterval)*time.Hour) log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(updateInterval)*time.Hour)
if err := UpdateGeoDatabases(); err != nil { if err := UpdateGeoDatabases(); err != nil {
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error()) log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
return return
@@ -163,7 +271,7 @@ func RegisterGeoUpdater() {
} }
for range ticker.C { for range ticker.C {
log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval) log.Infoln("[GEO] updating database every %d hours", updateInterval)
if err := UpdateGeoDatabases(); err != nil { if err := UpdateGeoDatabases(); err != nil {
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error()) log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
} }

View File

@@ -17,12 +17,12 @@ import (
var ( var (
ExternalUIURL string ExternalUIURL string
ExternalUIPath string ExternalUIPath string
AutoUpdateUI bool AutoDownloadUI bool
) )
var xdMutex sync.Mutex var xdMutex sync.Mutex
func UpdateUI() error { func DownloadUI() error {
xdMutex.Lock() xdMutex.Lock()
defer xdMutex.Unlock() defer xdMutex.Unlock()

View File

@@ -9,15 +9,16 @@ import (
"time" "time"
mihomoHttp "github.com/metacubex/mihomo/component/http" mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
) )
const defaultHttpTimeout = time.Second * 90
func downloadForBytes(url string) ([]byte, error) { func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) ctx, cancel := context.WithTimeout(context.Background(), defaultHttpTimeout)
defer cancel() defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil) resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -8,7 +8,6 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"path" "path"
"regexp"
"strings" "strings"
"time" "time"
@@ -16,20 +15,21 @@ import (
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/adapter/provider"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/cidr" "github.com/metacubex/mihomo/component/cidr"
"github.com/metacubex/mihomo/component/fakeip" "github.com/metacubex/mihomo/component/fakeip"
"github.com/metacubex/mihomo/component/geodata" "github.com/metacubex/mihomo/component/geodata"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/keepalive"
P "github.com/metacubex/mihomo/component/process" P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/resource"
"github.com/metacubex/mihomo/component/sniffer" "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/mihomo/component/updater" "github.com/metacubex/mihomo/component/updater"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
providerTypes "github.com/metacubex/mihomo/constant/provider" providerTypes "github.com/metacubex/mihomo/constant/provider"
snifferTypes "github.com/metacubex/mihomo/constant/sniffer" snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
"github.com/metacubex/mihomo/dns" "github.com/metacubex/mihomo/dns"
@@ -66,6 +66,7 @@ type General struct {
Sniffing bool `json:"sniffing"` Sniffing bool `json:"sniffing"`
GlobalClientFingerprint string `json:"global-client-fingerprint"` GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"` GlobalUA string `json:"global-ua"`
ETagSupport bool `json:"etag-support"`
} }
// Inbound config // Inbound config
@@ -102,9 +103,16 @@ type Controller struct {
ExternalController string ExternalController string
ExternalControllerTLS string ExternalControllerTLS string
ExternalControllerUnix string ExternalControllerUnix string
ExternalControllerPipe string
ExternalUI string ExternalUI string
ExternalDohServer string ExternalDohServer string
Secret string Secret string
Cors Cors
}
type Cors struct {
AllowOrigins []string
AllowPrivateNetwork bool
} }
// Experimental config // Experimental config
@@ -189,6 +197,11 @@ type Config struct {
TLS *TLS TLS *TLS
} }
type RawCors struct {
AllowOrigins []string `yaml:"allow-origins" json:"allow-origins"`
AllowPrivateNetwork bool `yaml:"allow-private-network" json:"allow-private-network"`
}
type RawDNS struct { type RawDNS struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"` PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
@@ -363,8 +376,10 @@ type RawConfig struct {
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"` LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
IPv6 bool `yaml:"ipv6" json:"ipv6"` IPv6 bool `yaml:"ipv6" json:"ipv6"`
ExternalController string `yaml:"external-controller" json:"external-controller"` ExternalController string `yaml:"external-controller" json:"external-controller"`
ExternalControllerPipe string `yaml:"external-controller-pipe" json:"external-controller-pipe"`
ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"` ExternalControllerUnix string `yaml:"external-controller-unix" json:"external-controller-unix"`
ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls" json:"external-controller-tls"`
ExternalControllerCors RawCors `yaml:"external-controller-cors" json:"external-controller-cors"`
ExternalUI string `yaml:"external-ui" json:"external-ui"` ExternalUI string `yaml:"external-ui" json:"external-ui"`
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"`
@@ -382,6 +397,7 @@ type RawConfig struct {
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
GlobalClientFingerprint string `yaml:"global-client-fingerprint" json:"global-client-fingerprint"` GlobalClientFingerprint string `yaml:"global-client-fingerprint" json:"global-client-fingerprint"`
GlobalUA string `yaml:"global-ua" json:"global-ua"` GlobalUA string `yaml:"global-ua" json:"global-ua"`
ETagSupport bool `yaml:"etag-support" json:"etag-support"`
KeepAliveIdle int `yaml:"keep-alive-idle" json:"keep-alive-idle"` KeepAliveIdle int `yaml:"keep-alive-idle" json:"keep-alive-idle"`
KeepAliveInterval int `yaml:"keep-alive-interval" json:"keep-alive-interval"` KeepAliveInterval int `yaml:"keep-alive-interval" json:"keep-alive-interval"`
DisableKeepAlive bool `yaml:"disable-keep-alive" json:"disable-keep-alive"` DisableKeepAlive bool `yaml:"disable-keep-alive" json:"disable-keep-alive"`
@@ -433,7 +449,7 @@ func DefaultRawConfig() *RawConfig {
Mode: T.Rule, Mode: T.Rule,
GeoAutoUpdate: false, GeoAutoUpdate: false,
GeoUpdateInterval: 24, GeoUpdateInterval: 24,
GeodataMode: C.GeodataMode, GeodataMode: geodata.GeodataMode(),
GeodataLoader: "memconservative", GeodataLoader: "memconservative",
UnifiedDelay: false, UnifiedDelay: false,
Authentication: []string{}, Authentication: []string{},
@@ -445,6 +461,7 @@ func DefaultRawConfig() *RawConfig {
TCPConcurrent: false, TCPConcurrent: false,
FindProcessMode: P.FindProcessStrict, FindProcessMode: P.FindProcessStrict,
GlobalUA: "clash.meta/" + C.Version, GlobalUA: "clash.meta/" + C.Version,
ETagSupport: true,
DNS: RawDNS{ DNS: RawDNS{
Enable: false, Enable: false,
IPv6: false, IPv6: false,
@@ -521,7 +538,7 @@ func DefaultRawConfig() *RawConfig {
}, },
GeoXUrl: RawGeoXUrl{ GeoXUrl: RawGeoXUrl{
Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb", Mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
ASN: "https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb", ASN: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb",
GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat", GeoIp: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat",
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat", GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
}, },
@@ -536,6 +553,10 @@ func DefaultRawConfig() *RawConfig {
OverrideDest: true, OverrideDest: true,
}, },
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
ExternalControllerCors: RawCors{
AllowOrigins: []string{"*"},
AllowPrivateNetwork: true,
},
} }
} }
@@ -648,7 +669,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.DNS = dnsCfg config.DNS = dnsCfg
err = parseTun(rawCfg.Tun, config.General) err = parseTun(rawCfg.Tun, config.General)
if !features.CMFA && err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -681,31 +702,29 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
func parseGeneral(cfg *RawConfig) (*General, error) { func parseGeneral(cfg *RawConfig) (*General, error) {
updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
geodata.SetGeodataMode(cfg.GeodataMode) geodata.SetGeodataMode(cfg.GeodataMode)
geodata.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
geodata.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
geodata.SetLoader(cfg.GeodataLoader) geodata.SetLoader(cfg.GeodataLoader)
geodata.SetSiteMatcher(cfg.GeositeMatcher) geodata.SetSiteMatcher(cfg.GeositeMatcher)
C.GeoAutoUpdate = cfg.GeoAutoUpdate geodata.SetGeoIpUrl(cfg.GeoXUrl.GeoIp)
C.GeoUpdateInterval = cfg.GeoUpdateInterval geodata.SetGeoSiteUrl(cfg.GeoXUrl.GeoSite)
C.GeoIpUrl = cfg.GeoXUrl.GeoIp geodata.SetMmdbUrl(cfg.GeoXUrl.Mmdb)
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite geodata.SetASNUrl(cfg.GeoXUrl.ASN)
C.MmdbUrl = cfg.GeoXUrl.Mmdb mihomoHttp.SetUA(cfg.GlobalUA)
C.ASNUrl = cfg.GeoXUrl.ASN resource.SetETag(cfg.ETagSupport)
C.GeodataMode = cfg.GeodataMode
C.UA = cfg.GlobalUA
if cfg.KeepAliveIdle != 0 { if cfg.KeepAliveIdle != 0 {
N.KeepAliveIdle = time.Duration(cfg.KeepAliveIdle) * time.Second keepalive.SetKeepAliveIdle(time.Duration(cfg.KeepAliveIdle) * time.Second)
} }
if cfg.KeepAliveInterval != 0 { if cfg.KeepAliveInterval != 0 {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second keepalive.SetKeepAliveInterval(time.Duration(cfg.KeepAliveInterval) * time.Second)
} }
N.DisableKeepAlive = cfg.DisableKeepAlive keepalive.SetDisableKeepAlive(cfg.DisableKeepAlive)
// checkout externalUI exist // checkout externalUI exist
if cfg.ExternalUI != "" { if cfg.ExternalUI != "" {
updater.AutoUpdateUI = true updater.AutoDownloadUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI) updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else { } else {
// default externalUI path // default externalUI path
@@ -714,7 +733,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
// checkout UIpath/name exist // checkout UIpath/name exist
if cfg.ExternalUIName != "" { if cfg.ExternalUIName != "" {
updater.AutoUpdateUI = true updater.AutoDownloadUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName) updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
} }
@@ -759,6 +778,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
GlobalClientFingerprint: cfg.GlobalClientFingerprint, GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA, GlobalUA: cfg.GlobalUA,
ETagSupport: cfg.ETagSupport,
}, nil }, nil
} }
@@ -767,9 +787,14 @@ func parseController(cfg *RawConfig) (*Controller, error) {
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI, ExternalUI: cfg.ExternalUI,
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerPipe: cfg.ExternalControllerPipe,
ExternalControllerUnix: cfg.ExternalControllerUnix, ExternalControllerUnix: cfg.ExternalControllerUnix,
ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalControllerTLS: cfg.ExternalControllerTLS,
ExternalDohServer: cfg.ExternalDohServer, ExternalDohServer: cfg.ExternalDohServer,
Cors: Cors{
AllowOrigins: cfg.ExternalControllerCors.AllowOrigins,
AllowPrivateNetwork: cfg.ExternalControllerCors.AllowPrivateNetwork,
},
}, nil }, nil
} }
@@ -1160,16 +1185,6 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
var nameservers []dns.NameServer var nameservers []dns.NameServer
for idx, server := range servers { for idx, server := range servers {
if strings.HasPrefix(server, "dhcp://") {
nameservers = append(
nameservers,
dns.NameServer{
Net: "dhcp",
Addr: server[len("dhcp://"):],
},
)
continue
}
server = parsePureDNSServer(server) server = parsePureDNSServer(server)
u, err := url.Parse(server) u, err := url.Parse(server)
if err != nil { if err != nil {
@@ -1220,6 +1235,13 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
dnsNetType = "quic" // DNS over QUIC dnsNetType = "quic" // DNS over QUIC
case "system": case "system":
dnsNetType = "system" // System DNS dnsNetType = "system" // System DNS
case "dhcp":
addr = server[len("dhcp://"):] // some special notation cannot be parsed by url
dnsNetType = "dhcp" // UDP from DHCP
if addr == "system" { // Compatible with old writing "dhcp://system"
dnsNetType = "system"
addr = ""
}
case "rcode": case "rcode":
dnsNetType = "rcode" dnsNetType = "rcode"
addr = u.Host addr = u.Host
@@ -1245,16 +1267,18 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
proxyName = dns.RespectRules proxyName = dns.RespectRules
} }
nameservers = append( nameserver := dns.NameServer{
nameservers, Net: dnsNetType,
dns.NameServer{ Addr: addr,
Net: dnsNetType, ProxyName: proxyName,
Addr: addr, Params: params,
ProxyName: proxyName, PreferH3: preferH3,
Params: params, }
PreferH3: preferH3, if slices.ContainsFunc(nameservers, nameserver.Equal) {
}, continue // skip duplicates nameserver
) }
nameservers = append(nameservers, nameserver)
} }
return nameservers, nil return nameservers, nil
} }
@@ -1290,7 +1314,6 @@ func parsePureDNSServer(server string) string {
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) { func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
var policy []dns.Policy var policy []dns.Policy
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() { for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() {
k, v := pair.Key, pair.Value k, v := pair.Key, pair.Value
@@ -1302,8 +1325,9 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.Contains(strings.ToLower(k), ",") { kLower := strings.ToLower(k)
if strings.Contains(k, "geosite:") { if strings.Contains(kLower, ",") {
if strings.Contains(kLower, "geosite:") {
subkeys := strings.Split(k, ":") subkeys := strings.Split(k, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1311,7 +1335,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
newKey := "geosite:" + subkey newKey := "geosite:" + subkey
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
} }
} else if strings.Contains(strings.ToLower(k), "rule-set:") { } else if strings.Contains(kLower, "rule-set:") {
subkeys := strings.Split(k, ":") subkeys := strings.Split(k, ":")
subkeys = subkeys[1:] subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",") subkeys = strings.Split(subkeys[0], ",")
@@ -1319,16 +1343,16 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
newKey := "rule-set:" + subkey newKey := "rule-set:" + subkey
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
} }
} else if re.MatchString(k) { } else {
subkeys := strings.Split(k, ",") subkeys := strings.Split(k, ",")
for _, subkey := range subkeys { for _, subkey := range subkeys {
policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers})
} }
} }
} else { } else {
if strings.Contains(strings.ToLower(k), "geosite:") { if strings.Contains(kLower, "geosite:") {
policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: "geosite:" + k[8:], NameServers: nameservers})
} else if strings.Contains(strings.ToLower(k), "rule-set:") { } else if strings.Contains(kLower, "rule-set:") {
policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: "rule-set:" + k[9:], NameServers: nameservers})
} else { } else {
policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers}) policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers})

View File

@@ -158,6 +158,7 @@ type DelayHistoryStoreType int
type Proxy interface { type Proxy interface {
ProxyAdapter ProxyAdapter
Adapter() ProxyAdapter
AliveForTestUrl(url string) bool AliveForTestUrl(url string) bool
DelayHistory() []DelayHistory DelayHistory() []DelayHistory
ExtraDelayHistories() map[string]ProxyState ExtraDelayHistories() map[string]ProxyState
@@ -255,12 +256,16 @@ type UDPPacketInAddr interface {
// PacketAdapter is a UDP Packet adapter for socks/redir/tun // PacketAdapter is a UDP Packet adapter for socks/redir/tun
type PacketAdapter interface { type PacketAdapter interface {
UDPPacket UDPPacket
// Metadata returns destination metadata
Metadata() *Metadata Metadata() *Metadata
// Key is a SNAT key
Key() string
} }
type packetAdapter struct { type packetAdapter struct {
UDPPacket UDPPacket
metadata *Metadata metadata *Metadata
key string
} }
// Metadata returns destination metadata // Metadata returns destination metadata
@@ -268,10 +273,16 @@ func (s *packetAdapter) Metadata() *Metadata {
return s.metadata return s.metadata
} }
// Key is a SNAT key
func (s *packetAdapter) Key() string {
return s.key
}
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter { func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
return &packetAdapter{ return &packetAdapter{
packet, packet,
metadata, metadata,
packet.LocalAddr().String(),
} }
} }
@@ -284,17 +295,23 @@ type WriteBackProxy interface {
UpdateWriteBack(wb WriteBack) UpdateWriteBack(wb WriteBack)
} }
type PacketSender interface {
// Send will send PacketAdapter nonblocking
// the implement must call UDPPacket.Drop() inside Send
Send(PacketAdapter)
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
Process(PacketConn, WriteBackProxy)
// ResolveUDP do a local resolve UDP dns blocking if metadata is not resolved
ResolveUDP(*Metadata) error
// Close stop the Process loop
Close()
}
type NatTable interface { type NatTable interface {
Set(key string, e PacketConn, w WriteBackProxy) GetOrCreate(key string, maker func() PacketSender) (PacketSender, bool)
Get(key string) (PacketConn, WriteBackProxy)
GetOrCreateLock(key string) (*sync.Cond, bool)
Delete(key string) Delete(key string)
DeleteLock(key string)
GetForLocalConn(lAddr, rAddr string) *net.UDPConn GetForLocalConn(lAddr, rAddr string) *net.UDPConn
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool

View File

@@ -3,6 +3,7 @@ package constant
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"strings"
) )
// DNSModeMapping is a mapping for EnhancedMode enum // DNSModeMapping is a mapping for EnhancedMode enum
@@ -27,7 +28,7 @@ func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
if err := unmarshal(&tp); err != nil { if err := unmarshal(&tp); err != nil {
return err return err
} }
mode, exist := DNSModeMapping[tp] mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist { if !exist {
return errors.New("invalid mode") return errors.New("invalid mode")
} }
@@ -46,7 +47,7 @@ func (e *DNSMode) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &tp); err != nil { if err := json.Unmarshal(data, &tp); err != nil {
return err return err
} }
mode, exist := DNSModeMapping[tp] mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist { if !exist {
return errors.New("invalid mode") return errors.New("invalid mode")
} }
@@ -59,6 +60,21 @@ func (e DNSMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String()) return json.Marshal(e.String())
} }
// UnmarshalText unserialize EnhancedMode
func (e *DNSMode) UnmarshalText(data []byte) error {
mode, exist := DNSModeMapping[strings.ToLower(string(data))]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalText serialize EnhancedMode
func (e DNSMode) MarshalText() ([]byte, error) {
return []byte(e.String()), nil
}
func (e DNSMode) String() string { func (e DNSMode) String() string {
switch e { switch e {
case DNSNormal: case DNSNormal:
@@ -150,7 +166,7 @@ func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&tp); err != nil { if err := unmarshal(&tp); err != nil {
return err return err
} }
mode, exist := FilterModeMapping[tp] mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist { if !exist {
return errors.New("invalid mode") return errors.New("invalid mode")
} }
@@ -167,7 +183,20 @@ func (e *FilterMode) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &tp); err != nil { if err := json.Unmarshal(data, &tp); err != nil {
return err return err
} }
mode, exist := FilterModeMapping[tp] mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
func (e FilterMode) MarshalText() ([]byte, error) {
return []byte(e.String()), nil
}
func (e *FilterMode) UnmarshalText(data []byte) error {
mode, exist := FilterModeMapping[strings.ToLower(string(data))]
if !exist { if !exist {
return errors.New("invalid mode") return errors.New("invalid mode")
} }

View File

@@ -1,12 +0,0 @@
package constant
var (
ASNEnable bool
GeodataMode bool
GeoAutoUpdate bool
GeoUpdateInterval int
GeoIpUrl string
MmdbUrl string
GeoSiteUrl string
ASNUrl string
)

View File

@@ -1,5 +0,0 @@
package constant
var (
UA string
)

View File

@@ -1,13 +1,14 @@
package constant package constant
import ( import (
"crypto/md5"
"encoding/hex"
"os" "os"
P "path" P "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/constant/features"
) )
const Name = "mihomo" const Name = "mihomo"
@@ -73,7 +74,7 @@ func (p *path) Resolve(path string) string {
// IsSafePath return true if path is a subpath of homedir // IsSafePath return true if path is a subpath of homedir
func (p *path) IsSafePath(path string) bool { func (p *path) IsSafePath(path string) bool {
if p.allowUnsafePath { if p.allowUnsafePath || features.CMFA {
return true return true
} }
homedir := p.HomeDir() homedir := p.HomeDir()
@@ -87,8 +88,8 @@ func (p *path) IsSafePath(path string) bool {
} }
func (p *path) GetPathByHash(prefix, name string) string { func (p *path) GetPathByHash(prefix, name string) string {
hash := md5.Sum([]byte(name)) hash := utils.MakeHash([]byte(name))
filename := hex.EncodeToString(hash[:]) filename := hash.String()
return filepath.Join(p.HomeDir(), prefix, filename) return filepath.Join(p.HomeDir(), prefix, filename)
} }

View File

@@ -32,8 +32,10 @@ func (v VehicleType) String() string {
} }
type Vehicle interface { type Vehicle interface {
Read(ctx context.Context) ([]byte, error) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error)
Write(buf []byte) error
Path() string Path() string
Url() string
Proxy() string Proxy() string
Type() VehicleType Type() VehicleType
} }
@@ -71,6 +73,7 @@ type Provider interface {
type ProxyProvider interface { type ProxyProvider interface {
Provider Provider
Proxies() []constant.Proxy Proxies() []constant.Proxy
Count() int
// Touch is used to inform the provider that the proxy is actually being used while getting the list of proxies. // Touch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
// Commonly used in DialContext and DialPacketConn // Commonly used in DialContext and DialPacketConn
Touch() Touch()

View File

@@ -56,6 +56,21 @@ func (e TUNStack) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String()) return json.Marshal(e.String())
} }
// UnmarshalText unserialize TUNStack
func (e *TUNStack) UnmarshalText(data []byte) error {
mode, exist := StackTypeMapping[strings.ToLower(string(data))]
if !exist {
return errors.New("invalid tun stack")
}
*e = mode
return nil
}
// MarshalText serialize TUNStack with json
func (e TUNStack) MarshalText() ([]byte, error) {
return []byte(e.String()), nil
}
func (e TUNStack) String() string { func (e TUNStack) String() string {
switch e { switch e {
case TunGvisor: case TunGvisor:

View File

@@ -103,3 +103,5 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
return ret.msg, ret.err return ret.msg, ret.err
} }
} }
func (c *client) ResetConnection() {}

View File

@@ -1,5 +1,3 @@
//go:build !(android && cmfa)
package dns package dns
import ( import (
@@ -55,6 +53,12 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg,
return return
} }
func (d *dhcpClient) ResetConnection() {
for _, client := range d.clients {
client.ResetConnection()
}
}
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) { func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
d.lock.Lock() d.lock.Lock()

View File

@@ -9,6 +9,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"runtime" "runtime"
"strconv" "strconv"
@@ -67,6 +68,8 @@ type dnsOverHTTPS struct {
dialer *dnsDialer dialer *dnsDialer
addr string addr string
skipCertVerify bool skipCertVerify bool
ecsPrefix netip.Prefix
ecsOverride bool
} }
// type check // type check
@@ -99,6 +102,28 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
doh.skipCertVerify = true doh.skipCertVerify = true
} }
if ecs := params["ecs"]; ecs != "" {
prefix, err := netip.ParsePrefix(ecs)
if err != nil {
addr, err := netip.ParseAddr(ecs)
if err != nil {
log.Warnln("DOH [%s] config with invalid ecs: %s", doh.addr, ecs)
} else {
doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
}
} else {
doh.ecsPrefix = prefix
}
}
if doh.ecsPrefix.IsValid() {
log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix)
}
if params["ecs-override"] == "true" {
doh.ecsOverride = true
}
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
return doh return doh
@@ -126,6 +151,10 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
} }
}() }()
if doh.ecsPrefix.IsValid() {
setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride)
}
// Check if there was already an active client before sending the request. // Check if there was already an active client before sending the request.
// We'll only attempt to re-connect if there was one. // We'll only attempt to re-connect if there was one.
client, isCached, err := doh.getClient(ctx) client, isCached, err := doh.getClient(ctx)
@@ -174,11 +203,23 @@ func (doh *dnsOverHTTPS) Close() (err error) {
return doh.closeClient(doh.client) return doh.closeClient(doh.client)
} }
// closeClient cleans up resources used by client if necessary. Note, that at func (doh *dnsOverHTTPS) ResetConnection() {
// this point it should only be done for HTTP/3 as it may leak due to keep-alive doh.clientMu.Lock()
// connections. defer doh.clientMu.Unlock()
if doh.client == nil {
return
}
_ = doh.closeClient(doh.client)
doh.client = nil
}
// closeClient cleans up resources used by client if necessary.
func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) { func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
if isHTTP3(client) { client.CloseIdleConnections()
if isHTTP3(client) { // HTTP/3 may leak due to keep-alive connections.
return client.Transport.(io.Closer).Close() return client.Transport.(io.Closer).Close()
} }
@@ -479,6 +520,13 @@ func (h *http3Transport) Close() (err error) {
return h.baseTransport.Close() return h.baseTransport.Close()
} }
func (h *http3Transport) CloseIdleConnections() {
h.mu.RLock()
defer h.mu.RUnlock()
h.baseTransport.CloseIdleConnections()
}
// createTransportH3 tries to create an HTTP/3 transport for this upstream. // createTransportH3 tries to create an HTTP/3 transport for this upstream.
// We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or // We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or
// if it is too slow. In order to do that, this method will run two probes // if it is too slow. In order to do that, this method will run two probes

View File

@@ -144,6 +144,10 @@ func (doq *dnsOverQUIC) Close() (err error) {
return err return err
} }
func (doq *dnsOverQUIC) ResetConnection() {
doq.closeConnWithError(nil)
}
// exchangeQUIC attempts to open a QUIC connection, send the DNS message // exchangeQUIC attempts to open a QUIC connection, send the DNS message
// through it and return the response it got from the server. // through it and return the response it got from the server.
func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) { func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) {

51
dns/edns0_subnet.go Normal file
View File

@@ -0,0 +1,51 @@
package dns
import (
"net/netip"
"github.com/miekg/dns"
)
func setEdns0Subnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) bool {
var (
optRecord *dns.OPT
subnetOption *dns.EDNS0_SUBNET
)
findExists:
for _, record := range message.Extra {
var isOPTRecord bool
if optRecord, isOPTRecord = record.(*dns.OPT); isOPTRecord {
for _, option := range optRecord.Option {
var isEDNS0Subnet bool
if subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET); isEDNS0Subnet {
if !override {
return false
}
break findExists
}
}
}
}
if optRecord == nil {
optRecord = &dns.OPT{
Hdr: dns.RR_Header{
Name: ".",
Rrtype: dns.TypeOPT,
},
}
message.Extra = append(message.Extra, optRecord)
}
if subnetOption == nil {
subnetOption = new(dns.EDNS0_SUBNET)
optRecord.Option = append(optRecord.Option, subnetOption)
}
subnetOption.Code = dns.EDNS0SUBNET
if clientSubnet.Addr().Is4() {
subnetOption.Family = 1
} else {
subnetOption.Family = 2
}
subnetOption.SourceNetmask = uint8(clientSubnet.Bits())
subnetOption.Address = clientSubnet.Addr().AsSlice()
return true
}

View File

@@ -3,54 +3,16 @@
package dns package dns
import ( import (
"context"
D "github.com/miekg/dns"
"github.com/metacubex/mihomo/common/lru"
"github.com/metacubex/mihomo/component/dhcp"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
) )
const SystemDNSPlaceholder = "system" var systemResolver []dnsClient
var systemResolver *Resolver
var isolateHandler handler
var _ dnsClient = (*dhcpClient)(nil)
type dhcpClient struct {
enable bool
}
func (d *dhcpClient) Address() string {
return SystemDNSPlaceholder
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return d.ExchangeContext(context.Background(), m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if s := systemResolver; s != nil {
return s.ExchangeContext(ctx, m)
}
return nil, dhcp.ErrNotFound
}
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
if h := isolateHandler; h != nil {
return handlerWithContext(context.Background(), h, msg)
}
return nil, D.ErrTime
}
func FlushCacheWithDefaultResolver() { func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil { if r := resolver.DefaultResolver; r != nil {
r.(*Resolver).cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true)) r.ClearCache()
} }
resolver.ResetConnection()
} }
func UpdateSystemDNS(addr []string) { func UpdateSystemDNS(addr []string) {
@@ -63,19 +25,15 @@ func UpdateSystemDNS(addr []string) {
ns = append(ns, NameServer{Addr: d}) ns = append(ns, NameServer{Addr: d})
} }
systemResolver = NewResolver(Config{Main: ns}) systemResolver = transform(ns, nil)
} }
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) { func (c *systemClient) getDnsClients() ([]dnsClient, error) {
if resolver == nil { return systemResolver, nil
isolateHandler = nil }
return func (c *systemClient) ResetConnection() {
for _, r := range systemResolver {
r.ResetConnection()
} }
isolateHandler = NewHandler(resolver, mapper)
}
func newDHCPClient(ifaceName string) *dhcpClient {
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
} }

View File

@@ -1,6 +0,0 @@
//go:build !(android && cmfa)
package dns
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
}

View File

@@ -48,3 +48,5 @@ func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, err
func (r rcodeClient) Address() string { func (r rcodeClient) Address() string {
return r.addr return r.addr
} }
func (r rcodeClient) ResetConnection() {}

View File

@@ -24,11 +24,13 @@ import (
type dnsClient interface { type dnsClient interface {
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string Address() string
ResetConnection()
} }
type dnsCache interface { type dnsCache interface {
GetWithExpire(key string) (*D.Msg, time.Time, bool) GetWithExpire(key string) (*D.Msg, time.Time, bool)
SetWithExpire(key string, value *D.Msg, expire time.Time) SetWithExpire(key string, value *D.Msg, expire time.Time)
Clear()
} }
type result struct { type result struct {
@@ -47,7 +49,7 @@ type Resolver struct {
group singleflight.Group[*D.Msg] group singleflight.Group[*D.Msg]
cache dnsCache cache dnsCache
policy []dnsPolicy policy []dnsPolicy
proxyServer []dnsClient defaultResolver *Resolver
} }
func (r *Resolver) LookupIPPrimaryIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) { func (r *Resolver) LookupIPPrimaryIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) {
@@ -369,6 +371,26 @@ func (r *Resolver) Invalid() bool {
return len(r.main) > 0 return len(r.main) > 0
} }
func (r *Resolver) ClearCache() {
if r != nil && r.cache != nil {
r.cache.Clear()
}
}
func (r *Resolver) ResetConnection() {
if r != nil {
for _, c := range r.main {
c.ResetConnection()
}
for _, c := range r.fallback {
c.ResetConnection()
}
if dr := r.defaultResolver; dr != nil {
dr.ResetConnection()
}
}
}
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
@@ -418,16 +440,18 @@ type Config struct {
CacheAlgorithm string CacheAlgorithm string
} }
func NewResolver(config Config) *Resolver { func (config Config) newCache() dnsCache {
var cache dnsCache if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" {
if config.CacheAlgorithm == "lru" { return lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
} else { } else {
cache = arc.New(arc.WithSize[string, *D.Msg](4096)) return arc.New(arc.WithSize[string, *D.Msg](4096))
} }
}
func NewResolver(config Config) (r *Resolver, pr *Resolver) {
defaultResolver := &Resolver{ defaultResolver := &Resolver{
main: transform(config.Default, nil), main: transform(config.Default, nil),
cache: cache, cache: config.newCache(),
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
} }
@@ -458,27 +482,29 @@ func NewResolver(config Config) *Resolver {
return return
} }
if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" { r = &Resolver{
cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
} else {
cache = arc.New(arc.WithSize[string, *D.Msg](4096))
}
r := &Resolver{
ipv6: config.IPv6, ipv6: config.IPv6,
main: cacheTransform(config.Main), main: cacheTransform(config.Main),
cache: cache, cache: config.newCache(),
hosts: config.Hosts, hosts: config.Hosts,
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond, ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
} }
r.defaultResolver = defaultResolver
if len(config.ProxyServer) != 0 {
pr = &Resolver{
ipv6: config.IPv6,
main: cacheTransform(config.ProxyServer),
cache: config.newCache(),
hosts: config.Hosts,
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
}
}
if len(config.Fallback) != 0 { if len(config.Fallback) != 0 {
r.fallback = cacheTransform(config.Fallback) r.fallback = cacheTransform(config.Fallback)
} }
if len(config.ProxyServer) != 0 {
r.proxyServer = cacheTransform(config.ProxyServer)
}
if len(config.Policy) != 0 { if len(config.Policy) != 0 {
r.policy = make([]dnsPolicy, 0) r.policy = make([]dnsPolicy, 0)
@@ -509,18 +535,7 @@ func NewResolver(config Config) *Resolver {
r.fallbackIPFilters = config.FallbackIPFilter r.fallbackIPFilters = config.FallbackIPFilter
r.fallbackDomainFilters = config.FallbackDomainFilter r.fallbackDomainFilters = config.FallbackDomainFilter
return r return
}
func NewProxyServerHostResolver(old *Resolver) *Resolver {
r := &Resolver{
ipv6: old.ipv6,
main: old.proxyServer,
cache: old.cache,
hosts: old.hosts,
ipv6Timeout: old.ipv6Timeout,
}
return r
} }
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go

View File

@@ -6,7 +6,6 @@ import (
"net" "net"
"github.com/metacubex/mihomo/common/sockopt" "github.com/metacubex/mihomo/common/sockopt"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/context" "github.com/metacubex/mihomo/context"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -50,10 +49,6 @@ func (s *Server) SetHandler(handler handler) {
} }
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) { func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
if features.CMFA {
UpdateIsolateHandler(resolver, mapper)
}
if addr == address && resolver != nil { if addr == address && resolver != nil {
handler := NewHandler(resolver, mapper) handler := NewHandler(resolver, mapper)
server.SetHandler(handler) server.SetHandler(handler)

View File

@@ -3,16 +3,11 @@ package dns
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"golang.org/x/exp/slices"
) )
const ( const (
@@ -31,64 +26,6 @@ type systemClient struct {
lastFlush time.Time lastFlush time.Time
} }
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
c.mu.Lock()
defer c.mu.Unlock()
var err error
if time.Since(c.lastFlush) > SystemDnsFlushTime {
var nameservers []string
if nameservers, err = dnsReadConfig(); err == nil {
log.Debugln("[DNS] system dns update to %s", nameservers)
for _, addr := range nameservers {
if resolver.IsSystemDnsBlacklisted(addr) {
continue
}
if _, ok := c.dnsClients[addr]; !ok {
clients := transform(
[]NameServer{{
Addr: net.JoinHostPort(addr, "53"),
Net: "udp",
}},
nil,
)
if len(clients) > 0 {
c.dnsClients[addr] = &systemDnsClient{
disableTimes: 0,
dnsClient: clients[0],
}
}
}
}
available := 0
for nameserver, sdc := range c.dnsClients {
if slices.Contains(nameservers, nameserver) {
sdc.disableTimes = 0 // enable
available++
} else {
if sdc.disableTimes > SystemDnsDeleteTimes {
delete(c.dnsClients, nameserver) // drop too old dnsClient
} else {
sdc.disableTimes++
}
}
}
if available > 0 {
c.lastFlush = time.Now()
}
}
}
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
for _, sdc := range c.dnsClients {
if sdc.disableTimes == 0 {
dnsClients = append(dnsClients, sdc.dnsClient)
}
}
if len(dnsClients) > 0 {
return dnsClients, nil
}
return nil, err
}
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
dnsClients, err := c.getDnsClients() dnsClients, err := c.getDnsClients()
if err != nil { if err != nil {

73
dns/system_common.go Normal file
View File

@@ -0,0 +1,73 @@
//go:build !(android && cmfa)
package dns
import (
"net"
"time"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log"
"golang.org/x/exp/slices"
)
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
c.mu.Lock()
defer c.mu.Unlock()
var err error
if time.Since(c.lastFlush) > SystemDnsFlushTime {
var nameservers []string
if nameservers, err = dnsReadConfig(); err == nil {
log.Debugln("[DNS] system dns update to %s", nameservers)
for _, addr := range nameservers {
if resolver.IsSystemDnsBlacklisted(addr) {
continue
}
if _, ok := c.dnsClients[addr]; !ok {
clients := transform(
[]NameServer{{
Addr: net.JoinHostPort(addr, "53"),
Net: "udp",
}},
nil,
)
if len(clients) > 0 {
c.dnsClients[addr] = &systemDnsClient{
disableTimes: 0,
dnsClient: clients[0],
}
}
}
}
available := 0
for nameserver, sdc := range c.dnsClients {
if slices.Contains(nameservers, nameserver) {
sdc.disableTimes = 0 // enable
available++
} else {
if sdc.disableTimes > SystemDnsDeleteTimes {
delete(c.dnsClients, nameserver) // drop too old dnsClient
} else {
sdc.disableTimes++
}
}
}
if available > 0 {
c.lastFlush = time.Now()
}
}
}
dnsClients := make([]dnsClient, 0, len(c.dnsClients))
for _, sdc := range c.dnsClients {
if sdc.disableTimes == 0 {
dnsClients = append(dnsClients, sdc.dnsClient)
}
}
if len(dnsClients) > 0 {
return dnsClients, nil
}
return nil, err
}
func (c *systemClient) ResetConnection() {}

View File

@@ -58,11 +58,21 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件 external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
# secret: "123456" # `Authorization:Bearer ${secret}` # secret: "123456" # `Authorization:Bearer ${secret}`
# RESTful API CORS标头配置
external-controller-cors:
allow-origins:
- *
allow-private-network: true
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用即大于等于1803/RS4版本即可使用 # RESTful API Unix socket 监听地址( windows版本大于17063也可以使用即大于等于1803/RS4版本即可使用
# !!!注意: 从Unix socket访问api接口不会验证secret 如果开启请自行保证安全问题 # !!!注意: 从Unix socket访问api接口不会验证secret 如果开启请自行保证安全问题
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/ # 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
external-controller-unix: mihomo.sock external-controller-unix: mihomo.sock
# RESTful API Windows namedpipe 监听地址
# !!!注意: 从Windows namedpipe访问api接口不会验证secret 如果开启请自行保证安全问题
external-controller-pipe: \\.\pipe\mihomo
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
@@ -760,6 +770,17 @@ proxies: # socks5
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
# allowed-ips: ['0.0.0.0/0'] # allowed-ips: ['0.0.0.0/0']
# reserved: [209,98,59] # reserved: [209,98,59]
# 如果存在则开启AmneziaWG功能
# amnezia-wg-option:
# jc: 5
# jmin: 500
# jmax: 501
# s1: 30
# s2: 40
# h1: 123456
# h2: 67543
# h4: 32345
# h3: 123123
# tuic # tuic
- name: tuic - name: tuic
@@ -929,6 +950,13 @@ proxy-providers:
# ip-version: ipv4-prefer # ip-version: ipv4-prefer
# additional-prefix: "[provider1]" # additional-prefix: "[provider1]"
# additional-suffix: "test" # additional-suffix: "test"
# # 名字替换,支持正则表达式
# proxy-name:
# - pattern: "test"
# target: "TEST"
# - pattern: "IPLC-(.*?)倍"
# target: "iplc x $1"
test: test:
type: file type: file
path: /test.yaml path: /test.yaml
@@ -955,12 +983,12 @@ rule-providers:
# 对于behavior=domain: # 对于behavior=domain:
# - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式 # - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换到mrs格式
# - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式 # - format=text 可以通过“mihomo convert-ruleset domain text XXX.text XXX.mrs”转换到mrs格式
# - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式暂不支持转换回ymal格式 # - XXX.mrs 可以通过"mihomo convert-ruleset domain mrs XXX.mrs XXX.text"转换回text格式暂不支持转换回yaml格式
# #
# 对于behavior=ipcidr: # 对于behavior=ipcidr:
# - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式 # - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml XXX.mrs”转换到mrs格式
# - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式 # - format=text 可以通过“mihomo convert-ruleset ipcidr text XXX.text XXX.mrs”转换到mrs格式
# - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式暂不支持转换回ymal格式 # - XXX.mrs 可以通过"mihomo convert-ruleset ipcidr mrs XXX.mrs XXX.text"转换回text格式暂不支持转换回yaml格式
# #
type: http type: http
url: "url" url: "url"

21
go.mod
View File

@@ -8,7 +8,6 @@ require (
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.11.4 github.com/dlclark/regexp2 v1.11.4
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.4.0 github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.3.0 github.com/gofrs/uuid/v5 v5.3.0
@@ -17,42 +16,45 @@ require (
github.com/klauspost/cpuid/v2 v2.2.8 github.com/klauspost/cpuid/v2 v2.2.8
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.2
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
github.com/metacubex/chacha v0.1.0 github.com/metacubex/chacha v0.1.0
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58 github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4
github.com/metacubex/sing-shadowsocks v0.2.8 github.com/metacubex/sing-shadowsocks v0.2.8
github.com/metacubex/sing-shadowsocks2 v0.2.2 github.com/metacubex/sing-shadowsocks2 v0.2.2
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785
github.com/metacubex/utls v1.6.6 github.com/metacubex/utls v1.6.6
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.62 github.com/miekg/dns v1.1.62
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21 github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/sagernet/sing v0.5.0-alpha.13 github.com/sagernet/sing v0.5.0-alpha.13
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e
github.com/samber/lo v1.47.0 github.com/samber/lo v1.47.0
github.com/shirou/gopsutil/v3 v3.24.5 github.com/shirou/gopsutil/v3 v3.24.5
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/vmihailenco/msgpack/v5 v5.4.1
github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/wk8/go-ordered-map/v2 v2.1.8
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.6.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.26.0 golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
golang.org/x/net v0.28.0 golang.org/x/net v0.29.0
golang.org/x/sys v0.24.0 golang.org/x/sys v0.25.0
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.3.0 lukechampine.com/blake3 v1.3.0
@@ -103,12 +105,13 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.20.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
) )

44
go.sum
View File

@@ -42,8 +42,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -96,6 +94,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc= github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
@@ -104,8 +104,8 @@ 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/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw= github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58 h1:T6OxROLZBr9SOQxN5TzUslv81hEREy/dEgaUKVjaG7U= github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o=
github.com/metacubex/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4/go.mod h1:Y7yRGqFE6UQL/3aKPYmiYdjfVkeujJaStP4+jiZMcN8=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA= github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
@@ -120,12 +120,14 @@ github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE= github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4= github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
github.com/metacubex/sing-wireguard v0.0.0-20240826061955-1e4e67afe5cd/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo= github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo= github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo=
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8= github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo= github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
@@ -156,6 +158,8 @@ 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/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
@@ -168,8 +172,6 @@ github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnV
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
@@ -208,6 +210,10 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -216,16 +222,16 @@ gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7 h1:UNrDfkQqiE
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs= gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7/go.mod h1:E+rxHvJG9H6PUdzq9NRG6csuLN3XUx98BfGOVWNYnXs=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -234,8 +240,8 @@ golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -255,12 +261,12 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=

View File

@@ -17,10 +17,12 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
G "github.com/metacubex/mihomo/component/geodata" G "github.com/metacubex/mihomo/component/geodata"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/metacubex/mihomo/component/profile" "github.com/metacubex/mihomo/component/profile"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/resource"
"github.com/metacubex/mihomo/component/sniffer" "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie" "github.com/metacubex/mihomo/component/trie"
@@ -81,6 +83,7 @@ func ParseWithBytes(buf []byte) (*config.Config, error) {
func ApplyConfig(cfg *config.Config, force bool) { func ApplyConfig(cfg *config.Config, force bool) {
mux.Lock() mux.Lock()
defer mux.Unlock() defer mux.Unlock()
log.SetLevel(cfg.General.LogLevel)
tunnel.OnSuspend() tunnel.OnSuspend()
@@ -116,7 +119,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
hcCompatibleProvider(cfg.Providers) hcCompatibleProvider(cfg.Providers)
initExternalUI() initExternalUI()
log.SetLevel(cfg.General.LogLevel) resolver.ResetConnection()
} }
func initInnerTcp() { func initInnerTcp() {
@@ -126,7 +129,7 @@ func initInnerTcp() {
func GetGeneral() *config.General { func GetGeneral() *config.General {
ports := listener.GetPorts() ports := listener.GetPorts()
var authenticator []string var authenticator []string
if auth := authStore.Authenticator(); auth != nil { if auth := authStore.Default.Authenticator(); auth != nil {
authenticator = auth.Users() authenticator = auth.Users()
} }
@@ -157,13 +160,13 @@ func GetGeneral() *config.General {
Interface: dialer.DefaultInterface.Load(), Interface: dialer.DefaultInterface.Load(),
RoutingMark: int(dialer.DefaultRoutingMark.Load()), RoutingMark: int(dialer.DefaultRoutingMark.Load()),
GeoXUrl: config.GeoXUrl{ GeoXUrl: config.GeoXUrl{
GeoIp: C.GeoIpUrl, GeoIp: G.GeoIpUrl(),
Mmdb: C.MmdbUrl, Mmdb: G.MmdbUrl(),
ASN: C.ASNUrl, ASN: G.ASNUrl(),
GeoSite: C.GeoSiteUrl, GeoSite: G.GeoSiteUrl(),
}, },
GeoAutoUpdate: G.GeoAutoUpdate(), GeoAutoUpdate: updater.GeoAutoUpdate(),
GeoUpdateInterval: G.GeoUpdateInterval(), GeoUpdateInterval: updater.GeoUpdateInterval(),
GeodataMode: G.GeodataMode(), GeodataMode: G.GeodataMode(),
GeodataLoader: G.LoaderName(), GeodataLoader: G.LoaderName(),
GeositeMatcher: G.SiteMatcherName(), GeositeMatcher: G.SiteMatcherName(),
@@ -171,7 +174,8 @@ func GetGeneral() *config.General {
FindProcessMode: tunnel.FindProcessMode(), FindProcessMode: tunnel.FindProcessMode(),
Sniffing: tunnel.IsSniffing(), Sniffing: tunnel.IsSniffing(),
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(), GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
GlobalUA: C.UA, GlobalUA: mihomoHttp.UA(),
ETagSupport: resource.ETag(),
} }
return general return general
@@ -251,8 +255,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
CacheAlgorithm: c.CacheAlgorithm, CacheAlgorithm: c.CacheAlgorithm,
} }
r := dns.NewResolver(cfg) r, pr := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)
// reuse cache of old host mapper // reuse cache of old host mapper
@@ -381,13 +384,13 @@ func updateTunnels(tunnels []LC.Tunnel) {
} }
func initExternalUI() { func initExternalUI() {
if updater.AutoUpdateUI { if updater.AutoDownloadUI {
dirEntries, _ := os.ReadDir(updater.ExternalUIPath) dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
if len(dirEntries) > 0 { if len(dirEntries) > 0 {
log.Infoln("UI already exists, skip downloading") log.Infoln("UI already exists, skip downloading")
} else { } else {
log.Infoln("External UI downloading ...") log.Infoln("External UI downloading ...")
updater.UpdateUI() updater.DownloadUI()
} }
} }
} }
@@ -420,7 +423,7 @@ func updateGeneral(general *config.General) {
func updateUsers(users []auth.AuthUser) { func updateUsers(users []auth.AuthUser) {
authenticator := auth.NewAuthenticator(users) authenticator := auth.NewAuthenticator(users)
authStore.SetAuthenticator(authenticator) authStore.Default.SetAuthenticator(authenticator)
if authenticator != nil { if authenticator != nil {
log.Infoln("Authentication of local server updated") log.Infoln("Authentication of local server updated")
} }
@@ -442,12 +445,12 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
} }
for name, proxy := range proxies { for name, proxy := range proxies {
outbound, ok := proxy.(*adapter.Proxy) outbound, ok := proxy.(C.Proxy)
if !ok { if !ok {
continue continue
} }
selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble) selector, ok := outbound.Adapter().(outboundgroup.SelectAble)
if !ok { if !ok {
continue continue
} }

View File

@@ -1,10 +1,7 @@
package hub package hub
import ( import (
"strings"
"github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/hub/route" "github.com/metacubex/mihomo/hub/route"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -30,6 +27,12 @@ func WithExternalControllerUnix(externalControllerUnix string) Option {
} }
} }
func WithExternalControllerPipe(externalControllerPipe string) Option {
return func(cfg *config.Config) {
cfg.Controller.ExternalControllerPipe = externalControllerPipe
}
}
func WithSecret(secret string) Option { func WithSecret(secret string) Option {
return func(cfg *config.Config) { return func(cfg *config.Config) {
cfg.Controller.Secret = secret cfg.Controller.Secret = secret
@@ -43,11 +46,6 @@ func ApplyConfig(cfg *config.Config) {
} }
func applyRoute(cfg *config.Config) { func applyRoute(cfg *config.Config) {
if features.CMFA && strings.HasSuffix(cfg.Controller.ExternalUI, ":0") {
// CMFA have set its default override value to end with ":0" for security.
// so we direct return at here
return
}
if cfg.Controller.ExternalUI != "" { if cfg.Controller.ExternalUI != "" {
route.SetUIPath(cfg.Controller.ExternalUI) route.SetUIPath(cfg.Controller.ExternalUI)
} }
@@ -55,17 +53,30 @@ func applyRoute(cfg *config.Config) {
Addr: cfg.Controller.ExternalController, Addr: cfg.Controller.ExternalController,
TLSAddr: cfg.Controller.ExternalControllerTLS, TLSAddr: cfg.Controller.ExternalControllerTLS,
UnixAddr: cfg.Controller.ExternalControllerUnix, UnixAddr: cfg.Controller.ExternalControllerUnix,
PipeAddr: cfg.Controller.ExternalControllerPipe,
Secret: cfg.Controller.Secret, Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate, Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey, PrivateKey: cfg.TLS.PrivateKey,
DohServer: cfg.Controller.ExternalDohServer, DohServer: cfg.Controller.ExternalDohServer,
IsDebug: cfg.General.LogLevel == log.DEBUG, IsDebug: cfg.General.LogLevel == log.DEBUG,
Cors: route.Cors{
AllowOrigins: cfg.Controller.Cors.AllowOrigins,
AllowPrivateNetwork: cfg.Controller.Cors.AllowPrivateNetwork,
},
}) })
} }
// Parse call at the beginning of mihomo // Parse call at the beginning of mihomo
func Parse(options ...Option) error { func Parse(configBytes []byte, options ...Option) error {
cfg, err := executor.Parse() var cfg *config.Config
var err error
if len(configBytes) != 0 {
cfg, err = executor.ParseWithBytes(configBytes)
} else {
cfg, err = executor.Parse()
}
if err != nil { if err != nil {
return err return err
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
@@ -32,7 +31,7 @@ func GroupRouter() http.Handler {
func getGroups(w http.ResponseWriter, r *http.Request) { func getGroups(w http.ResponseWriter, r *http.Request) {
var gs []C.Proxy var gs []C.Proxy
for _, p := range tunnel.Proxies() { for _, p := range tunnel.Proxies() {
if _, ok := p.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { if _, ok := p.Adapter().(C.Group); ok {
gs = append(gs, p) gs = append(gs, p)
} }
} }
@@ -43,7 +42,7 @@ func getGroups(w http.ResponseWriter, r *http.Request) {
func getGroup(w http.ResponseWriter, r *http.Request) { func getGroup(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
if _, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { if _, ok := proxy.Adapter().(C.Group); ok {
render.JSON(w, r, proxy) render.JSON(w, r, proxy)
return return
} }
@@ -53,25 +52,15 @@ func getGroup(w http.ResponseWriter, r *http.Request) {
func getGroupDelay(w http.ResponseWriter, r *http.Request) { func getGroupDelay(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group) group, ok := proxy.Adapter().(C.Group)
if !ok { if !ok {
render.Status(r, http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
return return
} }
switch proxy.(*adapter.Proxy).Type() { if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector {
case C.URLTest: selectAble.ForceSet("")
if urlTestGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest); ok {
urlTestGroup.ForceSet("")
}
case C.Fallback:
if fallbackGroup, ok := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.Fallback); ok {
fallbackGroup.ForceSet("")
}
}
if proxy.(*adapter.Proxy).Type() != C.Selector {
cachefile.Cache().SetSelected(proxy.Name(), "") cachefile.Cache().SetSelected(proxy.Name(), "")
} }

View File

@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
@@ -31,6 +30,7 @@ func proxyRouter() http.Handler {
r.Get("/", getProxy) r.Get("/", getProxy)
r.Get("/delay", getProxyDelay) r.Get("/delay", getProxyDelay)
r.Put("/", updateProxy) r.Put("/", updateProxy)
r.Delete("/", unfixedProxy)
}) })
return r return r
} }
@@ -81,8 +81,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
return return
} }
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy) proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble) selector, ok := proxy.Adapter().(outboundgroup.SelectAble)
if !ok { if !ok {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("Must be a Selector")) render.JSON(w, r, newError("Must be a Selector"))
@@ -146,3 +146,15 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
"delay": delay, "delay": delay,
}) })
} }
func unfixedProxy(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector {
selectAble.ForceSet("")
cachefile.Cache().SetSelected(proxy.Name(), "")
render.NoContent(w, r)
return
}
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
}

View File

@@ -23,10 +23,10 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
"github.com/sagernet/cors"
) )
var ( var (
@@ -35,6 +35,7 @@ var (
httpServer *http.Server httpServer *http.Server
tlsServer *http.Server tlsServer *http.Server
unixServer *http.Server unixServer *http.Server
pipeServer *http.Server
) )
type Traffic struct { type Traffic struct {
@@ -51,33 +52,46 @@ type Config struct {
Addr string Addr string
TLSAddr string TLSAddr string
UnixAddr string UnixAddr string
PipeAddr string
Secret string Secret string
Certificate string Certificate string
PrivateKey string PrivateKey string
DohServer string DohServer string
IsDebug bool IsDebug bool
Cors Cors
}
type Cors struct {
AllowOrigins []string
AllowPrivateNetwork bool
}
func (c Cors) Apply(r chi.Router) {
r.Use(cors.New(cors.Options{
AllowedOrigins: c.AllowOrigins,
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowPrivateNetwork: c.AllowPrivateNetwork,
MaxAge: 300,
}).Handler)
} }
func ReCreateServer(cfg *Config) { func ReCreateServer(cfg *Config) {
go start(cfg) go start(cfg)
go startTLS(cfg) go startTLS(cfg)
go startUnix(cfg) go startUnix(cfg)
if inbound.SupportNamedPipe {
go startPipe(cfg)
}
} }
func SetUIPath(path string) { func SetUIPath(path string) {
uiPath = C.Path.Resolve(path) uiPath = C.Path.Resolve(path)
} }
func router(isDebug bool, secret string, dohServer string) *chi.Mux { func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
corsM := cors.New(cors.Options{ cors.Apply(r)
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 300,
})
r.Use(setPrivateNetworkAccess)
r.Use(corsM.Handler)
if isDebug { if isDebug {
r.Mount("/debug", func() http.Handler { r.Mount("/debug", func() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
@@ -146,12 +160,12 @@ func start(cfg *Config) {
log.Infoln("RESTful API listening at: %s", l.Addr().String()) log.Infoln("RESTful API listening at: %s", l.Addr().String())
server := &http.Server{ server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
} }
httpServer = server
if err = server.Serve(l); err != nil { if err = server.Serve(l); err != nil {
log.Errorln("External controller serve error: %s", err) log.Errorln("External controller serve error: %s", err)
} }
httpServer = server
} }
} }
@@ -178,15 +192,15 @@ func startTLS(cfg *Config) {
log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
server := &http.Server{ server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer), Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c}, Certificates: []tls.Certificate{c},
}, },
} }
tlsServer = server
if err = server.ServeTLS(l, "", ""); err != nil { if err = server.ServeTLS(l, "", ""); err != nil {
log.Errorln("External controller tls serve error: %s", err) log.Errorln("External controller tls serve error: %s", err)
} }
tlsServer = server
} }
} }
@@ -223,26 +237,48 @@ func startUnix(cfg *Config) {
log.Errorln("External controller unix listen error: %s", err) log.Errorln("External controller unix listen error: %s", err)
return return
} }
_ = os.Chmod(addr, 0o666)
log.Infoln("RESTful API unix listening at: %s", l.Addr().String()) log.Infoln("RESTful API unix listening at: %s", l.Addr().String())
server := &http.Server{ server := &http.Server{
Handler: router(cfg.IsDebug, "", cfg.DohServer), Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors),
} }
unixServer = server
if err = server.Serve(l); err != nil { if err = server.Serve(l); err != nil {
log.Errorln("External controller unix serve error: %s", err) log.Errorln("External controller unix serve error: %s", err)
} }
unixServer = server
} }
} }
func setPrivateNetworkAccess(next http.Handler) http.Handler { func startPipe(cfg *Config) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // first stop existing server
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { if pipeServer != nil {
w.Header().Add("Access-Control-Allow-Private-Network", "true") _ = pipeServer.Close()
pipeServer = nil
}
// handle addr
if len(cfg.PipeAddr) > 0 {
if !strings.HasPrefix(cfg.PipeAddr, "\\\\.\\pipe\\") { // windows namedpipe must start with "\\.\pipe\"
log.Errorln("External controller pipe listen error: windows namedpipe must start with \"\\\\.\\pipe\\\"")
return
} }
next.ServeHTTP(w, r)
}) l, err := inbound.ListenNamedPipe(cfg.PipeAddr)
if err != nil {
log.Errorln("External controller pipe listen error: %s", err)
return
}
log.Infoln("RESTful API pipe listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors),
}
pipeServer = server
if err = server.Serve(l); err != nil {
log.Errorln("External controller pipe serve error: %s", err)
}
}
} }
func safeEuqal(a, b string) bool { func safeEuqal(a, b string) bool {

View File

@@ -47,7 +47,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
} }
func updateUI(w http.ResponseWriter, r *http.Request) { func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateUI() err := updater.DownloadUI()
if err != nil { if err != nil {
log.Warnln("%s", err) log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError) render.Status(r, http.StatusInternalServerError)

View File

@@ -4,14 +4,30 @@ import (
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
) )
var authenticator auth.Authenticator type authStore struct {
authenticator auth.Authenticator
func Authenticator() auth.Authenticator {
return authenticator
} }
func SetAuthenticator(au auth.Authenticator) { func (a *authStore) Authenticator() auth.Authenticator {
authenticator = au return a.authenticator
} }
func Nil() auth.Authenticator { return nil } func (a *authStore) SetAuthenticator(authenticator auth.Authenticator) {
a.authenticator = authenticator
}
func NewAuthStore(authenticator auth.Authenticator) auth.AuthStore {
return &authStore{authenticator}
}
var Default auth.AuthStore = NewAuthStore(nil)
type nilAuthStore struct{}
func (a *nilAuthStore) Authenticator() auth.Authenticator {
return nil
}
func (a *nilAuthStore) SetAuthenticator(authenticator auth.Authenticator) {}
var Nil auth.AuthStore = (*nilAuthStore)(nil) // always return nil, even call SetAuthenticator() with a non-nil authenticator

View File

@@ -1,9 +0,0 @@
//go:build android && cmfa
package http
import "net"
func (l *Listener) Listener() net.Listener {
return l.listener
}

View File

@@ -30,7 +30,7 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) {
return n, err return n, err
} }
func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser
inUserIdx := len(additions) - 1 inUserIdx := len(additions) - 1
client := newClient(c, tunnel, additions) client := newClient(c, tunnel, additions)
@@ -41,7 +41,7 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator,
conn := N.NewBufferedConn(c) conn := N.NewBufferedConn(c)
authenticator := getAuth() authenticator := store.Authenticator()
keepAlive := true keepAlive := true
trusted := authenticator == nil // disable authenticate if lru is nil trusted := authenticator == nil // disable authenticate if lru is nil
lastUser := "" lastUser := ""

View File

@@ -4,7 +4,6 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
@@ -33,20 +32,20 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...) return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
} }
// NewWithAuthenticate // NewWithAuthenticate
// never change type traits because it's used in CFMA // never change type traits because it's used in CMFA
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
getAuth := authStore.Authenticator store := authStore.Default
if !authenticate { if !authenticate {
getAuth = authStore.Nil store = authStore.Default
} }
return NewWithAuthenticator(addr, tunnel, getAuth, additions...) return NewWithAuthenticator(addr, tunnel, store, additions...)
} }
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@@ -55,8 +54,8 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
} }
} }
l, err := inbound.Listen("tcp", addr)
l, err := inbound.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -74,19 +73,18 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
} }
continue continue
} }
N.TCPKeepAlive(conn)
getAuth := getAuth store := store
if isDefault { // only apply on default listener if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
_ = conn.Close() _ = conn.Close()
continue continue
} }
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) { if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
getAuth = authStore.Nil store = authStore.Nil
} }
} }
go HandleConn(conn, tunnel, getAuth, additions...) go HandleConn(conn, tunnel, store, additions...)
} }
}() }()

View File

@@ -12,7 +12,7 @@ type AuthUser struct {
type AuthUsers []AuthUser type AuthUsers []AuthUser
func (a AuthUsers) GetAuth() func() auth.Authenticator { func (a AuthUsers) GetAuthStore() auth.AuthStore {
if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
if len(a) == 0 { if len(a) == 0 {
return authStore.Nil return authStore.Nil
@@ -25,7 +25,7 @@ func (a AuthUsers) GetAuth() func() auth.Authenticator {
} }
} }
authenticator := auth.NewAuthenticator(users) authenticator := auth.NewAuthenticator(users)
return func() auth.Authenticator { return authenticator } return authStore.NewAuthStore(authenticator)
} }
return authStore.Authenticator return authStore.Default
} }

View File

@@ -45,7 +45,7 @@ func (h *HTTP) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (h *HTTP) Listen(tunnel C.Tunnel) error { func (h *HTTP) Listen(tunnel C.Tunnel) error {
var err error var err error
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuth(), h.Additions()...) h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -53,7 +53,7 @@ func (m *Mixed) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (m *Mixed) Listen(tunnel C.Tunnel) error { func (m *Mixed) Listen(tunnel C.Tunnel) error {
var err error var err error
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuth(), m.Additions()...) m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -71,7 +71,7 @@ func (s *Socks) Address() string {
// Listen implements constant.InboundListener // Listen implements constant.InboundListener
func (s *Socks) Listen(tunnel C.Tunnel) error { func (s *Socks) Listen(tunnel C.Tunnel) error {
var err error var err error
if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuth(), s.Additions()...); err != nil { if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuthStore(), s.Additions()...); err != nil {
return err return err
} }
if s.udp { if s.udp {

View File

@@ -37,10 +37,10 @@ func (l *Listener) Close() error {
} }
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) { func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...) return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
} }
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) { func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
isDefault := false isDefault := false
if len(additions) == 0 { if len(additions) == 0 {
isDefault = true isDefault = true
@@ -49,6 +49,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
} }
} }
l, err := inbound.Listen("tcp", addr) l, err := inbound.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -67,26 +68,24 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
} }
continue continue
} }
getAuth := getAuth store := store
if isDefault { // only apply on default listener if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close() _ = c.Close()
continue continue
} }
if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) { if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) {
getAuth = authStore.Nil store = authStore.Nil
} }
} }
go handleConn(c, tunnel, getAuth, additions...) go handleConn(c, tunnel, store, additions...)
} }
}() }()
return ml, nil return ml, nil
} }
func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) { func handleConn(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
N.TCPKeepAlive(conn)
bufConn := N.NewBufferedConn(conn) bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1) head, err := bufConn.Peek(1)
if err != nil { if err != nil {
@@ -95,10 +94,10 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticato
switch head[0] { switch head[0] {
case socks4.Version: case socks4.Version:
socks.HandleSocks4(bufConn, tunnel, getAuth, additions...) socks.HandleSocks4(bufConn, tunnel, store, additions...)
case socks5.Version: case socks5.Version:
socks.HandleSocks5(bufConn, tunnel, getAuth, additions...) socks.HandleSocks5(bufConn, tunnel, store, additions...)
default: default:
http.HandleConn(bufConn, tunnel, getAuth, additions...) http.HandleConn(bufConn, tunnel, store, additions...)
} }
} }

View File

@@ -4,7 +4,7 @@ import (
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/keepalive"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
) )
@@ -37,10 +37,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
inbound.WithSpecialRules(""), inbound.WithSpecialRules(""),
} }
} }
l, err := net.Listen("tcp", addr) l, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rl := &Listener{ rl := &Listener{
listener: l, listener: l,
addr: addr, addr: addr,
@@ -68,6 +70,6 @@ func handleRedir(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
conn.Close() conn.Close()
return return
} }
N.TCPKeepAlive(conn) keepalive.TCPKeepAlive(conn)
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, additions...)) tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, additions...))
} }

Some files were not shown because too many files have changed in this diff Show More