mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-28 09:39:54 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc9d5cfee9 | ||
|
|
264713571d | ||
|
|
a67c379884 | ||
|
|
af5ad3254b | ||
|
|
acfc9f8baa | ||
|
|
1633885794 | ||
|
|
2afa2798b1 | ||
|
|
cd2d1c6bb0 | ||
|
|
88bfe7cffe | ||
|
|
43cb48231a | ||
|
|
4fa15c6334 | ||
|
|
5812a7bdeb | ||
|
|
3922b17067 | ||
|
|
a4e84f0479 | ||
|
|
6c0383026e | ||
|
|
59a2b24593 | ||
|
|
966eeae41b | ||
|
|
150c6ccd25 | ||
|
|
33823f1728 | ||
|
|
781b783346 | ||
|
|
ddfa9e8671 | ||
|
|
b7cb6774bf | ||
|
|
5d242510c8 | ||
|
|
223eae0e06 | ||
|
|
7dafe7889e | ||
|
|
d80e8bb0c2 | ||
|
|
f52fe6aa74 | ||
|
|
a08aa10630 | ||
|
|
794645b7f8 | ||
|
|
f020b20ab9 | ||
|
|
3676d1b79f | ||
|
|
58c973ee2b | ||
|
|
fb4d3c41c8 | ||
|
|
e33d4a4769 | ||
|
|
4c3fe98ebd | ||
|
|
0a2f606e1b | ||
|
|
8230bc8e7d | ||
|
|
ecbbf9d220 | ||
|
|
f305e440ef | ||
|
|
910f236696 | ||
|
|
417d709d60 | ||
|
|
f8557f5fd8 | ||
|
|
89b9438fc0 | ||
|
|
7c8f451892 | ||
|
|
ef244b896a | ||
|
|
595a575cde | ||
|
|
b1301b1b41 | ||
|
|
dc29514fb6 | ||
|
|
8940bdd56f | ||
|
|
ade4234615 | ||
|
|
faaa90f8a6 |
@@ -39,6 +39,11 @@ type Proxy struct {
|
||||
extra *xsync.MapOf[string, *internalProxyState]
|
||||
}
|
||||
|
||||
// Adapter implements C.Proxy
|
||||
func (p *Proxy) Adapter() C.ProxyAdapter {
|
||||
return p.ProxyAdapter
|
||||
}
|
||||
|
||||
// AliveForTestUrl implements C.Proxy
|
||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPS
|
||||
metadata.RawSrcAddr = conn.RemoteAddr()
|
||||
metadata.RawDstAddr = conn.LocalAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
|
||||
@@ -3,6 +3,9 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@@ -11,28 +14,47 @@ var (
|
||||
lc = tfo.ListenConfig{
|
||||
DisableTFO: true,
|
||||
}
|
||||
mutex sync.RWMutex
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func Tfo() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return !lc.DisableTFO
|
||||
}
|
||||
|
||||
func SetMPTCP(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
}
|
||||
|
||||
func MPTCP() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return getMultiPathTCP(&lc.ListenConfig)
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return lc.Listen(ctx, network, address)
|
||||
}
|
||||
|
||||
func Listen(network, address string) (net.Listener, error) {
|
||||
return ListenContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func init() {
|
||||
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
keepalive.SetNetListenConfig(&lc.ListenConfig)
|
||||
})
|
||||
}
|
||||
|
||||
14
adapter/inbound/listen_notwindows.go
Normal file
14
adapter/inbound/listen_notwindows.go
Normal 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
|
||||
}
|
||||
32
adapter/inbound/listen_windows.go
Normal file
32
adapter/inbound/listen_windows.go
Normal 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)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
@@ -38,7 +37,6 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return d.loopBack.NewConn(NewConn(c, d)), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,11 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -76,7 +74,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
||||
@@ -37,7 +37,7 @@ func NewRejectWithOption(option RejectOption) *Reject {
|
||||
return &Reject{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Direct,
|
||||
tp: C.Reject,
|
||||
udp: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
||||
@@ -80,7 +80,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -94,7 +93,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -122,7 +120,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
@@ -207,8 +204,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -82,7 +81,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -128,7 +126,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
|
||||
@@ -77,7 +77,6 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -154,7 +153,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -212,7 +210,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
N.TCPKeepAlive(c)
|
||||
c, err = t.plainStream(ctx, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
@@ -314,7 +311,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -262,7 +262,6 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -327,7 +326,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -505,17 +503,14 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
var addons *vless.Addons
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
option.Flow = option.Flow[:16]
|
||||
switch option.Flow {
|
||||
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:
|
||||
if option.Flow != vless.XRV {
|
||||
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 {
|
||||
@@ -577,7 +572,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,6 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -373,7 +372,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -473,7 +471,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,18 +24,24 @@ import (
|
||||
"github.com/metacubex/mihomo/dns"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
amnezia "github.com/metacubex/amneziawg-go/device"
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
"github.com/metacubex/wireguard-go/device"
|
||||
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/wireguard-go/device"
|
||||
)
|
||||
|
||||
type wireguardGoDevice interface {
|
||||
Close()
|
||||
IpcSet(uapiConf string) error
|
||||
}
|
||||
|
||||
type WireGuard struct {
|
||||
*Base
|
||||
bind *wireguard.ClientBind
|
||||
device *device.Device
|
||||
device wireguardGoDevice
|
||||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
resolver *dns.Resolver
|
||||
@@ -67,6 +73,8 @@ type WireGuardOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
||||
|
||||
AmneziaWGOption *AmneziaWGOption `proxy:"amnezia-wg-option,omitempty"`
|
||||
|
||||
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
|
||||
|
||||
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
|
||||
@@ -84,6 +92,18 @@ type WireGuardPeerOption struct {
|
||||
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 {
|
||||
name string
|
||||
}
|
||||
@@ -243,14 +263,20 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
logger := &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
Errorf: func(format string, args ...interface{}) {
|
||||
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
|
||||
for _, address := range outbound.localPrefixes {
|
||||
@@ -270,7 +296,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
for i := range nss {
|
||||
nss[i].ProxyAdapter = refP
|
||||
}
|
||||
outbound.resolver = dns.NewResolver(dns.Config{
|
||||
outbound.resolver, _ = dns.NewResolver(dns.Config{
|
||||
Main: nss,
|
||||
IPv6: has6,
|
||||
})
|
||||
@@ -367,6 +393,17 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||
ipcConf := ""
|
||||
if !updateOnly {
|
||||
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 {
|
||||
for i, peer := range w.option.Peers {
|
||||
|
||||
@@ -4,3 +4,7 @@ type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
}
|
||||
|
||||
var _ SelectAble = (*Fallback)(nil)
|
||||
var _ SelectAble = (*URLTest)(nil)
|
||||
var _ SelectAble = (*Selector)(nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -9,8 +10,9 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,6 +29,15 @@ type healthCheckSchema struct {
|
||||
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 {
|
||||
TFO *bool `provider:"tfo,omitempty"`
|
||||
MPTcp *bool `provider:"mptcp,omitempty"`
|
||||
@@ -41,6 +52,8 @@ type OverrideSchema struct {
|
||||
IPVersion *string `provider:"ip-version,omitempty"`
|
||||
AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
|
||||
AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
|
||||
|
||||
ProxyName []OverrideProxyNameSchema `provider:"proxy-name,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
@@ -94,11 +107,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
@@ -72,19 +71,15 @@ func (pp *proxySetProvider) HealthCheck() {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Update() error {
|
||||
elm, same, err := pp.Fetcher.Update()
|
||||
if err == nil && !same {
|
||||
pp.OnUpdate(elm)
|
||||
}
|
||||
_, _, err := pp.Fetcher.Update()
|
||||
return err
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Initial() error {
|
||||
elm, err := pp.Fetcher.Initial()
|
||||
_, err := pp.Fetcher.Initial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp.OnUpdate(elm)
|
||||
pp.getSubscriptionInfo()
|
||||
pp.closeAllConnections()
|
||||
return nil
|
||||
@@ -98,6 +93,10 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||
return pp.proxies
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Count() int {
|
||||
return len(pp.proxies)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Touch() {
|
||||
pp.healthCheck.touch()
|
||||
}
|
||||
@@ -125,8 +124,8 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
|
||||
http.MethodGet, nil, nil, pp.Vehicle().Proxy())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -134,7 +133,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
|
||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
||||
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())
|
||||
if err != nil {
|
||||
return
|
||||
@@ -145,10 +144,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
return
|
||||
}
|
||||
}
|
||||
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
|
||||
if err != nil {
|
||||
log.Warnln("[Provider] get subscription-userinfo: %e", err)
|
||||
}
|
||||
pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -267,6 +263,10 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Count() int {
|
||||
return len(cp.proxies)
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
@@ -399,6 +399,16 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
case "additional-suffix":
|
||||
name := mapping["name"].(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:
|
||||
mapping[fieldName] = field.Elem().Interface()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type SubscriptionInfo struct {
|
||||
@@ -12,28 +15,46 @@ type SubscriptionInfo struct {
|
||||
Expire int64
|
||||
}
|
||||
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||
userinfo = strings.ToLower(userinfo)
|
||||
userinfo = strings.ReplaceAll(userinfo, " ", "")
|
||||
si = new(SubscriptionInfo)
|
||||
|
||||
for _, field := range strings.Split(userinfo, ";") {
|
||||
switch name, value, _ := strings.Cut(field, "="); name {
|
||||
case "upload":
|
||||
si.Upload, err = strconv.ParseInt(value, 10, 64)
|
||||
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)
|
||||
}
|
||||
name, value, ok := strings.Cut(field, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
intValue, err := parseValue(value)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -33,15 +33,8 @@ type ARC[K comparable, V any] struct {
|
||||
|
||||
// New returns a new Adaptive Replacement Cache (ARC).
|
||||
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
|
||||
arc := &ARC[K, V]{
|
||||
p: 0,
|
||||
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]),
|
||||
}
|
||||
arc := &ARC[K, V]{}
|
||||
arc.Clear()
|
||||
|
||||
for _, option := range options {
|
||||
option(arc)
|
||||
@@ -49,6 +42,19 @@ func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
|
||||
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.
|
||||
// This optimizes future access to this entry (side effect).
|
||||
func (a *ARC[K, V]) Set(key K, value V) {
|
||||
|
||||
@@ -68,10 +68,8 @@ type LruCache[K comparable, V any] struct {
|
||||
|
||||
// New creates an LruCache
|
||||
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
lc := &LruCache[K, V]{
|
||||
lru: list.New[*entry[K, V]](),
|
||||
cache: make(map[K]*list.Element[*entry[K, V]]),
|
||||
}
|
||||
lc := &LruCache[K, V]{}
|
||||
lc.Clear()
|
||||
|
||||
for _, option := range options {
|
||||
option(lc)
|
||||
@@ -80,6 +78,14 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
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
|
||||
// set to true if the key was found.
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package structure
|
||||
// references: https://github.com/mitchellh/mapstructure
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"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 {
|
||||
kind := val.Kind()
|
||||
switch {
|
||||
case isInt(kind):
|
||||
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() {
|
||||
for {
|
||||
kind := val.Kind()
|
||||
if kind == reflect.Pointer && val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
return d.decode(name, data, val.Elem())
|
||||
case reflect.String:
|
||||
return d.decodeString(name, data, val)
|
||||
case reflect.Bool:
|
||||
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())
|
||||
if ok, err := d.decodeTextUnmarshaller(name, data, val); ok {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case isInt(kind):
|
||||
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:
|
||||
val = val.Elem()
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package structure
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -179,3 +180,90 @@ func TestStructure_SliceNilValueComplex(t *testing.T) {
|
||||
err = decoder.Decode(rawMap, ss)
|
||||
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
62
common/utils/hash.go
Normal 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
|
||||
}
|
||||
@@ -5,6 +5,11 @@ type Authenticator interface {
|
||||
Users() []string
|
||||
}
|
||||
|
||||
type AuthStore interface {
|
||||
Authenticator() Authenticator
|
||||
SetAuthenticator(Authenticator)
|
||||
}
|
||||
|
||||
type AuthUser struct {
|
||||
User string
|
||||
Pass string
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
@@ -84,7 +85,7 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
|
||||
if cfg.addrReuse {
|
||||
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)
|
||||
} else {
|
||||
if cfg.interfaceName != "" {
|
||||
@@ -144,11 +145,12 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
}
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
keepalive.SetNetDialer(dialer)
|
||||
if opt.mpTcp {
|
||||
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)
|
||||
} else {
|
||||
if opt.interfaceName != "" {
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// DefaultSocketHook
|
||||
// never change type traits because it's used in CFMA
|
||||
// never change type traits because it's used in CMFA
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func socketHookToToDialer(dialer *net.Dialer) {
|
||||
|
||||
@@ -7,46 +7,32 @@ import (
|
||||
)
|
||||
|
||||
type cachefileStore struct {
|
||||
cache *cachefile.CacheFile
|
||||
cache *cachefile.FakeIpStore
|
||||
}
|
||||
|
||||
// GetByHost implements store.GetByHost
|
||||
func (c *cachefileStore) GetByHost(host string) (netip.Addr, bool) {
|
||||
elm := c.cache.GetFakeip([]byte(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
|
||||
}
|
||||
return c.cache.GetByHost(host)
|
||||
}
|
||||
|
||||
// PutByHost implements store.PutByHost
|
||||
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
|
||||
func (c *cachefileStore) GetByIP(ip netip.Addr) (string, bool) {
|
||||
elm := c.cache.GetFakeip(ip.AsSlice())
|
||||
if elm == nil {
|
||||
return "", false
|
||||
}
|
||||
return string(elm), true
|
||||
return c.cache.GetByIP(ip)
|
||||
}
|
||||
|
||||
// PutByIP implements store.PutByIP
|
||||
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
|
||||
func (c *cachefileStore) DelByIP(ip netip.Addr) {
|
||||
addr := ip.AsSlice()
|
||||
c.cache.DelFakeipPair(addr, c.cache.GetFakeip(addr))
|
||||
c.cache.DelByIP(ip)
|
||||
}
|
||||
|
||||
// Exist implements store.Exist
|
||||
@@ -63,3 +49,7 @@ func (c *cachefileStore) CloneTo(store store) {}
|
||||
func (c *cachefileStore) FlushFakeIP() error {
|
||||
return c.cache.FlushFakeIP()
|
||||
}
|
||||
|
||||
func newCachefileStore(cache *cachefile.CacheFile) *cachefileStore {
|
||||
return &cachefileStore{cache.FakeIpStore()}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ func (m *memoryStore) CloneTo(store store) {
|
||||
|
||||
// FlushFakeIP implements store.FlushFakeIP
|
||||
func (m *memoryStore) FlushFakeIP() error {
|
||||
_ = m.cacheIP.Clear()
|
||||
return m.cacheHost.Clear()
|
||||
m.cacheIP.Clear()
|
||||
m.cacheHost.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMemoryStore(size int) *memoryStore {
|
||||
|
||||
@@ -201,9 +201,7 @@ func New(options Options) (*Pool, error) {
|
||||
ipnet: options.IPNet,
|
||||
}
|
||||
if options.Persistence {
|
||||
pool.store = &cachefileStore{
|
||||
cache: cachefile.Cache(),
|
||||
}
|
||||
pool.store = newCachefileStore(cachefile.Cache())
|
||||
} else {
|
||||
pool.store = newMemoryStore(options.Size)
|
||||
}
|
||||
|
||||
@@ -43,9 +43,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
pool.store = &cachefileStore{
|
||||
cache: &cachefile.CacheFile{DB: db},
|
||||
}
|
||||
pool.store = newCachefileStore(&cachefile.CacheFile{DB: db})
|
||||
return pool, f.Name(), nil
|
||||
}
|
||||
|
||||
@@ -63,13 +61,13 @@ func TestPool_Basic(t *testing.T) {
|
||||
last := pool.Lookup("bar.com")
|
||||
bar, exist := pool.LookBack(last)
|
||||
|
||||
assert.True(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.True(t, last == netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
assert.Equal(t, first, netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.Equal(t, pool.Lookup("foo.com"), netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, bar, "bar.com")
|
||||
assert.True(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.Gateway(), netip.AddrFrom4([4]byte{192, 168, 0, 1}))
|
||||
assert.Equal(t, pool.Broadcast(), netip.AddrFrom4([4]byte{192, 168, 0, 15}))
|
||||
assert.Equal(t, pool.IPNet().String(), ipnet.String())
|
||||
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})))
|
||||
@@ -91,13 +89,13 @@ func TestPool_BasicV6(t *testing.T) {
|
||||
last := pool.Lookup("bar.com")
|
||||
bar, exist := pool.LookBack(last)
|
||||
|
||||
assert.True(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.True(t, last == netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))
|
||||
assert.Equal(t, first, 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.Equal(t, last, netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8805"))
|
||||
assert.True(t, exist)
|
||||
assert.Equal(t, bar, "bar.com")
|
||||
assert.True(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.Gateway(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8801"))
|
||||
assert.Equal(t, pool.Broadcast(), netip.MustParseAddr("2001:4860:4860:0000:0000:0000:0000:8bff"))
|
||||
assert.Equal(t, pool.IPNet().String(), ipnet.String())
|
||||
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")))
|
||||
@@ -143,8 +141,8 @@ func TestPool_CycleUsed(t *testing.T) {
|
||||
}
|
||||
baz := pool.Lookup("baz.com")
|
||||
next := pool.Lookup("foo.com")
|
||||
assert.True(t, foo == baz)
|
||||
assert.True(t, next == bar)
|
||||
assert.Equal(t, foo, baz)
|
||||
assert.Equal(t, next, bar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +199,7 @@ func TestPool_MaxCacheSize(t *testing.T) {
|
||||
pool.Lookup("baz.com")
|
||||
next := pool.Lookup("foo.com")
|
||||
|
||||
assert.False(t, first == next)
|
||||
assert.NotEqual(t, first, next)
|
||||
}
|
||||
|
||||
func TestPool_DoubleMapping(t *testing.T) {
|
||||
@@ -231,7 +229,7 @@ func TestPool_DoubleMapping(t *testing.T) {
|
||||
assert.False(t, bazExist)
|
||||
assert.True(t, barExist)
|
||||
|
||||
assert.False(t, bazIP == newBazIP)
|
||||
assert.NotEqual(t, bazIP, newBazIP)
|
||||
}
|
||||
|
||||
func TestPool_Clone(t *testing.T) {
|
||||
@@ -243,8 +241,8 @@ func TestPool_Clone(t *testing.T) {
|
||||
|
||||
first := pool.Lookup("foo.com")
|
||||
last := pool.Lookup("bar.com")
|
||||
assert.True(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, first, netip.AddrFrom4([4]byte{192, 168, 0, 4}))
|
||||
assert.Equal(t, last, netip.AddrFrom4([4]byte{192, 168, 0, 5}))
|
||||
|
||||
newPool, _ := New(Options{
|
||||
IPNet: ipnet,
|
||||
@@ -289,13 +287,13 @@ func TestPool_FlushFileCache(t *testing.T) {
|
||||
baz := pool.Lookup("foo.com")
|
||||
nero := pool.Lookup("foo.com")
|
||||
|
||||
assert.True(t, foo == fox)
|
||||
assert.True(t, foo == next)
|
||||
assert.False(t, foo == baz)
|
||||
assert.True(t, bar == bax)
|
||||
assert.True(t, bar == baz)
|
||||
assert.False(t, bar == next)
|
||||
assert.True(t, baz == nero)
|
||||
assert.Equal(t, foo, fox)
|
||||
assert.Equal(t, foo, next)
|
||||
assert.NotEqual(t, foo, baz)
|
||||
assert.Equal(t, bar, bax)
|
||||
assert.Equal(t, bar, baz)
|
||||
assert.NotEqual(t, bar, next)
|
||||
assert.Equal(t, baz, nero)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,11 +316,11 @@ func TestPool_FlushMemoryCache(t *testing.T) {
|
||||
baz := pool.Lookup("foo.com")
|
||||
nero := pool.Lookup("foo.com")
|
||||
|
||||
assert.True(t, foo == fox)
|
||||
assert.True(t, foo == next)
|
||||
assert.False(t, foo == baz)
|
||||
assert.True(t, bar == bax)
|
||||
assert.True(t, bar == baz)
|
||||
assert.False(t, bar == next)
|
||||
assert.True(t, baz == nero)
|
||||
assert.Equal(t, foo, fox)
|
||||
assert.Equal(t, foo, next)
|
||||
assert.NotEqual(t, foo, baz)
|
||||
assert.Equal(t, bar, bax)
|
||||
assert.Equal(t, bar, baz)
|
||||
assert.NotEqual(t, bar, next)
|
||||
assert.Equal(t, baz, nero)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@@ -18,12 +20,79 @@ var (
|
||||
initGeoSite bool
|
||||
initGeoIP int
|
||||
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 {
|
||||
geoSiteEnable.Store(true)
|
||||
initGeoSiteMutex.Lock()
|
||||
defer initGeoSiteMutex.Unlock()
|
||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||
log.Infoln("Can't find GeoSite.dat, start download")
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
log.Infoln("Download GeoSite.dat finish")
|
||||
@@ -35,7 +104,7 @@ func InitGeoSite() error {
|
||||
if err := os.Remove(C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||
if err := downloadToPath(GeoSiteUrl(), C.Path.GeoSite()); err != nil {
|
||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -44,49 +113,14 @@ func InitGeoSite() error {
|
||||
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 {
|
||||
if C.GeodataMode {
|
||||
geoIpEnable.Store(true)
|
||||
initGeoIPMutex.Lock()
|
||||
defer initGeoIPMutex.Unlock()
|
||||
if GeodataMode() {
|
||||
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
|
||||
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())
|
||||
}
|
||||
log.Infoln("Download GeoIP.dat finish")
|
||||
@@ -99,7 +133,7 @@ func InitGeoIP() error {
|
||||
if err := os.Remove(C.Path.GeoIP()); err != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -110,7 +144,7 @@ func InitGeoIP() error {
|
||||
|
||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -121,7 +155,7 @@ func InitGeoIP() error {
|
||||
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -131,9 +165,12 @@ func InitGeoIP() error {
|
||||
}
|
||||
|
||||
func InitASN() error {
|
||||
asnEnable.Store(true)
|
||||
initASNMutex.Lock()
|
||||
defer initASNMutex.Unlock()
|
||||
if _, err := os.Stat(C.Path.ASN()); os.IsNotExist(err) {
|
||||
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())
|
||||
}
|
||||
log.Infoln("Download ASN.mmdb finish")
|
||||
@@ -145,7 +182,7 @@ func InitASN() error {
|
||||
if err := os.Remove(C.Path.ASN()); err != nil {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -153,3 +190,15 @@ func InitASN() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GeoIpEnable() bool {
|
||||
return geoIpEnable.Load()
|
||||
}
|
||||
|
||||
func GeoSiteEnable() bool {
|
||||
return geoSiteEnable.Load()
|
||||
}
|
||||
|
||||
func ASNEnable() bool {
|
||||
return asnEnable.Load()
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
|
||||
var (
|
||||
geoMode bool
|
||||
AutoUpdate bool
|
||||
UpdateInterval int
|
||||
geoLoaderName = "memconservative"
|
||||
geoSiteMatcher = "succinct"
|
||||
)
|
||||
@@ -25,14 +23,6 @@ func GeodataMode() bool {
|
||||
return geoMode
|
||||
}
|
||||
|
||||
func GeoAutoUpdate() bool {
|
||||
return AutoUpdate
|
||||
}
|
||||
|
||||
func GeoUpdateInterval() int {
|
||||
return UpdateInterval
|
||||
}
|
||||
|
||||
func LoaderName() string {
|
||||
return geoLoaderName
|
||||
}
|
||||
@@ -44,12 +34,6 @@ func SiteMatcherName() string {
|
||||
func SetGeodataMode(newGeodataMode bool) {
|
||||
geoMode = newGeodataMode
|
||||
}
|
||||
func SetGeoAutoUpdate(newAutoUpdate bool) {
|
||||
AutoUpdate = newAutoUpdate
|
||||
}
|
||||
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
|
||||
UpdateInterval = newGeoUpdateInterval
|
||||
}
|
||||
|
||||
func SetLoader(newLoader string) {
|
||||
if newLoader == "memc" {
|
||||
@@ -209,8 +193,11 @@ func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func ClearCache() {
|
||||
func ClearGeoSiteCache() {
|
||||
loadGeoSiteMatcherListSF.Reset()
|
||||
loadGeoSiteMatcherSF.Reset()
|
||||
}
|
||||
|
||||
func ClearGeoIPCache() {
|
||||
loadGeoIPMatcherSF.Reset()
|
||||
}
|
||||
|
||||
@@ -12,10 +12,21 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"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) {
|
||||
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 {
|
||||
req.Header.Set("User-Agent", C.UA)
|
||||
req.Header.Set("User-Agent", UA())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
65
component/keepalive/tcp_keepalive.go
Normal file
65
component/keepalive/tcp_keepalive.go
Normal 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)
|
||||
}
|
||||
}
|
||||
30
component/keepalive/tcp_keepalive_go122.go
Normal file
30
component/keepalive/tcp_keepalive_go122.go
Normal 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()
|
||||
}
|
||||
}
|
||||
45
component/keepalive/tcp_keepalive_go123.go
Normal file
45
component/keepalive/tcp_keepalive_go123.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build go1.23 && unix
|
||||
|
||||
package net
|
||||
package keepalive
|
||||
|
||||
func SupportTCPKeepAliveIdle() bool {
|
||||
return true
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go
|
||||
|
||||
package net
|
||||
package keepalive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,15 +1,9 @@
|
||||
package mmdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
mihomoOnce "github.com/metacubex/mihomo/common/once"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -25,26 +19,26 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
IPreader IPReader
|
||||
ASNreader ASNReader
|
||||
IPonce sync.Once
|
||||
ASNonce sync.Once
|
||||
ipReader IPReader
|
||||
asnReader ASNReader
|
||||
ipOnce sync.Once
|
||||
asnOnce sync.Once
|
||||
)
|
||||
|
||||
func LoadFromBytes(buffer []byte) {
|
||||
IPonce.Do(func() {
|
||||
ipOnce.Do(func() {
|
||||
mmdb, err := maxminddb.FromBytes(buffer)
|
||||
if err != nil {
|
||||
log.Fatalln("Can't load mmdb: %s", err.Error())
|
||||
}
|
||||
IPreader = IPReader{Reader: mmdb}
|
||||
ipReader = IPReader{Reader: mmdb}
|
||||
switch mmdb.Metadata.DatabaseType {
|
||||
case "sing-geoip":
|
||||
IPreader.databaseType = typeSing
|
||||
ipReader.databaseType = typeSing
|
||||
case "Meta-geoip0":
|
||||
IPreader.databaseType = typeMetaV0
|
||||
ipReader.databaseType = typeMetaV0
|
||||
default:
|
||||
IPreader.databaseType = typeMaxmind
|
||||
ipReader.databaseType = typeMaxmind
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -58,83 +52,45 @@ func Verify(path string) bool {
|
||||
}
|
||||
|
||||
func IPInstance() IPReader {
|
||||
IPonce.Do(func() {
|
||||
ipOnce.Do(func() {
|
||||
mmdbPath := C.Path.MMDB()
|
||||
log.Infoln("Load MMDB file: %s", mmdbPath)
|
||||
mmdb, err := maxminddb.Open(mmdbPath)
|
||||
if err != nil {
|
||||
log.Fatalln("Can't load MMDB: %s", err.Error())
|
||||
}
|
||||
IPreader = IPReader{Reader: mmdb}
|
||||
ipReader = IPReader{Reader: mmdb}
|
||||
switch mmdb.Metadata.DatabaseType {
|
||||
case "sing-geoip":
|
||||
IPreader.databaseType = typeSing
|
||||
ipReader.databaseType = typeSing
|
||||
case "Meta-geoip0":
|
||||
IPreader.databaseType = typeMetaV0
|
||||
ipReader.databaseType = typeMetaV0
|
||||
default:
|
||||
IPreader.databaseType = typeMaxmind
|
||||
ipReader.databaseType = typeMaxmind
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
return ipReader
|
||||
}
|
||||
|
||||
func ASNInstance() ASNReader {
|
||||
ASNonce.Do(func() {
|
||||
asnOnce.Do(func() {
|
||||
ASNPath := C.Path.ASN()
|
||||
log.Infoln("Load ASN file: %s", ASNPath)
|
||||
asn, err := maxminddb.Open(ASNPath)
|
||||
if err != nil {
|
||||
log.Fatalln("Can't load ASN: %s", err.Error())
|
||||
}
|
||||
ASNreader = ASNReader{Reader: asn}
|
||||
asnReader = ASNReader{Reader: asn}
|
||||
})
|
||||
|
||||
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
|
||||
return asnReader
|
||||
}
|
||||
|
||||
func ReloadIP() {
|
||||
mihomoOnce.Reset(&IPonce)
|
||||
mihomoOnce.Reset(&ipOnce)
|
||||
}
|
||||
|
||||
func ReloadASN() {
|
||||
mihomoOnce.Reset(&ASNonce)
|
||||
mihomoOnce.Reset(&asnOnce)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -10,47 +10,30 @@ import (
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
mapping *xsync.MapOf[string, *Entry]
|
||||
lockMap *xsync.MapOf[string, *sync.Cond]
|
||||
mapping *xsync.MapOf[string, *entry]
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
PacketConn C.PacketConn
|
||||
WriteBackProxy C.WriteBackProxy
|
||||
type entry struct {
|
||||
PacketSender C.PacketSender
|
||||
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
|
||||
LocalLockMap *xsync.MapOf[string, *sync.Cond]
|
||||
}
|
||||
|
||||
func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) {
|
||||
t.mapping.Store(key, &Entry{
|
||||
PacketConn: e,
|
||||
WriteBackProxy: w,
|
||||
LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](),
|
||||
LocalLockMap: xsync.NewMapOf[string, *sync.Cond](),
|
||||
func (t *Table) GetOrCreate(key string, maker func() C.PacketSender) (C.PacketSender, bool) {
|
||||
item, loaded := t.mapping.LoadOrCompute(key, func() *entry {
|
||||
return &entry{
|
||||
PacketSender: maker(),
|
||||
LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](),
|
||||
LocalLockMap: xsync.NewMapOf[string, *sync.Cond](),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
return item.PacketSender, loaded
|
||||
}
|
||||
|
||||
func (t *Table) Delete(key string) {
|
||||
t.mapping.Delete(key)
|
||||
}
|
||||
|
||||
func (t *Table) DeleteLock(lockKey string) {
|
||||
t.lockMap.Delete(lockKey)
|
||||
}
|
||||
|
||||
func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn {
|
||||
entry, exist := t.getEntry(lAddr)
|
||||
if !exist {
|
||||
@@ -105,7 +88,7 @@ func (t *Table) DeleteLockForLocalConn(lAddr, key string) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -116,7 +99,6 @@ func makeLock() *sync.Cond {
|
||||
// New return *Cache
|
||||
func New() *Table {
|
||||
return &Table{
|
||||
mapping: xsync.NewMapOf[string, *Entry](),
|
||||
lockMap: xsync.NewMapOf[string, *sync.Cond](),
|
||||
mapping: xsync.NewMapOf[string, *entry](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, str
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// DefaultPackageNameResolver
|
||||
// never change type traits because it's used in CFMA
|
||||
// never change type traits because it's used in CMFA
|
||||
var DefaultPackageNameResolver PackageNameResolver
|
||||
|
||||
func FindPackageName(metadata *C.Metadata) (string, error) {
|
||||
|
||||
@@ -19,6 +19,7 @@ var (
|
||||
|
||||
bucketSelected = []byte("selected")
|
||||
bucketFakeip = []byte("fakeip")
|
||||
bucketETag = []byte("etag")
|
||||
)
|
||||
|
||||
// CacheFile store and update the cache file
|
||||
@@ -69,80 +70,6 @@ func (c *CacheFile) SelectedMap() map[string]string {
|
||||
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 {
|
||||
return c.DB.Close()
|
||||
}
|
||||
|
||||
58
component/profile/cachefile/etag.go
Normal file
58
component/profile/cachefile/etag.go
Normal 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
|
||||
}
|
||||
115
component/profile/cachefile/fakeip.go
Normal file
115
component/profile/cachefile/fakeip.go
Normal 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
|
||||
}
|
||||
@@ -46,6 +46,8 @@ type Resolver interface {
|
||||
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
|
||||
Invalid() bool
|
||||
ClearCache()
|
||||
ResetConnection()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func ResetConnection() {
|
||||
if DefaultResolver != nil {
|
||||
go DefaultResolver.ResetConnection()
|
||||
}
|
||||
if ProxyServerHostResolver != nil {
|
||||
go ProxyServerHostResolver.ResetConnection()
|
||||
}
|
||||
}
|
||||
|
||||
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||
for _, v := range ips {
|
||||
if v.Unmap().Is4() {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -15,11 +13,6 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
fileMode os.FileMode = 0o666
|
||||
dirMode os.FileMode = 0o755
|
||||
)
|
||||
|
||||
type Parser[V any] func([]byte) (V, error)
|
||||
|
||||
type Fetcher[V any] struct {
|
||||
@@ -29,10 +22,10 @@ type Fetcher[V any] struct {
|
||||
name string
|
||||
vehicle types.Vehicle
|
||||
updatedAt time.Time
|
||||
hash [16]byte
|
||||
hash utils.HashType
|
||||
parser Parser[V]
|
||||
interval time.Duration
|
||||
OnUpdate func(V)
|
||||
onUpdate func(V)
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
@@ -54,107 +47,63 @@ func (f *Fetcher[V]) UpdatedAt() time.Time {
|
||||
|
||||
func (f *Fetcher[V]) Initial() (V, error) {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
isLocal bool
|
||||
forceUpdate bool
|
||||
buf []byte
|
||||
contents V
|
||||
err error
|
||||
)
|
||||
|
||||
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
|
||||
// local file exists, use it first
|
||||
buf, err = os.ReadFile(f.vehicle.Path())
|
||||
modTime := stat.ModTime()
|
||||
f.updatedAt = modTime
|
||||
isLocal = true
|
||||
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())
|
||||
forceUpdate = true
|
||||
contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false)
|
||||
f.updatedAt = modTime // reset updatedAt to file's modTime
|
||||
|
||||
if err == nil {
|
||||
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 {
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = f.startPullLoop(false)
|
||||
if err != nil {
|
||||
if !isLocal {
|
||||
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
|
||||
return lo.Empty[V](), err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
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()
|
||||
hash := md5.Sum(buf)
|
||||
if bytes.Equal(f.hash[:], hash[:]) {
|
||||
if f.hash.Equal(hash) {
|
||||
if updateFile {
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, 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
|
||||
}
|
||||
|
||||
@@ -163,15 +112,18 @@ func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) {
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
|
||||
if f.vehicle.Type() != types.File {
|
||||
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
|
||||
if updateFile {
|
||||
if err = f.vehicle.Write(buf); err != nil {
|
||||
return lo.Empty[V](), false, err
|
||||
}
|
||||
}
|
||||
|
||||
f.updatedAt = now
|
||||
f.hash = hash
|
||||
|
||||
if f.onUpdate != nil {
|
||||
f.onUpdate(contents)
|
||||
}
|
||||
|
||||
return contents, false, nil
|
||||
}
|
||||
|
||||
@@ -183,27 +135,57 @@ func (f *Fetcher[V]) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) pullLoop() {
|
||||
func (f *Fetcher[V]) pullLoop(forceUpdate bool) {
|
||||
initialInterval := f.interval - time.Since(f.updatedAt)
|
||||
if 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)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.Reset(f.interval)
|
||||
f.update(f.vehicle.Path())
|
||||
f.updateWithLog()
|
||||
case <-f.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fetcher[V]) update(path string) {
|
||||
elm, same, err := f.Update()
|
||||
func (f *Fetcher[V]) startPullLoop(forceUpdate bool) (err error) {
|
||||
// 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 {
|
||||
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
|
||||
return
|
||||
@@ -215,21 +197,7 @@ func (f *Fetcher[V]) update(path string) {
|
||||
}
|
||||
|
||||
log.Infoln("[Provider] %s's content update", f.Name())
|
||||
if f.OnUpdate != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
vehicle: vehicle,
|
||||
parser: parser,
|
||||
OnUpdate: onUpdate,
|
||||
onUpdate: onUpdate,
|
||||
interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,46 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
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 {
|
||||
path string
|
||||
}
|
||||
@@ -24,23 +58,37 @@ func (f *FileVehicle) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f *FileVehicle) Read(ctx context.Context) ([]byte, error) {
|
||||
return os.ReadFile(f.path)
|
||||
func (f *FileVehicle) Url() string {
|
||||
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 {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *FileVehicle) Write(buf []byte) error {
|
||||
return safeWrite(f.path, buf)
|
||||
}
|
||||
|
||||
func NewFileVehicle(path string) *FileVehicle {
|
||||
return &FileVehicle{path: path}
|
||||
}
|
||||
|
||||
type HTTPVehicle struct {
|
||||
url string
|
||||
path string
|
||||
proxy string
|
||||
header http.Header
|
||||
url string
|
||||
path string
|
||||
proxy string
|
||||
header http.Header
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Url() string {
|
||||
@@ -59,24 +107,60 @@ func (h *HTTPVehicle) Proxy() string {
|
||||
return h.proxy
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Read(ctx context.Context) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*20)
|
||||
func (h *HTTPVehicle) Write(buf []byte) error {
|
||||
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()
|
||||
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 {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
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 {
|
||||
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 {
|
||||
return &HTTPVehicle{url, path, proxy, header}
|
||||
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration) *HTTPVehicle {
|
||||
return &HTTPVehicle{
|
||||
url: url,
|
||||
path: path,
|
||||
proxy: proxy,
|
||||
header: header,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package trie
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +27,14 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
|
||||
if domain != "" && domain[len(domain)-1] == '.' {
|
||||
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)
|
||||
parts := strings.Split(domain, domainStep)
|
||||
if len(parts) == 1 {
|
||||
|
||||
@@ -127,3 +127,14 @@ func TestTrie_Foreach(t *testing.T) {
|
||||
})
|
||||
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"))
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
func downloadPackageFile() (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
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 {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
@@ -418,7 +418,7 @@ func copyFile(src, dst string) error {
|
||||
func getLatestVersion() (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
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 {
|
||||
return "", fmt.Errorf("get Latest Version fail: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -8,9 +9,12 @@ import (
|
||||
"time"
|
||||
|
||||
"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/standard"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -18,82 +22,186 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
UpdatingGeo atomic.Bool
|
||||
autoUpdate bool
|
||||
updateInterval int
|
||||
|
||||
updatingGeo atomic.Bool
|
||||
)
|
||||
|
||||
func updateGeoDatabases() error {
|
||||
defer runtime.GC()
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
func GeoAutoUpdate() bool {
|
||||
return autoUpdate
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
data, err := downloadForBytes(C.GeoIpUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download GeoIP database file: %w", err)
|
||||
}
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MMDB database file: %s", err)
|
||||
}
|
||||
_ = instance.Close()
|
||||
|
||||
if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
|
||||
return fmt.Errorf("invalid GeoIP database file: %s", err)
|
||||
}
|
||||
defer mmdb.ReloadIP()
|
||||
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 {
|
||||
return fmt.Errorf("can't save GeoIP database file: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
defer mmdb.ReloadIP()
|
||||
data, err := downloadForBytes(C.MmdbUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download MMDB database file: %w", err)
|
||||
}
|
||||
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid MMDB database file: %s", err)
|
||||
}
|
||||
_ = 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)
|
||||
}
|
||||
func UpdateASN() (err error) {
|
||||
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", 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 ASN database file: %w", err)
|
||||
}
|
||||
if oldHash.Equal(hash) { // same hash, ignored
|
||||
return nil
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("can't download ASN database file: no data")
|
||||
}
|
||||
|
||||
if C.ASNEnable {
|
||||
defer mmdb.ReloadASN()
|
||||
data, err := downloadForBytes(C.ASNUrl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download ASN database file: %w", err)
|
||||
}
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid ASN database file: %s", err)
|
||||
}
|
||||
_ = instance.Close()
|
||||
|
||||
instance, err := maxminddb.FromBytes(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid ASN database file: %s", err)
|
||||
}
|
||||
_ = instance.Close()
|
||||
defer mmdb.ReloadASN()
|
||||
mmdb.ASNInstance().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 ASN database file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
mmdb.ASNInstance().Reader.Close()
|
||||
if err = saveFile(data, C.Path.ASN()); err != nil {
|
||||
return fmt.Errorf("can't save ASN database file: %w", err)
|
||||
}
|
||||
func UpdateGeoIp() (err error) {
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 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
|
||||
}
|
||||
@@ -103,12 +211,12 @@ var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip")
|
||||
func UpdateGeoDatabases() error {
|
||||
log.Infoln("[GEO] Start updating GEO database")
|
||||
|
||||
if UpdatingGeo.Load() {
|
||||
if updatingGeo.Load() {
|
||||
return ErrGetDatabaseUpdateSkip
|
||||
}
|
||||
|
||||
UpdatingGeo.Store(true)
|
||||
defer UpdatingGeo.Store(false)
|
||||
updatingGeo.Store(true)
|
||||
defer updatingGeo.Store(false)
|
||||
|
||||
log.Infoln("[GEO] Updating GEO database")
|
||||
|
||||
@@ -122,7 +230,7 @@ func UpdateGeoDatabases() error {
|
||||
|
||||
func getUpdateTime() (err error, time time.Time) {
|
||||
var fileInfo os.FileInfo
|
||||
if C.GeodataMode {
|
||||
if geodata.GeodataMode() {
|
||||
fileInfo, err = os.Stat(C.Path.GeoIP())
|
||||
if err != nil {
|
||||
return err, time
|
||||
@@ -138,13 +246,13 @@ func getUpdateTime() (err error, time time.Time) {
|
||||
}
|
||||
|
||||
func RegisterGeoUpdater() {
|
||||
if C.GeoUpdateInterval <= 0 {
|
||||
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
||||
if updateInterval <= 0 {
|
||||
log.Errorln("[GEO] Invalid update interval: %d", updateInterval)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour)
|
||||
ticker := time.NewTicker(time.Duration(updateInterval) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
err, lastUpdate := getUpdateTime()
|
||||
@@ -154,8 +262,8 @@ func RegisterGeoUpdater() {
|
||||
}
|
||||
|
||||
log.Infoln("[GEO] last update time %s", lastUpdate)
|
||||
if lastUpdate.Add(time.Duration(C.GeoUpdateInterval) * time.Hour).Before(time.Now()) {
|
||||
log.Infoln("[GEO] Database has not been updated for %v, update now", time.Duration(C.GeoUpdateInterval)*time.Hour)
|
||||
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(updateInterval)*time.Hour)
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
return
|
||||
@@ -163,7 +271,7 @@ func RegisterGeoUpdater() {
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
var (
|
||||
ExternalUIURL string
|
||||
ExternalUIPath string
|
||||
AutoUpdateUI bool
|
||||
AutoDownloadUI bool
|
||||
)
|
||||
|
||||
var xdMutex sync.Mutex
|
||||
|
||||
func UpdateUI() error {
|
||||
func DownloadUI() error {
|
||||
xdMutex.Lock()
|
||||
defer xdMutex.Unlock()
|
||||
|
||||
|
||||
@@ -9,15 +9,16 @@ import (
|
||||
"time"
|
||||
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
const defaultHttpTimeout = time.Second * 90
|
||||
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultHttpTimeout)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
120
config/config.go
120
config/config.go
@@ -8,7 +8,6 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -16,20 +15,21 @@ import (
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
"github.com/metacubex/mihomo/component/cidr"
|
||||
"github.com/metacubex/mihomo/component/fakeip"
|
||||
"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"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
"github.com/metacubex/mihomo/component/sniffer"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
providerTypes "github.com/metacubex/mihomo/constant/provider"
|
||||
snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
|
||||
"github.com/metacubex/mihomo/dns"
|
||||
@@ -66,6 +66,7 @@ type General struct {
|
||||
Sniffing bool `json:"sniffing"`
|
||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||
GlobalUA string `json:"global-ua"`
|
||||
ETagSupport bool `json:"etag-support"`
|
||||
}
|
||||
|
||||
// Inbound config
|
||||
@@ -102,9 +103,16 @@ type Controller struct {
|
||||
ExternalController string
|
||||
ExternalControllerTLS string
|
||||
ExternalControllerUnix string
|
||||
ExternalControllerPipe string
|
||||
ExternalUI string
|
||||
ExternalDohServer string
|
||||
Secret string
|
||||
Cors Cors
|
||||
}
|
||||
|
||||
type Cors struct {
|
||||
AllowOrigins []string
|
||||
AllowPrivateNetwork bool
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
@@ -189,6 +197,11 @@ type Config struct {
|
||||
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 {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
||||
@@ -363,8 +376,10 @@ type RawConfig struct {
|
||||
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
|
||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||
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"`
|
||||
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"`
|
||||
ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"`
|
||||
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"`
|
||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint" json:"global-client-fingerprint"`
|
||||
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"`
|
||||
KeepAliveInterval int `yaml:"keep-alive-interval" json:"keep-alive-interval"`
|
||||
DisableKeepAlive bool `yaml:"disable-keep-alive" json:"disable-keep-alive"`
|
||||
@@ -433,7 +449,7 @@ func DefaultRawConfig() *RawConfig {
|
||||
Mode: T.Rule,
|
||||
GeoAutoUpdate: false,
|
||||
GeoUpdateInterval: 24,
|
||||
GeodataMode: C.GeodataMode,
|
||||
GeodataMode: geodata.GeodataMode(),
|
||||
GeodataLoader: "memconservative",
|
||||
UnifiedDelay: false,
|
||||
Authentication: []string{},
|
||||
@@ -445,6 +461,7 @@ func DefaultRawConfig() *RawConfig {
|
||||
TCPConcurrent: false,
|
||||
FindProcessMode: P.FindProcessStrict,
|
||||
GlobalUA: "clash.meta/" + C.Version,
|
||||
ETagSupport: true,
|
||||
DNS: RawDNS{
|
||||
Enable: false,
|
||||
IPv6: false,
|
||||
@@ -521,7 +538,7 @@ func DefaultRawConfig() *RawConfig {
|
||||
},
|
||||
GeoXUrl: RawGeoXUrl{
|
||||
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",
|
||||
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
|
||||
},
|
||||
@@ -536,6 +553,10 @@ func DefaultRawConfig() *RawConfig {
|
||||
OverrideDest: true,
|
||||
},
|
||||
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
|
||||
|
||||
err = parseTun(rawCfg.Tun, config.General)
|
||||
if !features.CMFA && err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -681,31 +702,29 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||
}
|
||||
|
||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
|
||||
updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
|
||||
geodata.SetGeodataMode(cfg.GeodataMode)
|
||||
geodata.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
|
||||
geodata.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
|
||||
geodata.SetLoader(cfg.GeodataLoader)
|
||||
geodata.SetSiteMatcher(cfg.GeositeMatcher)
|
||||
C.GeoAutoUpdate = cfg.GeoAutoUpdate
|
||||
C.GeoUpdateInterval = cfg.GeoUpdateInterval
|
||||
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
||||
C.GeoSiteUrl = cfg.GeoXUrl.GeoSite
|
||||
C.MmdbUrl = cfg.GeoXUrl.Mmdb
|
||||
C.ASNUrl = cfg.GeoXUrl.ASN
|
||||
C.GeodataMode = cfg.GeodataMode
|
||||
C.UA = cfg.GlobalUA
|
||||
geodata.SetGeoIpUrl(cfg.GeoXUrl.GeoIp)
|
||||
geodata.SetGeoSiteUrl(cfg.GeoXUrl.GeoSite)
|
||||
geodata.SetMmdbUrl(cfg.GeoXUrl.Mmdb)
|
||||
geodata.SetASNUrl(cfg.GeoXUrl.ASN)
|
||||
mihomoHttp.SetUA(cfg.GlobalUA)
|
||||
resource.SetETag(cfg.ETagSupport)
|
||||
|
||||
if cfg.KeepAliveIdle != 0 {
|
||||
N.KeepAliveIdle = time.Duration(cfg.KeepAliveIdle) * time.Second
|
||||
keepalive.SetKeepAliveIdle(time.Duration(cfg.KeepAliveIdle) * time.Second)
|
||||
}
|
||||
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
|
||||
if cfg.ExternalUI != "" {
|
||||
updater.AutoUpdateUI = true
|
||||
updater.AutoDownloadUI = true
|
||||
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
|
||||
} else {
|
||||
// default externalUI path
|
||||
@@ -714,7 +733,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
|
||||
// checkout UIpath/name exist
|
||||
if cfg.ExternalUIName != "" {
|
||||
updater.AutoUpdateUI = true
|
||||
updater.AutoDownloadUI = true
|
||||
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
|
||||
}
|
||||
|
||||
@@ -759,6 +778,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
FindProcessMode: cfg.FindProcessMode,
|
||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||
GlobalUA: cfg.GlobalUA,
|
||||
ETagSupport: cfg.ETagSupport,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -767,9 +787,14 @@ func parseController(cfg *RawConfig) (*Controller, error) {
|
||||
ExternalController: cfg.ExternalController,
|
||||
ExternalUI: cfg.ExternalUI,
|
||||
Secret: cfg.Secret,
|
||||
ExternalControllerPipe: cfg.ExternalControllerPipe,
|
||||
ExternalControllerUnix: cfg.ExternalControllerUnix,
|
||||
ExternalControllerTLS: cfg.ExternalControllerTLS,
|
||||
ExternalDohServer: cfg.ExternalDohServer,
|
||||
Cors: Cors{
|
||||
AllowOrigins: cfg.ExternalControllerCors.AllowOrigins,
|
||||
AllowPrivateNetwork: cfg.ExternalControllerCors.AllowPrivateNetwork,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1160,16 +1185,6 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
||||
var nameservers []dns.NameServer
|
||||
|
||||
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)
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
@@ -1220,6 +1235,13 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
||||
dnsNetType = "quic" // DNS over QUIC
|
||||
case "system":
|
||||
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":
|
||||
dnsNetType = "rcode"
|
||||
addr = u.Host
|
||||
@@ -1245,16 +1267,18 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
||||
proxyName = dns.RespectRules
|
||||
}
|
||||
|
||||
nameservers = append(
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyName: proxyName,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
},
|
||||
)
|
||||
nameserver := dns.NameServer{
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyName: proxyName,
|
||||
Params: params,
|
||||
PreferH3: preferH3,
|
||||
}
|
||||
if slices.ContainsFunc(nameservers, nameserver.Equal) {
|
||||
continue // skip duplicates nameserver
|
||||
}
|
||||
|
||||
nameservers = append(nameservers, nameserver)
|
||||
}
|
||||
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) {
|
||||
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() {
|
||||
k, v := pair.Key, pair.Value
|
||||
@@ -1302,8 +1325,9 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(strings.ToLower(k), ",") {
|
||||
if strings.Contains(k, "geosite:") {
|
||||
kLower := strings.ToLower(k)
|
||||
if strings.Contains(kLower, ",") {
|
||||
if strings.Contains(kLower, "geosite:") {
|
||||
subkeys := strings.Split(k, ":")
|
||||
subkeys = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
@@ -1311,7 +1335,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
|
||||
newKey := "geosite:" + subkey
|
||||
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 = subkeys[1:]
|
||||
subkeys = strings.Split(subkeys[0], ",")
|
||||
@@ -1319,16 +1343,16 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
|
||||
newKey := "rule-set:" + subkey
|
||||
policy = append(policy, dns.Policy{Domain: newKey, NameServers: nameservers})
|
||||
}
|
||||
} else if re.MatchString(k) {
|
||||
} else {
|
||||
subkeys := strings.Split(k, ",")
|
||||
for _, subkey := range subkeys {
|
||||
policy = append(policy, dns.Policy{Domain: subkey, NameServers: nameservers})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(strings.ToLower(k), "geosite:") {
|
||||
if strings.Contains(kLower, "geosite:") {
|
||||
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})
|
||||
} else {
|
||||
policy = append(policy, dns.Policy{Domain: k, NameServers: nameservers})
|
||||
|
||||
@@ -158,6 +158,7 @@ type DelayHistoryStoreType int
|
||||
|
||||
type Proxy interface {
|
||||
ProxyAdapter
|
||||
Adapter() ProxyAdapter
|
||||
AliveForTestUrl(url string) bool
|
||||
DelayHistory() []DelayHistory
|
||||
ExtraDelayHistories() map[string]ProxyState
|
||||
@@ -255,12 +256,16 @@ type UDPPacketInAddr interface {
|
||||
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
|
||||
type PacketAdapter interface {
|
||||
UDPPacket
|
||||
// Metadata returns destination metadata
|
||||
Metadata() *Metadata
|
||||
// Key is a SNAT key
|
||||
Key() string
|
||||
}
|
||||
|
||||
type packetAdapter struct {
|
||||
UDPPacket
|
||||
metadata *Metadata
|
||||
key string
|
||||
}
|
||||
|
||||
// Metadata returns destination metadata
|
||||
@@ -268,10 +273,16 @@ func (s *packetAdapter) Metadata() *Metadata {
|
||||
return s.metadata
|
||||
}
|
||||
|
||||
// Key is a SNAT key
|
||||
func (s *packetAdapter) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter {
|
||||
return &packetAdapter{
|
||||
packet,
|
||||
metadata,
|
||||
packet.LocalAddr().String(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,17 +295,23 @@ type WriteBackProxy interface {
|
||||
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 {
|
||||
Set(key string, e PacketConn, w WriteBackProxy)
|
||||
|
||||
Get(key string) (PacketConn, WriteBackProxy)
|
||||
|
||||
GetOrCreateLock(key string) (*sync.Cond, bool)
|
||||
GetOrCreate(key string, maker func() PacketSender) (PacketSender, bool)
|
||||
|
||||
Delete(key string)
|
||||
|
||||
DeleteLock(key string)
|
||||
|
||||
GetForLocalConn(lAddr, rAddr string) *net.UDPConn
|
||||
|
||||
AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
|
||||
|
||||
@@ -3,6 +3,7 @@ package constant
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
mode, exist := DNSModeMapping[tp]
|
||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func (e *DNSMode) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := DNSModeMapping[tp]
|
||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
@@ -59,6 +60,21 @@ func (e DNSMode) MarshalJSON() ([]byte, error) {
|
||||
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 {
|
||||
switch e {
|
||||
case DNSNormal:
|
||||
@@ -150,7 +166,7 @@ func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := FilterModeMapping[tp]
|
||||
mode, exist := FilterModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
@@ -167,7 +183,20 @@ func (e *FilterMode) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &tp); err != nil {
|
||||
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 {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
ASNEnable bool
|
||||
GeodataMode bool
|
||||
GeoAutoUpdate bool
|
||||
GeoUpdateInterval int
|
||||
GeoIpUrl string
|
||||
MmdbUrl string
|
||||
GeoSiteUrl string
|
||||
ASNUrl string
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
package constant
|
||||
|
||||
var (
|
||||
UA string
|
||||
)
|
||||
@@ -1,13 +1,14 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
)
|
||||
|
||||
const Name = "mihomo"
|
||||
@@ -73,7 +74,7 @@ func (p *path) Resolve(path string) string {
|
||||
|
||||
// IsSafePath return true if path is a subpath of homedir
|
||||
func (p *path) IsSafePath(path string) bool {
|
||||
if p.allowUnsafePath {
|
||||
if p.allowUnsafePath || features.CMFA {
|
||||
return true
|
||||
}
|
||||
homedir := p.HomeDir()
|
||||
@@ -87,8 +88,8 @@ func (p *path) IsSafePath(path string) bool {
|
||||
}
|
||||
|
||||
func (p *path) GetPathByHash(prefix, name string) string {
|
||||
hash := md5.Sum([]byte(name))
|
||||
filename := hex.EncodeToString(hash[:])
|
||||
hash := utils.MakeHash([]byte(name))
|
||||
filename := hash.String()
|
||||
return filepath.Join(p.HomeDir(), prefix, filename)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,10 @@ func (v VehicleType) String() string {
|
||||
}
|
||||
|
||||
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
|
||||
Url() string
|
||||
Proxy() string
|
||||
Type() VehicleType
|
||||
}
|
||||
@@ -71,6 +73,7 @@ type Provider interface {
|
||||
type ProxyProvider interface {
|
||||
Provider
|
||||
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.
|
||||
// Commonly used in DialContext and DialPacketConn
|
||||
Touch()
|
||||
|
||||
@@ -56,6 +56,21 @@ func (e TUNStack) MarshalJSON() ([]byte, error) {
|
||||
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 {
|
||||
switch e {
|
||||
case TunGvisor:
|
||||
|
||||
@@ -103,3 +103,5 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
return ret.msg, ret.err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) ResetConnection() {}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !(android && cmfa)
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
@@ -55,6 +53,12 @@ func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg,
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dhcpClient) ResetConnection() {
|
||||
for _, client := range d.clients {
|
||||
client.ResetConnection()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
|
||||
d.lock.Lock()
|
||||
|
||||
|
||||
56
dns/doh.go
56
dns/doh.go
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -67,6 +68,8 @@ type dnsOverHTTPS struct {
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
skipCertVerify bool
|
||||
ecsPrefix netip.Prefix
|
||||
ecsOverride bool
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -99,6 +102,28 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||
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)
|
||||
|
||||
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.
|
||||
// We'll only attempt to re-connect if there was one.
|
||||
client, isCached, err := doh.getClient(ctx)
|
||||
@@ -174,11 +203,23 @@ func (doh *dnsOverHTTPS) Close() (err error) {
|
||||
return doh.closeClient(doh.client)
|
||||
}
|
||||
|
||||
// closeClient cleans up resources used by client if necessary. Note, that at
|
||||
// this point it should only be done for HTTP/3 as it may leak due to keep-alive
|
||||
// connections.
|
||||
func (doh *dnsOverHTTPS) ResetConnection() {
|
||||
doh.clientMu.Lock()
|
||||
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) {
|
||||
if isHTTP3(client) {
|
||||
client.CloseIdleConnections()
|
||||
|
||||
if isHTTP3(client) { // HTTP/3 may leak due to keep-alive connections.
|
||||
return client.Transport.(io.Closer).Close()
|
||||
}
|
||||
|
||||
@@ -479,6 +520,13 @@ func (h *http3Transport) Close() (err error) {
|
||||
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.
|
||||
// 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
|
||||
|
||||
@@ -144,6 +144,10 @@ func (doq *dnsOverQUIC) Close() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (doq *dnsOverQUIC) ResetConnection() {
|
||||
doq.closeConnWithError(nil)
|
||||
}
|
||||
|
||||
// exchangeQUIC attempts to open a QUIC connection, send the DNS message
|
||||
// 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) {
|
||||
|
||||
51
dns/edns0_subnet.go
Normal file
51
dns/edns0_subnet.go
Normal 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
|
||||
}
|
||||
@@ -3,54 +3,16 @@
|
||||
package dns
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
const SystemDNSPlaceholder = "system"
|
||||
|
||||
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
|
||||
}
|
||||
var systemResolver []dnsClient
|
||||
|
||||
func FlushCacheWithDefaultResolver() {
|
||||
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) {
|
||||
@@ -63,19 +25,15 @@ func UpdateSystemDNS(addr []string) {
|
||||
ns = append(ns, NameServer{Addr: d})
|
||||
}
|
||||
|
||||
systemResolver = NewResolver(Config{Main: ns})
|
||||
systemResolver = transform(ns, nil)
|
||||
}
|
||||
|
||||
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
if resolver == nil {
|
||||
isolateHandler = nil
|
||||
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
|
||||
return systemResolver, 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}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
//go:build !(android && cmfa)
|
||||
|
||||
package dns
|
||||
|
||||
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
}
|
||||
@@ -48,3 +48,5 @@ func (r rcodeClient) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, err
|
||||
func (r rcodeClient) Address() string {
|
||||
return r.addr
|
||||
}
|
||||
|
||||
func (r rcodeClient) ResetConnection() {}
|
||||
|
||||
@@ -24,11 +24,13 @@ import (
|
||||
type dnsClient interface {
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
Address() string
|
||||
ResetConnection()
|
||||
}
|
||||
|
||||
type dnsCache interface {
|
||||
GetWithExpire(key string) (*D.Msg, time.Time, bool)
|
||||
SetWithExpire(key string, value *D.Msg, expire time.Time)
|
||||
Clear()
|
||||
}
|
||||
|
||||
type result struct {
|
||||
@@ -47,7 +49,7 @@ type Resolver struct {
|
||||
group singleflight.Group[*D.Msg]
|
||||
cache dnsCache
|
||||
policy []dnsPolicy
|
||||
proxyServer []dnsClient
|
||||
defaultResolver *Resolver
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
Net string
|
||||
Addr string
|
||||
@@ -418,16 +440,18 @@ type Config struct {
|
||||
CacheAlgorithm string
|
||||
}
|
||||
|
||||
func NewResolver(config Config) *Resolver {
|
||||
var cache dnsCache
|
||||
if config.CacheAlgorithm == "lru" {
|
||||
cache = lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
|
||||
func (config Config) newCache() dnsCache {
|
||||
if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" {
|
||||
return lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
|
||||
} 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{
|
||||
main: transform(config.Default, nil),
|
||||
cache: cache,
|
||||
cache: config.newCache(),
|
||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||
}
|
||||
|
||||
@@ -458,27 +482,29 @@ func NewResolver(config Config) *Resolver {
|
||||
return
|
||||
}
|
||||
|
||||
if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" {
|
||||
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{
|
||||
r = &Resolver{
|
||||
ipv6: config.IPv6,
|
||||
main: cacheTransform(config.Main),
|
||||
cache: cache,
|
||||
cache: config.newCache(),
|
||||
hosts: config.Hosts,
|
||||
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 {
|
||||
r.fallback = cacheTransform(config.Fallback)
|
||||
}
|
||||
|
||||
if len(config.ProxyServer) != 0 {
|
||||
r.proxyServer = cacheTransform(config.ProxyServer)
|
||||
}
|
||||
|
||||
if len(config.Policy) != 0 {
|
||||
r.policy = make([]dnsPolicy, 0)
|
||||
|
||||
@@ -509,18 +535,7 @@ func NewResolver(config Config) *Resolver {
|
||||
r.fallbackIPFilters = config.FallbackIPFilter
|
||||
r.fallbackDomainFilters = config.FallbackDomainFilter
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
||||
r := &Resolver{
|
||||
ipv6: old.ipv6,
|
||||
main: old.proxyServer,
|
||||
cache: old.cache,
|
||||
hosts: old.hosts,
|
||||
ipv6Timeout: old.ipv6Timeout,
|
||||
}
|
||||
return r
|
||||
return
|
||||
}
|
||||
|
||||
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/common/sockopt"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/context"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
@@ -50,10 +49,6 @@ func (s *Server) SetHandler(handler handler) {
|
||||
}
|
||||
|
||||
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
||||
if features.CMFA {
|
||||
UpdateIsolateHandler(resolver, mapper)
|
||||
}
|
||||
|
||||
if addr == address && resolver != nil {
|
||||
handler := NewHandler(resolver, mapper)
|
||||
server.SetHandler(handler)
|
||||
|
||||
@@ -3,16 +3,11 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,64 +26,6 @@ type systemClient struct {
|
||||
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) {
|
||||
dnsClients, err := c.getDnsClients()
|
||||
if err != nil {
|
||||
|
||||
73
dns/system_common.go
Normal file
73
dns/system_common.go
Normal 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() {}
|
||||
@@ -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 部分配置文件
|
||||
# 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版本即可使用 )
|
||||
# !!!注意: 从Unix socket访问api接口不会验证secret, 如果开启请自行保证安全问题 !!!
|
||||
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
|
||||
external-controller-unix: mihomo.sock
|
||||
|
||||
# RESTful API Windows namedpipe 监听地址
|
||||
# !!!注意: 从Windows namedpipe访问api接口不会验证secret, 如果开启请自行保证安全问题 !!!
|
||||
external-controller-pipe: \\.\pipe\mihomo
|
||||
|
||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
||||
|
||||
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||
@@ -760,6 +770,17 @@ proxies: # socks5
|
||||
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
|
||||
# allowed-ips: ['0.0.0.0/0']
|
||||
# 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
|
||||
- name: tuic
|
||||
@@ -929,6 +950,13 @@ proxy-providers:
|
||||
# ip-version: ipv4-prefer
|
||||
# additional-prefix: "[provider1]"
|
||||
# additional-suffix: "test"
|
||||
# # 名字替换,支持正则表达式
|
||||
# proxy-name:
|
||||
# - pattern: "test"
|
||||
# target: "TEST"
|
||||
# - pattern: "IPLC-(.*?)倍"
|
||||
# target: "iplc x $1"
|
||||
|
||||
test:
|
||||
type: file
|
||||
path: /test.yaml
|
||||
@@ -955,12 +983,12 @@ rule-providers:
|
||||
# 对于behavior=domain:
|
||||
# - format=yaml 可以通过“mihomo convert-ruleset domain yaml XXX.yaml 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:
|
||||
# - format=yaml 可以通过“mihomo convert-ruleset ipcidr yaml XXX.yaml 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
|
||||
url: "url"
|
||||
|
||||
21
go.mod
21
go.mod
@@ -8,7 +8,6 @@ require (
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
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/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.3.0
|
||||
@@ -17,42 +16,45 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
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/chacha v0.1.0
|
||||
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/sing-quic v0.0.0-20240827003841-cd97758ed8b4
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||
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-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/utls v1.6.6
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/sagernet/cors v1.2.1
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/sing v0.5.0-alpha.13
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
|
||||
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/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
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
|
||||
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
|
||||
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/net v0.28.0
|
||||
golang.org/x/sys v0.24.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/sys v0.25.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0
|
||||
@@ -103,12 +105,13 @@ require (
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // 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
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.20.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/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
44
go.sum
44
go.sum
@@ -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/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/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
@@ -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/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
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/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
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/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/quic-go v0.46.1-0.20240807232329-1c6cb2d67f58 h1:T6OxROLZBr9SOQxN5TzUslv81hEREy/dEgaUKVjaG7U=
|
||||
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 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o=
|
||||
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/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
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-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-wireguard v0.0.0-20240826061955-1e4e67afe5cd h1:r7alry8u4qlUFLNMwGvG1A8ZcfPM6AMSmrm6E2yKdB4=
|
||||
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 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
|
||||
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/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/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/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
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/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/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/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
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/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/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/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
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.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
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/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
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/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
|
||||
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.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
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/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
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=
|
||||
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
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/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
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/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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
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.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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
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/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
||||
@@ -17,10 +17,12 @@ import (
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
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/profile"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
"github.com/metacubex/mihomo/component/sniffer"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
@@ -81,6 +83,7 @@ func ParseWithBytes(buf []byte) (*config.Config, error) {
|
||||
func ApplyConfig(cfg *config.Config, force bool) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
|
||||
tunnel.OnSuspend()
|
||||
|
||||
@@ -116,7 +119,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
hcCompatibleProvider(cfg.Providers)
|
||||
initExternalUI()
|
||||
|
||||
log.SetLevel(cfg.General.LogLevel)
|
||||
resolver.ResetConnection()
|
||||
}
|
||||
|
||||
func initInnerTcp() {
|
||||
@@ -126,7 +129,7 @@ func initInnerTcp() {
|
||||
func GetGeneral() *config.General {
|
||||
ports := listener.GetPorts()
|
||||
var authenticator []string
|
||||
if auth := authStore.Authenticator(); auth != nil {
|
||||
if auth := authStore.Default.Authenticator(); auth != nil {
|
||||
authenticator = auth.Users()
|
||||
}
|
||||
|
||||
@@ -157,13 +160,13 @@ func GetGeneral() *config.General {
|
||||
Interface: dialer.DefaultInterface.Load(),
|
||||
RoutingMark: int(dialer.DefaultRoutingMark.Load()),
|
||||
GeoXUrl: config.GeoXUrl{
|
||||
GeoIp: C.GeoIpUrl,
|
||||
Mmdb: C.MmdbUrl,
|
||||
ASN: C.ASNUrl,
|
||||
GeoSite: C.GeoSiteUrl,
|
||||
GeoIp: G.GeoIpUrl(),
|
||||
Mmdb: G.MmdbUrl(),
|
||||
ASN: G.ASNUrl(),
|
||||
GeoSite: G.GeoSiteUrl(),
|
||||
},
|
||||
GeoAutoUpdate: G.GeoAutoUpdate(),
|
||||
GeoUpdateInterval: G.GeoUpdateInterval(),
|
||||
GeoAutoUpdate: updater.GeoAutoUpdate(),
|
||||
GeoUpdateInterval: updater.GeoUpdateInterval(),
|
||||
GeodataMode: G.GeodataMode(),
|
||||
GeodataLoader: G.LoaderName(),
|
||||
GeositeMatcher: G.SiteMatcherName(),
|
||||
@@ -171,7 +174,8 @@ func GetGeneral() *config.General {
|
||||
FindProcessMode: tunnel.FindProcessMode(),
|
||||
Sniffing: tunnel.IsSniffing(),
|
||||
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
|
||||
GlobalUA: C.UA,
|
||||
GlobalUA: mihomoHttp.UA(),
|
||||
ETagSupport: resource.ETag(),
|
||||
}
|
||||
|
||||
return general
|
||||
@@ -251,8 +255,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
CacheAlgorithm: c.CacheAlgorithm,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
pr := dns.NewProxyServerHostResolver(r)
|
||||
r, pr := dns.NewResolver(cfg)
|
||||
m := dns.NewEnhancer(cfg)
|
||||
|
||||
// reuse cache of old host mapper
|
||||
@@ -381,13 +384,13 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
||||
}
|
||||
|
||||
func initExternalUI() {
|
||||
if updater.AutoUpdateUI {
|
||||
if updater.AutoDownloadUI {
|
||||
dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
|
||||
if len(dirEntries) > 0 {
|
||||
log.Infoln("UI already exists, skip downloading")
|
||||
} else {
|
||||
log.Infoln("External UI downloading ...")
|
||||
updater.UpdateUI()
|
||||
updater.DownloadUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,7 +423,7 @@ func updateGeneral(general *config.General) {
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
authenticator := auth.NewAuthenticator(users)
|
||||
authStore.SetAuthenticator(authenticator)
|
||||
authStore.Default.SetAuthenticator(authenticator)
|
||||
if authenticator != nil {
|
||||
log.Infoln("Authentication of local server updated")
|
||||
}
|
||||
@@ -442,12 +445,12 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
}
|
||||
|
||||
for name, proxy := range proxies {
|
||||
outbound, ok := proxy.(*adapter.Proxy)
|
||||
outbound, ok := proxy.(C.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selector, ok := outbound.ProxyAdapter.(outboundgroup.SelectAble)
|
||||
selector, ok := outbound.Adapter().(outboundgroup.SelectAble)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
31
hub/hub.go
31
hub/hub.go
@@ -1,10 +1,7 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/config"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/hub/route"
|
||||
"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 {
|
||||
return func(cfg *config.Config) {
|
||||
cfg.Controller.Secret = secret
|
||||
@@ -43,11 +46,6 @@ func ApplyConfig(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 != "" {
|
||||
route.SetUIPath(cfg.Controller.ExternalUI)
|
||||
}
|
||||
@@ -55,17 +53,30 @@ func applyRoute(cfg *config.Config) {
|
||||
Addr: cfg.Controller.ExternalController,
|
||||
TLSAddr: cfg.Controller.ExternalControllerTLS,
|
||||
UnixAddr: cfg.Controller.ExternalControllerUnix,
|
||||
PipeAddr: cfg.Controller.ExternalControllerPipe,
|
||||
Secret: cfg.Controller.Secret,
|
||||
Certificate: cfg.TLS.Certificate,
|
||||
PrivateKey: cfg.TLS.PrivateKey,
|
||||
DohServer: cfg.Controller.ExternalDohServer,
|
||||
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
|
||||
func Parse(options ...Option) error {
|
||||
cfg, err := executor.Parse()
|
||||
func Parse(configBytes []byte, options ...Option) error {
|
||||
var cfg *config.Config
|
||||
var err error
|
||||
|
||||
if len(configBytes) != 0 {
|
||||
cfg, err = executor.ParseWithBytes(configBytes)
|
||||
} else {
|
||||
cfg, err = executor.Parse()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
@@ -32,7 +31,7 @@ func GroupRouter() http.Handler {
|
||||
func getGroups(w http.ResponseWriter, r *http.Request) {
|
||||
var gs []C.Proxy
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -43,7 +42,7 @@ func getGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func getGroup(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
return
|
||||
}
|
||||
@@ -53,25 +52,15 @@ func getGroup(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func getGroupDelay(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group)
|
||||
group, ok := proxy.Adapter().(C.Group)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
switch proxy.(*adapter.Proxy).Type() {
|
||||
case C.URLTest:
|
||||
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 {
|
||||
if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector {
|
||||
selectAble.ForceSet("")
|
||||
cachefile.Cache().SetSelected(proxy.Name(), "")
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
@@ -31,6 +30,7 @@ func proxyRouter() http.Handler {
|
||||
r.Get("/", getProxy)
|
||||
r.Get("/delay", getProxyDelay)
|
||||
r.Put("/", updateProxy)
|
||||
r.Delete("/", unfixedProxy)
|
||||
})
|
||||
return r
|
||||
}
|
||||
@@ -81,8 +81,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||
selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble)
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
selector, ok := proxy.Adapter().(outboundgroup.SelectAble)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("Must be a Selector"))
|
||||
@@ -146,3 +146,15 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/sagernet/cors"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,6 +35,7 @@ var (
|
||||
httpServer *http.Server
|
||||
tlsServer *http.Server
|
||||
unixServer *http.Server
|
||||
pipeServer *http.Server
|
||||
)
|
||||
|
||||
type Traffic struct {
|
||||
@@ -51,33 +52,46 @@ type Config struct {
|
||||
Addr string
|
||||
TLSAddr string
|
||||
UnixAddr string
|
||||
PipeAddr string
|
||||
Secret string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
DohServer string
|
||||
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) {
|
||||
go start(cfg)
|
||||
go startTLS(cfg)
|
||||
go startUnix(cfg)
|
||||
if inbound.SupportNamedPipe {
|
||||
go startPipe(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func SetUIPath(path string) {
|
||||
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()
|
||||
corsM := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
||||
MaxAge: 300,
|
||||
})
|
||||
r.Use(setPrivateNetworkAccess)
|
||||
r.Use(corsM.Handler)
|
||||
cors.Apply(r)
|
||||
if isDebug {
|
||||
r.Mount("/debug", func() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
@@ -146,12 +160,12 @@ func start(cfg *Config) {
|
||||
log.Infoln("RESTful API listening at: %s", l.Addr().String())
|
||||
|
||||
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 {
|
||||
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())
|
||||
server := &http.Server{
|
||||
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer),
|
||||
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{c},
|
||||
},
|
||||
}
|
||||
tlsServer = server
|
||||
if err = server.ServeTLS(l, "", ""); err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
_ = os.Chmod(addr, 0o666)
|
||||
log.Infoln("RESTful API unix listening at: %s", l.Addr().String())
|
||||
|
||||
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 {
|
||||
log.Errorln("External controller unix serve error: %s", err)
|
||||
}
|
||||
unixServer = server
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setPrivateNetworkAccess(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
w.Header().Add("Access-Control-Allow-Private-Network", "true")
|
||||
func startPipe(cfg *Config) {
|
||||
// first stop existing server
|
||||
if pipeServer != nil {
|
||||
_ = 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 {
|
||||
|
||||
@@ -47,7 +47,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func updateUI(w http.ResponseWriter, r *http.Request) {
|
||||
err := updater.UpdateUI()
|
||||
err := updater.DownloadUI()
|
||||
if err != nil {
|
||||
log.Warnln("%s", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
|
||||
@@ -4,14 +4,30 @@ import (
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
)
|
||||
|
||||
var authenticator auth.Authenticator
|
||||
|
||||
func Authenticator() auth.Authenticator {
|
||||
return authenticator
|
||||
type authStore struct {
|
||||
authenticator auth.Authenticator
|
||||
}
|
||||
|
||||
func SetAuthenticator(au auth.Authenticator) {
|
||||
authenticator = au
|
||||
func (a *authStore) Authenticator() auth.Authenticator {
|
||||
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
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package http
|
||||
|
||||
import "net"
|
||||
|
||||
func (l *Listener) Listener() net.Listener {
|
||||
return l.listener
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) {
|
||||
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
|
||||
inUserIdx := len(additions) - 1
|
||||
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)
|
||||
|
||||
authenticator := getAuth()
|
||||
authenticator := store.Authenticator()
|
||||
keepAlive := true
|
||||
trusted := authenticator == nil // disable authenticate if lru is nil
|
||||
lastUser := ""
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
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) {
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
getAuth := authStore.Authenticator
|
||||
store := authStore.Default
|
||||
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
|
||||
if len(additions) == 0 {
|
||||
isDefault = true
|
||||
@@ -55,8 +54,8 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,19 +73,18 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
}
|
||||
continue
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
getAuth := getAuth
|
||||
if isDefault { // only apply on default listener
|
||||
store := store
|
||||
if isDefault || store == authStore.Default { // only apply on default listener
|
||||
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
|
||||
_ = conn.Close()
|
||||
continue
|
||||
}
|
||||
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
|
||||
getAuth = authStore.Nil
|
||||
store = authStore.Nil
|
||||
}
|
||||
}
|
||||
go HandleConn(conn, tunnel, getAuth, additions...)
|
||||
go HandleConn(conn, tunnel, store, additions...)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type AuthUser struct {
|
||||
|
||||
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 len(a) == 0 {
|
||||
return authStore.Nil
|
||||
@@ -25,7 +25,7 @@ func (a AuthUsers) GetAuth() func() auth.Authenticator {
|
||||
}
|
||||
}
|
||||
authenticator := auth.NewAuthenticator(users)
|
||||
return func() auth.Authenticator { return authenticator }
|
||||
return authStore.NewAuthStore(authenticator)
|
||||
}
|
||||
return authStore.Authenticator
|
||||
return authStore.Default
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (h *HTTP) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (h *HTTP) Listen(tunnel C.Tunnel) 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (m *Mixed) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (m *Mixed) Listen(tunnel C.Tunnel) 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (s *Socks) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (s *Socks) Listen(tunnel C.Tunnel) 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
|
||||
}
|
||||
if s.udp {
|
||||
|
||||
@@ -37,10 +37,10 @@ func (l *Listener) Close() 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
|
||||
if len(additions) == 0 {
|
||||
isDefault = true
|
||||
@@ -49,6 +49,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -67,26 +68,24 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
}
|
||||
continue
|
||||
}
|
||||
getAuth := getAuth
|
||||
if isDefault { // only apply on default listener
|
||||
store := store
|
||||
if isDefault || store == authStore.Default { // only apply on default listener
|
||||
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
func handleConn(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
if err != nil {
|
||||
@@ -95,10 +94,10 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticato
|
||||
|
||||
switch head[0] {
|
||||
case socks4.Version:
|
||||
socks.HandleSocks4(bufConn, tunnel, getAuth, additions...)
|
||||
socks.HandleSocks4(bufConn, tunnel, store, additions...)
|
||||
case socks5.Version:
|
||||
socks.HandleSocks5(bufConn, tunnel, getAuth, additions...)
|
||||
socks.HandleSocks5(bufConn, tunnel, store, additions...)
|
||||
default:
|
||||
http.HandleConn(bufConn, tunnel, getAuth, additions...)
|
||||
http.HandleConn(bufConn, tunnel, store, additions...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -37,10 +37,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl := &Listener{
|
||||
listener: l,
|
||||
addr: addr,
|
||||
@@ -68,6 +70,6 @@ func handleRedir(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
keepalive.TCPKeepAlive(conn)
|
||||
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
Reference in New Issue
Block a user