Merge branch 'Alpha' into parental_control_1

merge the 18.8.10
This commit is contained in:
xiaoming zhang 2024-11-12 20:20:10 -08:00
commit 75a7a7e463
102 changed files with 1615 additions and 1081 deletions

View File

@ -10,6 +10,7 @@ import (
"net/netip"
"net/url"
"strconv"
"strings"
"time"
"github.com/metacubex/mihomo/common/atomic"
@ -18,6 +19,7 @@ import (
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/puzpuzpuz/xsync/v3"
)
@ -39,6 +41,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 {
@ -255,10 +262,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
if unifiedDelay {
second := time.Now()
resp, err = client.Do(req)
if err == nil {
var ignoredErr error
var secondResp *http.Response
secondResp, ignoredErr = client.Do(req)
if ignoredErr == nil {
resp = secondResp
_ = resp.Body.Close()
start = second
} else {
if strings.HasPrefix(url, "http://") {
log.Errorln("%s failed to get the second response from %s: %v", p.Name(), url, ignoredErr)
log.Warnln("It is recommended to use HTTPS for provider.health-check.url and group.url to ensure better reliability. Due to some proxy providers hijacking test addresses and not being compatible with repeated HEAD requests, using HTTP may result in failed tests.")
}
}
}

View File

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

View File

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

View File

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

View File

@ -3,19 +3,12 @@ package outbound
import (
"context"
"errors"
"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"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
)
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
type Direct struct {
*Base
loopBack *loopback.Detector
@ -28,30 +21,25 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if !features.CMFA && !DisableLoopBackDetector {
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
N.TCPKeepAlive(c)
return d.loopBack.NewConn(NewConn(c, d)), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if !features.CMFA && !DisableLoopBackDetector {
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
}
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
}
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}

View File

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

View File

@ -69,7 +69,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
return &hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) {
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
if len(h.option.DialerProxy) > 0 {
@ -78,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
},
remoteAddr: func(addr string) (net.Addr, error) {
@ -131,11 +131,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) {
}
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{
Dialer: &net.Dialer{
Timeout: 8 * time.Second,
},
}
clientTransport := &transport.ClientTransport{}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
ports := option.Ports
@ -284,7 +280,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
}
type hyDialerWithContext struct {
hyDialer func(network string) (net.PacketConn, error)
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error)
ctx context.Context
remoteAddr func(host string) (net.Addr, error)
}
@ -294,7 +290,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
network = dialer.ParseNetwork(network, addrPort.Addr())
}
return h.hyDialer(network)
return h.hyDialer(network, rAddr)
}
func (h *hyDialerWithContext) Context() context.Context {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,7 +55,7 @@ func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr,
return nil, err
}
ip, err := resolver.ResolveProxyServerHost(ctx, host)
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err != nil {
return nil, err
}
@ -71,12 +71,12 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
var fallback netip.Addr
switch prefer {
case C.IPv4Only:
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Only:
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Prefer:
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err == nil {
for _, addr := range ips {
if addr.Is6() {
@ -92,7 +92,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
default:
// C.IPv4Prefer, C.DualStack and other
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err == nil {
for _, addr := range ips {
if addr.Is4() {

View File

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

View File

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

View File

@ -44,7 +44,7 @@ type WireGuard struct {
device wireguardGoDevice
tunDevice wireguard.Device
dialer proxydialer.SingDialer
resolver *dns.Resolver
resolver resolver.Resolver
refP *refProxyAdapter
initOk atomic.Bool
@ -272,6 +272,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
},
}
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)

View File

@ -204,7 +204,7 @@ func strategyStickySessions(url string) strategyFn {
for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx]
if proxy.AliveForTestUrl(url) {
if nowIdx != idx {
if !has || nowIdx != idx {
lruCache.Set(key, nowIdx)
}

View File

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

View File

@ -1,11 +1,9 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
@ -14,7 +12,7 @@ import (
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/common/convert"
"github.com/metacubex/mihomo/common/utils"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/resource"
C "github.com/metacubex/mihomo/constant"
types "github.com/metacubex/mihomo/constant/provider"
@ -80,7 +78,9 @@ func (pp *proxySetProvider) Initial() error {
if err != nil {
return err
}
pp.getSubscriptionInfo()
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
pp.SetSubscriptionInfo(subscriptionInfo)
}
pp.closeAllConnections()
return nil
}
@ -117,35 +117,14 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
}
}
func (pp *proxySetProvider) getSubscriptionInfo() {
if pp.VehicleType() != types.HTTP {
return
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
http.MethodGet, nil, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
defer resp.Body.Close()
func (pp *proxySetProvider) SetSubscriptionInfo(userInfo string) {
pp.subscriptionInfo = NewSubscriptionInfo(userInfo)
}
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
if err != nil {
return
}
defer resp2.Body.Close()
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
return
}
}
pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr)
}()
func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) {
if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok {
httpVehicle.SetProvider(provider)
}
}
func (pp *proxySetProvider) closeAllConnections() {
@ -196,6 +175,9 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd}
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
httpVehicle.SetProvider(wrapper)
}
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
return wrapper, nil
}
@ -205,16 +187,21 @@ func (pp *ProxySetProvider) Close() error {
return pp.proxySetProvider.Close()
}
func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) {
pp.proxySetProvider.SetProvider(provider)
}
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
version uint32
name string
healthCheck *HealthCheck
subscriptionInfo *SubscriptionInfo
proxies []C.Proxy
version uint32
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
@ -284,6 +271,10 @@ func (cp *compatibleProvider) Close() error {
return nil
}
func (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) {
cp.subscriptionInfo = NewSubscriptionInfo(userInfo)
}
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("provider need one proxy at least")
@ -313,7 +304,6 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
pd.version += 1
pd.getSubscriptionInfo()
}
}

View File

@ -16,8 +16,7 @@ type SubscriptionInfo struct {
}
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
userinfo = strings.ToLower(userinfo)
userinfo = strings.ReplaceAll(userinfo, " ", "")
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "")
si = new(SubscriptionInfo)
for _, field := range strings.Split(userinfo, ";") {

View File

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

View File

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

View File

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

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

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

View File

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

View File

@ -12,6 +12,7 @@ import (
"sync"
"time"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log"
)
@ -144,6 +145,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
}
dialer := netDialer.(*net.Dialer)
keepalive.SetNetDialer(dialer)
if opt.mpTcp {
setMultiPathTCP(dialer)
}
@ -338,26 +340,18 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
return nil, "-1", err
}
if preferResolver == nil {
preferResolver = resolver.ProxyServerHostResolver
}
var ips []netip.Addr
switch network {
case "tcp4", "udp4":
if preferResolver == nil {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
}
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
case "tcp6", "udp6":
if preferResolver == nil {
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
}
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
default:
if preferResolver == nil {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
}
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
}
if err != nil {
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)

View File

@ -1,29 +0,0 @@
package dialer
import (
"context"
"net"
"net/netip"
)
func init() {
// We must use this DialContext to query DNS
// when using net default resolver.
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = resolverDialContext
}
func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
interfaceName := DefaultInterface.Load()
if interfaceName != "" {
dstIP, err := netip.ParseAddr(address)
if err == nil {
_ = bindIfaceToDialer(interfaceName, d, network, dstIP)
}
}
return d.DialContext(ctx, network, address)
}

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import (
"time"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/listener/inner"
)
@ -71,8 +72,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
return conn, nil
} else {
d := net.Dialer{}
return d.DialContext(ctx, network, address)
return dialer.DialContext(ctx, network, address)
}
},
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),

View File

@ -15,6 +15,7 @@ type Interface struct {
Name string
Addresses []netip.Prefix
HardwareAddr net.HardwareAddr
Flags net.Flags
}
var (
@ -66,6 +67,7 @@ func Interfaces() (map[string]*Interface, error) {
Name: iface.Name,
Addresses: ipNets,
HardwareAddr: iface.HardwareAddr,
Flags: iface.Flags,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,14 +4,25 @@ import (
"errors"
"fmt"
"net/netip"
"os"
"strconv"
"github.com/metacubex/mihomo/common/callback"
"github.com/metacubex/mihomo/component/iface"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
"github.com/puzpuzpuz/xsync/v3"
)
var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
func init() {
if features.CMFA {
disableLoopBackDetector = true
}
}
var ErrReject = errors.New("reject loopback connection")
type Detector struct {
@ -20,6 +31,9 @@ type Detector struct {
}
func NewDetector() *Detector {
if disableLoopBackDetector {
return nil
}
return &Detector{
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
@ -27,6 +41,9 @@ func NewDetector() *Detector {
}
func (l *Detector) NewConn(conn C.Conn) C.Conn {
if l == nil || l.connMap == nil {
return conn
}
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
@ -42,6 +59,9 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn {
}
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
if l == nil || l.packetConnMap == nil {
return conn
}
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
@ -58,6 +78,9 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
}
func (l *Detector) CheckConn(metadata *C.Metadata) error {
if l == nil || l.connMap == nil {
return nil
}
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil
@ -69,6 +92,9 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error {
}
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
if l == nil || l.packetConnMap == nil {
return nil
}
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil

View File

@ -5,6 +5,7 @@ import (
"net"
"strings"
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang"
)
@ -23,11 +24,16 @@ type ASNReader struct {
*maxminddb.Reader
}
type ASNResult struct {
type GeoLite2 struct {
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
}
type IPInfo struct {
ASN string `maxminddb:"asn"`
Name string `maxminddb:"name"`
}
func (r IPReader) LookupCode(ipAddress net.IP) []string {
switch r.databaseType {
case typeMaxmind:
@ -66,8 +72,18 @@ func (r IPReader) LookupCode(ipAddress net.IP) []string {
}
}
func (r ASNReader) LookupASN(ip net.IP) ASNResult {
var result ASNResult
r.Lookup(ip, &result)
return result
func (r ASNReader) LookupASN(ip net.IP) (string, string) {
switch r.Metadata.DatabaseType {
case "GeoLite2-ASN", "DBIP-ASN-Lite (compat=GeoLite2-ASN)":
var result GeoLite2
_ = r.Lookup(ip, &result)
return fmt.Sprint(result.AutonomousSystemNumber), result.AutonomousSystemOrganization
case "ipinfo generic_asn_free.mmdb":
var result IPInfo
_ = r.Lookup(ip, &result)
return result.ASN[2:], result.Name
default:
log.Warnln("Unsupported ASN type: %s", r.Metadata.DatabaseType)
}
return "", ""
}

View File

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

View File

@ -180,7 +180,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; {
ptr := unsafe.Pointer(&buf[0])
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
switch err {
case 0:
@ -215,13 +215,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf))
r1, _, err := syscall.SyscallN(
queryProcName,
r1, _, err := syscall.Syscall6(
queryProcName, 4,
uintptr(h),
uintptr(0),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
)
0, 0)
if r1 == 0 {
return "", err
}

View File

@ -1,7 +1,6 @@
package cachefile
import (
"math"
"os"
"sync"
"time"
@ -18,9 +17,10 @@ var (
fileMode os.FileMode = 0o666
defaultCache *CacheFile
bucketSelected = []byte("selected")
bucketFakeip = []byte("fakeip")
bucketETag = []byte("etag")
bucketSelected = []byte("selected")
bucketFakeip = []byte("fakeip")
bucketETag = []byte("etag")
bucketSubscriptionInfo = []byte("subscriptioninfo")
)
// CacheFile store and update the cache file
@ -71,133 +71,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) SetETagWithHash(url string, hash []byte, etag string) {
if c.DB == nil {
return
}
lenHash := len(hash)
if lenHash > math.MaxUint8 {
return // maybe panic is better
}
data := make([]byte, 1, 1+lenHash+len(etag))
data[0] = uint8(lenHash)
data = append(data, hash...)
data = append(data, etag...)
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) (hash []byte, etag string) {
if c.DB == nil {
return
}
var value []byte
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketETag); bucket != nil {
if v := bucket.Get([]byte(key)); v != nil {
value = v
}
}
return nil
})
if len(value) == 0 {
return
}
lenHash := int(value[0])
if len(value) < 1+lenHash {
return
}
hash = value[1 : 1+lenHash]
etag = string(value[1+lenHash:])
return
}
func (c *CacheFile) Close() error {
return c.DB.Close()
}

View File

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

View File

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

View File

@ -0,0 +1,41 @@
package cachefile
import (
"github.com/metacubex/mihomo/log"
"github.com/metacubex/bbolt"
)
func (c *CacheFile) SetSubscriptionInfo(name string, userInfo string) {
if c.DB == nil {
return
}
err := c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketSubscriptionInfo)
if err != nil {
return err
}
return bucket.Put([]byte(name), []byte(userInfo))
})
if err != nil {
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
return
}
}
func (c *CacheFile) GetSubscriptionInfo(name string) (userInfo string) {
if c.DB == nil {
return
}
c.DB.View(func(t *bbolt.Tx) error {
if bucket := t.Bucket(bucketSubscriptionInfo); bucket != nil {
if v := bucket.Get([]byte(name)); v != nil {
userInfo = string(v)
}
}
return nil
})
return
}

View File

@ -1,12 +0,0 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
package resolver
import _ "unsafe"
//go:linkname defaultNS net.defaultNS
var defaultNS []string
func init() {
defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"}
}

View File

@ -1,19 +0,0 @@
//go:build !go1.22
// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
package resolver
import (
"golang.org/x/sys/windows"
_ "unsafe"
)
//go:linkname testHookHostsPath net.testHookHostsPath
var testHookHostsPath string
func init() {
if dir, err := windows.GetSystemDirectory(); err == nil {
testHookHostsPath = dir + "/Drivers/etc/hosts"
}
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"strings"
"time"
@ -20,9 +19,15 @@ var (
// DefaultResolver aim to resolve ip
DefaultResolver Resolver
// ProxyServerHostResolver resolve ip to proxies server host
// ProxyServerHostResolver resolve ip for proxies server host, only nil when DefaultResolver is nil
ProxyServerHostResolver Resolver
// DirectHostResolver resolve ip for direct outbound host, only nil when DefaultResolver is nil
DirectHostResolver Resolver
// SystemResolver always using system dns, and was init in dns module
SystemResolver Resolver
// DisableIPv6 means don't resolve ipv6 host
// default value is true
DisableIPv6 = true
@ -47,6 +52,7 @@ type Resolver interface {
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
Invalid() bool
ClearCache()
ResetConnection()
}
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
@ -71,14 +77,7 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
return r.LookupIPv4(ctx, host)
}
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs, nil
return SystemResolver.LookupIPv4(ctx, host)
}
// LookupIPv4 with a host, return ipv4 list
@ -127,14 +126,7 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
return r.LookupIPv6(ctx, host)
}
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
if err != nil {
return nil, err
} else if len(ipAddrs) == 0 {
return nil, ErrIPNotFound
}
return ipAddrs, nil
return SystemResolver.LookupIPv6(ctx, host)
}
// LookupIPv6 with a host, return ipv6 list
@ -176,14 +168,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
return []netip.Addr{ip}, nil
}
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
if err != nil {
return nil, err
} else if len(ips) == 0 {
return nil, ErrIPNotFound
}
return ips, nil
return SystemResolver.LookupIP(ctx, host)
}
// LookupIP with a host, return ip
@ -211,49 +196,10 @@ func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPWithResolver(ctx, host, DefaultResolver)
}
// ResolveIPv4ProxyServerHost proxies server host only
func ResolveIPv4ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPv4WithResolver(ctx, host, ProxyServerHostResolver)
func ResetConnection() {
if DefaultResolver != nil {
go DefaultResolver.ResetConnection()
}
return ResolveIPv4(ctx, host)
}
// ResolveIPv6ProxyServerHost proxies server host only
func ResolveIPv6ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPv6WithResolver(ctx, host, ProxyServerHostResolver)
}
return ResolveIPv6(ctx, host)
}
// ResolveProxyServerHost proxies server host only
func ResolveProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveIPWithResolver(ctx, host, ProxyServerHostResolver)
}
return ResolveIP(ctx, host)
}
func LookupIPv6ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return LookupIPv6WithResolver(ctx, host, ProxyServerHostResolver)
}
return LookupIPv6(ctx, host)
}
func LookupIPv4ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return LookupIPv4WithResolver(ctx, host, ProxyServerHostResolver)
}
return LookupIPv4(ctx, host)
}
func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return LookupIPWithResolver(ctx, host, ProxyServerHostResolver)
}
return LookupIP(ctx, host)
}
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {

View File

@ -5,6 +5,7 @@ import (
"os"
"time"
"github.com/metacubex/mihomo/common/utils"
types "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
@ -21,7 +22,7 @@ type Fetcher[V any] struct {
name string
vehicle types.Vehicle
updatedAt time.Time
hash types.HashType
hash utils.HashType
parser Parser[V]
interval time.Duration
onUpdate func(V)
@ -55,7 +56,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
// local file exists, use it first
buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
contents, _, err = f.loadBuf(buf, types.MakeHash(buf), false)
contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false)
f.updatedAt = modTime // reset updatedAt to file's modTime
if err == nil {
@ -89,10 +90,10 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
}
func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) {
return f.loadBuf(buf, types.MakeHash(buf), true)
return f.loadBuf(buf, utils.MakeHash(buf), true)
}
func (f *Fetcher[V]) loadBuf(buf []byte, hash types.HashType, updateFile bool) (V, bool, error) {
func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (V, bool, error) {
now := time.Now()
if f.hash.Equal(hash) {
if updateFile {

View File

@ -9,6 +9,7 @@ import (
"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"
@ -61,12 +62,12 @@ func (f *FileVehicle) Url() string {
return "file://" + f.path
}
func (f *FileVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) {
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 = types.MakeHash(buf)
hash = utils.MakeHash(buf)
return
}
@ -83,11 +84,12 @@ func NewFileVehicle(path string) *FileVehicle {
}
type HTTPVehicle struct {
url string
path string
proxy string
header http.Header
timeout time.Duration
url string
path string
proxy string
header http.Header
timeout time.Duration
provider types.ProxyProvider
}
func (h *HTTPVehicle) Url() string {
@ -110,20 +112,24 @@ func (h *HTTPVehicle) Write(buf []byte) error {
return safeWrite(h.path, buf)
}
func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []byte, hash types.HashType, err error) {
func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) {
h.provider = provider
}
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()
header := h.header
setIfNoneMatch := false
if etag && oldHash.IsValid() {
hashBytes, etag := cachefile.Cache().GetETagWithHash(h.url)
if oldHash.EqualBytes(hashBytes) && etag != "" {
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", etag)
header.Set("If-None-Match", etagWithHash.ETag)
setIfNoneMatch = true
}
}
@ -132,6 +138,12 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []b
return
}
defer resp.Body.Close()
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" {
cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo)
h.provider.SetSubscriptionInfo(subscriptionInfo)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
if setIfNoneMatch && resp.StatusCode == http.StatusNotModified {
return nil, oldHash, nil
@ -143,9 +155,13 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash types.HashType) (buf []b
if err != nil {
return
}
hash = types.MakeHash(buf)
hash = utils.MakeHash(buf)
if etag {
cachefile.Cache().SetETagWithHash(h.url, hash.Bytes(), resp.Header.Get("ETag"))
cachefile.Cache().SetETagWithHash(h.url, cachefile.EtagWithHash{
Hash: hash,
ETag: resp.Header.Get("ETag"),
Time: time.Now(),
})
}
return
}

View File

@ -10,12 +10,12 @@ import (
"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"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang"
@ -46,9 +46,9 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) {
func UpdateMMDB() (err error) {
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
var oldHash P.HashType
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf)
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil {
@ -77,9 +77,9 @@ func UpdateMMDB() (err error) {
func UpdateASN() (err error) {
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
var oldHash P.HashType
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf)
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil {
@ -110,9 +110,9 @@ func UpdateGeoIp() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard")
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
var oldHash P.HashType
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf)
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil {
@ -140,9 +140,9 @@ func UpdateGeoSite() (err error) {
geoLoader, err := geodata.GetGeoDataLoader("standard")
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
var oldHash P.HashType
var oldHash utils.HashType
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = P.MakeHash(buf)
oldHash = utils.MakeHash(buf)
}
data, hash, err := vehicle.Read(context.Background(), oldHash)
if err != nil {
@ -229,20 +229,22 @@ func UpdateGeoDatabases() error {
}
func getUpdateTime() (err error, time time.Time) {
var fileInfo os.FileInfo
if geodata.GeodataMode() {
fileInfo, err = os.Stat(C.Path.GeoIP())
if err != nil {
return err, time
}
} else {
fileInfo, err = os.Stat(C.Path.MMDB())
if err != nil {
return err, time
filesToCheck := []string{
C.Path.GeoIP(),
C.Path.MMDB(),
C.Path.ASN(),
C.Path.GeoSite(),
}
for _, file := range filesToCheck {
var fileInfo os.FileInfo
fileInfo, err = os.Stat(file)
if err == nil {
return nil, fileInfo.ModTime()
}
}
return nil, fileInfo.ModTime()
return
}
func RegisterGeoUpdater() {

View File

@ -14,24 +14,69 @@ import (
"github.com/metacubex/mihomo/log"
)
var (
ExternalUIURL string
ExternalUIPath string
AutoDownloadUI bool
)
type UIUpdater struct {
externalUIURL string
externalUIPath string
autoDownloadUI bool
var xdMutex sync.Mutex
mutex sync.Mutex
}
func DownloadUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
var DefaultUiUpdater = &UIUpdater{}
err := prepareUIPath()
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
updater := &UIUpdater{}
// checkout externalUI exist
if externalUI != "" {
updater.autoDownloadUI = true
updater.externalUIPath = C.Path.Resolve(externalUI)
} else {
// default externalUI path
updater.externalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
// checkout UIpath/name exist
if externalUIName != "" {
updater.autoDownloadUI = true
updater.externalUIPath = path.Join(updater.externalUIPath, externalUIName)
}
if externalUIURL != "" {
updater.externalUIURL = externalUIURL
}
return updater
}
func (u *UIUpdater) AutoDownloadUI() {
u.mutex.Lock()
defer u.mutex.Unlock()
if u.autoDownloadUI {
dirEntries, _ := os.ReadDir(u.externalUIPath)
if len(dirEntries) > 0 {
log.Infoln("UI already exists, skip downloading")
} else {
log.Infoln("External UI downloading ...")
err := u.downloadUI()
if err != nil {
log.Errorln("Error downloading UI: %s", err)
}
}
}
}
func (u *UIUpdater) DownloadUI() error {
u.mutex.Lock()
defer u.mutex.Unlock()
return u.downloadUI()
}
func (u *UIUpdater) downloadUI() error {
err := u.prepareUIPath()
if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err)
}
data, err := downloadForBytes(ExternalUIURL)
data, err := downloadForBytes(u.externalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
}
@ -42,7 +87,7 @@ func DownloadUI() error {
}
defer os.Remove(saved)
err = cleanup(ExternalUIPath)
err = cleanup(u.externalUIPath)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
@ -54,18 +99,18 @@ func DownloadUI() error {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIPath)
err = os.Rename(unzipFolder, u.externalUIPath)
if err != nil {
return fmt.Errorf("rename UI folder failed: %w", err)
}
return nil
}
func prepareUIPath() error {
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", ExternalUIPath)
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
log.Warnln("create dir %s error: %s", ExternalUIPath, err)
func (u *UIUpdater) prepareUIPath() error {
if _, err := os.Stat(u.externalUIPath); os.IsNotExist(err) {
log.Infoln("dir %s does not exist, creating", u.externalUIPath)
if err := os.MkdirAll(u.externalUIPath, os.ModePerm); err != nil {
log.Warnln("create dir %s error: %s", u.externalUIPath, err)
}
}
return nil

View File

@ -7,28 +7,23 @@ import (
"net"
"net/netip"
"net/url"
"path"
"strings"
"time"
_ "unsafe"
"github.com/metacubex/mihomo/adapter"
"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"
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"
providerTypes "github.com/metacubex/mihomo/constant/provider"
snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
@ -67,6 +62,9 @@ type General struct {
GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"`
ETagSupport bool `json:"etag-support"`
KeepAliveIdle int `json:"keep-alive-idle"`
KeepAliveInterval int `json:"keep-alive-interval"`
DisableKeepAlive bool `json:"disable-keep-alive"`
}
// Inbound config
@ -103,9 +101,18 @@ type Controller struct {
ExternalController string
ExternalControllerTLS string
ExternalControllerUnix string
ExternalControllerPipe string
ExternalUI string
ExternalUIURL string
ExternalUIName string
ExternalDohServer string
Secret string
Cors Cors
}
type Cors struct {
AllowOrigins []string
AllowPrivateNetwork bool
}
// Experimental config
@ -153,6 +160,8 @@ type DNS struct {
Hosts *trie.DomainTrie[resolver.HostValue]
NameServerPolicy []dns.Policy
ProxyServerNameserver []dns.NameServer
DirectNameServer []dns.NameServer
DirectFollowPolicy bool
}
// Profile config
@ -190,26 +199,33 @@ 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"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"`
RespectRules bool `yaml:"respect-rules" json:"respect-rules"`
NameServer []string `yaml:"nameserver" json:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
Listen string `yaml:"listen" json:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
Enable bool `yaml:"enable" json:"enable"`
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"`
RespectRules bool `yaml:"respect-rules" json:"respect-rules"`
NameServer []string `yaml:"nameserver" json:"nameserver"`
Fallback []string `yaml:"fallback" json:"fallback"`
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
Listen string `yaml:"listen" json:"listen"`
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
DirectNameServer []string `yaml:"direct-nameserver" json:"direct-nameserver"`
DirectNameServerFollowPolicy bool `yaml:"direct-nameserver-follow-policy" json:"direct-nameserver-follow-policy"`
}
type RawFallbackFilter struct {
@ -364,8 +380,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"`
@ -539,6 +557,10 @@ func DefaultRawConfig() *RawConfig {
OverrideDest: true,
},
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
ExternalControllerCors: RawCors{
AllowOrigins: []string{"*"},
AllowPrivateNetwork: true,
},
}
}
@ -564,10 +586,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.General = general
if len(config.General.GlobalClientFingerprint) != 0 {
log.Debugln("GlobalClientFingerprint: %s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
// We need to temporarily apply some configuration in general and roll back after parsing the complete configuration.
// The loading and downloading of geodata in the parseRules and parseRuleProviders rely on these.
// This implementation is very disgusting, but there is currently no better solution
rollback := temporaryUpdateGeneral(config.General)
defer rollback()
controller, err := parseController(rawCfg)
if err != nil {
@ -683,46 +706,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
return config, nil
}
//go:linkname temporaryUpdateGeneral
func temporaryUpdateGeneral(general *General) func()
func parseGeneral(cfg *RawConfig) (*General, error) {
updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
geodata.SetGeodataMode(cfg.GeodataMode)
geodata.SetLoader(cfg.GeodataLoader)
geodata.SetSiteMatcher(cfg.GeositeMatcher)
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
}
if cfg.KeepAliveInterval != 0 {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
}
N.DisableKeepAlive = cfg.DisableKeepAlive
// checkout externalUI exist
if cfg.ExternalUI != "" {
updater.AutoDownloadUI = true
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
} else {
// default externalUI path
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.AutoDownloadUI = true
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
}
return &General{
Inbound: Inbound{
Port: cfg.Port,
@ -756,11 +743,15 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeoUpdateInterval: cfg.GeoUpdateInterval,
GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader,
GeositeMatcher: cfg.GeositeMatcher,
TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA,
ETagSupport: cfg.ETagSupport,
KeepAliveIdle: cfg.KeepAliveIdle,
KeepAliveInterval: cfg.KeepAliveInterval,
DisableKeepAlive: cfg.DisableKeepAlive,
}, nil
}
@ -768,10 +759,17 @@ func parseController(cfg *RawConfig) (*Controller, error) {
return &Controller{
ExternalController: cfg.ExternalController,
ExternalUI: cfg.ExternalUI,
ExternalUIURL: cfg.ExternalUIURL,
ExternalUIName: cfg.ExternalUIName,
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
}
@ -1162,16 +1160,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 {
@ -1222,6 +1210,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
@ -1247,16 +1242,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
}
@ -1401,6 +1398,11 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err
}
if dnsCfg.DirectNameServer, err = parseNameServer(cfg.DirectNameServer, false, cfg.PreferH3); err != nil {
return nil, err
}
dnsCfg.DirectFollowPolicy = cfg.DirectNameServerFollowPolicy
if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver")
}

View File

@ -158,6 +158,7 @@ type DelayHistoryStoreType int
type Proxy interface {
ProxyAdapter
Adapter() ProxyAdapter
AliveForTestUrl(url string) bool
DelayHistory() []DelayHistory
ExtraDelayHistories() map[string]ProxyState
@ -212,6 +213,8 @@ func (at AdapterType) String() string {
return "WireGuard"
case Tuic:
return "Tuic"
case Ssh:
return "Ssh"
case Relay:
return "Relay"
@ -223,8 +226,6 @@ func (at AdapterType) String() string {
return "URLTest"
case LoadBalance:
return "LoadBalance"
case Ssh:
return "Ssh"
default:
return "Unknown"
}
@ -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

View File

@ -1,14 +1,13 @@
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"
)
@ -89,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)
}

View File

@ -1,29 +0,0 @@
package provider
import (
"bytes"
"crypto/md5"
)
type HashType [md5.Size]byte // MD5
func MakeHash(data []byte) HashType {
return md5.Sum(data)
}
func (h HashType) Equal(hash HashType) bool {
return h == hash
}
func (h HashType) EqualBytes(hashBytes []byte) bool {
return bytes.Equal(hashBytes, h[:])
}
func (h HashType) Bytes() []byte {
return h[:]
}
func (h HashType) IsValid() bool {
var zero HashType
return h != zero
}

View File

@ -32,7 +32,7 @@ func (v VehicleType) String() string {
}
type Vehicle interface {
Read(ctx context.Context, oldHash HashType) (buf []byte, hash HashType, err error)
Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error)
Write(buf []byte) error
Path() string
Url() string
@ -81,6 +81,7 @@ type ProxyProvider interface {
Version() uint32
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
HealthCheckURL() string
SetSubscriptionInfo(userInfo string)
}
// RuleProvider interface

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,10 @@ func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil {
r.ClearCache()
}
if r := resolver.SystemResolver; r != nil {
r.ClearCache()
}
resolver.ResetConnection()
}
func UpdateSystemDNS(addr []string) {
@ -30,3 +34,9 @@ func UpdateSystemDNS(addr []string) {
func (c *systemClient) getDnsClients() ([]dnsClient, error) {
return systemResolver, nil
}
func (c *systemClient) ResetConnection() {
for _, r := range systemResolver {
r.ResetConnection()
}
}

View File

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

View File

@ -13,7 +13,6 @@ import (
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/log"
D "github.com/miekg/dns"
@ -24,6 +23,7 @@ import (
type dnsClient interface {
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string
ResetConnection()
}
type dnsCache interface {
@ -48,7 +48,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) {
@ -376,6 +376,20 @@ func (r *Resolver) ClearCache() {
}
}
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
@ -413,6 +427,8 @@ type Config struct {
Main, Fallback []NameServer
Default []NameServer
ProxyServer []NameServer
DirectServer []NameServer
DirectFollowPolicy bool
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
@ -421,20 +437,39 @@ type Config struct {
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy []Policy
Tunnel provider.Tunnel
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))
}
}
type Resolvers struct {
*Resolver
ProxyResolver *Resolver
DirectResolver *Resolver
}
func (rs Resolvers) ClearCache() {
rs.Resolver.ClearCache()
rs.ProxyResolver.ClearCache()
rs.DirectResolver.ClearCache()
}
func (rs Resolvers) ResetConnection() {
rs.Resolver.ResetConnection()
rs.ProxyResolver.ResetConnection()
rs.DirectResolver.ResetConnection()
}
func NewResolver(config Config) (rs Resolvers) {
defaultResolver := &Resolver{
main: transform(config.Default, nil),
cache: cache,
cache: config.newCache(),
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
}
@ -465,25 +500,40 @@ 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{
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
rs.Resolver = r
if len(config.ProxyServer) != 0 {
rs.ProxyResolver = &Resolver{
ipv6: config.IPv6,
main: cacheTransform(config.ProxyServer),
cache: config.newCache(),
hosts: config.Hosts,
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
}
}
if len(config.DirectServer) != 0 {
rs.DirectResolver = &Resolver{
ipv6: config.IPv6,
main: cacheTransform(config.DirectServer),
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)
r.fallbackIPFilters = config.FallbackIPFilter
r.fallbackDomainFilters = config.FallbackDomainFilter
}
if len(config.Policy) != 0 {
@ -512,22 +562,13 @@ func NewResolver(config Config) *Resolver {
}
}
insertPolicy(nil)
}
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,
if rs.DirectResolver != nil && config.DirectFollowPolicy {
rs.DirectResolver.policy = r.policy
}
}
return r
return
}
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go

View File

@ -7,6 +7,8 @@ import (
"sync"
"time"
"github.com/metacubex/mihomo/component/resolver"
D "github.com/miekg/dns"
)
@ -24,10 +26,15 @@ type systemClient struct {
mu sync.Mutex
dnsClients map[string]*systemDnsClient
lastFlush time.Time
defaultNS []dnsClient
}
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
dnsClients, err := c.getDnsClients()
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
dnsClients = c.defaultNS
err = nil
}
if err != nil {
return
}
@ -38,11 +45,16 @@ func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms
// Address implements dnsClient
func (c *systemClient) Address() string {
dnsClients, _ := c.getDnsClients()
isDefault := ""
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
dnsClients = c.defaultNS
isDefault = "[defaultNS]"
}
addrs := make([]string, 0, len(dnsClients))
for _, c := range dnsClients {
addrs = append(addrs, c.Address())
}
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
return fmt.Sprintf("system%s(%s)", isDefault, strings.Join(addrs, ","))
}
var _ dnsClient = (*systemClient)(nil)
@ -52,3 +64,11 @@ func newSystemClient() *systemClient {
dnsClients: map[string]*systemDnsClient{},
}
}
func init() {
r := NewResolver(Config{})
c := newSystemClient()
c.defaultNS = transform([]NameServer{{Addr: "114.114.114.114:53"}, {Addr: "8.8.8.8:53"}}, nil)
r.main = []dnsClient{c}
resolver.SystemResolver = r
}

View File

@ -69,3 +69,5 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) {
}
return nil, err
}
func (c *systemClient) ResetConnection() {}

View File

@ -99,10 +99,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName))
continue
case "dhcp":
if s.Addr == "system" { // Compatible with old writing
ret = append(ret, newSystemClient())
continue
}
ret = append(ret, newDHCPClient(s.Addr))
continue
case "system":

View File

@ -58,11 +58,21 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
# 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 访问
@ -284,10 +294,15 @@ dns:
# - tcp://1.1.1.1
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
# 专用于节点域名解析的 DNS 服务器,非必要配置项
# 专用于节点域名解析的 DNS 服务器,非必要配置项如果不填则遵循nameserver-policy、nameserver和fallback的配置
# proxy-server-nameserver:
# - https://dns.google/dns-query
# - tls://one.one.one.one
# - https://dns.google/dns-query
# - tls://one.one.one.one
# 专用于direct出口域名解析的 DNS 服务器非必要配置项如果不填则遵循nameserver-policy、nameserver和fallback的配置
# direct-nameserver:
# - system://
# direct-nameserver-follow-policy: false # 是否遵循nameserver-policy默认为不遵守仅当direct-nameserver不为空时生效
# 配置 fallback 使用条件
# fallback-filter:

32
go.mod
View File

@ -5,14 +5,13 @@ go 1.20
require (
github.com/3andne/restls-client-go v0.1.6
github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.7.0
github.com/coreos/go-iptables v0.8.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
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/klauspost/compress v1.17.9
github.com/klauspost/cpuid/v2 v2.2.8
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
@ -21,15 +20,15 @@ require (
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.47.1-0.20240909010619-6b38f24bfcc4
github.com/metacubex/quic-go v0.48.2-0.20241105005628-a3e65bac65b2
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-tun v0.2.7-0.20241106120309-53606a70db98
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
github.com/metacubex/sing-wireguard v0.0.0-20240922131718-0f10c39a5531
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/metacubex/utls v1.6.6
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.62
@ -37,23 +36,25 @@ require (
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 v0.5.0
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4
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/exp v0.0.0-20240808152545-0cdaa3abc0fa
golang.org/x/net v0.28.0
golang.org/x/sys v0.24.0
golang.org/x/crypto v0.28.0
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
golang.org/x/net v0.30.0
golang.org/x/sys v0.26.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.3.0
@ -105,14 +106,15 @@ 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.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.24.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908

62
go.sum
View File

@ -19,8 +19,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -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=
@ -73,8 +71,8 @@ github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8=
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@ -106,26 +104,26 @@ 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.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/quic-go v0.48.2-0.20241105005628-a3e65bac65b2 h1:1prpWzQnhN/LgGTMA6nz86MGcppDUOwfRkhxPOnrzAk=
github.com/metacubex/quic-go v0.48.2-0.20241105005628-a3e65bac65b2/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA=
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=
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908 h1:cZYdGEQKfLsw//TI7dk9bdplz48zitpEDbDGusB9d40=
github.com/metacubex/sing v0.0.0-20241105005934-13bf5e941908/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y=
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
github.com/metacubex/sing-tun v0.2.7-0.20241106120309-53606a70db98 h1:vW0QDrzUc4k1yi3A76lDW064zonPj880QFcpTD58u7A=
github.com/metacubex/sing-tun v0.2.7-0.20241106120309-53606a70db98/go.mod h1:GRcrj7VnhvYFsS34cv0J2qTVm5h9DvQuGwliVyVLVvE=
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-20240922131718-0f10c39a5531 h1:BoIL2fZZTPzvSxuhng9kWwvUZ8fiMJyrWbgdHIX0CDs=
github.com/metacubex/sing-wireguard v0.0.0-20240922131718-0f10c39a5531/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/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-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
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=
@ -162,6 +160,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=
@ -212,6 +212,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=
@ -220,26 +224,26 @@ 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/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/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
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=
@ -259,12 +263,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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.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=

View File

@ -9,6 +9,7 @@ import (
"strconv"
"sync"
"time"
_ "unsafe"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/inbound"
@ -16,9 +17,10 @@ import (
"github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer"
G "github.com/metacubex/mihomo/component/geodata"
"github.com/metacubex/mihomo/component/geodata"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/component/iface"
"github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/profile"
"github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/resolver"
@ -100,7 +102,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
updateSniffer(cfg.Sniffer)
updateHosts(cfg.Hosts)
updateGeneral(cfg.General)
updateGeneral(cfg.General, true)
updateNTP(cfg.NTP)
updateDNS(cfg.DNS, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force)
@ -117,7 +119,9 @@ func ApplyConfig(cfg *config.Config, force bool) {
runtime.GC()
tunnel.OnRunning()
hcCompatibleProvider(cfg.Providers)
initExternalUI()
updateUpdater(cfg)
resolver.ResetConnection()
}
func initInnerTcp() {
@ -127,7 +131,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()
}
@ -158,22 +162,25 @@ func GetGeneral() *config.General {
Interface: dialer.DefaultInterface.Load(),
RoutingMark: int(dialer.DefaultRoutingMark.Load()),
GeoXUrl: config.GeoXUrl{
GeoIp: G.GeoIpUrl(),
Mmdb: G.MmdbUrl(),
ASN: G.ASNUrl(),
GeoSite: G.GeoSiteUrl(),
GeoIp: geodata.GeoIpUrl(),
Mmdb: geodata.MmdbUrl(),
ASN: geodata.ASNUrl(),
GeoSite: geodata.GeoSiteUrl(),
},
GeoAutoUpdate: updater.GeoAutoUpdate(),
GeoUpdateInterval: updater.GeoUpdateInterval(),
GeodataMode: G.GeodataMode(),
GeodataLoader: G.LoaderName(),
GeositeMatcher: G.SiteMatcherName(),
GeodataMode: geodata.GeodataMode(),
GeodataLoader: geodata.LoaderName(),
GeositeMatcher: geodata.SiteMatcherName(),
TCPConcurrent: dialer.GetTcpConcurrent(),
FindProcessMode: tunnel.FindProcessMode(),
Sniffing: tunnel.IsSniffing(),
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
GlobalUA: mihomoHttp.UA(),
ETagSupport: resource.ETag(),
KeepAliveInterval: int(keepalive.KeepAliveInterval() / time.Second),
KeepAliveIdle: int(keepalive.KeepAliveIdle() / time.Second),
DisableKeepAlive: keepalive.DisableKeepAlive(),
}
return general
@ -233,6 +240,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil
resolver.DefaultLocalServer = nil
resolver.ProxyServerHostResolver = nil
resolver.DirectHostResolver = nil
dns.ReCreateServer("", nil, nil)
return
}
@ -249,12 +258,12 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
Default: c.DefaultNameserver,
Policy: c.NameServerPolicy,
ProxyServer: c.ProxyServerNameserver,
Tunnel: tunnel.Tunnel,
DirectServer: c.DirectNameServer,
DirectFollowPolicy: c.DirectFollowPolicy,
CacheAlgorithm: c.CacheAlgorithm,
}
r := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg)
// reuse cache of old host mapper
@ -264,14 +273,22 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
resolver.DefaultResolver = r
resolver.DefaultHostMapper = m
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
resolver.DefaultLocalServer = dns.NewLocalServer(r.Resolver, m)
resolver.UseSystemHosts = c.UseSystemHosts
if pr.Invalid() {
resolver.ProxyServerHostResolver = pr
if r.ProxyResolver.Invalid() {
resolver.ProxyServerHostResolver = r.ProxyResolver
} else {
resolver.ProxyServerHostResolver = r.Resolver
}
dns.ReCreateServer(c.Listen, r, m)
if r.DirectResolver.Invalid() {
resolver.DirectHostResolver = r.DirectResolver
} else {
resolver.DirectHostResolver = r.Resolver
}
dns.ReCreateServer(c.Listen, r.Resolver, m)
}
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
@ -382,47 +399,68 @@ func updateTunnels(tunnels []LC.Tunnel) {
listener.PatchTunnel(tunnels, tunnel.Tunnel)
}
func initExternalUI() {
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.DownloadUI()
}
func updateUpdater(cfg *config.Config) {
general := cfg.General
updater.SetGeoAutoUpdate(general.GeoAutoUpdate)
updater.SetGeoUpdateInterval(general.GeoUpdateInterval)
controller := cfg.Controller
updater.DefaultUiUpdater = updater.NewUiUpdater(controller.ExternalUI, controller.ExternalUIURL, controller.ExternalUIName)
updater.DefaultUiUpdater.AutoDownloadUI()
}
//go:linkname temporaryUpdateGeneral github.com/metacubex/mihomo/config.temporaryUpdateGeneral
func temporaryUpdateGeneral(general *config.General) func() {
oldGeneral := GetGeneral()
updateGeneral(general, false)
return func() {
updateGeneral(oldGeneral, false)
}
}
func updateGeneral(general *config.General) {
func updateGeneral(general *config.General, logging bool) {
tunnel.SetMode(general.Mode)
tunnel.SetFindProcessMode(general.FindProcessMode)
resolver.DisableIPv6 = !general.IPv6
if general.TCPConcurrent {
dialer.SetTcpConcurrent(general.TCPConcurrent)
dialer.SetTcpConcurrent(general.TCPConcurrent)
if logging && general.TCPConcurrent {
log.Infoln("Use tcp concurrent")
}
inbound.SetTfo(general.InboundTfo)
inbound.SetMPTCP(general.InboundMPTCP)
keepalive.SetKeepAliveIdle(time.Duration(general.KeepAliveIdle) * time.Second)
keepalive.SetKeepAliveInterval(time.Duration(general.KeepAliveInterval) * time.Second)
keepalive.SetDisableKeepAlive(general.DisableKeepAlive)
adapter.UnifiedDelay.Store(general.UnifiedDelay)
dialer.DefaultInterface.Store(general.Interface)
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
if general.RoutingMark > 0 {
if logging && general.RoutingMark > 0 {
log.Infoln("Use routing mark: %#x", general.RoutingMark)
}
iface.FlushCache()
G.SetLoader(general.GeodataLoader)
G.SetSiteMatcher(general.GeositeMatcher)
geodata.SetGeodataMode(general.GeodataMode)
geodata.SetLoader(general.GeodataLoader)
geodata.SetSiteMatcher(general.GeositeMatcher)
geodata.SetGeoIpUrl(general.GeoXUrl.GeoIp)
geodata.SetGeoSiteUrl(general.GeoXUrl.GeoSite)
geodata.SetMmdbUrl(general.GeoXUrl.Mmdb)
geodata.SetASNUrl(general.GeoXUrl.ASN)
mihomoHttp.SetUA(general.GlobalUA)
resource.SetETag(general.ETagSupport)
tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint)
}
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")
}
@ -444,12 +482,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
}

View File

@ -27,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
@ -47,11 +53,16 @@ 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,
},
})
}

View File

@ -24,9 +24,11 @@ import (
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
r.Put("/", updateConfigs)
r.Post("/geo", updateGeoDatabases)
r.Patch("/", patchConfigs)
if !embedMode { // disallow update/patch configs in embed mode
r.Put("/", updateConfigs)
r.Post("/geo", updateGeoDatabases)
r.Patch("/", patchConfigs)
}
return r
}

View File

@ -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"
@ -17,7 +16,7 @@ import (
"github.com/metacubex/mihomo/tunnel"
)
func GroupRouter() http.Handler {
func groupRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getGroups)
@ -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(), "")
}

View File

@ -0,0 +1,7 @@
//go:build android && cmfa
package route
func init() {
SetEmbedMode(true) // set embed mode default
}

View File

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

View File

@ -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,8 +35,15 @@ var (
httpServer *http.Server
tlsServer *http.Server
unixServer *http.Server
pipeServer *http.Server
embedMode = false
)
func SetEmbedMode(embed bool) {
embedMode = embed
}
type Traffic struct {
Up int64 `json:"up"`
Down int64 `json:"down"`
@ -51,33 +58,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()
@ -100,15 +120,17 @@ func router(isDebug bool, secret string, dohServer string) *chi.Mux {
r.Get("/version", version)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/group", GroupRouter())
r.Mount("/group", groupRouter())
r.Mount("/rules", ruleRouter())
r.Mount("/connections", connectionRouter())
r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/cache", cacheRouter())
r.Mount("/dns", dnsRouter())
r.Mount("/restart", restartRouter())
r.Mount("/upgrade", upgradeRouter())
if !embedMode { // disallow restart and upgrade in embed mode
r.Mount("/restart", restartRouter())
r.Mount("/upgrade", upgradeRouter())
}
addExternalRouters(r)
})
@ -146,7 +168,7 @@ 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 {
@ -178,7 +200,7 @@ 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},
},
@ -223,26 +245,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)
}
}
}
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,6 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
}
continue
}
N.TCPKeepAlive(c)
go sl.HandleConn(c, tunnel, additions...)
}
}()

View File

@ -7,7 +7,6 @@ import (
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/sockopt"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
@ -153,7 +152,6 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
}
continue
}
N.TCPKeepAlive(c)
go sl.HandleConn(c, tunnel)
}

View File

@ -279,7 +279,11 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
return
}
defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true})
overrideAndroidVPN := true
if disable, _ := strconv.ParseBool(os.Getenv("DISABLE_OVERRIDE_ANDROID_VPN")); disable {
overrideAndroidVPN = false
}
defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{InterfaceFinder: interfaceFinder, OverrideAndroidVPN: overrideAndroidVPN})
if err != nil {
err = E.Cause(err, "create DefaultInterfaceMonitor")
return
@ -440,6 +444,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
//l.openAndroidHotspot(tunOptions)
if !l.options.AutoDetectInterface {
resolver.ResetConnection()
}
if options.FileDescriptor != 0 {
tunName = fmt.Sprintf("%s(fd=%d)", tunName, options.FileDescriptor)
}
@ -507,6 +515,7 @@ func (l *Listener) FlushDefaultInterface() {
if old := dialer.DefaultInterface.Swap(autoDetectInterfaceName); old != autoDetectInterfaceName {
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, autoDetectInterfaceName)
iface.FlushCache()
resolver.ResetConnection() // reset resolver's connection after default interface changed
}
return
}

View File

@ -121,7 +121,6 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
}
continue
}
N.TCPKeepAlive(c)
go sl.HandleConn(c, tunnel)
}

View File

@ -36,10 +36,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
@ -48,6 +48,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
@ -66,25 +67,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 handleSocks(c, tunnel, getAuth, additions...)
go handleSocks(c, tunnel, store, additions...)
}
}()
return sl, nil
}
func handleSocks(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
N.TCPKeepAlive(conn)
func handleSocks(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
bufConn := N.NewBufferedConn(conn)
head, err := bufConn.Peek(1)
if err != nil {
@ -94,16 +94,16 @@ func handleSocks(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticat
switch head[0] {
case socks4.Version:
HandleSocks4(bufConn, tunnel, getAuth, additions...)
HandleSocks4(bufConn, tunnel, store, additions...)
case socks5.Version:
HandleSocks5(bufConn, tunnel, getAuth, additions...)
HandleSocks5(bufConn, tunnel, store, additions...)
default:
conn.Close()
}
}
func HandleSocks4(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
authenticator := getAuth()
func HandleSocks4(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
authenticator := store.Authenticator()
addr, _, user, err := socks4.ServerHandshake(conn, authenticator)
if err != nil {
conn.Close()
@ -113,8 +113,8 @@ func HandleSocks4(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authentica
tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...))
}
func HandleSocks5(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
authenticator := getAuth()
func HandleSocks5(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
authenticator := store.Authenticator()
target, command, user, err := socks5.ServerHandshake(conn, authenticator)
if err != nil {
conn.Close()

View File

@ -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"
"github.com/metacubex/mihomo/transport/socks5"
)
@ -33,7 +33,7 @@ func (l *Listener) Close() error {
func (l *Listener) handleTProxy(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
target := socks5.ParseAddrToSocksAddr(conn.LocalAddr())
N.TCPKeepAlive(conn)
keepalive.TCPKeepAlive(conn)
// TProxy's conn.LocalAddr() is target address, so we set from l.listener
additions = append([]inbound.Addition{inbound.WithInAddr(l.listener.Addr())}, additions...)
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TPROXY, additions...))

View File

@ -5,7 +5,6 @@ import (
"net"
"github.com/metacubex/mihomo/adapter/inbound"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5"
)
@ -35,7 +34,6 @@ func (l *Listener) Close() error {
}
func (l *Listener) handleTCP(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
N.TCPKeepAlive(conn)
tunnel.HandleTCPConn(inbound.NewSocket(l.target, conn, C.TUNNEL, additions...))
}

25
main.go
View File

@ -1,9 +1,11 @@
package main
import (
"context"
"encoding/base64"
"flag"
"fmt"
"net"
"os"
"os/signal"
"path/filepath"
@ -35,6 +37,7 @@ var (
externalUI string
externalController string
externalControllerUnix string
externalControllerPipe string
secret string
)
@ -45,6 +48,7 @@ func init() {
flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory")
flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address")
flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address")
flag.StringVar(&externalControllerPipe, "ext-ctl-pipe", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_PIPE"), "override external controller pipe address")
flag.StringVar(&secret, "secret", os.Getenv("CLASH_OVERRIDE_SECRET"), "override secret for RESTful API")
flag.BoolVar(&geodataMode, "m", false, "set geodata mode")
flag.BoolVar(&version, "v", false, "show current version of mihomo")
@ -53,6 +57,12 @@ func init() {
}
func main() {
// Defensive programming: panic when code mistakenly calls net.DefaultResolver
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
panic("should never be called")
}
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
if len(os.Args) > 1 && os.Args[1] == "convert-ruleset" {
@ -133,6 +143,9 @@ func main() {
if externalControllerUnix != "" {
options = append(options, hub.WithExternalControllerUnix(externalControllerUnix))
}
if externalControllerPipe != "" {
options = append(options, hub.WithExternalControllerPipe(externalControllerPipe))
}
if secret != "" {
options = append(options, hub.WithSecret(secret))
}
@ -156,19 +169,9 @@ func main() {
case <-termSign:
return
case <-hupSign:
var cfg *config.Config
var err error
if configString != "" {
cfg, err = executor.ParseWithBytes(configBytes)
} else {
cfg, err = executor.ParseWithPath(C.Path.Config())
}
if err == nil {
hub.ApplyConfig(cfg)
} else {
if err := hub.Parse(configBytes, options...); err != nil {
log.Errorln("Parse config error: %s", err.Error())
}
}
}
}

View File

@ -1,8 +1,6 @@
package common
import (
"strconv"
"github.com/metacubex/mihomo/component/geodata"
"github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant"
@ -26,17 +24,14 @@ func (a *ASN) Match(metadata *C.Metadata) (bool, string) {
return false, ""
}
result := mmdb.ASNInstance().LookupASN(ip.AsSlice())
asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10)
ipASN := asnNumber + " " + result.AutonomousSystemOrganization
asn, aso := mmdb.ASNInstance().LookupASN(ip.AsSlice())
if a.isSourceIP {
metadata.SrcIPASN = ipASN
metadata.SrcIPASN = asn + " " + aso
} else {
metadata.DstIPASN = ipASN
metadata.DstIPASN = asn + " " + aso
}
match := a.asn == asnNumber
return match, a.adapter
return a.asn == asn, a.adapter
}
func (a *ASN) RuleType() C.RuleType {

View File

@ -4,7 +4,6 @@ import (
"crypto/tls"
"fmt"
"net"
"strings"
"time"
"github.com/metacubex/quic-go"
@ -16,9 +15,7 @@ import (
"github.com/metacubex/mihomo/transport/hysteria/utils"
)
type ClientTransport struct {
Dialer *net.Dialer
}
type ClientTransport struct{}
func (ct *ClientTransport) quicPacketConn(proto string, rAddr net.Addr, serverPorts string, obfs obfsPkg.Obfuscator, hopInterval time.Duration, dialer utils.PacketDialer) (net.PacketConn, error) {
server := rAddr.String()
@ -86,23 +83,3 @@ func (ct *ClientTransport) QUICDial(proto string, server string, serverPorts str
}
return qs, nil
}
func (ct *ClientTransport) DialTCP(raddr *net.TCPAddr) (*net.TCPConn, error) {
conn, err := ct.Dialer.Dial("tcp", raddr.String())
if err != nil {
return nil, err
}
return conn.(*net.TCPConn), nil
}
func (ct *ClientTransport) ListenUDP() (*net.UDPConn, error) {
return net.ListenUDP("udp", nil)
}
func isMultiPortAddr(addr string) bool {
_, portStr, err := net.SplitHostPort(addr)
if err == nil && (strings.Contains(portStr, ",") || strings.Contains(portStr, "-")) {
return true
}
return false
}

View File

@ -1,16 +1,109 @@
package tunnel
import (
"context"
"errors"
"net"
"net/netip"
"time"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
type packetSender struct {
ctx context.Context
cancel context.CancelFunc
ch chan C.PacketAdapter
cache *lru.LruCache[string, netip.Addr]
}
// newPacketSender return a chan based C.PacketSender
// It ensures that packets can be sent sequentially and without blocking
func newPacketSender() C.PacketSender {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan C.PacketAdapter, senderCapacity)
return &packetSender{
ctx: ctx,
cancel: cancel,
ch: ch,
cache: lru.New[string, netip.Addr](lru.WithSize[string, netip.Addr](senderCapacity)),
}
}
func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) {
for {
select {
case <-s.ctx.Done():
return // sender closed
case packet := <-s.ch:
if proxy != nil {
proxy.UpdateWriteBack(packet)
}
if err := s.ResolveUDP(packet.Metadata()); err != nil {
log.Warnln("[UDP] Resolve Ip error: %s", err)
} else {
_ = handleUDPToRemote(packet, pc, packet.Metadata())
}
packet.Drop()
}
}
}
func (s *packetSender) dropAll() {
for {
select {
case data := <-s.ch:
data.Drop() // drop all data still in chan
default:
return // no data, exit goroutine
}
}
}
func (s *packetSender) Send(packet C.PacketAdapter) {
select {
case <-s.ctx.Done():
packet.Drop() // sender closed before Send()
return
default:
}
select {
case s.ch <- packet:
// put ok, so don't drop packet, will process by other side of chan
case <-s.ctx.Done():
packet.Drop() // sender closed when putting data to chan
default:
packet.Drop() // chan is full
}
}
func (s *packetSender) Close() {
s.cancel()
s.dropAll()
}
func (s *packetSender) ResolveUDP(metadata *C.Metadata) (err error) {
// local resolve UDP dns
if !metadata.Resolved() {
ip, ok := s.cache.Get(metadata.Host)
if !ok {
ip, err = resolver.ResolveIP(s.ctx, metadata.Host)
if err != nil {
return err
}
s.cache.Set(metadata.Host, ip)
}
metadata.DstIP = ip
}
return nil
}
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
addr := metadata.UDPAddr()
if addr == nil {
@ -26,8 +119,9 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return nil
}
func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) {
func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, sender C.PacketSender, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) {
defer func() {
sender.Close()
_ = pc.Close()
closeAllLocalCoon(key)
natTable.Delete(key)

View File

@ -114,10 +114,8 @@ func (m *Manager) handle() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
m.uploadBlip.Store(m.uploadTemp.Load())
m.uploadTemp.Store(0)
m.downloadBlip.Store(m.downloadTemp.Load())
m.downloadTemp.Store(0)
m.uploadBlip.Store(m.uploadTemp.Swap(0))
m.downloadBlip.Store(m.downloadTemp.Swap(0))
}
}

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