diff --git a/pkg/configutils/ip.go b/pkg/configutils/ip.go index 7037bd1..3c68403 100644 --- a/pkg/configutils/ip.go +++ b/pkg/configutils/ip.go @@ -1,41 +1,10 @@ package configutils import ( - "encoding/binary" - "math/big" "net" "strings" ) -// IPString2Long 将IP转换为整型 -// 注意IPv6没有顺序 -func IPString2Long(ip string) uint64 { - if len(ip) == 0 { - return 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 - } - - var b4 = netIP.To4() - if b4 != nil { - return uint64(binary.BigEndian.Uint32(b4.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 { diff --git a/pkg/configutils/ip_test.go b/pkg/configutils/ip_test.go index aab0f26..983b1fc 100644 --- a/pkg/configutils/ip_test.go +++ b/pkg/configutils/ip_test.go @@ -3,7 +3,6 @@ package configutils_test import ( - "fmt" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/iwind/TeaGo/assert" "net" @@ -16,12 +15,6 @@ func TestParseCIDR(t *testing.T) { 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"))) @@ -40,7 +33,6 @@ func TestIPVersion(t *testing.T) { a.IsTrue(configutils.IPVersion(net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) == 6) } - func TestQuoteIP(t *testing.T) { t.Log(configutils.QuoteIP(configutils.QuoteIP("2001:da8:22::10"))) -} \ No newline at end of file +} diff --git a/pkg/iplibrary/.gitignore b/pkg/iplibrary/.gitignore index c657966..ce80e5b 100644 --- a/pkg/iplibrary/.gitignore +++ b/pkg/iplibrary/.gitignore @@ -1 +1,2 @@ -*-plus.db \ No newline at end of file +*-plus.db +internal-ip-library-test.db \ No newline at end of file diff --git a/pkg/iplibrary/default_ip_library.go b/pkg/iplibrary/default_ip_library.go index afbac15..b0de02d 100644 --- a/pkg/iplibrary/default_ip_library.go +++ b/pkg/iplibrary/default_ip_library.go @@ -33,7 +33,7 @@ func InitDefault() error { } var library = NewIPLibrary() - err := library.InitFromData(ipLibraryData, "") + err := library.InitFromData(ipLibraryData, "", ReaderVersionV1) if err != nil { return err } @@ -66,18 +66,18 @@ func LookupIPSummaries(ipList []string) map[string]string /** ip => summary **/ } type IPLibrary struct { - reader *Reader + reader ReaderInterface } func NewIPLibrary() *IPLibrary { return &IPLibrary{} } -func NewIPLibraryWithReader(reader *Reader) *IPLibrary { +func NewIPLibraryWithReader(reader ReaderInterface) *IPLibrary { return &IPLibrary{reader: reader} } -func (this *IPLibrary) InitFromData(data []byte, password string) error { +func (this *IPLibrary) InitFromData(data []byte, password string, version ReaderVersion) error { if len(data) == 0 || this.reader != nil { return nil } @@ -99,7 +99,12 @@ func (this *IPLibrary) InitFromData(data []byte, password string) error { _ = gzipReader.Close() }() - libReader, err := NewReader(gzipReader) + var libReader ReaderInterface + if version == ReaderVersionV2 { + libReader, err = NewReaderV2(gzipReader) + } else { + libReader, err = NewReaderV1(gzipReader) + } if err != nil { return err } diff --git a/pkg/iplibrary/default_ip_library_test.go b/pkg/iplibrary/default_ip_library_test.go index 99eee9b..a8c38c6 100644 --- a/pkg/iplibrary/default_ip_library_test.go +++ b/pkg/iplibrary/default_ip_library_test.go @@ -15,7 +15,7 @@ import ( func TestIPLibrary_Init(t *testing.T) { var lib = iplibrary.NewIPLibrary() - err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "") + err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1) if err != nil { t.Fatal(err) } @@ -38,7 +38,7 @@ func TestIPLibrary_Lookup(t *testing.T) { var before = time.Now() - err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "") + err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1) if err != nil { t.Fatal(err) } @@ -69,7 +69,7 @@ func TestIPLibrary_Lookup(t *testing.T) { func TestIPLibrary_LookupIP(t *testing.T) { var lib = iplibrary.NewIPLibrary() - err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "") + err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1) if err != nil { t.Fatal(err) } @@ -88,7 +88,7 @@ func TestIPLibrary_LookupIP(t *testing.T) { func TestIPLibrary_LookupIP_Summary(t *testing.T) { var lib = iplibrary.NewIPLibrary() - err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "") + err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1) if err != nil { t.Fatal(err) } @@ -123,7 +123,7 @@ func TestIPLibrary_LookupIPSummaries(t *testing.T) { func BenchmarkIPLibrary_Lookup(b *testing.B) { var lib = iplibrary.NewIPLibrary() - err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "") + err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1) if err != nil { b.Fatal(err) } diff --git a/pkg/iplibrary/ip_item.go b/pkg/iplibrary/ip_item.go index 4c9a8c5..70defa5 100644 --- a/pkg/iplibrary/ip_item.go +++ b/pkg/iplibrary/ip_item.go @@ -6,20 +6,34 @@ import ( "github.com/iwind/TeaGo/types" ) -type ipv4Item struct { +type ipv4ItemV1 struct { IPFrom uint32 IPTo uint32 Region *ipRegion } -type ipv6Item struct { +type ipv6ItemV1 struct { IPFrom uint64 IPTo uint64 Region *ipRegion } +type ipv4ItemV2 struct { + IPFrom [4]byte + IPTo [4]byte + + Region *ipRegion +} + +type ipv6ItemV2 struct { + IPFrom [16]byte + IPTo [16]byte + + Region *ipRegion +} + type ipRegion struct { CountryId uint16 ProvinceId uint16 diff --git a/pkg/iplibrary/reader.go b/pkg/iplibrary/reader.go index bb2c91a..53987b8 100644 --- a/pkg/iplibrary/reader.go +++ b/pkg/iplibrary/reader.go @@ -4,10 +4,12 @@ package iplibrary import ( "bytes" + "encoding/binary" "encoding/json" "errors" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" "io" + "math/big" "net" "runtime" "sort" @@ -21,8 +23,8 @@ type Reader struct { regionMap map[string]*ipRegion // 缓存重复的区域用来节约内存 - ipV4Items []ipv4Item - ipV6Items []ipv6Item + ipV4Items []ipv4ItemV1 + ipV6Items []ipv6ItemV1 lastIPFrom uint64 lastCountryId uint16 @@ -32,16 +34,16 @@ type Reader struct { lastProviderId uint16 } -// NewReader 创建新Reader对象 -func NewReader(reader io.Reader) (*Reader, error) { +// NewReaderV1 创建新Reader对象 +func NewReaderV1(reader io.Reader) (*Reader, error) { var libReader = &Reader{ regionMap: map[string]*ipRegion{}, } if runtime.NumCPU() >= 4 /** CPU数量较多的通常有着大内存 **/ { - libReader.ipV4Items = make([]ipv4Item, 0, 6_000_000) + libReader.ipV4Items = make([]ipv4ItemV1, 0, 6_000_000) } else { - libReader.ipV4Items = make([]ipv4Item, 0, 600_000) + libReader.ipV4Items = make([]ipv4ItemV1, 0, 600_000) } err := libReader.load(reader) @@ -131,7 +133,7 @@ func (this *Reader) Lookup(ip net.IP) *QueryResult { return &QueryResult{} } - var ipLong = configutils.IP2Long(ip) + var ipLong = this.ip2long(ip) var isV4 = configutils.IsIPv4(ip) var resultItem any if isV4 { @@ -170,11 +172,11 @@ func (this *Reader) Meta() *Meta { return this.meta } -func (this *Reader) IPv4Items() []ipv4Item { +func (this *Reader) IPv4Items() []ipv4ItemV1 { return this.ipV4Items } -func (this *Reader) IPv6Items() []ipv6Item { +func (this *Reader) IPv6Items() []ipv6ItemV1 { return this.ipV6Items } @@ -304,13 +306,13 @@ func (this *Reader) parseLine(line []byte) error { } if version == "4" { - this.ipV4Items = append(this.ipV4Items, ipv4Item{ + this.ipV4Items = append(this.ipV4Items, ipv4ItemV1{ IPFrom: uint32(ipFrom), IPTo: uint32(ipTo), Region: region, }) } else { - this.ipV6Items = append(this.ipV6Items, ipv6Item{ + this.ipV6Items = append(this.ipV6Items, ipv6ItemV1{ IPFrom: ipFrom, IPTo: ipTo, Region: region, @@ -328,3 +330,18 @@ func (this *Reader) decodeUint64(s string) uint64 { i, _ := strconv.ParseUint(s, 10, 64) return i } + +func (this *Reader) ip2long(netIP net.IP) uint64 { + if len(netIP) == 0 { + return 0 + } + + var b4 = netIP.To4() + if b4 != nil { + return uint64(binary.BigEndian.Uint32(b4.To4())) + } + + var i = big.NewInt(0) + i.SetBytes(netIP.To16()) + return i.Uint64() +} diff --git a/pkg/iplibrary/reader_file.go b/pkg/iplibrary/reader_file.go index 8c1da78..bcc23d9 100644 --- a/pkg/iplibrary/reader_file.go +++ b/pkg/iplibrary/reader_file.go @@ -9,10 +9,12 @@ import ( "io" "net" "os" + "path/filepath" + "strings" ) type FileReader struct { - rawReader *Reader + rawReader ReaderInterface //password string } @@ -25,10 +27,15 @@ func NewFileReader(path string, password string) (*FileReader, error) { _ = fp.Close() }() - return NewFileDataReader(fp, password) + var version = ReaderVersionV1 + if strings.HasSuffix(filepath.Base(path), ".v2.db") { + version = ReaderVersionV2 + } + + return NewFileDataReader(fp, password, version) } -func NewFileDataReader(dataReader io.Reader, password string) (*FileReader, error) { +func NewFileDataReader(dataReader io.Reader, password string, readerVersion ReaderVersion) (*FileReader, error) { if len(password) > 0 { data, err := io.ReadAll(dataReader) if err != nil { @@ -48,7 +55,12 @@ func NewFileDataReader(dataReader io.Reader, password string) (*FileReader, erro return nil, fmt.Errorf("create gzip reader failed: %w", err) } - reader, err := NewReader(gzReader) + var reader ReaderInterface + if readerVersion == ReaderVersionV2 { + reader, err = NewReaderV2(gzReader) + } else { + reader, err = NewReaderV1(gzReader) + } if err != nil { return nil, err } @@ -59,13 +71,13 @@ func NewFileDataReader(dataReader io.Reader, password string) (*FileReader, erro } func (this *FileReader) Meta() *Meta { - return this.rawReader.meta + return this.rawReader.Meta() } func (this *FileReader) Lookup(ip net.IP) *QueryResult { return this.rawReader.Lookup(ip) } -func (this *FileReader) RawReader() *Reader { +func (this *FileReader) RawReader() ReaderInterface { return this.rawReader } diff --git a/pkg/iplibrary/reader_file_test.go b/pkg/iplibrary/reader_file_test.go index b26d8ea..94f6ae0 100644 --- a/pkg/iplibrary/reader_file_test.go +++ b/pkg/iplibrary/reader_file_test.go @@ -6,12 +6,13 @@ import ( "encoding/json" "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" "github.com/iwind/TeaGo/maps" + stringutil "github.com/iwind/TeaGo/utils/string" "net" "testing" ) func TestNewFileReader(t *testing.T) { - reader, err := iplibrary.NewFileReader("./ip-20c1461c.db", "123456") + reader, err := iplibrary.NewFileReader("./default_ip_library_plus_test.go", stringutil.Md5("123456")) if err != nil { t.Fatal(err) } diff --git a/pkg/iplibrary/reader_interface.go b/pkg/iplibrary/reader_interface.go new file mode 100644 index 0000000..ff2c560 --- /dev/null +++ b/pkg/iplibrary/reader_interface.go @@ -0,0 +1,18 @@ +// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iplibrary + +import "net" + +type ReaderVersion = int + +const ( + ReaderVersionV1 ReaderVersion = 0 + ReaderVersionV2 ReaderVersion = 2 +) + +type ReaderInterface interface { + Meta() *Meta + Lookup(ip net.IP) *QueryResult + Destroy() +} diff --git a/pkg/iplibrary/reader_result.go b/pkg/iplibrary/reader_result.go index 034a557..8a3f607 100644 --- a/pkg/iplibrary/reader_result.go +++ b/pkg/iplibrary/reader_result.go @@ -213,13 +213,21 @@ func (this *QueryResult) RegionSummary() string { func (this *QueryResult) realCountryId() uint16 { if this.item != nil { switch item := this.item.(type) { - case *ipv4Item: + case *ipv4ItemV1: return item.Region.CountryId - case ipv4Item: + case ipv4ItemV1: return item.Region.CountryId - case *ipv6Item: + case *ipv6ItemV1: return item.Region.CountryId - case ipv6Item: + case ipv6ItemV1: + return item.Region.CountryId + case *ipv4ItemV2: + return item.Region.CountryId + case ipv4ItemV2: + return item.Region.CountryId + case *ipv6ItemV2: + return item.Region.CountryId + case ipv6ItemV2: return item.Region.CountryId } @@ -230,13 +238,21 @@ func (this *QueryResult) realCountryId() uint16 { func (this *QueryResult) realProvinceId() uint16 { if this.item != nil { switch item := this.item.(type) { - case *ipv4Item: + case *ipv4ItemV1: return item.Region.ProvinceId - case ipv4Item: + case ipv4ItemV1: return item.Region.ProvinceId - case *ipv6Item: + case *ipv6ItemV1: return item.Region.ProvinceId - case ipv6Item: + case ipv6ItemV1: + return item.Region.ProvinceId + case *ipv4ItemV2: + return item.Region.ProvinceId + case ipv4ItemV2: + return item.Region.ProvinceId + case *ipv6ItemV2: + return item.Region.ProvinceId + case ipv6ItemV2: return item.Region.ProvinceId } @@ -247,13 +263,21 @@ func (this *QueryResult) realProvinceId() uint16 { func (this *QueryResult) realCityId() uint32 { if this.item != nil { switch item := this.item.(type) { - case *ipv4Item: + case *ipv4ItemV1: return item.Region.CityId - case ipv4Item: + case ipv4ItemV1: return item.Region.CityId - case *ipv6Item: + case *ipv6ItemV1: return item.Region.CityId - case ipv6Item: + case ipv6ItemV1: + return item.Region.CityId + case *ipv4ItemV2: + return item.Region.CityId + case ipv4ItemV2: + return item.Region.CityId + case *ipv6ItemV2: + return item.Region.CityId + case ipv6ItemV2: return item.Region.CityId } @@ -264,13 +288,21 @@ func (this *QueryResult) realCityId() uint32 { func (this *QueryResult) realTownId() uint32 { if this.item != nil { switch item := this.item.(type) { - case *ipv4Item: + case *ipv4ItemV1: return item.Region.TownId - case ipv4Item: + case ipv4ItemV1: return item.Region.TownId - case *ipv6Item: + case *ipv6ItemV1: return item.Region.TownId - case ipv6Item: + case ipv6ItemV1: + return item.Region.TownId + case *ipv4ItemV2: + return item.Region.TownId + case ipv4ItemV2: + return item.Region.TownId + case *ipv6ItemV2: + return item.Region.TownId + case ipv6ItemV2: return item.Region.TownId } @@ -281,13 +313,21 @@ func (this *QueryResult) realTownId() uint32 { func (this *QueryResult) realProviderId() uint16 { if this.item != nil { switch item := this.item.(type) { - case *ipv4Item: + case *ipv4ItemV1: return item.Region.ProviderId - case ipv4Item: + case ipv4ItemV1: return item.Region.ProviderId - case *ipv6Item: + case *ipv6ItemV1: return item.Region.ProviderId - case ipv6Item: + case ipv6ItemV1: + return item.Region.ProviderId + case *ipv4ItemV2: + return item.Region.ProviderId + case ipv4ItemV2: + return item.Region.ProviderId + case *ipv6ItemV2: + return item.Region.ProviderId + case ipv6ItemV2: return item.Region.ProviderId } diff --git a/pkg/iplibrary/reader_test.go b/pkg/iplibrary/reader_test.go index dbad3d7..39f657d 100644 --- a/pkg/iplibrary/reader_test.go +++ b/pkg/iplibrary/reader_test.go @@ -16,7 +16,7 @@ import ( func TestNewReader(t *testing.T) { var buf = &bytes.Buffer{} - var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{ + var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{ Author: "GoEdge ", }) @@ -63,7 +63,7 @@ func TestNewReader(t *testing.T) { var stat = &runtime.MemStats{} runtime.ReadMemStats(stat) - reader, err := iplibrary.NewReader(buf) + reader, err := iplibrary.NewReaderV2(buf) var stat2 = &runtime.MemStats{} runtime.ReadMemStats(stat2) @@ -115,7 +115,7 @@ func BenchmarkNewReader(b *testing.B) { runtime.GOMAXPROCS(1) var buf = &bytes.Buffer{} - var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{ + var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{ Author: "GoEdge ", }) @@ -135,7 +135,7 @@ func BenchmarkNewReader(b *testing.B) { } } - reader, err := iplibrary.NewReader(buf) + reader, err := iplibrary.NewReaderV2(buf) if err != nil { b.Fatal(err) } diff --git a/pkg/iplibrary/reader_v2.go b/pkg/iplibrary/reader_v2.go new file mode 100644 index 0000000..bbe44e1 --- /dev/null +++ b/pkg/iplibrary/reader_v2.go @@ -0,0 +1,360 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iplibrary + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "net" + "runtime" + "sort" + "strconv" + "strings" +) + +// ReaderV2 IP库Reader V2 +type ReaderV2 struct { + meta *Meta + + regionMap map[string]*ipRegion // 缓存重复的区域用来节约内存 + + ipV4Items []ipv4ItemV2 + ipV6Items []ipv6ItemV2 + + lastCountryId uint16 + lastProvinceId uint16 + lastCityId uint32 + lastTownId uint32 + lastProviderId uint16 +} + +// NewReaderV2 创建新Reader对象 +func NewReaderV2(reader io.Reader) (*ReaderV2, error) { + var libReader = &ReaderV2{ + regionMap: map[string]*ipRegion{}, + } + + if runtime.NumCPU() >= 4 /** CPU数量较多的通常有着大内存 **/ { + libReader.ipV4Items = make([]ipv4ItemV2, 0, 6_000_000) + } else { + libReader.ipV4Items = make([]ipv4ItemV2, 0, 600_000) + } + + err := libReader.load(reader) + if err != nil { + return nil, err + } + return libReader, nil +} + +// 从Reader中加载数据 +func (this *ReaderV2) load(reader io.Reader) error { + var buf = make([]byte, 1024) + var metaLine []byte + var metaLineFound = false + var dataBuf = []byte{} + for { + n, err := reader.Read(buf) + if n > 0 { + var data = buf[:n] + dataBuf = append(dataBuf, data...) + if metaLineFound { + left, err := this.parse(dataBuf) + if err != nil { + return err + } + dataBuf = left + } else { + var index = bytes.IndexByte(dataBuf, '\n') + if index > 0 { + metaLine = dataBuf[:index] + dataBuf = dataBuf[index+1:] + metaLineFound = true + var meta = &Meta{} + err = json.Unmarshal(metaLine, &meta) + if err != nil { + return err + } + meta.Init() + this.meta = meta + + left, err := this.parse(dataBuf) + if err != nil { + return err + } + dataBuf = left + } + } + } + if err != nil { + if err != io.EOF { + return err + } + break + } + } + + sort.Slice(this.ipV4Items, func(i, j int) bool { + var from0 = this.ipV4Items[i].IPFrom + var to0 = this.ipV4Items[i].IPTo + var from1 = this.ipV4Items[j].IPFrom + var to1 = this.ipV4Items[j].IPTo + if from0 == from1 { + return bytes.Compare(to0[:], to1[:]) < 0 + } + return bytes.Compare(from0[:], from1[:]) < 0 + }) + + sort.Slice(this.ipV6Items, func(i, j int) bool { + var from0 = this.ipV6Items[i].IPFrom + var to0 = this.ipV6Items[i].IPTo + var from1 = this.ipV6Items[j].IPFrom + var to1 = this.ipV6Items[j].IPTo + if from0 == from1 { + return bytes.Compare(to0[:], to1[:]) < 0 + } + return bytes.Compare(from0[:], from1[:]) < 0 + }) + + // 清理内存 + this.regionMap = nil + + return nil +} + +func (this *ReaderV2) Lookup(ip net.IP) *QueryResult { + if ip == nil { + return &QueryResult{} + } + + var isV4 = ip.To4() != nil + var resultItem any + if isV4 { + sort.Search(len(this.ipV4Items), func(i int) bool { + var item = this.ipV4Items[i] + if bytes.Compare(item.IPFrom[:], ip) <= 0 { + if bytes.Compare(item.IPTo[:], ip) >= 0 { + resultItem = item + return false + } + return false + } + return true + }) + } else { + sort.Search(len(this.ipV6Items), func(i int) bool { + var item = this.ipV6Items[i] + if bytes.Compare(item.IPFrom[:], ip) <= 0 { + if bytes.Compare(item.IPTo[:], ip) >= 0 { + resultItem = item + return false + } + return false + } + return true + }) + } + + return &QueryResult{ + item: resultItem, + meta: this.meta, + } +} + +func (this *ReaderV2) Meta() *Meta { + return this.meta +} + +func (this *ReaderV2) IPv4Items() []ipv4ItemV2 { + return this.ipV4Items +} + +func (this *ReaderV2) IPv6Items() []ipv6ItemV2 { + return this.ipV6Items +} + +func (this *ReaderV2) Destroy() { + this.meta = nil + this.regionMap = nil + this.ipV4Items = nil + this.ipV6Items = nil +} + +// 分析数据 +func (this *ReaderV2) parse(data []byte) (left []byte, err error) { + if len(data) == 0 { + return + } + + for { + if len(data) == 0 { + break + } + + var offset int + if data[0] == '|' { + offset = 1 + 8 + 1 + } else if data[0] == '4' { + offset = 2 + 8 + 1 + } else if data[0] == '6' { + offset = 2 + 32 + 1 + } + + var index = bytes.IndexByte(data[offset:], '\n') + if index >= 0 { + index += offset + var line = data[:index] + err = this.parseLine(line) + if err != nil { + return nil, err + } + data = data[index+1:] + } else { + left = data + break + } + } + return +} + +// 单行分析 +func (this *ReaderV2) parseLine(line []byte) error { + if len(line) == 0 { + return nil + } + + const maxPieces = 8 + var pieces []string + + var offset int + if line[0] == '|' { + offset = 1 + 8 + 1 + pieces = append(pieces, "", string(line[1:5]), string(line[5:9])) + } else if line[0] == '4' { + offset = 2 + 8 + 1 + pieces = append(pieces, "", string(line[2:6]), string(line[6:10])) + } else if line[0] == '6' { + offset = 2 + 32 + 1 + pieces = append(pieces, "6", string(line[2:18]), string(line[18:34])) + } + + pieces = append(pieces, strings.Split(string(line[offset:]), "|")...) + + var countPieces = len(pieces) + if countPieces < maxPieces { // 补足一行 + for i := 0; i < maxPieces-countPieces; i++ { + pieces = append(pieces, "") + } + } else if countPieces > maxPieces { + return errors.New("invalid ip definition '" + string(line) + "'") + } + + var version = pieces[0] + if len(version) == 0 { + version = "4" + } + + if version != "4" && version != "6" { + return errors.New("invalid ip version '" + string(line) + "'") + } + + // ip range + var ipFromV4 [4]byte + var ipToV4 [4]byte + + var ipFromV6 [16]byte + var ipToV6 [16]byte + + if version == "6" { + ipFromV6 = [16]byte([]byte(pieces[1])) + ipToV6 = [16]byte([]byte(pieces[2])) + } else { + ipFromV4 = [4]byte([]byte(pieces[1])) + ipToV4 = [4]byte([]byte(pieces[2])) + } + + // country + var countryId uint16 + if pieces[3] == "+" { + countryId = this.lastCountryId + } else { + countryId = uint16(this.decodeUint64(pieces[3])) + } + this.lastCountryId = countryId + + var provinceId uint16 + if pieces[4] == "+" { + provinceId = this.lastProvinceId + } else { + provinceId = uint16(this.decodeUint64(pieces[4])) + } + this.lastProvinceId = provinceId + + // city + var cityId uint32 + if pieces[5] == "+" { + cityId = this.lastCityId + } else { + cityId = uint32(this.decodeUint64(pieces[5])) + } + this.lastCityId = cityId + + // town + var townId uint32 + if pieces[6] == "+" { + townId = this.lastTownId + } else { + townId = uint32(this.decodeUint64(pieces[6])) + } + this.lastTownId = townId + + // provider + var providerId uint16 + if pieces[7] == "+" { + providerId = this.lastProviderId + } else { + providerId = uint16(this.decodeUint64(pieces[7])) + } + this.lastProviderId = providerId + + var hash = HashRegion(countryId, provinceId, cityId, townId, providerId) + + region, ok := this.regionMap[hash] + if !ok { + region = &ipRegion{ + CountryId: countryId, + ProvinceId: provinceId, + CityId: cityId, + TownId: townId, + ProviderId: providerId, + } + this.regionMap[hash] = region + } + + if version == "4" { + this.ipV4Items = append(this.ipV4Items, ipv4ItemV2{ + IPFrom: ipFromV4, + IPTo: ipToV4, + Region: region, + }) + } else { + this.ipV6Items = append(this.ipV6Items, ipv6ItemV2{ + IPFrom: ipFromV6, + IPTo: ipToV6, + Region: region, + }) + } + + return nil +} + +func (this *ReaderV2) decodeUint64(s string) uint64 { + if this.meta != nil && this.meta.Version == Version2 { + i, _ := strconv.ParseUint(s, 32, 64) + return i + } + i, _ := strconv.ParseUint(s, 10, 64) + return i +} diff --git a/pkg/iplibrary/updater.go b/pkg/iplibrary/updater.go index 30c1218..c05c67a 100644 --- a/pkg/iplibrary/updater.go +++ b/pkg/iplibrary/updater.go @@ -214,7 +214,12 @@ func (this *Updater) Loop() error { func (this *Updater) loadFile(fp *os.File) error { this.source.LogInfo("load ip library from '" + fp.Name() + "' ...") - fileReader, err := NewFileDataReader(fp, "") + var version = ReaderVersionV1 + if strings.HasSuffix(fp.Name(), ".v2.db") { + version = ReaderVersionV2 + } + + fileReader, err := NewFileDataReader(fp, "", version) if err != nil { return fmt.Errorf("load ip library from reader failed: %w", err) } diff --git a/pkg/iplibrary/writer.go b/pkg/iplibrary/writer.go index ac30163..2a311da 100644 --- a/pkg/iplibrary/writer.go +++ b/pkg/iplibrary/writer.go @@ -3,42 +3,19 @@ package iplibrary import ( - "crypto/md5" + "encoding/binary" "encoding/json" "errors" - "fmt" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" - "hash" "io" + "math/big" "net" "strconv" "strings" "time" ) -type hashWriter struct { - rawWriter io.Writer - hash hash.Hash -} - -func newHashWriter(writer io.Writer) *hashWriter { - return &hashWriter{ - rawWriter: writer, - hash: md5.New(), - } -} - -func (this *hashWriter) Write(p []byte) (n int, err error) { - n, err = this.rawWriter.Write(p) - this.hash.Write(p) - return -} - -func (this *hashWriter) Sum() string { - return fmt.Sprintf("%x", this.hash.Sum(nil)) -} - -type Writer struct { +type WriterV1 struct { writer *hashWriter meta *Meta @@ -50,21 +27,21 @@ type Writer struct { lastProviderId int64 } -func NewWriter(writer io.Writer, meta *Meta) *Writer { +func NewWriterV1(writer io.Writer, meta *Meta) *WriterV1 { if meta == nil { meta = &Meta{} } meta.Version = Version2 meta.CreatedAt = time.Now().Unix() - var libWriter = &Writer{ + var libWriter = &WriterV1{ writer: newHashWriter(writer), meta: meta, } return libWriter } -func (this *Writer) WriteMeta() error { +func (this *WriterV1) WriteMeta() error { metaJSON, err := json.Marshal(this.meta) if err != nil { return err @@ -77,7 +54,7 @@ func (this *Writer) WriteMeta() error { return err } -func (this *Writer) Write(ipFrom string, ipTo string, countryId int64, provinceId int64, cityId int64, townId int64, providerId int64) error { +func (this *WriterV1) Write(ipFrom string, ipTo string, countryId int64, provinceId int64, cityId int64, townId int64, providerId int64) error { // validate IP var fromIP = net.ParseIP(ipFrom) if fromIP == nil { @@ -100,11 +77,14 @@ func (this *Writer) Write(ipFrom string, ipTo string, countryId int64, provinceI pieces = append(pieces, "") } else { pieces = append(pieces, "6") + + // we do NOT support v6 yet + return nil } // 1 - var fromIPLong = configutils.IP2Long(fromIP) - var toIPLong = configutils.IP2Long(toIP) + var fromIPLong = this.ip2long(fromIP) + var toIPLong = this.ip2long(toIP) if toIPLong < fromIPLong { fromIPLong, toIPLong = toIPLong, fromIPLong @@ -193,10 +173,25 @@ func (this *Writer) Write(ipFrom string, ipTo string, countryId int64, provinceI return err } -func (this *Writer) Sum() string { +func (this *WriterV1) Sum() string { return this.writer.Sum() } -func (this *Writer) formatUint64(i uint64) string { +func (this *WriterV1) formatUint64(i uint64) string { return strconv.FormatUint(i, 32) } + +func (this *WriterV1) ip2long(netIP net.IP) uint64 { + if len(netIP) == 0 { + return 0 + } + + var b4 = netIP.To4() + if b4 != nil { + return uint64(binary.BigEndian.Uint32(b4.To4())) + } + + var i = big.NewInt(0) + i.SetBytes(netIP.To16()) + return i.Uint64() +} diff --git a/pkg/iplibrary/writer_file.go b/pkg/iplibrary/writer_file.go index df648fd..90ae816 100644 --- a/pkg/iplibrary/writer_file.go +++ b/pkg/iplibrary/writer_file.go @@ -12,7 +12,7 @@ type FileWriter struct { gzWriter *gzip.Writer password string - rawWriter *Writer + rawWriter WriterInterface } func NewFileWriter(path string, meta *Meta, password string) (*FileWriter, error) { @@ -29,7 +29,7 @@ func NewFileWriter(path string, meta *Meta, password string) (*FileWriter, error var writer = &FileWriter{ fp: fp, gzWriter: gzWriter, - rawWriter: NewWriter(gzWriter, meta), + rawWriter: NewWriterV1(gzWriter, meta), password: password, } return writer, nil @@ -64,11 +64,13 @@ func (this *FileWriter) Close() error { if err != nil { return err } + if len(data) > 0 { - encodedData, err := NewEncrypt().Encode(data, this.password) - if err != nil { - return err + encodedData, encodeErr := NewEncrypt().Encode(data, this.password) + if encodeErr != nil { + return encodeErr } + _ = os.Remove(filePath) err = os.WriteFile(filePath, encodedData, 0666) if err != nil { return err diff --git a/pkg/iplibrary/writer_file_test.go b/pkg/iplibrary/writer_file_test.go index d531375..f9ec2e4 100644 --- a/pkg/iplibrary/writer_file_test.go +++ b/pkg/iplibrary/writer_file_test.go @@ -6,13 +6,14 @@ import ( "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" "github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/types" + stringutil "github.com/iwind/TeaGo/utils/string" "testing" ) func TestNewFileWriter(t *testing.T) { writer, err := iplibrary.NewFileWriter("./internal-ip-library-test.db", &iplibrary.Meta{ Author: "GoEdge", - }, "") + }, stringutil.Md5("123456")) if err != nil { t.Fatal(err) } diff --git a/pkg/iplibrary/writer_interface.go b/pkg/iplibrary/writer_interface.go new file mode 100644 index 0000000..a23b1e2 --- /dev/null +++ b/pkg/iplibrary/writer_interface.go @@ -0,0 +1,9 @@ +// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iplibrary + +type WriterInterface interface { + WriteMeta() error + Write(ipFrom string, ipTo string, countryId int64, provinceId int64, cityId int64, townId int64, providerId int64) error + Sum() string +} diff --git a/pkg/iplibrary/writer_v2.go b/pkg/iplibrary/writer_v2.go new file mode 100644 index 0000000..f7fb443 --- /dev/null +++ b/pkg/iplibrary/writer_v2.go @@ -0,0 +1,190 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iplibrary + +import ( + "bytes" + "crypto/md5" + "encoding/json" + "errors" + "fmt" + "hash" + "io" + "net" + "strconv" + "strings" + "time" +) + +type hashWriter struct { + rawWriter io.Writer + hash hash.Hash +} + +func newHashWriter(writer io.Writer) *hashWriter { + return &hashWriter{ + rawWriter: writer, + hash: md5.New(), + } +} + +func (this *hashWriter) Write(p []byte) (n int, err error) { + n, err = this.rawWriter.Write(p) + this.hash.Write(p) + return +} + +func (this *hashWriter) Sum() string { + return fmt.Sprintf("%x", this.hash.Sum(nil)) +} + +type WriterV2 struct { + writer *hashWriter + meta *Meta + + lastCountryId int64 + lastProvinceId int64 + lastCityId int64 + lastTownId int64 + lastProviderId int64 +} + +func NewWriterV2(writer io.Writer, meta *Meta) *WriterV2 { + if meta == nil { + meta = &Meta{} + } + meta.Version = Version2 + meta.CreatedAt = time.Now().Unix() + + var libWriter = &WriterV2{ + writer: newHashWriter(writer), + meta: meta, + } + return libWriter +} + +func (this *WriterV2) WriteMeta() error { + metaJSON, err := json.Marshal(this.meta) + if err != nil { + return err + } + _, err = this.writer.Write(metaJSON) + if err != nil { + return err + } + _, err = this.writer.Write([]byte("\n")) + return err +} + +func (this *WriterV2) Write(ipFrom string, ipTo string, countryId int64, provinceId int64, cityId int64, townId int64, providerId int64) error { + // validate IP + var fromIP = net.ParseIP(ipFrom) + if fromIP == nil { + return errors.New("invalid 'ipFrom': '" + ipFrom + "'") + } + var fromIsIPv4 = fromIP.To4() != nil + var toIP = net.ParseIP(ipTo) + if toIP == nil { + return errors.New("invalid 'ipTo': " + ipTo) + } + var toIsIPv4 = toIP.To4() != nil + if fromIsIPv4 != toIsIPv4 { + return errors.New("'ipFrom(" + ipFrom + ")' and 'ipTo(" + ipTo + ")' should have the same IP version") + } + + var pieces = []string{} + + // 0 + if fromIsIPv4 { + pieces = append(pieces, "") + } else { + pieces = append(pieces, "6") + } + + // 1 + if bytes.Compare(fromIP, toIP) > 0 { + fromIP, toIP = toIP, fromIP + } + + if fromIsIPv4 { + pieces = append(pieces, string(fromIP.To4())+string(toIP.To4())) + } else { + pieces = append(pieces, string(fromIP.To16())+string(toIP.To16())) + } + + // 2 + if countryId > 0 { + if countryId == this.lastCountryId { + pieces = append(pieces, "+") + } else { + pieces = append(pieces, this.formatUint64(uint64(countryId))) + } + } else { + pieces = append(pieces, "") + } + this.lastCountryId = countryId + + // 3 + if provinceId > 0 { + if provinceId == this.lastProvinceId { + pieces = append(pieces, "+") + } else { + pieces = append(pieces, this.formatUint64(uint64(provinceId))) + } + } else { + pieces = append(pieces, "") + } + this.lastProvinceId = provinceId + + // 4 + if cityId > 0 { + if cityId == this.lastCityId { + pieces = append(pieces, "+") + } else { + pieces = append(pieces, this.formatUint64(uint64(cityId))) + } + } else { + pieces = append(pieces, "") + } + this.lastCityId = cityId + + // 5 + if townId > 0 { + if townId == this.lastTownId { + pieces = append(pieces, "+") + } else { + pieces = append(pieces, this.formatUint64(uint64(townId))) + } + } else { + pieces = append(pieces, "") + } + this.lastTownId = townId + + // 6 + if providerId > 0 { + if providerId == this.lastProviderId { + pieces = append(pieces, "+") + } else { + pieces = append(pieces, this.formatUint64(uint64(providerId))) + } + } else { + pieces = append(pieces, "") + } + this.lastProviderId = providerId + + _, err := this.writer.Write([]byte(strings.TrimRight(strings.Join(pieces, "|"), "|"))) + if err != nil { + return err + } + + _, err = this.writer.Write([]byte("\n")) + return err +} + +func (this *WriterV2) Sum() string { + return this.writer.Sum() +} + +func (this *WriterV2) formatUint64(i uint64) string { + return strconv.FormatUint(i, 32) +} diff --git a/pkg/iplibrary/writer_test.go b/pkg/iplibrary/writer_v2_test.go similarity index 64% rename from pkg/iplibrary/writer_test.go rename to pkg/iplibrary/writer_v2_test.go index 34f8642..8cbb024 100644 --- a/pkg/iplibrary/writer_test.go +++ b/pkg/iplibrary/writer_v2_test.go @@ -5,12 +5,14 @@ package iplibrary_test import ( "bytes" "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" + "github.com/iwind/TeaGo/logs" "testing" ) func TestNewWriter(t *testing.T) { + //write var buf = &bytes.Buffer{} - var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{ + var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{ Author: "GoEdge ", }) @@ -39,6 +41,27 @@ func TestNewWriter(t *testing.T) { t.Fatal(err) } + err = writer.Write("10.0.0.1", "10.0.0.2", 101, 201, 301, 401, 501) + if err != nil { + t.Fatal(err) + } + + err = writer.Write("10.0.0.3", "10.0.0.4", 101, 201, 301, 401, 501) + if err != nil { + t.Fatal(err) + } + t.Log(buf.String()) t.Log("sum:", writer.Sum()) + + // read + reader, err := iplibrary.NewReaderV2(buf) + if err != nil { + t.Fatal(err) + } + + logs.PrintAsJSON(reader.IPv4Items(), t) + logs.PrintAsJSON(reader.IPv6Items(), t) + + _ = reader } diff --git a/pkg/iputils/ip.go b/pkg/iputils/ip.go new file mode 100644 index 0000000..4c84e07 --- /dev/null +++ b/pkg/iputils/ip.go @@ -0,0 +1,216 @@ +// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iputils + +import ( + "encoding/binary" + "fmt" + "math" + "math/big" + "net" + "strconv" + "sync" +) + +type IP struct { + rawIP net.IP + bigInt *big.Int +} + +var uint32BigInt = big.NewInt(int64(math.MaxUint32)) + +func ParseIP(ipString string) IP { + return NewIP(net.ParseIP(ipString)) +} + +func NewIP(rawIP net.IP) IP { + if rawIP == nil { + return IP{} + } + + if rawIP.To4() == nil { + var bigInt = big.NewInt(0) + bigInt.SetBytes(rawIP.To16()) + bigInt.Add(bigInt, uint32BigInt) + return IP{ + rawIP: rawIP, + bigInt: bigInt, + } + } + + return IP{ + rawIP: rawIP, + } +} + +func IsIPv4(ipString string) bool { + var rawIP = net.ParseIP(ipString) + return rawIP != nil && rawIP.To4() != nil +} + +func IsIPv6(ipString string) bool { + var rawIP = net.ParseIP(ipString) + return rawIP != nil && rawIP.To4() == nil && rawIP.To16() != nil +} + +func CompareLong(i1 string, i2 string) int { + if i1 == "" { + i1 = "0" + } + if i2 == "" { + i2 = "0" + } + + var l = len(i1) - len(i2) + if l > 0 { + return 1 + } + if l < 0 { + return -1 + } + + if i1 > i2 { + return 1 + } + if i1 < i2 { + return -1 + } + + return 0 +} + +var bigIntPool = &sync.Pool{ + New: func() any { + return big.NewInt(0) + }, +} + +func ToLong(ip string) string { + var rawIP = net.ParseIP(ip) + if rawIP == nil { + return "0" + } + + var i4 = rawIP.To4() + if i4 != nil { + return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10) + } + + var bigInt = bigIntPool.Get().(*big.Int) + bigInt.SetBytes(rawIP.To16()) + bigInt.Add(bigInt, uint32BigInt) + var s = bigInt.String() + bigIntPool.Put(bigInt) + return s +} + +func ToHex(ip string) string { + var rawIP = net.ParseIP(ip) + if rawIP == nil { + return "" + } + + if rawIP.To4() != nil { + return fmt.Sprintf("%x", rawIP.To4()) + } + + return fmt.Sprintf("%x", rawIP.To16()) +} + +func ToLittleLong(ip string) string { + var rawIP = net.ParseIP(ip) + if rawIP == nil { + return "0" + } + + var i4 = rawIP.To4() + if i4 != nil { + return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10) + } + + var bigInt = bigIntPool.Get().(*big.Int) + bigInt.SetBytes(rawIP.To16()) + var s = bigInt.String() + bigIntPool.Put(bigInt) + return s +} + +func (this IP) ToLong() string { + if this.rawIP == nil { + return "0" + } + if this.bigInt != nil { + return this.bigInt.String() + } + return strconv.FormatUint(uint64(binary.BigEndian.Uint32(this.rawIP.To4())), 10) +} + +func (this IP) Mod(d int) int { + if this.rawIP == nil { + return 0 + } + if this.bigInt != nil { + return int(this.bigInt.Mod(this.bigInt, big.NewInt(int64(d))).Int64()) + } + return int(binary.BigEndian.Uint32(this.rawIP.To4()) % uint32(d)) +} + +func (this IP) Compare(anotherIP IP) int { + if this.rawIP == nil { + if anotherIP.rawIP == nil { + return 0 + } + return -1 + } else if anotherIP.rawIP == nil { + return 1 + } + + if this.bigInt != nil { + if anotherIP.bigInt == nil { + return 1 // IPv6 always greater than IPv4 + } + return this.bigInt.Cmp(anotherIP.bigInt) + } + + if anotherIP.bigInt == nil { + var i1 = binary.BigEndian.Uint32(this.rawIP.To4()) + var i2 = binary.BigEndian.Uint32(anotherIP.rawIP.To4()) + + if i1 > i2 { + return 1 + } + if i1 < i2 { + return -1 + } + return 0 + } + + return -1 +} + +func (this IP) Between(ipFrom IP, ipTo IP) bool { + return ipFrom.Compare(this) <= 0 && ipTo.Compare(this) >= 0 +} + +func (this IP) IsIPv4() bool { + return this.rawIP != nil && this.bigInt == nil +} + +func (this IP) IsIPv6() bool { + return this.bigInt != nil +} + +func (this IP) IsValid() bool { + return this.rawIP != nil +} + +func (this IP) Raw() net.IP { + return this.rawIP +} + +func (this IP) String() string { + if this.rawIP == nil { + return "" + } + return this.rawIP.String() +} diff --git a/pkg/iputils/ip_test.go b/pkg/iputils/ip_test.go new file mode 100644 index 0000000..30ca1db --- /dev/null +++ b/pkg/iputils/ip_test.go @@ -0,0 +1,230 @@ +// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package iputils_test + +import ( + "github.com/TeaOSLab/EdgeCommon/pkg/iputils" + "github.com/iwind/TeaGo/assert" + "runtime" + "testing" +) + +func TestIP_ParseIP(t *testing.T) { + var a = assert.NewAssertion(t) + + { + var i = iputils.ParseIP("127.0.0.1") + a.IsTrue(i.IsIPv4()) + a.IsFalse(i.IsIPv6()) + a.IsTrue(i.IsValid()) + a.IsTrue(iputils.IsIPv4("127.0.0.1")) + a.IsFalse(iputils.IsIPv6("127.0.0.1")) + t.Log(i.String(), i.ToLong()) + t.Log("raw:", i.Raw()) + } + + { + var i = iputils.ParseIP("0.0.0.1") + a.IsTrue(i.IsIPv4()) + a.IsFalse(i.IsIPv6()) + t.Log(i.String(), i.ToLong()) + } + + for j := 0; j < 3; j++ /** repeat test **/ { + var i = iputils.ParseIP("::1") + a.IsFalse(i.IsIPv4()) + a.IsTrue(i.IsIPv6()) + a.IsTrue(i.IsValid()) + t.Log(i.String(), i.ToLong()) + } + + { + { + var i = iputils.ParseIP("2001:db8:0:1::1:101") + t.Log(i.String(), i.ToLong()) + a.IsFalse(i.IsIPv4()) + a.IsTrue(i.IsIPv6()) + a.IsFalse(iputils.IsIPv4("2001:db8:0:1::1:101")) + a.IsTrue(iputils.IsIPv6("2001:db8:0:1::1:101")) + a.IsTrue(i.IsValid()) + } + + { + var i = iputils.ParseIP("2001:db8:0:1::1:102") + t.Log(i.String(), i.ToLong()) + a.IsFalse(i.IsIPv4()) + a.IsTrue(i.IsIPv6()) + a.IsTrue(i.IsValid()) + } + + { + var i = iputils.ParseIP("2001:db8:0:1::2:101") + t.Log(i.String(), i.ToLong()) + a.IsFalse(i.IsIPv4()) + a.IsTrue(i.IsIPv6()) + a.IsTrue(i.IsValid()) + } + } + + { + var i = iputils.ParseIP("WRONG IP") + t.Log(i.String(), i.ToLong()) + a.IsFalse(i.IsIPv4()) + a.IsFalse(i.IsIPv6()) + a.IsFalse(i.IsValid()) + a.IsFalse(iputils.IsIPv4("WRONG IP")) + a.IsFalse(iputils.IsIPv6("WRONG IP")) + } +} + +func TestIP_Mod(t *testing.T) { + for _, ip := range []string{ + "127.0.0.1", + "::1", + "2001:db8:0:1::1:101", + "2001:db8:0:1::1:102", + "WRONG IP", + } { + var i = iputils.ParseIP(ip) + t.Log(ip, "=>", i.ToLong(), "=>", i.Mod(5)) + } +} + +func TestIP_Compare(t *testing.T) { + var a = assert.NewAssertion(t) + + { + var i1 = iputils.ParseIP("127.0.0.1") + var i2 = iputils.ParseIP("127.0.0.1") + a.IsTrue(i1.Compare(i2) == 0) + } + + { + var i1 = iputils.ParseIP("127.0.0.1") + var i2 = iputils.ParseIP("127.0.0.2") + a.IsTrue(i1.Compare(i2) == -1) + } + + { + var i1 = iputils.ParseIP("127.0.0.2") + var i2 = iputils.ParseIP("127.0.0.1") + a.IsTrue(i1.Compare(i2) == 1) + } + + { + var i1 = iputils.ParseIP("2001:db8:0:1::101") + var i2 = iputils.ParseIP("127.0.0.1") + a.IsTrue(i1.Compare(i2) == 1) + } + + { + var i1 = iputils.ParseIP("127.0.0.1") + var i2 = iputils.ParseIP("2001:db8:0:1::101") + a.IsTrue(i1.Compare(i2) == -1) + } + + { + var i1 = iputils.ParseIP("2001:db8:0:1::101") + var i2 = iputils.ParseIP("2001:db8:0:1::101") + a.IsTrue(i1.Compare(i2) == 0) + } + + { + var i1 = iputils.ParseIP("2001:db8:0:1::101") + var i2 = iputils.ParseIP("2001:db8:0:1::102") + a.IsTrue(i1.Compare(i2) == -1) + } + + { + var i1 = iputils.ParseIP("2001:db8:0:1::102") + var i2 = iputils.ParseIP("2001:db8:0:1::101") + a.IsTrue(i1.Compare(i2) == 1) + } + + { + var i1 = iputils.ParseIP("2001:db8:0:1::2:100") + var i2 = iputils.ParseIP("2001:db8:0:1::1:101") + a.IsTrue(i1.Compare(i2) == 1) + } +} + +func TestIP_Between(t *testing.T) { + var a = assert.NewAssertion(t) + a.IsTrue(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.1"), iputils.ParseIP("127.0.0.3"))) + a.IsTrue(iputils.ParseIP("127.0.0.1").Between(iputils.ParseIP("127.0.0.1"), iputils.ParseIP("127.0.0.3"))) + a.IsFalse(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.4"))) + a.IsFalse(iputils.ParseIP("127.0.0.5").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.4"))) + a.IsFalse(iputils.ParseIP("127.0.0.2").Between(iputils.ParseIP("127.0.0.3"), iputils.ParseIP("127.0.0.1"))) +} + +func TestIP_ToLong(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", + "2001:db8:0:1::101", + "2001:db8:0:2::101", + "wrong ip", + } { + var goIP = iputils.ParseIP(ip) + t.Log(ip, "=>", "\n", goIP.String(), "\n", "=>", "\n", "long1:", goIP.ToLong(), "\n", "long2:", iputils.ToLong(ip), "\n", "little long:", iputils.ToLittleLong(ip)) + } +} + +func TestIP_CompareLong(t *testing.T) { + var a = assert.NewAssertion(t) + a.IsTrue(iputils.CompareLong("1", "2") == -1) + a.IsTrue(iputils.CompareLong("11", "2") == 1) + a.IsTrue(iputils.CompareLong("11", "22") == -1) + a.IsTrue(iputils.CompareLong("22", "101") == -1) + a.IsTrue(iputils.CompareLong("33", "22") == 1) + a.IsTrue(iputils.CompareLong("101", "22") == 1) + a.IsTrue(iputils.CompareLong("22", "22") == 0) +} + +func TestIP_Memory(t *testing.T) { + var list []iputils.IP + + var stat1 = &runtime.MemStats{} + runtime.ReadMemStats(stat1) + + for i := 0; i < 1_000_000; i++ { + list = append(list, iputils.ParseIP("fd00:6868:6868:0:10ac:d056:3bf6:7452")) + } + + //runtime.GC() + + var stat2 = &runtime.MemStats{} + runtime.ReadMemStats(stat2) + + t.Log((stat2.Alloc-stat1.Alloc)>>10, "KB", (stat2.HeapInuse-stat1.HeapInuse)>>10, "KB") + + // hold the memory + for _, v := range list { + _ = v + } +} + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + iputils.ParseIP("fd00:6868:6868:0:10ac:d056:3bf6:7452") + } +} + +func BenchmarkToLongV4(b *testing.B) { + for i := 0; i < b.N; i++ { + iputils.ToLong("192.168.2.100") + } +} + +func BenchmarkToLongV6(b *testing.B) { + runtime.GOMAXPROCS(1) + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + iputils.ToLong("fd00:6868:6868:0:10ac:d056:3bf6:7452") + } +}