mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-12-22 23:57:26 +08:00
Merge branch 'Alpha' into parental_control_1
merge the 18.8.10
This commit is contained in:
commit
75a7a7e463
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,9 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@ -11,28 +14,47 @@ var (
|
||||
lc = tfo.ListenConfig{
|
||||
DisableTFO: true,
|
||||
}
|
||||
mutex sync.RWMutex
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func Tfo() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return !lc.DisableTFO
|
||||
}
|
||||
|
||||
func SetMPTCP(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
}
|
||||
|
||||
func MPTCP() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return getMultiPathTCP(&lc.ListenConfig)
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return lc.Listen(ctx, network, address)
|
||||
}
|
||||
|
||||
func Listen(network, address string) (net.Listener, error) {
|
||||
return ListenContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func init() {
|
||||
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
keepalive.SetNetListenConfig(&lc.ListenConfig)
|
||||
})
|
||||
}
|
||||
|
14
adapter/inbound/listen_notwindows.go
Normal file
14
adapter/inbound/listen_notwindows.go
Normal file
@ -0,0 +1,14 @@
|
||||
//go:build !windows
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
const SupportNamedPipe = false
|
||||
|
||||
func ListenNamedPipe(path string) (net.Listener, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
32
adapter/inbound/listen_windows.go
Normal file
32
adapter/inbound/listen_windows.go
Normal file
@ -0,0 +1,32 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/metacubex/wireguard-go/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const SupportNamedPipe = true
|
||||
|
||||
// windowsSDDL is the Security Descriptor set on the namedpipe.
|
||||
// It provides read/write access to all users and the local system.
|
||||
const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)"
|
||||
|
||||
func ListenNamedPipe(path string) (net.Listener, error) {
|
||||
sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL")
|
||||
if sddl == "" {
|
||||
sddl = windowsSDDL
|
||||
}
|
||||
securityDescriptor, err := windows.SecurityDescriptorFromString(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namedpipeLC := namedpipe.ListenConfig{
|
||||
SecurityDescriptor: securityDescriptor,
|
||||
InputBufferSize: 256 * 1024,
|
||||
OutputBufferSize: 256 * 1024,
|
||||
}
|
||||
return namedpipeLC.Listen(path)
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -149,7 +149,6 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
@ -80,7 +80,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@ -94,7 +93,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -122,7 +120,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
@ -207,8 +204,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@ -82,7 +81,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -128,7 +126,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
|
@ -77,7 +77,6 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@ -154,7 +153,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@ -212,7 +210,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
N.TCPKeepAlive(c)
|
||||
c, err = t.plainStream(ctx, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
@ -314,7 +311,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -4,3 +4,7 @@ type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
}
|
||||
|
||||
var _ SelectAble = (*Fallback)(nil)
|
||||
var _ SelectAble = (*URLTest)(nil)
|
||||
var _ SelectAble = (*Selector)(nil)
|
||||
|
@ -1,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, ";") {
|
||||
|
@ -1,23 +0,0 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
KeepAliveIdle = 0 * time.Second
|
||||
KeepAliveInterval = 0 * time.Second
|
||||
DisableKeepAlive = false
|
||||
)
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
if runtime.GOOS == "android" || DisableKeepAlive {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
tcpKeepAlive(tcp)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//go:build go1.23
|
||||
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
config := net.KeepAliveConfig{
|
||||
Enable: true,
|
||||
Idle: KeepAliveIdle,
|
||||
Interval: KeepAliveInterval,
|
||||
}
|
||||
if !SupportTCPKeepAliveCount() {
|
||||
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
|
||||
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
|
||||
config.Count = -1
|
||||
}
|
||||
_ = tcp.SetKeepAliveConfig(config)
|
||||
}
|
62
common/utils/hash.go
Normal file
62
common/utils/hash.go
Normal file
@ -0,0 +1,62 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// HashType warps hash array inside struct
|
||||
// someday can change to other hash algorithm simply
|
||||
type HashType struct {
|
||||
md5 [md5.Size]byte // MD5
|
||||
}
|
||||
|
||||
func MakeHash(data []byte) HashType {
|
||||
return HashType{md5.Sum(data)}
|
||||
}
|
||||
|
||||
func (h HashType) Equal(hash HashType) bool {
|
||||
return h.md5 == hash.md5
|
||||
}
|
||||
|
||||
func (h HashType) Bytes() []byte {
|
||||
return h.md5[:]
|
||||
}
|
||||
|
||||
func (h HashType) String() string {
|
||||
return hex.EncodeToString(h.Bytes())
|
||||
}
|
||||
|
||||
func (h HashType) MarshalText() ([]byte, error) {
|
||||
return []byte(h.String()), nil
|
||||
}
|
||||
|
||||
func (h *HashType) UnmarshalText(data []byte) error {
|
||||
if hex.DecodedLen(len(data)) != md5.Size {
|
||||
return errors.New("invalid hash length")
|
||||
}
|
||||
_, err := hex.Decode(h.md5[:], data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h HashType) MarshalBinary() ([]byte, error) {
|
||||
return h.md5[:], nil
|
||||
}
|
||||
|
||||
func (h *HashType) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != md5.Size {
|
||||
return errors.New("invalid hash length")
|
||||
}
|
||||
copy(h.md5[:], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h HashType) Len() int {
|
||||
return len(h.md5)
|
||||
}
|
||||
|
||||
func (h HashType) IsValid() bool {
|
||||
var zero HashType
|
||||
return h != zero
|
||||
}
|
@ -5,6 +5,11 @@ type Authenticator interface {
|
||||
Users() []string
|
||||
}
|
||||
|
||||
type AuthStore interface {
|
||||
Authenticator() Authenticator
|
||||
SetAuthenticator(Authenticator)
|
||||
}
|
||||
|
||||
type AuthUser struct {
|
||||
User string
|
||||
Pass string
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
@ -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)
|
||||
|
@ -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)
|
||||
}
|
@ -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()}
|
||||
}
|
||||
|
@ -201,9 +201,7 @@ func New(options Options) (*Pool, error) {
|
||||
ipnet: options.IPNet,
|
||||
}
|
||||
if options.Persistence {
|
||||
pool.store = &cachefileStore{
|
||||
cache: cachefile.Cache(),
|
||||
}
|
||||
pool.store = newCachefileStore(cachefile.Cache())
|
||||
} else {
|
||||
pool.store = newMemoryStore(options.Size)
|
||||
}
|
||||
|
@ -43,9 +43,7 @@ func createCachefileStore(options Options) (*Pool, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
pool.store = &cachefileStore{
|
||||
cache: &cachefile.CacheFile{DB: db},
|
||||
}
|
||||
pool.store = newCachefileStore(&cachefile.CacheFile{DB: db})
|
||||
return pool, f.Name(), nil
|
||||
}
|
||||
|
||||
|
@ -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{}),
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
65
component/keepalive/tcp_keepalive.go
Normal file
65
component/keepalive/tcp_keepalive.go
Normal file
@ -0,0 +1,65 @@
|
||||
package keepalive
|
||||
|
||||
import (
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
disableKeepAlive = atomic.NewBool(false)
|
||||
|
||||
SetDisableKeepAliveCallback = utils.NewCallback[bool]()
|
||||
)
|
||||
|
||||
func SetKeepAliveIdle(t time.Duration) {
|
||||
keepAliveIdle.Store(t)
|
||||
}
|
||||
|
||||
func SetKeepAliveInterval(t time.Duration) {
|
||||
keepAliveInterval.Store(t)
|
||||
}
|
||||
|
||||
func KeepAliveIdle() time.Duration {
|
||||
return keepAliveIdle.Load()
|
||||
}
|
||||
|
||||
func KeepAliveInterval() time.Duration {
|
||||
return keepAliveInterval.Load()
|
||||
}
|
||||
|
||||
func SetDisableKeepAlive(disable bool) {
|
||||
if runtime.GOOS == "android" {
|
||||
setDisableKeepAlive(false)
|
||||
} else {
|
||||
setDisableKeepAlive(disable)
|
||||
}
|
||||
}
|
||||
|
||||
func setDisableKeepAlive(disable bool) {
|
||||
disableKeepAlive.Store(disable)
|
||||
SetDisableKeepAliveCallback.Emit(disable)
|
||||
}
|
||||
|
||||
func DisableKeepAlive() bool {
|
||||
return disableKeepAlive.Load()
|
||||
}
|
||||
|
||||
func SetNetDialer(dialer *net.Dialer) {
|
||||
setNetDialer(dialer)
|
||||
}
|
||||
|
||||
func SetNetListenConfig(lc *net.ListenConfig) {
|
||||
setNetListenConfig(lc)
|
||||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil {
|
||||
tcpKeepAlive(tcp)
|
||||
}
|
||||
}
|
30
component/keepalive/tcp_keepalive_go122.go
Normal file
30
component/keepalive/tcp_keepalive_go122.go
Normal file
@ -0,0 +1,30 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package keepalive
|
||||
|
||||
import "net"
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
if DisableKeepAlive() {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval())
|
||||
}
|
||||
}
|
||||
|
||||
func setNetDialer(dialer *net.Dialer) {
|
||||
if DisableKeepAlive() {
|
||||
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
|
||||
} else {
|
||||
dialer.KeepAlive = KeepAliveInterval()
|
||||
}
|
||||
}
|
||||
|
||||
func setNetListenConfig(lc *net.ListenConfig) {
|
||||
if DisableKeepAlive() {
|
||||
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
|
||||
} else {
|
||||
lc.KeepAlive = KeepAliveInterval()
|
||||
}
|
||||
}
|
45
component/keepalive/tcp_keepalive_go123.go
Normal file
45
component/keepalive/tcp_keepalive_go123.go
Normal file
@ -0,0 +1,45 @@
|
||||
//go:build go1.23
|
||||
|
||||
package keepalive
|
||||
|
||||
import "net"
|
||||
|
||||
func keepAliveConfig() net.KeepAliveConfig {
|
||||
config := net.KeepAliveConfig{
|
||||
Enable: true,
|
||||
Idle: KeepAliveIdle(),
|
||||
Interval: KeepAliveInterval(),
|
||||
}
|
||||
if !SupportTCPKeepAliveCount() {
|
||||
// it's recommended to set both Idle and Interval to non-negative values in conjunction with a -1
|
||||
// for Count on those old Windows if you intend to customize the TCP keep-alive settings.
|
||||
config.Count = -1
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
if DisableKeepAlive() {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
_ = tcp.SetKeepAliveConfig(keepAliveConfig())
|
||||
}
|
||||
}
|
||||
|
||||
func setNetDialer(dialer *net.Dialer) {
|
||||
if DisableKeepAlive() {
|
||||
dialer.KeepAlive = -1 // If negative, keep-alive probes are disabled.
|
||||
dialer.KeepAliveConfig.Enable = false
|
||||
} else {
|
||||
dialer.KeepAliveConfig = keepAliveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func setNetListenConfig(lc *net.ListenConfig) {
|
||||
if DisableKeepAlive() {
|
||||
lc.KeepAlive = -1 // If negative, keep-alive probes are disabled.
|
||||
lc.KeepAliveConfig.Enable = false
|
||||
} else {
|
||||
lc.KeepAliveConfig = keepAliveConfig()
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
//go:build go1.23 && unix
|
||||
|
||||
package net
|
||||
package keepalive
|
||||
|
||||
func SupportTCPKeepAliveIdle() bool {
|
||||
return true
|
@ -2,7 +2,7 @@
|
||||
|
||||
// copy and modify from golang1.23's internal/syscall/windows/version_windows.go
|
||||
|
||||
package net
|
||||
package keepalive
|
||||
|
||||
import (
|
||||
"errors"
|
@ -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
|
||||
|
@ -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 "", ""
|
||||
}
|
||||
|
@ -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](),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
58
component/profile/cachefile/etag.go
Normal file
58
component/profile/cachefile/etag.go
Normal file
@ -0,0 +1,58 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/metacubex/bbolt"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
type EtagWithHash struct {
|
||||
Hash utils.HashType
|
||||
ETag string
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
func (c *CacheFile) SetETagWithHash(url string, etagWithHash EtagWithHash) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := msgpack.Marshal(etagWithHash)
|
||||
if err != nil {
|
||||
return // maybe panic is better
|
||||
}
|
||||
|
||||
err = c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketETag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put([]byte(url), data)
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
func (c *CacheFile) GetETagWithHash(key string) (etagWithHash EtagWithHash) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
c.DB.View(func(t *bbolt.Tx) error {
|
||||
if bucket := t.Bucket(bucketETag); bucket != nil {
|
||||
if v := bucket.Get([]byte(key)); v != nil {
|
||||
if err := msgpack.Unmarshal(v, &etagWithHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
115
component/profile/cachefile/fakeip.go
Normal file
115
component/profile/cachefile/fakeip.go
Normal file
@ -0,0 +1,115 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/metacubex/bbolt"
|
||||
)
|
||||
|
||||
type FakeIpStore struct {
|
||||
*CacheFile
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIpStore() *FakeIpStore {
|
||||
return &FakeIpStore{c}
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) GetByHost(host string) (ip netip.Addr, exist bool) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
c.DB.View(func(t *bbolt.Tx) error {
|
||||
if bucket := t.Bucket(bucketFakeip); bucket != nil {
|
||||
if v := bucket.Get([]byte(host)); v != nil {
|
||||
ip, exist = netip.AddrFromSlice(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) PutByHost(host string, ip netip.Addr) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put([]byte(host), ip.AsSlice())
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) GetByIP(ip netip.Addr) (host string, exist bool) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
c.DB.View(func(t *bbolt.Tx) error {
|
||||
if bucket := t.Bucket(bucketFakeip); bucket != nil {
|
||||
if v := bucket.Get(ip.AsSlice()); v != nil {
|
||||
host, exist = string(v), true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) PutByIP(ip netip.Addr, host string) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put(ip.AsSlice(), []byte(host))
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) DelByIP(ip netip.Addr) {
|
||||
if c.DB == nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr := ip.AsSlice()
|
||||
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := t.CreateBucketIfNotExists(bucketFakeip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host := bucket.Get(addr)
|
||||
err = bucket.Delete(addr)
|
||||
if len(host) > 0 {
|
||||
if err = bucket.Delete(host); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *FakeIpStore) FlushFakeIP() error {
|
||||
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket := t.Bucket(bucketFakeip)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
return t.DeleteBucket(bucketFakeip)
|
||||
})
|
||||
return err
|
||||
}
|
41
component/profile/cachefile/subscriptioninfo.go
Normal file
41
component/profile/cachefile/subscriptioninfo.go
Normal 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
|
||||
}
|
@ -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"}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
178
config/config.go
178
config/config.go
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
@ -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()
|
||||
|
||||
|
27
dns/doh.go
27
dns/doh.go
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
107
dns/resolver.go
107
dns/resolver.go
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -69,3 +69,5 @@ func (c *systemClient) getDnsClients() ([]dnsClient, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *systemClient) ResetConnection() {}
|
||||
|
@ -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":
|
||||
|
@ -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
32
go.mod
@ -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
62
go.sum
@ -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=
|
||||
|
@ -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
|
||||
}
|
||||
|
11
hub/hub.go
11
hub/hub.go
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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(), "")
|
||||
}
|
||||
|
||||
|
7
hub/route/patch_android.go
Normal file
7
hub/route/patch_android.go
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package route
|
||||
|
||||
func init() {
|
||||
SetEmbedMode(true) // set embed mode default
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
@ -31,6 +30,7 @@ func proxyRouter() http.Handler {
|
||||
r.Get("/", getProxy)
|
||||
r.Get("/delay", getProxyDelay)
|
||||
r.Put("/", updateProxy)
|
||||
r.Delete("/", unfixedProxy)
|
||||
})
|
||||
return r
|
||||
}
|
||||
@ -81,8 +81,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
proxy := r.Context().Value(CtxKeyProxy).(*adapter.Proxy)
|
||||
selector, ok := proxy.ProxyAdapter.(outboundgroup.SelectAble)
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
selector, ok := proxy.Adapter().(outboundgroup.SelectAble)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("Must be a Selector"))
|
||||
@ -146,3 +146,15 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||
"delay": delay,
|
||||
})
|
||||
}
|
||||
|
||||
func unfixedProxy(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||
if selectAble, ok := proxy.Adapter().(outboundgroup.SelectAble); ok && proxy.Type() != C.Selector {
|
||||
selectAble.ForceSet("")
|
||||
cachefile.Cache().SetSelected(proxy.Name(), "")
|
||||
render.NoContent(w, r)
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/sagernet/cors"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -35,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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -30,7 +30,7 @@ func (b *bodyWrapper) Read(p []byte) (n int, err error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
|
||||
func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
|
||||
additions = append(additions, inbound.Placeholder) // Add a placeholder for InUser
|
||||
inUserIdx := len(additions) - 1
|
||||
client := newClient(c, tunnel, additions)
|
||||
@ -41,7 +41,7 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator,
|
||||
|
||||
conn := N.NewBufferedConn(c)
|
||||
|
||||
authenticator := getAuth()
|
||||
authenticator := store.Authenticator()
|
||||
keepAlive := true
|
||||
trusted := authenticator == nil // disable authenticate if lru is nil
|
||||
lastUser := ""
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||
@ -33,20 +32,20 @@ func (l *Listener) Close() error {
|
||||
}
|
||||
|
||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
|
||||
}
|
||||
|
||||
// NewWithAuthenticate
|
||||
// never change type traits because it's used in CMFA
|
||||
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
|
||||
getAuth := authStore.Authenticator
|
||||
store := authStore.Default
|
||||
if !authenticate {
|
||||
getAuth = authStore.Nil
|
||||
store = authStore.Default
|
||||
}
|
||||
return NewWithAuthenticator(addr, tunnel, getAuth, additions...)
|
||||
return NewWithAuthenticator(addr, tunnel, store, additions...)
|
||||
}
|
||||
|
||||
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
|
||||
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
|
||||
isDefault := false
|
||||
if len(additions) == 0 {
|
||||
isDefault = true
|
||||
@ -55,8 +54,8 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -74,19 +73,18 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
}
|
||||
continue
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
getAuth := getAuth
|
||||
if isDefault { // only apply on default listener
|
||||
store := store
|
||||
if isDefault || store == authStore.Default { // only apply on default listener
|
||||
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
|
||||
_ = conn.Close()
|
||||
continue
|
||||
}
|
||||
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
|
||||
getAuth = authStore.Nil
|
||||
store = authStore.Nil
|
||||
}
|
||||
}
|
||||
go HandleConn(conn, tunnel, getAuth, additions...)
|
||||
go HandleConn(conn, tunnel, store, additions...)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -12,7 +12,7 @@ type AuthUser struct {
|
||||
|
||||
type AuthUsers []AuthUser
|
||||
|
||||
func (a AuthUsers) GetAuth() func() auth.Authenticator {
|
||||
func (a AuthUsers) GetAuthStore() auth.AuthStore {
|
||||
if a != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
if len(a) == 0 {
|
||||
return authStore.Nil
|
||||
@ -25,7 +25,7 @@ func (a AuthUsers) GetAuth() func() auth.Authenticator {
|
||||
}
|
||||
}
|
||||
authenticator := auth.NewAuthenticator(users)
|
||||
return func() auth.Authenticator { return authenticator }
|
||||
return authStore.NewAuthStore(authenticator)
|
||||
}
|
||||
return authStore.Authenticator
|
||||
return authStore.Default
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func (h *HTTP) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
||||
var err error
|
||||
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuth(), h.Additions()...)
|
||||
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func (m *Mixed) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
||||
var err error
|
||||
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuth(), m.Additions()...)
|
||||
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func (s *Socks) Address() string {
|
||||
// Listen implements constant.InboundListener
|
||||
func (s *Socks) Listen(tunnel C.Tunnel) error {
|
||||
var err error
|
||||
if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuth(), s.Additions()...); err != nil {
|
||||
if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuthStore(), s.Additions()...); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.udp {
|
||||
|
@ -37,10 +37,10 @@ func (l *Listener) Close() error {
|
||||
}
|
||||
|
||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Authenticator, additions...)
|
||||
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
|
||||
}
|
||||
|
||||
func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) (*Listener, error) {
|
||||
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
|
||||
isDefault := false
|
||||
if len(additions) == 0 {
|
||||
isDefault = true
|
||||
@ -49,6 +49,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -67,26 +68,24 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, getAuth func() auth.Auth
|
||||
}
|
||||
continue
|
||||
}
|
||||
getAuth := getAuth
|
||||
if isDefault { // only apply on default listener
|
||||
store := store
|
||||
if isDefault || store == authStore.Default { // only apply on default listener
|
||||
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
|
||||
_ = c.Close()
|
||||
continue
|
||||
}
|
||||
if inbound.SkipAuthRemoteAddr(c.RemoteAddr()) {
|
||||
getAuth = authStore.Nil
|
||||
store = authStore.Nil
|
||||
}
|
||||
}
|
||||
go handleConn(c, tunnel, getAuth, additions...)
|
||||
go handleConn(c, tunnel, store, additions...)
|
||||
}
|
||||
}()
|
||||
|
||||
return ml, nil
|
||||
}
|
||||
|
||||
func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticator, additions ...inbound.Addition) {
|
||||
N.TCPKeepAlive(conn)
|
||||
|
||||
func handleConn(conn net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) {
|
||||
bufConn := N.NewBufferedConn(conn)
|
||||
head, err := bufConn.Peek(1)
|
||||
if err != nil {
|
||||
@ -95,10 +94,10 @@ func handleConn(conn net.Conn, tunnel C.Tunnel, getAuth func() auth.Authenticato
|
||||
|
||||
switch head[0] {
|
||||
case socks4.Version:
|
||||
socks.HandleSocks4(bufConn, tunnel, getAuth, additions...)
|
||||
socks.HandleSocks4(bufConn, tunnel, store, additions...)
|
||||
case socks5.Version:
|
||||
socks.HandleSocks5(bufConn, tunnel, getAuth, additions...)
|
||||
socks.HandleSocks5(bufConn, tunnel, store, additions...)
|
||||
default:
|
||||
http.HandleConn(bufConn, tunnel, getAuth, additions...)
|
||||
http.HandleConn(bufConn, tunnel, store, additions...)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
@ -37,10 +37,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl := &Listener{
|
||||
listener: l,
|
||||
addr: addr,
|
||||
@ -68,6 +70,6 @@ func handleRedir(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
N.TCPKeepAlive(conn)
|
||||
keepalive.TCPKeepAlive(conn)
|
||||
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.REDIR, additions...))
|
||||
}
|
||||
|
@ -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...)
|
||||
}
|
||||
}()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -121,7 +121,6 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
continue
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
go sl.HandleConn(c, tunnel)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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...))
|
||||
|
@ -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
25
main.go
@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user