Compare commits

...

7 Commits

Author SHA1 Message Date
Aubrey Yang
70fb352b9c
Merge 256e9d8c3d into 2b4741fbc7 2024-06-12 17:18:20 +08:00
wwqgtxx
2b4741fbc7 chore: add inner dns proxied connection statistic to restful api 2024-06-12 17:10:47 +08:00
wwqgtxx
f317baa8de feat: add respect-rules for dns 2024-06-12 15:25:34 +08:00
wwqgtxx
5678131591 fix: wireguard server resolve when only a server in peers
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
2024-06-12 11:37:23 +08:00
xishang0128
10f8ba4434 chore: Disable the loop back detector for CMFA 2024-06-12 04:46:13 +08:00
Aubrey Yang
256e9d8c3d
Update loadbalance.go 2024-04-17 18:21:59 +09:00
Aubrey Yang
828ba83ef3
Optimizations on the Round Robin strategies
Implemented optimizations on the Round Robin proxy selection strategies to enhance performance and stability under varying network conditions and proxy availabilities.

Dynamic Update Mechanism: Integrated an event-driven approach that triggers the proxy list update process when significant changes in proxy status are detected, rather than on every touch.

Memory and Performance: Optimized the management of the available proxies list to update in-place where possible.

Load Distribution: Improved the fairness in proxy usage by introducing a weighted round-robin mechanism that accounts for proxy response times and error rates, ensuring a more balanced load across the proxies.
2024-04-17 18:20:30 +09:00
7 changed files with 153 additions and 86 deletions

View File

@ -4,14 +4,19 @@ import (
"context"
"errors"
"net/netip"
"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
@ -24,8 +29,10 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
if !features.CMFA && !DisableLoopBackDetector {
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
}
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
@ -38,8 +45,10 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
if !features.CMFA && !DisableLoopBackDetector {
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() {

View File

@ -304,13 +304,16 @@ func (w *WireGuard) init(ctx context.Context) error {
ipcConf := "private_key=" + w.option.PrivateKey
if len(w.option.Peers) > 0 {
for i, peer := range w.option.Peers {
ipcConf += "\npublic_key=" + peer.PublicKey
destination, err := w.resolve(ctx, peer.Addr())
if err != nil {
// !!! do not set initErr here !!!
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain for peer ", i)
}
ipcConf += "\npublic_key=" + peer.PublicKey
if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true
w.bind.SetConnectAddr(destination)
}
ipcConf += "\nendpoint=" + destination.String()
if peer.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + peer.PreSharedKey
@ -332,7 +335,7 @@ func (w *WireGuard) init(ctx context.Context) error {
// let us can retry domain resolve in next time
return E.Cause(err, "resolve endpoint domain")
}
w.bind.SetConnectAddr(destination)
w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true
ipcConf += "\nendpoint=" + destination.String()
if w.option.PreSharedKey != "" {
ipcConf += "\npreshared_key=" + w.option.PreSharedKey

View File

@ -5,8 +5,8 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"sync"
"net"
"time"
"github.com/metacubex/mihomo/adapter/outbound"
@ -134,31 +134,31 @@ func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
}
func strategyRoundRobin(url string) strategyFn {
var availableProxies []C.Proxy
idx := 0
idxMutex := sync.Mutex{}
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
idxMutex.Lock()
defer idxMutex.Unlock()
i := 0
length := len(proxies)
if touch {
defer func() {
idx = (idx + i) % length
}()
}
for ; i < length; i++ {
id := (idx + i) % length
proxy := proxies[id]
if proxy.AliveForTestUrl(url) {
i++
return proxy
// check list
availableProxies = []C.Proxy{}
for _, proxy := range proxies {
if proxy.AliveForTestUrl(url) {
availableProxies = append(availableProxies, proxy)
}
}
// fallback
if len(availableProxies) == 0 {
return proxies[0]
}
}
return proxies[0]
proxy := availableProxies[idx]
// reset idx
idx = (idx + 1) % len(availableProxies)
return proxy
}
}

View File

@ -212,6 +212,7 @@ type RawDNS struct {
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"`
@ -1039,7 +1040,7 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
return net.JoinHostPort(hostname, port), nil
}
func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) {
func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.NameServer, error) {
var nameservers []dns.NameServer
for idx, server := range servers {
@ -1114,6 +1115,10 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
}
if respectRules && len(proxyName) == 0 {
proxyName = dns.RespectRules
}
nameservers = append(
nameservers,
dns.NameServer{
@ -1130,7 +1135,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
func init() {
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
return parseNameServer(servers, false)
return parseNameServer(servers, false, false)
}
}
@ -1156,7 +1161,7 @@ func parsePureDNSServer(server string) string {
}
}
}
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) {
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) (*orderedmap.OrderedMap[string, []dns.NameServer], error) {
policy := orderedmap.New[string, []dns.NameServer]()
updatedPolicy := orderedmap.New[string, any]()
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@ -1202,7 +1207,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro
if err != nil {
return nil, err
}
nameservers, err := parseNameServer(servers, preferH3)
nameservers, err := parseNameServer(servers, respectRules, preferH3)
if err != nil {
return nil, err
}
@ -1296,6 +1301,10 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
}
if cfg.RespectRules && len(cfg.ProxyServerNameserver) == 0 {
return nil, fmt.Errorf("if “respect-rules” is turned on, “proxy-server-nameserver” cannot be empty")
}
dnsCfg := &DNS{
Enable: cfg.Enable,
Listen: cfg.Listen,
@ -1310,26 +1319,26 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
},
}
var err error
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.PreferH3); err != nil {
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.PreferH3); err != nil {
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.RespectRules, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil {
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, cfg.PreferH3); err != nil {
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, false, cfg.PreferH3); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver")
}
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, cfg.PreferH3); err != nil {
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, false, cfg.PreferH3); err != nil {
return nil, err
}
// check default nameserver is pure ip addr

View File

@ -7,7 +7,6 @@ import (
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"time"
@ -19,6 +18,7 @@ import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
D "github.com/miekg/dns"
"github.com/samber/lo"
@ -175,6 +175,8 @@ func msgToDomain(msg *D.Msg) string {
return ""
}
const RespectRules = "RULES"
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler {
@ -183,54 +185,76 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
} else {
host, port, err := net.SplitHostPort(addr)
metadata := &C.Metadata{
NetWork: C.TCP,
Type: C.INNER,
}
err := metadata.SetRemoteAddress(addr) // tcp can resolve host by remote
if err != nil {
return nil, err
}
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return nil, err
if !strings.Contains(network, "tcp") {
metadata.NetWork = C.UDP
if !metadata.Resolved() {
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
}
var rule C.Rule
if proxyAdapter == nil {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
if proxyName == RespectRules {
if !metadata.Resolved() {
// resolve here before ResolveMetadata to avoid its inner resolver.ResolveIP
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
proxyAdapter, rule, err = tunnel.ResolveMetadata(metadata)
if err != nil {
return nil, err
}
} else {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
}
}
if strings.Contains(network, "tcp") {
// tcp can resolve host by remote
metadata := &C.Metadata{
NetWork: C.TCP,
Host: host,
DstPort: uint16(uintPort),
if proxyAdapter == nil {
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
}
if proxyAdapter != nil {
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback
if !metadata.Resolved() {
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.Host = ""
metadata.DstIP = dstIP
}
return proxyAdapter.DialContext(ctx, metadata, opts...)
metadata.Host = "" // clear host to avoid double resolve in proxy
}
opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...)
} else {
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
conn, err := proxyAdapter.DialContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
metadata := &C.Metadata{
NetWork: C.UDP,
Host: "",
DstIP: dstIP,
DstPort: uint16(uintPort),
}
conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return conn, nil
} else {
if proxyAdapter == nil {
return dialer.DialContext(ctx, network, addr, opts...)
}
@ -244,6 +268,8 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
return nil, err
}
packetConn = statistic.NewUDPTracker(packetConn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return N.NewBindPacketConn(packetConn, metadata.UDPAddr()), nil
}
}
@ -251,33 +277,38 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
}
func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
host, port, err := net.SplitHostPort(addr)
metadata := &C.Metadata{
NetWork: C.UDP,
}
err := metadata.SetRemoteAddress(addr)
if err != nil {
return nil, err
}
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return nil, err
if !metadata.Resolved() {
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, err
}
metadata.DstIP = dstIP
}
var rule C.Rule
if proxyAdapter == nil {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
if proxyName == RespectRules {
proxyAdapter, rule, err = tunnel.ResolveMetadata(metadata)
if err != nil {
return nil, err
}
} else {
var ok bool
proxyAdapter, ok = tunnel.Proxies()[proxyName]
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
}
}
// udp must resolve host first
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
if err != nil {
return nil, err
}
metadata := &C.Metadata{
NetWork: C.UDP,
Host: "",
DstIP: dstIP,
DstPort: uint16(uintPort),
}
if proxyAdapter == nil {
return dialer.NewDialer(opts...).ListenPacket(ctx, network, "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
}
@ -286,7 +317,14 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
}
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
packetConn = statistic.NewUDPTracker(packetConn, statistic.DefaultManager, metadata, rule, 0, 0, false)
return packetConn, nil
}
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {

View File

@ -209,7 +209,7 @@ tunnels: # one line config
dns:
cache-algorithm: arc
enable: false # 关闭将使用系统 DNS
prefer-h3: true # 开启 DoH 支持 HTTP/3将并发尝试
prefer-h3: false # 是否开启 DoH 支持 HTTP/3将并发尝试
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
# ipv6: false # false 将返回 AAAA 的空结果
# ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
@ -227,6 +227,13 @@ dns:
# use-hosts: true # 查询 hosts
# 配置后面的nameserver、fallback和nameserver-policy向dns服务器的连接过程是否遵守遵守rules规则
# 如果为false默认值则这三部分的dns服务器在未特别指定的情况下会直连
# 如果为true将会按照rules的规则匹配链接方式走代理或直连如果有特别指定则任然以指定值为准
# 仅当proxy-server-nameserver非空时可以开启此选项, 强烈不建议和prefer-h3一起使用
# 此外这三者配置中的dns服务器如果出现域名会采用default-nameserver配置项解析也请确保正确配置default-nameserver
respect-rules: false
# 配置不使用 fake-ip 的域名
# fake-ip-filter:
# - '*.lan'
@ -244,6 +251,7 @@ dns:
- https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3
- dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#RULES' # 效果同respect-rules但仅对该服务器生效
# - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置

View File

@ -278,7 +278,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
return nil
}
func resolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
func ResolveMetadata(metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
if metadata.SpecialProxy != "" {
var exist bool
proxy, exist = proxies[metadata.SpecialProxy]
@ -375,7 +375,7 @@ func handleUDPConn(packet C.PacketAdapter) {
cond.Broadcast()
}()
proxy, rule, err := resolveMetadata(metadata)
proxy, rule, err := ResolveMetadata(metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return
@ -486,7 +486,7 @@ func handleTCPConn(connCtx C.ConnContext) {
}()
}
proxy, rule, err := resolveMetadata(metadata)
proxy, rule, err := ResolveMetadata(metadata)
if err != nil {
log.Warnln("[Metadata] parse failed: %s", err.Error())
return