From f3743fc7f91410be8711fb6a1dcaa2d4fc20fea9 Mon Sep 17 00:00:00 2001 From: xishang0128 Date: Mon, 1 Apr 2024 16:21:34 +0800 Subject: [PATCH 1/4] chore: Introducing Punycode conversion for domain matching --- rules/common/domain.go | 4 +++- rules/common/domain_keyword.go | 4 +++- rules/common/domain_suffix.go | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rules/common/domain.go b/rules/common/domain.go index 23f21185a6..306eb65fd8 100644 --- a/rules/common/domain.go +++ b/rules/common/domain.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type Domain struct { @@ -29,9 +30,10 @@ func (d *Domain) Payload() string { } func NewDomain(domain string, adapter string) *Domain { + punycode, _ := idna.ToASCII(strings.ToLower(domain)) return &Domain{ Base: &Base{}, - domain: strings.ToLower(domain), + domain: punycode, adapter: adapter, } } diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go index ec01293a12..9d6f1c1559 100644 --- a/rules/common/domain_keyword.go +++ b/rules/common/domain_keyword.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type DomainKeyword struct { @@ -30,9 +31,10 @@ func (dk *DomainKeyword) Payload() string { } func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { + punycode, _ := idna.ToASCII(strings.ToLower(keyword)) return &DomainKeyword{ Base: &Base{}, - keyword: strings.ToLower(keyword), + keyword: punycode, adapter: adapter, } } diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go index b7b1794d8c..c5b87208eb 100644 --- a/rules/common/domain_suffix.go +++ b/rules/common/domain_suffix.go @@ -4,6 +4,7 @@ import ( "strings" C "github.com/metacubex/mihomo/constant" + "golang.org/x/net/idna" ) type DomainSuffix struct { @@ -30,9 +31,10 @@ func (ds *DomainSuffix) Payload() string { } func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { + punycode, _ := idna.ToASCII(strings.ToLower(suffix)) return &DomainSuffix{ Base: &Base{}, - suffix: strings.ToLower(suffix), + suffix: punycode, adapter: adapter, } } From 3b472f786ece5511a9e56872132396474e1df5f5 Mon Sep 17 00:00:00 2001 From: xishang0128 Date: Mon, 1 Apr 2024 18:16:34 +0800 Subject: [PATCH 2/4] chore: Add source matching for ip type rules --- constant/rule.go | 12 +++++++++--- rules/common/geoip.go | 23 +++++++++++++++++++++-- rules/common/ipasn.go | 15 ++++++++++++--- rules/parser.go | 12 ++++++++---- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/constant/rule.go b/constant/rule.go index fcefaba6ca..8fdd75a6c0 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -8,8 +8,10 @@ const ( DomainRegex GEOSITE GEOIP - IPCIDR + SrcGEOIP IPASN + SrcIPASN + IPCIDR SrcIPCIDR IPSuffix SrcIPSuffix @@ -48,10 +50,14 @@ func (rt RuleType) String() string { return "GeoSite" case GEOIP: return "GeoIP" - case IPCIDR: - return "IPCIDR" + case SrcGEOIP: + return "SrcGeoIP" case IPASN: return "IPASN" + case SrcIPASN: + return "SrcIPASN" + case IPCIDR: + return "IPCIDR" case SrcIPCIDR: return "SrcIPCIDR" case IPSuffix: diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 223a79042d..b50680a47a 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -17,6 +17,7 @@ type GEOIP struct { country string adapter string noResolveIP bool + isSourceIP bool geoIPMatcher *router.GeoIPMatcher recodeSize int } @@ -24,11 +25,17 @@ type GEOIP struct { var _ C.Rule = (*GEOIP)(nil) func (g *GEOIP) RuleType() C.RuleType { + if g.isSourceIP { + return C.SrcGEOIP + } return C.GEOIP } func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP + if g.isSourceIP { + ip = metadata.SrcIP + } if !ip.IsValid() { return false, "" } @@ -49,6 +56,16 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { } if !C.GeodataMode { + if g.isSourceIP { + codes := mmdb.IPInstance().LookupCode(ip.AsSlice()) + for _, code := range codes { + if g.country == code { + return true, g.adapter + } + } + return false, g.adapter + } + if metadata.DstGeoIP != nil { return false, g.adapter } @@ -62,7 +79,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { } match := g.geoIPMatcher.Match(ip) - if match { + if match && !g.isSourceIP { metadata.DstGeoIP = append(metadata.DstGeoIP, g.country) } return match, g.adapter @@ -92,7 +109,7 @@ func (g *GEOIP) GetRecodeSize() int { return g.recodeSize } -func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { +func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) { if err := geodata.InitGeoIP(); err != nil { log.Errorln("can't initial GeoIP: %s", err) return nil, err @@ -105,6 +122,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) country: country, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, } return geoip, nil } @@ -120,6 +138,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) country: country, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, geoIPMatcher: geoIPMatcher, recodeSize: size, } diff --git a/rules/common/ipasn.go b/rules/common/ipasn.go index 1fce8af482..df4b6531c6 100644 --- a/rules/common/ipasn.go +++ b/rules/common/ipasn.go @@ -14,24 +14,32 @@ type ASN struct { asn string adapter string noResolveIP bool + isSourceIP bool } func (a *ASN) Match(metadata *C.Metadata) (bool, string) { ip := metadata.DstIP + if a.isSourceIP { + ip = metadata.SrcIP + } if !ip.IsValid() { return false, "" } result := mmdb.ASNInstance().LookupASN(ip.AsSlice()) - asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10) - metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization + if !a.isSourceIP { + metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization + } match := a.asn == asnNumber return match, a.adapter } func (a *ASN) RuleType() C.RuleType { + if a.isSourceIP { + return C.SrcIPASN + } return C.IPASN } @@ -51,7 +59,7 @@ func (a *ASN) GetASN() string { return a.asn } -func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) { +func NewIPASN(asn string, adapter string, isSrc, noResolveIP bool) (*ASN, error) { C.ASNEnable = true if err := geodata.InitASN(); err != nil { log.Errorln("can't initial ASN: %s", err) @@ -63,5 +71,6 @@ func NewIPASN(asn string, adapter string, noResolveIP bool) (*ASN, error) { asn: asn, adapter: adapter, noResolveIP: noResolveIP, + isSourceIP: isSrc, }, nil } diff --git a/rules/parser.go b/rules/parser.go index b69cc4fd9c..032a02e4b0 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -23,13 +23,17 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string] parsed, parseErr = RC.NewGEOSITE(payload, target) case "GEOIP": noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) + parsed, parseErr = RC.NewGEOIP(payload, target, false, noResolve) + case "SRC-GEOIP": + parsed, parseErr = RC.NewGEOIP(payload, target, true, true) + case "IP-ASN": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPASN(payload, target, false, noResolve) + case "SRC-IP-ASN": + parsed, parseErr = RC.NewIPASN(payload, target, true, true) case "IP-CIDR", "IP-CIDR6": noResolve := RC.HasNoResolve(params) parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) - case "IP-ASN": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPASN(payload, target, noResolve) case "SRC-IP-CIDR": parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) case "IP-SUFFIX": From d48517b29d0ae20b7a6ccf0e82c5ccf08cb51ef7 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 2 Apr 2024 23:00:48 +0800 Subject: [PATCH 3/4] fix: timer usage for monitor check update --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c70aca9784..78f50d02be 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01 github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd + github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85 github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 diff --git a/go.sum b/go.sum index 7422a0627a..12c207e427 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwV github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd h1:NgLb6Lvr8ZxX0inWswVYjal2SUzsJJ54dFQNOluUJuE= -github.com/metacubex/sing-tun v0.2.1-0.20240320004934-5d2b35447bfd/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= +github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85 h1:r7XXIvooixabmv2Ry95I1Xv3T0c+9VWtes9LhkXGg34= +github.com/metacubex/sing-tun v0.2.1-0.20240402145739-0223b8bb1c85/go.mod h1:GfLZG/QgGpW9+BPjltzONrL5vVms86TWqmZ23J68ISc= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= From 40f5c5b98756122b1329cee7d25df23571170db0 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Wed, 3 Apr 2024 08:42:15 +0800 Subject: [PATCH 4/4] chore: don't retry when rejected loopback connection --- adapter/outbound/direct.go | 18 ++++----- .../loopback/detector.go | 38 ++++++++++++------- tunnel/tunnel.go | 4 ++ 3 files changed, 37 insertions(+), 23 deletions(-) rename adapter/outbound/direct_loopback_detect.go => component/loopback/detector.go (58%) diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 7def7b208f..1b01a576c4 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,18 +3,18 @@ package outbound import ( "context" "errors" - "fmt" "net/netip" 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" ) type Direct struct { *Base - loopBack *loopBackDetector + loopBack *loopback.Detector } type DirectOption struct { @@ -24,8 +24,8 @@ type DirectOption struct { // DialContext implements C.ProxyAdapter func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - if d.loopBack.CheckConn(metadata.SourceAddrPort()) { - return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + 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 +38,8 @@ 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 d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) { - return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + 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() { @@ -68,7 +68,7 @@ func NewDirectWithOption(option DirectOption) *Direct { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - loopBack: newLoopBackDetector(), + loopBack: loopback.NewDetector(), } } @@ -80,7 +80,7 @@ func NewDirect() *Direct { udp: true, prefer: C.DualStack, }, - loopBack: newLoopBackDetector(), + loopBack: loopback.NewDetector(), } } @@ -92,6 +92,6 @@ func NewCompatible() *Direct { udp: true, prefer: C.DualStack, }, - loopBack: newLoopBackDetector(), + loopBack: loopback.NewDetector(), } } diff --git a/adapter/outbound/direct_loopback_detect.go b/component/loopback/detector.go similarity index 58% rename from adapter/outbound/direct_loopback_detect.go rename to component/loopback/detector.go index 410d5a2fc8..b07270ed0a 100644 --- a/adapter/outbound/direct_loopback_detect.go +++ b/component/loopback/detector.go @@ -1,6 +1,8 @@ -package outbound +package loopback import ( + "errors" + "fmt" "net/netip" "github.com/metacubex/mihomo/common/callback" @@ -9,19 +11,21 @@ import ( "github.com/puzpuzpuz/xsync/v3" ) -type loopBackDetector struct { +var ErrReject = errors.New("reject loopback connection") + +type Detector struct { connMap *xsync.MapOf[netip.AddrPort, struct{}] packetConnMap *xsync.MapOf[netip.AddrPort, struct{}] } -func newLoopBackDetector() *loopBackDetector { - return &loopBackDetector{ +func NewDetector() *Detector { + return &Detector{ connMap: xsync.NewMapOf[netip.AddrPort, struct{}](), packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](), } } -func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn { +func (l *Detector) NewConn(conn C.Conn) C.Conn { metadata := C.Metadata{} if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { return conn @@ -36,7 +40,7 @@ func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn { }) } -func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn { +func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn { metadata := C.Metadata{} if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { return conn @@ -51,18 +55,24 @@ func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn { }) } -func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool { +func (l *Detector) CheckConn(metadata *C.Metadata) error { + connAddr := metadata.SourceAddrPort() if !connAddr.IsValid() { - return false + return nil + } + if _, ok := l.connMap.Load(connAddr); ok { + return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress()) } - _, ok := l.connMap.Load(connAddr) - return ok + return nil } -func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool { +func (l *Detector) CheckPacketConn(metadata *C.Metadata) error { + connAddr := metadata.SourceAddrPort() if !connAddr.IsValid() { - return false + return nil + } + if _, ok := l.packetConnMap.Load(connAddr); ok { + return fmt.Errorf("%w to: %s", ErrReject, metadata.RemoteAddress()) } - _, ok := l.packetConnMap.Load(connAddr) - return ok + return nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d5a226e961..608ab2c5bd 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -12,6 +12,7 @@ import ( "time" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/nat" P "github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/resolver" @@ -694,6 +695,9 @@ func shouldStopRetry(err error) bool { if errors.Is(err, resolver.ErrIPv6Disabled) { return true } + if errors.Is(err, loopback.ErrReject) { + return true + } return false }