diff --git a/bypass/bypass.go b/bypass/bypass.go index 05dd559b..de2e5560 100644 --- a/bypass/bypass.go +++ b/bypass/bypass.go @@ -15,6 +15,7 @@ import ( "github.com/go-gost/core/logger" "github.com/go-gost/x/internal/loader" "github.com/go-gost/x/internal/matcher" + xnet "github.com/go-gost/x/internal/net" ) var ( @@ -79,6 +80,7 @@ type localBypass struct { cidrMatcher matcher.Matcher addrMatcher matcher.Matcher wildcardMatcher matcher.Matcher + ipRangeMatcher matcher.Matcher cancelFunc context.CancelFunc options options mu sync.RWMutex @@ -141,15 +143,24 @@ func (bp *localBypass) reload(ctx context.Context) error { var addrs []string var inets []*net.IPNet var wildcards []string + var ipRanges []xnet.IPRange for _, pattern := range patterns { if _, inet, err := net.ParseCIDR(pattern); err == nil { inets = append(inets, inet) continue } + if strings.ContainsAny(pattern, "*?") { wildcards = append(wildcards, pattern) continue } + + r := xnet.IPRange{} + if err := r.Parse(pattern); err == nil { + ipRanges = append(ipRanges, r) + continue + } + addrs = append(addrs, pattern) } @@ -159,6 +170,7 @@ func (bp *localBypass) reload(ctx context.Context) error { bp.cidrMatcher = matcher.CIDRMatcher(inets) bp.addrMatcher = matcher.AddrMatcher(addrs) bp.wildcardMatcher = matcher.WildcardMatcher(wildcards) + bp.ipRangeMatcher = matcher.IPRangeMatcher(ipRanges) return nil } @@ -263,6 +275,10 @@ func (bp *localBypass) matched(addr string) bool { bp.mu.RLock() defer bp.mu.RUnlock() + if bp.ipRangeMatcher.Match(addr) { + return true + } + if bp.addrMatcher.Match(addr) { return true } diff --git a/internal/matcher/matcher.go b/internal/matcher/matcher.go index 8a2b3c03..33e77aa7 100644 --- a/internal/matcher/matcher.go +++ b/internal/matcher/matcher.go @@ -2,6 +2,7 @@ package matcher import ( "net" + "net/netip" "strconv" "strings" @@ -177,6 +178,7 @@ type wildcardMatcherPattern struct { glob glob.Glob pr *xnet.PortRange } + type wildcardMatcher struct { patterns []wildcardMatcherPattern } @@ -224,3 +226,36 @@ func (m *wildcardMatcher) Match(addr string) bool { return false } + +type ipRangeMatcher struct { + ranges []xnet.IPRange +} + +func IPRangeMatcher(ranges []xnet.IPRange) Matcher { + matcher := &ipRangeMatcher{ + ranges: ranges, + } + return matcher +} + +func (m *ipRangeMatcher) Match(addr string) bool { + if m == nil || len(m.ranges) == 0 { + return false + } + + host, _, _ := net.SplitHostPort(addr) + if host == "" { + host = addr + } + adr, err := netip.ParseAddr(host) + if err != nil { + return false + } + + for _, ra := range m.ranges { + if ra.Contains(adr) { + return true + } + } + return false +} diff --git a/internal/net/addr.go b/internal/net/addr.go index db84ca4e..fd19be34 100644 --- a/internal/net/addr.go +++ b/internal/net/addr.go @@ -3,6 +3,7 @@ package net import ( "fmt" "net" + "net/netip" "strconv" "strings" ) @@ -146,7 +147,7 @@ func (pr *PortRange) Parse(s string) error { return nil default: - return fmt.Errorf("invalid range: %s", s) + return fmt.Errorf("invalid port range: %s", s) } } @@ -154,6 +155,45 @@ func (pr *PortRange) Contains(port int) bool { return port >= pr.Min && port <= pr.Max } +type IPRange struct { + Min netip.Addr + Max netip.Addr +} + +func (r *IPRange) Parse(s string) error { + minmax := strings.Split(s, "-") + switch len(minmax) { + case 1: + addr, err := netip.ParseAddr(strings.TrimSpace(s)) + if err != nil { + return err + } + + r.Min, r.Max = addr, addr + return nil + + case 2: + min, err := netip.ParseAddr(strings.TrimSpace(minmax[0])) + if err != nil { + return err + } + max, err := netip.ParseAddr(strings.TrimSpace(minmax[1])) + if err != nil { + return err + } + + r.Min, r.Max = min, max + return nil + + default: + return fmt.Errorf("invalid ip range: %s", s) + } +} + +func (r *IPRange) Contains(addr netip.Addr) bool { + return !(addr.Less(r.Min) || r.Max.Less(addr)) +} + type ClientAddr interface { ClientAddr() net.Addr }