diff --git a/internal/waf/rule.go b/internal/waf/rule.go index 8d1491d..46a71a8 100644 --- a/internal/waf/rule.go +++ b/internal/waf/rule.go @@ -12,6 +12,7 @@ import ( "github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints" "github.com/TeaOSLab/EdgeNode/internal/waf/requests" "github.com/TeaOSLab/EdgeNode/internal/waf/utils" + "github.com/TeaOSLab/EdgeNode/internal/waf/values" "github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/types" @@ -47,6 +48,10 @@ type Rule struct { isIP bool ipValue net.IP + ipRangeListValue *values.IPRangeList + numberListValue *values.NumberList + stringListValue *values.StringList + floatValue float64 reg *re.Regexp } @@ -104,32 +109,7 @@ func (this *Rule) Init() error { return errors.New("value should be a valid ip") } case RuleOperatorIPRange, RuleOperatorNotIPRange: - if strings.Contains(this.Value, ",") { - ipList := strings.SplitN(this.Value, ",", 2) - ipString1 := strings.TrimSpace(ipList[0]) - ipString2 := strings.TrimSpace(ipList[1]) - - if len(ipString1) > 0 { - ip1 := net.ParseIP(ipString1) - if ip1 == nil { - return errors.New("start ip is invalid") - } - } - - if len(ipString2) > 0 { - ip2 := net.ParseIP(ipString2) - if ip2 == nil { - return errors.New("end ip is invalid") - } - } - } else if strings.Contains(this.Value, "/") { - _, _, err := net.ParseCIDR(this.Value) - if err != nil { - return err - } - } else { - return errors.New("invalid ip range") - } + this.ipRangeListValue = values.ParseIPRangeList(this.Value) } if singleParamRegexp.MatchString(this.Param) { @@ -643,50 +623,11 @@ func (this *Rule) unescape(v string) string { return v } -func (this *Rule) containsIP(value interface{}) bool { - ip := net.ParseIP(types.String(value)) - if ip == nil { - return false - } - - // 检查IP范围格式 - if strings.Contains(this.Value, ",") { - ipList := strings.SplitN(this.Value, ",", 2) - ipString1 := strings.TrimSpace(ipList[0]) - ipString2 := strings.TrimSpace(ipList[1]) - - if len(ipString1) > 0 { - ip1 := net.ParseIP(ipString1) - if ip1 == nil { - return false - } - - if bytes.Compare(ip, ip1) < 0 { - return false - } - } - - if len(ipString2) > 0 { - ip2 := net.ParseIP(ipString2) - if ip2 == nil { - return false - } - - if bytes.Compare(ip, ip2) > 0 { - return false - } - } - - return true - } else if strings.Contains(this.Value, "/") { - _, ipNet, err := net.ParseCIDR(this.Value) - if err != nil { - return false - } - return ipNet.Contains(ip) - } else { +func (this *Rule) containsIP(value any) bool { + if this.ipRangeListValue == nil { return false } + return this.ipRangeListValue.Contains(types.String(value)) } func (this *Rule) ipToInt64(ip net.IP) int64 { diff --git a/internal/waf/values/ip_list.go b/internal/waf/values/ip_list.go new file mode 100644 index 0000000..8d717bc --- /dev/null +++ b/internal/waf/values/ip_list.go @@ -0,0 +1,7 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values + +func ParseIPList(v string) *StringList { + return ParseStringList(v) +} diff --git a/internal/waf/values/ip_list_test.go b/internal/waf/values/ip_list_test.go new file mode 100644 index 0000000..1937fb9 --- /dev/null +++ b/internal/waf/values/ip_list_test.go @@ -0,0 +1,26 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values_test + +import ( + "github.com/TeaOSLab/EdgeNode/internal/waf/values" + "github.com/iwind/TeaGo/assert" + "testing" +) + +func TestParseIPList(t *testing.T) { + var a = assert.NewAssertion(t) + + { + var list = values.ParseIPList("") + a.IsFalse(list.Contains("192.168.1.100")) + } + + { + var list = values.ParseIPList(` +192.168.1.1 +192.168.1.101`) + a.IsFalse(list.Contains("192.168.1.100")) + a.IsTrue(list.Contains("192.168.1.101")) + } +} diff --git a/internal/waf/values/ip_range.go b/internal/waf/values/ip_range.go new file mode 100644 index 0000000..ddd4c97 --- /dev/null +++ b/internal/waf/values/ip_range.go @@ -0,0 +1,132 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values + +import ( + "bytes" + "net" + "strings" +) + +type IPRangeType = string + +const ( + IPRangeTypeCIDR IPRangeType = "cidr" // CIDR + IPRangeTypeSingeIP IPRangeType = "singleIP" // 单个IP + IPRangeTypeRange IPRangeType = "range" // IP范围,IP1-IP2 +) + +type IPRange struct { + Type IPRangeType + CIDR *net.IPNet + IPFrom net.IP + IPTo net.IP +} + +func (this *IPRange) Contains(netIP net.IP) bool { + if netIP == nil { + return false + } + switch this.Type { + case IPRangeTypeCIDR: + if this.CIDR != nil { + return this.CIDR.Contains(netIP) + } + case IPRangeTypeSingeIP: + if this.IPFrom != nil { + return bytes.Equal(this.IPFrom, netIP) + } + case IPRangeTypeRange: + return bytes.Compare(this.IPFrom, netIP) <= 0 && bytes.Compare(this.IPTo, netIP) >= 0 + } + return false +} + +type IPRangeList struct { + Ranges []*IPRange +} + +func NewIPRangeList() *IPRangeList { + return &IPRangeList{} +} + +func ParseIPRangeList(value string) *IPRangeList { + var list = NewIPRangeList() + + if len(value) == 0 { + return list + } + + var lines = strings.Split(value, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + + if strings.Contains(line, ",") { // IPFrom,IPTo + var pieces = strings.SplitN(line, ",", 2) + if len(pieces) == 2 { + var ipFrom = net.ParseIP(strings.TrimSpace(pieces[0])) + var ipTo = net.ParseIP(strings.TrimSpace(pieces[1])) + if ipFrom != nil && ipTo != nil { + if bytes.Compare(ipFrom, ipTo) > 0 { + ipFrom, ipTo = ipTo, ipFrom + } + list.Ranges = append(list.Ranges, &IPRange{ + Type: IPRangeTypeRange, + IPFrom: ipFrom, + IPTo: ipTo, + }) + } + } + } else if strings.Contains(line, "-") { // IPFrom-IPTo + var pieces = strings.SplitN(line, "-", 2) + if len(pieces) == 2 { + var ipFrom = net.ParseIP(strings.TrimSpace(pieces[0])) + var ipTo = net.ParseIP(strings.TrimSpace(pieces[1])) + if ipFrom != nil && ipTo != nil { + if bytes.Compare(ipFrom, ipTo) > 0 { + ipFrom, ipTo = ipTo, ipFrom + } + list.Ranges = append(list.Ranges, &IPRange{ + Type: IPRangeTypeRange, + IPFrom: ipFrom, + IPTo: ipTo, + }) + } + } + } else if strings.Contains(line, "/") { // CIDR + _, cidr, _ := net.ParseCIDR(line) + if cidr != nil { + list.Ranges = append(list.Ranges, &IPRange{ + Type: IPRangeTypeCIDR, + CIDR: cidr, + }) + } + } else { // single ip + var netIP = net.ParseIP(line) + if netIP != nil { + list.Ranges = append(list.Ranges, &IPRange{ + Type: IPRangeTypeSingeIP, + IPFrom: netIP, + }) + } + } + } + + return list +} + +func (this *IPRangeList) Contains(ip string) bool { + var netIP = net.ParseIP(ip) + if netIP == nil { + return false + } + for _, r := range this.Ranges { + if r.Contains(netIP) { + return true + } + } + return false +} diff --git a/internal/waf/values/ip_range_test.go b/internal/waf/values/ip_range_test.go new file mode 100644 index 0000000..a776a72 --- /dev/null +++ b/internal/waf/values/ip_range_test.go @@ -0,0 +1,54 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values_test + +import ( + "github.com/TeaOSLab/EdgeNode/internal/waf/values" + "github.com/iwind/TeaGo/assert" + "github.com/iwind/TeaGo/logs" + "testing" +) + +func TestIPRange_ParseIPRangeList(t *testing.T) { + { + var r = values.ParseIPRangeList("") + logs.PrintAsJSON(r, t) + } + { + var r = values.ParseIPRangeList("192.168.2.1") + logs.PrintAsJSON(r, t) + } + { + var r = values.ParseIPRangeList(`192.168.2.1 +192.168.1.100/24 +192.168.1.1-192.168.2.100 +192.168.1.2,192.168.2.200 +192.168.2.200 - 192.168.2.100 +# 以下是错误的 +192.168 +192.168.100.1-1`) + logs.PrintAsJSON(r, t) + } +} + +func TestIPRange_Contains(t *testing.T) { + { + var a = assert.NewAssertion(t) + var r = values.ParseIPRangeList(`192.168.2.1 +192.168.1.100/24 +192.168.1.1-192.168.2.100 +192.168.2.2,192.168.2.200 +192.168.3.200 - 192.168.3.100 +192.168.4.100 +192.168.5.1/26`) + a.IsTrue(r.Contains("192.168.1.102")) + a.IsTrue(r.Contains("192.168.2.101")) + a.IsTrue(r.Contains("192.168.1.1")) + a.IsTrue(r.Contains("192.168.2.100")) + a.IsFalse(r.Contains("192.168.2.201")) + a.IsTrue(r.Contains("192.168.3.101")) + a.IsTrue(r.Contains("192.168.4.100")) + a.IsTrue(r.Contains("192.168.5.63")) + a.IsFalse(r.Contains("192.168.5.128")) + } +} diff --git a/internal/waf/values/number_list.go b/internal/waf/values/number_list.go new file mode 100644 index 0000000..7be9e5b --- /dev/null +++ b/internal/waf/values/number_list.go @@ -0,0 +1,48 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values + +import ( + "github.com/TeaOSLab/EdgeNode/internal/zero" + "github.com/iwind/TeaGo/types" + "strings" +) + +type NumberList struct { + ValueMap map[float64]zero.Zero +} + +func NewNumberList() *NumberList { + return &NumberList{ + ValueMap: map[float64]zero.Zero{}, + } +} + +func ParseNumberList(v string) *NumberList { + var list = NewNumberList() + if len(v) == 0 { + return list + } + + var lines = strings.Split(v, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + + var values = strings.Split(line, ",") + for _, value := range values { + value = strings.TrimSpace(value) + if len(value) > 0 { + list.ValueMap[types.Float64(value)] = zero.Zero{} + } + } + } + return list +} + +func (this *NumberList) Contains(f float64) bool { + _, ok := this.ValueMap[f] + return ok +} diff --git a/internal/waf/values/number_list_test.go b/internal/waf/values/number_list_test.go new file mode 100644 index 0000000..736c3c8 --- /dev/null +++ b/internal/waf/values/number_list_test.go @@ -0,0 +1,29 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values_test + +import ( + "github.com/TeaOSLab/EdgeNode/internal/waf/values" + "github.com/iwind/TeaGo/assert" + "testing" +) + +func TestParseNumberList(t *testing.T) { + var a = assert.NewAssertion(t) + + { + var list = values.ParseNumberList("") + a.IsFalse(list.Contains(123)) + } + + { + var list = values.ParseNumberList(`123 +456 + +789.1234`) + a.IsTrue(list.Contains(123)) + a.IsFalse(list.Contains(0)) + a.IsFalse(list.Contains(789.123)) + a.IsTrue(list.Contains(789.1234)) + } +} diff --git a/internal/waf/values/string_list.go b/internal/waf/values/string_list.go new file mode 100644 index 0000000..76c2e68 --- /dev/null +++ b/internal/waf/values/string_list.go @@ -0,0 +1,47 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values + +import ( + "github.com/TeaOSLab/EdgeNode/internal/zero" + "strings" +) + +type StringList struct { + ValueMap map[string]zero.Zero +} + +func NewStringList() *StringList { + return &StringList{ + ValueMap: map[string]zero.Zero{}, + } +} + +func ParseStringList(v string) *StringList { + var list = NewStringList() + if len(v) == 0 { + return list + } + + var lines = strings.Split(v, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + + var values = strings.Split(line, ",") + for _, value := range values { + value = strings.TrimSpace(value) + if len(value) > 0 { + list.ValueMap[value] = zero.Zero{} + } + } + } + return list +} + +func (this *StringList) Contains(f string) bool { + _, ok := this.ValueMap[f] + return ok +} diff --git a/internal/waf/values/string_list_test.go b/internal/waf/values/string_list_test.go new file mode 100644 index 0000000..225c5be --- /dev/null +++ b/internal/waf/values/string_list_test.go @@ -0,0 +1,30 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package values_test + +import ( + "github.com/TeaOSLab/EdgeNode/internal/waf/values" + "github.com/iwind/TeaGo/assert" + "testing" +) + +func TestParseStringList(t *testing.T) { + var a = assert.NewAssertion(t) + + { + var list = values.ParseStringList("") + a.IsFalse(list.Contains("hello")) + } + + { + var list = values.ParseStringList(`hello + +world +hi + +people`) + a.IsTrue(list.Contains("hello")) + a.IsFalse(list.Contains("hello1")) + a.IsTrue(list.Contains("hi")) + } +}