mirror of
https://github.com/TeaOSLab/EdgeCommon.git
synced 2025-11-03 04:10:25 +08:00
更好地支持IPv6
This commit is contained in:
@@ -1,41 +1,10 @@
|
|||||||
package configutils
|
package configutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"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
|
// IsIPv4 检查是否为IPv4
|
||||||
func IsIPv4(netIP net.IP) bool {
|
func IsIPv4(netIP net.IP) bool {
|
||||||
if len(netIP) == 0 {
|
if len(netIP) == 0 {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package configutils_test
|
package configutils_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"github.com/iwind/TeaGo/assert"
|
"github.com/iwind/TeaGo/assert"
|
||||||
"net"
|
"net"
|
||||||
@@ -16,12 +15,6 @@ func TestParseCIDR(t *testing.T) {
|
|||||||
t.Log(configutils.ParseCIDR("192.168.1.1/16"))
|
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) {
|
func TestIsIPv4(t *testing.T) {
|
||||||
t.Log(configutils.IsIPv4(net.ParseIP("192.168.1.100")))
|
t.Log(configutils.IsIPv4(net.ParseIP("192.168.1.100")))
|
||||||
t.Log(configutils.IsIPv4(net.ParseIP("::1")))
|
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)
|
a.IsTrue(configutils.IPVersion(net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) == 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestQuoteIP(t *testing.T) {
|
func TestQuoteIP(t *testing.T) {
|
||||||
t.Log(configutils.QuoteIP(configutils.QuoteIP("2001:da8:22::10")))
|
t.Log(configutils.QuoteIP(configutils.QuoteIP("2001:da8:22::10")))
|
||||||
}
|
}
|
||||||
|
|||||||
3
pkg/iplibrary/.gitignore
vendored
3
pkg/iplibrary/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
*-plus.db
|
*-plus.db
|
||||||
|
internal-ip-library-test.db
|
||||||
@@ -33,7 +33,7 @@ func InitDefault() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var library = NewIPLibrary()
|
var library = NewIPLibrary()
|
||||||
err := library.InitFromData(ipLibraryData, "")
|
err := library.InitFromData(ipLibraryData, "", ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -66,18 +66,18 @@ func LookupIPSummaries(ipList []string) map[string]string /** ip => summary **/
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IPLibrary struct {
|
type IPLibrary struct {
|
||||||
reader *Reader
|
reader ReaderInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPLibrary() *IPLibrary {
|
func NewIPLibrary() *IPLibrary {
|
||||||
return &IPLibrary{}
|
return &IPLibrary{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPLibraryWithReader(reader *Reader) *IPLibrary {
|
func NewIPLibraryWithReader(reader ReaderInterface) *IPLibrary {
|
||||||
return &IPLibrary{reader: reader}
|
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 {
|
if len(data) == 0 || this.reader != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,12 @@ func (this *IPLibrary) InitFromData(data []byte, password string) error {
|
|||||||
_ = gzipReader.Close()
|
_ = gzipReader.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
libReader, err := NewReader(gzipReader)
|
var libReader ReaderInterface
|
||||||
|
if version == ReaderVersionV2 {
|
||||||
|
libReader, err = NewReaderV2(gzipReader)
|
||||||
|
} else {
|
||||||
|
libReader, err = NewReaderV1(gzipReader)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
func TestIPLibrary_Init(t *testing.T) {
|
func TestIPLibrary_Init(t *testing.T) {
|
||||||
var lib = iplibrary.NewIPLibrary()
|
var lib = iplibrary.NewIPLibrary()
|
||||||
|
|
||||||
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "")
|
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func TestIPLibrary_Lookup(t *testing.T) {
|
|||||||
|
|
||||||
var before = time.Now()
|
var before = time.Now()
|
||||||
|
|
||||||
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "")
|
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func TestIPLibrary_Lookup(t *testing.T) {
|
|||||||
|
|
||||||
func TestIPLibrary_LookupIP(t *testing.T) {
|
func TestIPLibrary_LookupIP(t *testing.T) {
|
||||||
var lib = iplibrary.NewIPLibrary()
|
var lib = iplibrary.NewIPLibrary()
|
||||||
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "")
|
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func TestIPLibrary_LookupIP(t *testing.T) {
|
|||||||
|
|
||||||
func TestIPLibrary_LookupIP_Summary(t *testing.T) {
|
func TestIPLibrary_LookupIP_Summary(t *testing.T) {
|
||||||
var lib = iplibrary.NewIPLibrary()
|
var lib = iplibrary.NewIPLibrary()
|
||||||
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "")
|
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func TestIPLibrary_LookupIPSummaries(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkIPLibrary_Lookup(b *testing.B) {
|
func BenchmarkIPLibrary_Lookup(b *testing.B) {
|
||||||
var lib = iplibrary.NewIPLibrary()
|
var lib = iplibrary.NewIPLibrary()
|
||||||
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "")
|
err := lib.InitFromData(iplibrary.DefaultIPLibraryData(), "", iplibrary.ReaderVersionV1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,34 @@ import (
|
|||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ipv4Item struct {
|
type ipv4ItemV1 struct {
|
||||||
IPFrom uint32
|
IPFrom uint32
|
||||||
IPTo uint32
|
IPTo uint32
|
||||||
|
|
||||||
Region *ipRegion
|
Region *ipRegion
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipv6Item struct {
|
type ipv6ItemV1 struct {
|
||||||
IPFrom uint64
|
IPFrom uint64
|
||||||
IPTo uint64
|
IPTo uint64
|
||||||
|
|
||||||
Region *ipRegion
|
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 {
|
type ipRegion struct {
|
||||||
CountryId uint16
|
CountryId uint16
|
||||||
ProvinceId uint16
|
ProvinceId uint16
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ package iplibrary
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -21,8 +23,8 @@ type Reader struct {
|
|||||||
|
|
||||||
regionMap map[string]*ipRegion // 缓存重复的区域用来节约内存
|
regionMap map[string]*ipRegion // 缓存重复的区域用来节约内存
|
||||||
|
|
||||||
ipV4Items []ipv4Item
|
ipV4Items []ipv4ItemV1
|
||||||
ipV6Items []ipv6Item
|
ipV6Items []ipv6ItemV1
|
||||||
|
|
||||||
lastIPFrom uint64
|
lastIPFrom uint64
|
||||||
lastCountryId uint16
|
lastCountryId uint16
|
||||||
@@ -32,16 +34,16 @@ type Reader struct {
|
|||||||
lastProviderId uint16
|
lastProviderId uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader 创建新Reader对象
|
// NewReaderV1 创建新Reader对象
|
||||||
func NewReader(reader io.Reader) (*Reader, error) {
|
func NewReaderV1(reader io.Reader) (*Reader, error) {
|
||||||
var libReader = &Reader{
|
var libReader = &Reader{
|
||||||
regionMap: map[string]*ipRegion{},
|
regionMap: map[string]*ipRegion{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.NumCPU() >= 4 /** CPU数量较多的通常有着大内存 **/ {
|
if runtime.NumCPU() >= 4 /** CPU数量较多的通常有着大内存 **/ {
|
||||||
libReader.ipV4Items = make([]ipv4Item, 0, 6_000_000)
|
libReader.ipV4Items = make([]ipv4ItemV1, 0, 6_000_000)
|
||||||
} else {
|
} else {
|
||||||
libReader.ipV4Items = make([]ipv4Item, 0, 600_000)
|
libReader.ipV4Items = make([]ipv4ItemV1, 0, 600_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := libReader.load(reader)
|
err := libReader.load(reader)
|
||||||
@@ -131,7 +133,7 @@ func (this *Reader) Lookup(ip net.IP) *QueryResult {
|
|||||||
return &QueryResult{}
|
return &QueryResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipLong = configutils.IP2Long(ip)
|
var ipLong = this.ip2long(ip)
|
||||||
var isV4 = configutils.IsIPv4(ip)
|
var isV4 = configutils.IsIPv4(ip)
|
||||||
var resultItem any
|
var resultItem any
|
||||||
if isV4 {
|
if isV4 {
|
||||||
@@ -170,11 +172,11 @@ func (this *Reader) Meta() *Meta {
|
|||||||
return this.meta
|
return this.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Reader) IPv4Items() []ipv4Item {
|
func (this *Reader) IPv4Items() []ipv4ItemV1 {
|
||||||
return this.ipV4Items
|
return this.ipV4Items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Reader) IPv6Items() []ipv6Item {
|
func (this *Reader) IPv6Items() []ipv6ItemV1 {
|
||||||
return this.ipV6Items
|
return this.ipV6Items
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,13 +306,13 @@ func (this *Reader) parseLine(line []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if version == "4" {
|
if version == "4" {
|
||||||
this.ipV4Items = append(this.ipV4Items, ipv4Item{
|
this.ipV4Items = append(this.ipV4Items, ipv4ItemV1{
|
||||||
IPFrom: uint32(ipFrom),
|
IPFrom: uint32(ipFrom),
|
||||||
IPTo: uint32(ipTo),
|
IPTo: uint32(ipTo),
|
||||||
Region: region,
|
Region: region,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.ipV6Items = append(this.ipV6Items, ipv6Item{
|
this.ipV6Items = append(this.ipV6Items, ipv6ItemV1{
|
||||||
IPFrom: ipFrom,
|
IPFrom: ipFrom,
|
||||||
IPTo: ipTo,
|
IPTo: ipTo,
|
||||||
Region: region,
|
Region: region,
|
||||||
@@ -328,3 +330,18 @@ func (this *Reader) decodeUint64(s string) uint64 {
|
|||||||
i, _ := strconv.ParseUint(s, 10, 64)
|
i, _ := strconv.ParseUint(s, 10, 64)
|
||||||
return i
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileReader struct {
|
type FileReader struct {
|
||||||
rawReader *Reader
|
rawReader ReaderInterface
|
||||||
//password string
|
//password string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +27,15 @@ func NewFileReader(path string, password string) (*FileReader, error) {
|
|||||||
_ = fp.Close()
|
_ = 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 {
|
if len(password) > 0 {
|
||||||
data, err := io.ReadAll(dataReader)
|
data, err := io.ReadAll(dataReader)
|
||||||
if err != nil {
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -59,13 +71,13 @@ func NewFileDataReader(dataReader io.Reader, password string) (*FileReader, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Meta() *Meta {
|
func (this *FileReader) Meta() *Meta {
|
||||||
return this.rawReader.meta
|
return this.rawReader.Meta()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) Lookup(ip net.IP) *QueryResult {
|
func (this *FileReader) Lookup(ip net.IP) *QueryResult {
|
||||||
return this.rawReader.Lookup(ip)
|
return this.rawReader.Lookup(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *FileReader) RawReader() *Reader {
|
func (this *FileReader) RawReader() ReaderInterface {
|
||||||
return this.rawReader
|
return this.rawReader
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFileReader(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
18
pkg/iplibrary/reader_interface.go
Normal file
18
pkg/iplibrary/reader_interface.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -213,13 +213,21 @@ func (this *QueryResult) RegionSummary() string {
|
|||||||
func (this *QueryResult) realCountryId() uint16 {
|
func (this *QueryResult) realCountryId() uint16 {
|
||||||
if this.item != nil {
|
if this.item != nil {
|
||||||
switch item := this.item.(type) {
|
switch item := this.item.(type) {
|
||||||
case *ipv4Item:
|
case *ipv4ItemV1:
|
||||||
return item.Region.CountryId
|
return item.Region.CountryId
|
||||||
case ipv4Item:
|
case ipv4ItemV1:
|
||||||
return item.Region.CountryId
|
return item.Region.CountryId
|
||||||
case *ipv6Item:
|
case *ipv6ItemV1:
|
||||||
return item.Region.CountryId
|
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
|
return item.Region.CountryId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +238,21 @@ func (this *QueryResult) realCountryId() uint16 {
|
|||||||
func (this *QueryResult) realProvinceId() uint16 {
|
func (this *QueryResult) realProvinceId() uint16 {
|
||||||
if this.item != nil {
|
if this.item != nil {
|
||||||
switch item := this.item.(type) {
|
switch item := this.item.(type) {
|
||||||
case *ipv4Item:
|
case *ipv4ItemV1:
|
||||||
return item.Region.ProvinceId
|
return item.Region.ProvinceId
|
||||||
case ipv4Item:
|
case ipv4ItemV1:
|
||||||
return item.Region.ProvinceId
|
return item.Region.ProvinceId
|
||||||
case *ipv6Item:
|
case *ipv6ItemV1:
|
||||||
return item.Region.ProvinceId
|
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
|
return item.Region.ProvinceId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,13 +263,21 @@ func (this *QueryResult) realProvinceId() uint16 {
|
|||||||
func (this *QueryResult) realCityId() uint32 {
|
func (this *QueryResult) realCityId() uint32 {
|
||||||
if this.item != nil {
|
if this.item != nil {
|
||||||
switch item := this.item.(type) {
|
switch item := this.item.(type) {
|
||||||
case *ipv4Item:
|
case *ipv4ItemV1:
|
||||||
return item.Region.CityId
|
return item.Region.CityId
|
||||||
case ipv4Item:
|
case ipv4ItemV1:
|
||||||
return item.Region.CityId
|
return item.Region.CityId
|
||||||
case *ipv6Item:
|
case *ipv6ItemV1:
|
||||||
return item.Region.CityId
|
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
|
return item.Region.CityId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,13 +288,21 @@ func (this *QueryResult) realCityId() uint32 {
|
|||||||
func (this *QueryResult) realTownId() uint32 {
|
func (this *QueryResult) realTownId() uint32 {
|
||||||
if this.item != nil {
|
if this.item != nil {
|
||||||
switch item := this.item.(type) {
|
switch item := this.item.(type) {
|
||||||
case *ipv4Item:
|
case *ipv4ItemV1:
|
||||||
return item.Region.TownId
|
return item.Region.TownId
|
||||||
case ipv4Item:
|
case ipv4ItemV1:
|
||||||
return item.Region.TownId
|
return item.Region.TownId
|
||||||
case *ipv6Item:
|
case *ipv6ItemV1:
|
||||||
return item.Region.TownId
|
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
|
return item.Region.TownId
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,13 +313,21 @@ func (this *QueryResult) realTownId() uint32 {
|
|||||||
func (this *QueryResult) realProviderId() uint16 {
|
func (this *QueryResult) realProviderId() uint16 {
|
||||||
if this.item != nil {
|
if this.item != nil {
|
||||||
switch item := this.item.(type) {
|
switch item := this.item.(type) {
|
||||||
case *ipv4Item:
|
case *ipv4ItemV1:
|
||||||
return item.Region.ProviderId
|
return item.Region.ProviderId
|
||||||
case ipv4Item:
|
case ipv4ItemV1:
|
||||||
return item.Region.ProviderId
|
return item.Region.ProviderId
|
||||||
case *ipv6Item:
|
case *ipv6ItemV1:
|
||||||
return item.Region.ProviderId
|
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
|
return item.Region.ProviderId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
func TestNewReader(t *testing.T) {
|
func TestNewReader(t *testing.T) {
|
||||||
var buf = &bytes.Buffer{}
|
var buf = &bytes.Buffer{}
|
||||||
var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{
|
var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{
|
||||||
Author: "GoEdge <https://goedge.cn>",
|
Author: "GoEdge <https://goedge.cn>",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func TestNewReader(t *testing.T) {
|
|||||||
|
|
||||||
var stat = &runtime.MemStats{}
|
var stat = &runtime.MemStats{}
|
||||||
runtime.ReadMemStats(stat)
|
runtime.ReadMemStats(stat)
|
||||||
reader, err := iplibrary.NewReader(buf)
|
reader, err := iplibrary.NewReaderV2(buf)
|
||||||
|
|
||||||
var stat2 = &runtime.MemStats{}
|
var stat2 = &runtime.MemStats{}
|
||||||
runtime.ReadMemStats(stat2)
|
runtime.ReadMemStats(stat2)
|
||||||
@@ -115,7 +115,7 @@ func BenchmarkNewReader(b *testing.B) {
|
|||||||
runtime.GOMAXPROCS(1)
|
runtime.GOMAXPROCS(1)
|
||||||
|
|
||||||
var buf = &bytes.Buffer{}
|
var buf = &bytes.Buffer{}
|
||||||
var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{
|
var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{
|
||||||
Author: "GoEdge <https://goedge.cn>",
|
Author: "GoEdge <https://goedge.cn>",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ func BenchmarkNewReader(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := iplibrary.NewReader(buf)
|
reader, err := iplibrary.NewReaderV2(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
360
pkg/iplibrary/reader_v2.go
Normal file
360
pkg/iplibrary/reader_v2.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -214,7 +214,12 @@ func (this *Updater) Loop() error {
|
|||||||
func (this *Updater) loadFile(fp *os.File) error {
|
func (this *Updater) loadFile(fp *os.File) error {
|
||||||
this.source.LogInfo("load ip library from '" + fp.Name() + "' ...")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("load ip library from reader failed: %w", err)
|
return fmt.Errorf("load ip library from reader failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,42 +3,19 @@
|
|||||||
package iplibrary
|
package iplibrary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||||
"hash"
|
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hashWriter struct {
|
type WriterV1 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 {
|
|
||||||
writer *hashWriter
|
writer *hashWriter
|
||||||
meta *Meta
|
meta *Meta
|
||||||
|
|
||||||
@@ -50,21 +27,21 @@ type Writer struct {
|
|||||||
lastProviderId int64
|
lastProviderId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWriter(writer io.Writer, meta *Meta) *Writer {
|
func NewWriterV1(writer io.Writer, meta *Meta) *WriterV1 {
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
meta = &Meta{}
|
meta = &Meta{}
|
||||||
}
|
}
|
||||||
meta.Version = Version2
|
meta.Version = Version2
|
||||||
meta.CreatedAt = time.Now().Unix()
|
meta.CreatedAt = time.Now().Unix()
|
||||||
|
|
||||||
var libWriter = &Writer{
|
var libWriter = &WriterV1{
|
||||||
writer: newHashWriter(writer),
|
writer: newHashWriter(writer),
|
||||||
meta: meta,
|
meta: meta,
|
||||||
}
|
}
|
||||||
return libWriter
|
return libWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Writer) WriteMeta() error {
|
func (this *WriterV1) WriteMeta() error {
|
||||||
metaJSON, err := json.Marshal(this.meta)
|
metaJSON, err := json.Marshal(this.meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -77,7 +54,7 @@ func (this *Writer) WriteMeta() error {
|
|||||||
return err
|
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
|
// validate IP
|
||||||
var fromIP = net.ParseIP(ipFrom)
|
var fromIP = net.ParseIP(ipFrom)
|
||||||
if fromIP == nil {
|
if fromIP == nil {
|
||||||
@@ -100,11 +77,14 @@ func (this *Writer) Write(ipFrom string, ipTo string, countryId int64, provinceI
|
|||||||
pieces = append(pieces, "")
|
pieces = append(pieces, "")
|
||||||
} else {
|
} else {
|
||||||
pieces = append(pieces, "6")
|
pieces = append(pieces, "6")
|
||||||
|
|
||||||
|
// we do NOT support v6 yet
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1
|
// 1
|
||||||
var fromIPLong = configutils.IP2Long(fromIP)
|
var fromIPLong = this.ip2long(fromIP)
|
||||||
var toIPLong = configutils.IP2Long(toIP)
|
var toIPLong = this.ip2long(toIP)
|
||||||
|
|
||||||
if toIPLong < fromIPLong {
|
if toIPLong < fromIPLong {
|
||||||
fromIPLong, toIPLong = toIPLong, fromIPLong
|
fromIPLong, toIPLong = toIPLong, fromIPLong
|
||||||
@@ -193,10 +173,25 @@ func (this *Writer) Write(ipFrom string, ipTo string, countryId int64, provinceI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Writer) Sum() string {
|
func (this *WriterV1) Sum() string {
|
||||||
return this.writer.Sum()
|
return this.writer.Sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Writer) formatUint64(i uint64) string {
|
func (this *WriterV1) formatUint64(i uint64) string {
|
||||||
return strconv.FormatUint(i, 32)
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type FileWriter struct {
|
|||||||
gzWriter *gzip.Writer
|
gzWriter *gzip.Writer
|
||||||
password string
|
password string
|
||||||
|
|
||||||
rawWriter *Writer
|
rawWriter WriterInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWriter(path string, meta *Meta, password string) (*FileWriter, error) {
|
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{
|
var writer = &FileWriter{
|
||||||
fp: fp,
|
fp: fp,
|
||||||
gzWriter: gzWriter,
|
gzWriter: gzWriter,
|
||||||
rawWriter: NewWriter(gzWriter, meta),
|
rawWriter: NewWriterV1(gzWriter, meta),
|
||||||
password: password,
|
password: password,
|
||||||
}
|
}
|
||||||
return writer, nil
|
return writer, nil
|
||||||
@@ -64,11 +64,13 @@ func (this *FileWriter) Close() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
encodedData, err := NewEncrypt().Encode(data, this.password)
|
encodedData, encodeErr := NewEncrypt().Encode(data, this.password)
|
||||||
if err != nil {
|
if encodeErr != nil {
|
||||||
return err
|
return encodeErr
|
||||||
}
|
}
|
||||||
|
_ = os.Remove(filePath)
|
||||||
err = os.WriteFile(filePath, encodedData, 0666)
|
err = os.WriteFile(filePath, encodedData, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
"github.com/iwind/TeaGo/rands"
|
"github.com/iwind/TeaGo/rands"
|
||||||
"github.com/iwind/TeaGo/types"
|
"github.com/iwind/TeaGo/types"
|
||||||
|
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewFileWriter(t *testing.T) {
|
func TestNewFileWriter(t *testing.T) {
|
||||||
writer, err := iplibrary.NewFileWriter("./internal-ip-library-test.db", &iplibrary.Meta{
|
writer, err := iplibrary.NewFileWriter("./internal-ip-library-test.db", &iplibrary.Meta{
|
||||||
Author: "GoEdge",
|
Author: "GoEdge",
|
||||||
}, "")
|
}, stringutil.Md5("123456"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
9
pkg/iplibrary/writer_interface.go
Normal file
9
pkg/iplibrary/writer_interface.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
190
pkg/iplibrary/writer_v2.go
Normal file
190
pkg/iplibrary/writer_v2.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -5,12 +5,14 @@ package iplibrary_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewWriter(t *testing.T) {
|
func TestNewWriter(t *testing.T) {
|
||||||
|
//write
|
||||||
var buf = &bytes.Buffer{}
|
var buf = &bytes.Buffer{}
|
||||||
var writer = iplibrary.NewWriter(buf, &iplibrary.Meta{
|
var writer = iplibrary.NewWriterV1(buf, &iplibrary.Meta{
|
||||||
Author: "GoEdge <https://goedge.cn>",
|
Author: "GoEdge <https://goedge.cn>",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -39,6 +41,27 @@ func TestNewWriter(t *testing.T) {
|
|||||||
t.Fatal(err)
|
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(buf.String())
|
||||||
t.Log("sum:", writer.Sum())
|
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
|
||||||
}
|
}
|
||||||
216
pkg/iputils/ip.go
Normal file
216
pkg/iputils/ip.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
230
pkg/iputils/ip_test.go
Normal file
230
pkg/iputils/ip_test.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user