diff --git a/constant/rule.go b/constant/rule.go index 7c7eea965d..2cc60cdd0d 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -9,6 +9,8 @@ const ( GEOIP IPCIDR SrcIPCIDR + IPSuffix + SrcIPSuffix SrcPort DstPort Process @@ -41,6 +43,10 @@ func (rt RuleType) String() string { return "IPCIDR" case SrcIPCIDR: return "SrcIPCIDR" + case IPSuffix: + return "IPSuffix" + case SrcIPSuffix: + return "SrcIPSuffix" case SrcPort: return "SrcPort" case DstPort: diff --git a/rule/common/ipsuffix.go b/rule/common/ipsuffix.go new file mode 100644 index 0000000000..182712448a --- /dev/null +++ b/rule/common/ipsuffix.go @@ -0,0 +1,79 @@ +package common + +import ( + C "github.com/Dreamacro/clash/constant" + "net/netip" +) + +type IPSuffix struct { + *Base + ipBytes []byte + bits int + payload string + adapter string + isSourceIP bool + noResolveIP bool +} + +func (is *IPSuffix) RuleType() C.RuleType { + if is.isSourceIP { + return C.SrcIPSuffix + } + return C.IPSuffix +} + +func (is *IPSuffix) Match(metadata *C.Metadata) bool { + ip := metadata.DstIP + if is.isSourceIP { + ip = metadata.SrcIP + } + + mIPBytes := ip.AsSlice() + if len(is.ipBytes) != len(mIPBytes) { + return false + } + + size := len(mIPBytes) + bits := is.bits + + for i := bits / 8; i > 0; i-- { + if is.ipBytes[size-i] != mIPBytes[size-i] { + return false + } + } + + if (is.ipBytes[size-bits/8-1] << (8 - bits%8)) != (mIPBytes[size-bits/8-1] << (8 - bits%8)) { + return false + } + + return true +} + +func (is *IPSuffix) Adapter() string { + return is.adapter +} + +func (is *IPSuffix) Payload() string { + return is.payload +} + +func (is *IPSuffix) ShouldResolveIP() bool { + return !is.noResolveIP +} + +func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) { + ipnet, err := netip.ParsePrefix(payload) + if err != nil { + return nil, errPayload + } + + return &IPSuffix{ + Base: &Base{}, + payload: payload, + ipBytes: ipnet.Addr().AsSlice(), + bits: ipnet.Bits(), + adapter: adapter, + isSourceIP: isSrc, + noResolveIP: noResolveIP, + }, nil +} diff --git a/rule/logic/common.go b/rule/logic/common.go index 66aa1d371b..c15591e3fa 100644 --- a/rule/logic/common.go +++ b/rule/logic/common.go @@ -83,6 +83,11 @@ func parseRule(tp, payload string, params []string) (C.Rule, error) { parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, "", false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, "", true, true) case "SRC-PORT": parsed, parseErr = RC.NewPort(payload, "", true) case "DST-PORT": diff --git a/rule/parser.go b/rule/parser.go index 660f1575fe..42855f1bb5 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -31,6 +31,11 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) case "SRC-PORT": parsed, parseErr = RC.NewPort(payload, target, true) case "DST-PORT": diff --git a/rule/provider/parse.go b/rule/provider/parse.go index ae53203b7d..bcf0d0e4b5 100644 --- a/rule/provider/parse.go +++ b/rule/provider/parse.go @@ -74,6 +74,11 @@ func parseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) case "SRC-IP-CIDR": parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) case "SRC-PORT": parsed, parseErr = RC.NewPort(payload, target, true) case "DST-PORT":