diff --git a/pkg/configutils/ip.go b/pkg/configutils/ip.go index de9f469..7037bd1 100644 --- a/pkg/configutils/ip.go +++ b/pkg/configutils/ip.go @@ -2,27 +2,71 @@ package configutils import ( "encoding/binary" - "github.com/cespare/xxhash/v2" - "math" + "math/big" "net" "strings" ) -// IP2Long 将IP转换为整型 +// IPString2Long 将IP转换为整型 // 注意IPv6没有顺序 -func IP2Long(ip string) uint64 { +func IPString2Long(ip string) uint64 { if len(ip) == 0 { return 0 } - s := net.ParseIP(ip) - if len(s) == 0 { + var netIP = net.ParseIP(ip) + if len(netIP) == 0 { + return 0 + } + return IP2Long(netIP) +} + +// IP2Long 将IP对象转换为整型 +func IP2Long(netIP net.IP) uint64 { + if len(netIP) == 0 { return 0 } - if strings.Contains(ip, ":") { - return math.MaxUint32 + xxhash.Sum64(s) + var b4 = netIP.To4() + if b4 != nil { + return uint64(binary.BigEndian.Uint32(b4.To4())) } - return uint64(binary.BigEndian.Uint32(s.To4())) + + var i = big.NewInt(0) + i.SetBytes(netIP.To16()) + return i.Uint64() +} + +// IsIPv4 检查是否为IPv4 +func IsIPv4(netIP net.IP) bool { + if len(netIP) == 0 { + return false + } + return netIP.To4() != nil +} + +// IsIPv6 检查是否为IPv6 +func IsIPv6(netIP net.IP) bool { + if len(netIP) == 0 { + return false + } + return netIP.To4() == nil && netIP.To16() != nil +} + +// IPVersion 获取IP版本号 +func IPVersion(netIP net.IP) int { + if len(netIP) == 0 { + return 0 + } + + if netIP.To4() != nil { + return 4 + } + + if netIP.To16() != nil { + return 6 + } + + return 0 } // ParseCIDR 计算CIDR最大值 diff --git a/pkg/configutils/ip_test.go b/pkg/configutils/ip_test.go index c53c082..d5ff020 100644 --- a/pkg/configutils/ip_test.go +++ b/pkg/configutils/ip_test.go @@ -1,11 +1,41 @@ // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. -package configutils +package configutils_test -import "testing" +import ( + "fmt" + "github.com/TeaOSLab/EdgeCommon/pkg/configutils" + "github.com/iwind/TeaGo/assert" + "net" + "testing" +) func TestParseCIDR(t *testing.T) { - t.Log(ParseCIDR("192.168.1.1/32")) - t.Log(ParseCIDR("192.168.1.1/24")) - t.Log(ParseCIDR("192.168.1.1/16")) + t.Log(configutils.ParseCIDR("192.168.1.1/32")) + t.Log(configutils.ParseCIDR("192.168.1.1/24")) + t.Log(configutils.ParseCIDR("192.168.1.1/16")) +} + +func TestIPString2Long(t *testing.T) { + for _, ip := range []string{"127.0.0.1", "192.168.1.100", "::1", "fd00:6868:6868:0:10ac:d056:3bf6:7452", "fd00:6868:6868:0:10ac:d056:3bf6:7453", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "wrong ip"} { + t.Log(fmt.Sprintf("%42s", ip), "=>", configutils.IPString2Long(ip)) + } +} + +func TestIsIPv4(t *testing.T) { + t.Log(configutils.IsIPv4(net.ParseIP("192.168.1.100"))) + t.Log(configutils.IsIPv4(net.ParseIP("::1"))) +} + +func TestIsIPv6(t *testing.T) { + t.Log(configutils.IsIPv6(net.ParseIP("192.168.1.100"))) + t.Log(configutils.IsIPv6(net.ParseIP("::1"))) +} + +func TestIPVersion(t *testing.T) { + var a = assert.NewAssertion(t) + a.IsTrue(configutils.IPVersion(net.ParseIP("192.168.1.100")) == 4) + a.IsTrue(configutils.IPVersion(net.ParseIP("1.2.3")) == 0) + a.IsTrue(configutils.IPVersion(net.ParseIP("::1")) == 6) + a.IsTrue(configutils.IPVersion(net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) == 6) } diff --git a/pkg/dnsconfigs/route_ranges.go b/pkg/dnsconfigs/route_ranges.go index f4f007a..a63b2eb 100644 --- a/pkg/dnsconfigs/route_ranges.go +++ b/pkg/dnsconfigs/route_ranges.go @@ -2,31 +2,107 @@ package dnsconfigs -import "github.com/TeaOSLab/EdgeCommon/pkg/configutils" +import ( + "encoding/json" + "errors" + "github.com/TeaOSLab/EdgeCommon/pkg/configutils" + "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" + "github.com/iwind/TeaGo/maps" + "net" +) type RouteRangeType = string const ( - RouteRangeTypeIP RouteRangeType = "ipRange" + RouteRangeTypeIP RouteRangeType = "ipRange" // IP范围 + RouteRangeTypeCIDR RouteRangeType = "cidr" // CIDR + RouteRangeTypeRegion RouteRangeType = "region" // 区域 ) +func AllRouteRangeTypes() []*shared.Definition { + return []*shared.Definition{ + { + Name: "IP范围", + Code: RouteRangeTypeIP, + }, + { + Name: "CIDR", + Code: RouteRangeTypeCIDR, + }, + { + Name: "区域", + Code: RouteRangeTypeRegion, + }, + } +} + +// RouteRegionResolver 解析IP接口 +type RouteRegionResolver interface { + Resolve(ip net.IP) (countryId int64, provinceId int64, cityId int64, providerId int64) +} + +// RouteRangeInterface 线路范围接口 type RouteRangeInterface interface { + // Init 初始化 Init() error - Contains(ip uint64) bool + + // Contains 判断是否包含 + Contains(ip net.IP) bool + + // SetRegionResolver 设置IP解析接口 + SetRegionResolver(resolver RouteRegionResolver) + + // IsExcluding 是否为排除 + IsExcluding() bool +} + +type BaseRouteRange struct { + IsReverse bool `json:"isReverse"` + + routeRegionResolver RouteRegionResolver +} + +func (this *BaseRouteRange) SetRegionResolver(resolver RouteRegionResolver) { + this.routeRegionResolver = resolver +} + +func (this *BaseRouteRange) IsExcluding() bool { + return this.IsReverse } // RouteRangeIPRange IP范围配置 +// IPv4和IPv6不能混用 type RouteRangeIPRange struct { + BaseRouteRange + IPFrom string `json:"ipFrom"` IPTo string `json:"ipTo"` ipFromLong uint64 ipToLong uint64 + + ipVersion int // 4|6 } func (this *RouteRangeIPRange) Init() error { - this.ipFromLong = configutils.IP2Long(this.IPFrom) - this.ipToLong = configutils.IP2Long(this.IPTo) + var ipFrom = net.ParseIP(this.IPFrom) + var ipTo = net.ParseIP(this.IPTo) + if ipFrom == nil { + return errors.New("invalid ipFrom '" + this.IPFrom + "'") + } + if ipTo == nil { + return errors.New("invalid ipTo '" + this.IPTo + "'") + } + + var ipFromVersion = configutils.IPVersion(ipFrom) + var ipToVersion = configutils.IPVersion(ipTo) + if ipFromVersion != ipToVersion { + return errors.New("ipFrom and ipTo version are not same") + } + this.ipVersion = ipFromVersion + + this.ipFromLong = configutils.IP2Long(ipFrom) + this.ipToLong = configutils.IP2Long(ipTo) if this.ipFromLong > this.ipToLong { this.ipFromLong, this.ipToLong = this.ipToLong, this.ipFromLong @@ -35,6 +111,152 @@ func (this *RouteRangeIPRange) Init() error { return nil } -func (this *RouteRangeIPRange) Contains(ip uint64) bool { - return this.ipFromLong <= ip && this.ipToLong >= ip +func (this *RouteRangeIPRange) Contains(netIP net.IP) bool { + if len(netIP) == 0 { + return false + } + + var version = configutils.IPVersion(netIP) + if version != this.ipVersion { + return false + } + + var ipLong = configutils.IP2Long(netIP) + return ipLong >= this.ipFromLong && ipLong <= this.ipToLong +} + +// RouteRangeCIDR CIDR范围配置 +type RouteRangeCIDR struct { + BaseRouteRange + + CIDR string `json:"cidr"` + + cidr *net.IPNet +} + +func (this *RouteRangeCIDR) Init() error { + _, ipNet, err := net.ParseCIDR(this.CIDR) + if err != nil { + return errors.New("parse cidr failed: " + err.Error()) + } + + this.cidr = ipNet + + return nil +} + +func (this *RouteRangeCIDR) Contains(netIP net.IP) bool { + if netIP == nil { + return false + } + + if this.cidr == nil { + return false + } + + return this.cidr.Contains(netIP) +} + +// RouteRangeRegion 区域范围 +// country:ID, province:ID, city:ID, isp:ID +type RouteRangeRegion struct { + BaseRouteRange + + Regions []*routeRegion `json:"regions"` +} + +func (this *RouteRangeRegion) Init() error { + return nil +} + +func (this *RouteRangeRegion) Contains(netIP net.IP) bool { + if this.routeRegionResolver == nil { + return false + } + + if len(this.Regions) == 0 { + return false + } + + countryId, provinceId, cityId, providerId := this.routeRegionResolver.Resolve(netIP) + if countryId <= 0 && provinceId <= 0 && cityId <= 0 && providerId <= 0 { + return false + } + + for _, region := range this.Regions { + if region.Id <= 0 { + continue + } + + switch region.Type { + case "country": + if region.Id == countryId { + return true + } + case "province": + if region.Id == provinceId { + return true + } + case "city": + if region.Id == cityId { + return true + } + case "isp": + if region.Id == providerId { + return true + } + } + } + + return false +} + +type routeRegion struct { + Type string `json:"type"` // country|province|city|isp + Id int64 `json:"id"` + Name string `json:"name"` +} + +// InitRangesFromJSON 从JSON中初始化线路范围 +func InitRangesFromJSON(rangesJSON []byte) (ranges []RouteRangeInterface, err error) { + if len(rangesJSON) == 0 { + return + } + + var rangeMaps = []maps.Map{} + err = json.Unmarshal(rangesJSON, &rangeMaps) + if err != nil { + return nil, err + } + for _, rangeMap := range rangeMaps { + var rangeType = rangeMap.GetString("type") + paramsJSON, err := json.Marshal(rangeMap.Get("params")) + if err != nil { + return nil, err + } + + var r RouteRangeInterface + + switch rangeType { + case RouteRangeTypeIP: + r = &RouteRangeIPRange{} + case RouteRangeTypeCIDR: + r = &RouteRangeCIDR{} + case RouteRangeTypeRegion: + r = &RouteRangeRegion{} + default: + return nil, errors.New("invalid route line type '" + rangeType + "'") + } + + err = json.Unmarshal(paramsJSON, r) + if err != nil { + return nil, err + } + err = r.Init() + if err != nil { + return nil, err + } + ranges = append(ranges, r) + } + return } diff --git a/pkg/dnsconfigs/route_ranges_test.go b/pkg/dnsconfigs/route_ranges_test.go new file mode 100644 index 0000000..b15ba52 --- /dev/null +++ b/pkg/dnsconfigs/route_ranges_test.go @@ -0,0 +1,100 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package dnsconfigs + +import ( + "github.com/iwind/TeaGo/assert" + "net" + "testing" +) + +func TestRouteRangeIPRange_Contains(t *testing.T) { + var a = assert.NewAssertion(t) + + // ipv4 + { + var r = &RouteRangeIPRange{ + IPFrom: "192.168.1.100", + IPTo: "192.168.3.200", + } + err := r.Init() + if err != nil { + t.Fatal(err) + } + a.IsFalse(r.Contains(net.ParseIP("aaa"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.1.200"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.3.200"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.4.1"))) + a.IsFalse(r.Contains(net.ParseIP("::1"))) + } + + // ipv6 + { + var prefix = "1:2:3:4:5:6" + var r = &RouteRangeIPRange{ + IPFrom: prefix + ":1:8", + IPTo: prefix + ":5:10", + } + err := r.Init() + if err != nil { + t.Fatal(err) + } + a.IsFalse(r.Contains(net.ParseIP("aaa"))) + a.IsTrue(r.Contains(net.ParseIP(prefix + ":3:4"))) + a.IsTrue(r.Contains(net.ParseIP(prefix + ":5:9"))) + a.IsTrue(r.Contains(net.ParseIP(prefix + ":5:10"))) + a.IsTrue(r.Contains(net.ParseIP(prefix + ":4:8"))) + a.IsFalse(r.Contains(net.ParseIP(prefix + ":5:11"))) + } + + { + var r = &RouteRangeCIDR{ + CIDR: "192.168.2.1/24", + } + err := r.Init() + if err != nil { + t.Fatal(err) + } + a.IsFalse(r.Contains(net.ParseIP("aaa"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.2.1"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.2.254"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.2.100"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.3.1"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.1.1"))) + } + + // reverse ipv4 + { + var r = &RouteRangeIPRange{ + IPFrom: "192.168.1.100", + IPTo: "192.168.3.200", + IsReverse: true, + } + err := r.Init() + if err != nil { + t.Fatal(err) + } + a.IsFalse(r.Contains(net.ParseIP("aaa"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.1.200"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.3.200"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.4.1"))) + } + + // reverse cidr + { + var r = &RouteRangeCIDR{ + CIDR: "192.168.2.1/24", + IsReverse: true, + } + err := r.Init() + if err != nil { + t.Fatal(err) + } + a.IsFalse(r.Contains(net.ParseIP("aaa"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.2.1"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.2.254"))) + a.IsFalse(r.Contains(net.ParseIP("192.168.2.100"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.3.1"))) + a.IsTrue(r.Contains(net.ParseIP("192.168.1.1"))) + } +} diff --git a/pkg/dnsconfigs/routes.go b/pkg/dnsconfigs/routes.go index c2e69c3..fd7c649 100644 --- a/pkg/dnsconfigs/routes.go +++ b/pkg/dnsconfigs/routes.go @@ -120,12 +120,12 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "河北省", Code: "china:province:heibei", - AliasNames: []string{"河北省"}, + AliasNames: []string{"河北省", "河北"}, }, { Name: "山西省", Code: "china:province:shanxi", - AliasNames: []string{"山西省"}, + AliasNames: []string{"山西省", "山西"}, }, { Name: "内蒙古自治区", @@ -135,17 +135,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "辽宁省", Code: "china:province:liaoning", - AliasNames: []string{"辽宁省"}, + AliasNames: []string{"辽宁省", "辽宁"}, }, { Name: "吉林省", Code: "china:jilin", - AliasNames: []string{"吉林省"}, + AliasNames: []string{"吉林省", "吉林"}, }, { Name: "黑龙江省", Code: "china:province:heilongjiang", - AliasNames: []string{"黑龙江省"}, + AliasNames: []string{"黑龙江省", "黑龙江"}, }, { Name: "上海市", @@ -155,52 +155,52 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "江苏省", Code: "china:province:jiangsu", - AliasNames: []string{"江苏省"}, + AliasNames: []string{"江苏省", "江苏"}, }, { Name: "浙江省", Code: "china:province:zhejiang", - AliasNames: []string{"浙江省"}, + AliasNames: []string{"浙江省", "浙江"}, }, { Name: "安徽省", Code: "china:province:anhui", - AliasNames: []string{"安徽省"}, + AliasNames: []string{"安徽省", "安徽"}, }, { Name: "福建省", Code: "china:province:fujian", - AliasNames: []string{"福建省"}, + AliasNames: []string{"福建省", "福建"}, }, { Name: "江西省", Code: "china:province:jiangxi", - AliasNames: []string{"江西省"}, + AliasNames: []string{"江西省", "江西"}, }, { Name: "山东省", Code: "china:province:shandong", - AliasNames: []string{"山东省"}, + AliasNames: []string{"山东省", "山东"}, }, { Name: "河南省", Code: "china:province:henan", - AliasNames: []string{"河南省"}, + AliasNames: []string{"河南省", "河南"}, }, { Name: "湖北省", Code: "china:province:hubei", - AliasNames: []string{"湖北省"}, + AliasNames: []string{"湖北省", "湖北"}, }, { Name: "湖南省", Code: "china:province:hunan", - AliasNames: []string{"湖南省"}, + AliasNames: []string{"湖南省", "湖南"}, }, { Name: "广东省", Code: "china:province:guangdong", - AliasNames: []string{"广东省"}, + AliasNames: []string{"广东省", "广东"}, }, { Name: "广西壮族自治区", @@ -210,7 +210,7 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "海南省", Code: "china:province:hainan", - AliasNames: []string{"海南省"}, + AliasNames: []string{"海南省", "海南"}, }, { Name: "重庆市", @@ -220,17 +220,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "四川省", Code: "china:province:sichuan", - AliasNames: []string{"四川省"}, + AliasNames: []string{"四川省", "四川"}, }, { Name: "贵州省", Code: "china:province:guizhou", - AliasNames: []string{"贵州省"}, + AliasNames: []string{"贵州省", "贵州"}, }, { Name: "云南省", Code: "china:province:yunnan", - AliasNames: []string{"云南省"}, + AliasNames: []string{"云南省", "云南"}, }, { Name: "西藏自治区", @@ -240,17 +240,17 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "陕西省", Code: "china:province:shaanxi", - AliasNames: []string{"陕西省"}, + AliasNames: []string{"陕西省", "陕西"}, }, { Name: "甘肃省", Code: "china:province:gansu", - AliasNames: []string{"甘肃省"}, + AliasNames: []string{"甘肃省", "甘肃"}, }, { Name: "青海省", Code: "china:province:qinghai", - AliasNames: []string{"青海省"}, + AliasNames: []string{"青海省", "青海"}, }, { Name: "宁夏回族自治区", @@ -275,7 +275,7 @@ var AllDefaultChinaProvinceRoutes = []*Route{ { Name: "台湾省", Code: "china:province:tw", - AliasNames: []string{"台湾省"}, + AliasNames: []string{"台湾省", "台湾"}, }, }