version expected for xdb gen and auto version detect for search test

This commit is contained in:
lion 2025-09-04 11:14:58 +08:00
parent b830361c52
commit c0058781c9
5 changed files with 107 additions and 25 deletions

View File

@ -6,6 +6,7 @@ package main
import ( import (
"bufio" "bufio"
"encoding/binary"
"fmt" "fmt"
"log" "log"
"log/slog" "log/slog"
@ -23,7 +24,7 @@ func printHelp() {
fmt.Printf("ip2region xdb maker\n") fmt.Printf("ip2region xdb maker\n")
fmt.Printf("%s [command] [command options]\n", os.Args[0]) fmt.Printf("%s [command] [command options]\n", os.Args[0])
fmt.Printf("Command: \n") fmt.Printf("Command: \n")
fmt.Printf(" gen generate the binary db file\n") fmt.Printf(" gen generate the binary xdb file\n")
fmt.Printf(" search binary xdb search test\n") fmt.Printf(" search binary xdb search test\n")
fmt.Printf(" bench binary xdb bench test\n") fmt.Printf(" bench binary xdb bench test\n")
fmt.Printf(" edit edit the source ip data\n") fmt.Printf(" edit edit the source ip data\n")
@ -152,7 +153,7 @@ func getFilterFields(fieldList string) ([]int, error) {
func genDb() { func genDb() {
var err error var err error
var srcFile, dstFile = "", "" var srcFile, dstFile = "", ""
var ipVersion, fieldList, logLevel = "ipv4", "", "info" var ipVersion, fieldList, logLevel = "", "", "info"
var indexPolicy = xdb.VectorIndexPolicy var indexPolicy = xdb.VectorIndexPolicy
var fErr = iterateFlags(func(key string, val string) error { var fErr = iterateFlags(func(key string, val string) error {
switch key { switch key {
@ -187,12 +188,23 @@ func genDb() {
fmt.Printf("options:\n") fmt.Printf("options:\n")
fmt.Printf(" --src string source ip text file path\n") fmt.Printf(" --src string source ip text file path\n")
fmt.Printf(" --dst string destination binary xdb file path\n") fmt.Printf(" --dst string destination binary xdb file path\n")
fmt.Printf(" --version string IP version, options: ipv4/ipv6, default to ipv4\n") fmt.Printf(" --version string IP version, options: ipv4/ipv6, specify this flag so you don't get confused \n")
fmt.Printf(" --field-list string field index list imploded with ',' eg: 0,1,2,3-6,7\n") fmt.Printf(" --field-list string field index list imploded with ',' eg: 0,1,2,3-6,7\n")
fmt.Printf(" --log-level string set the log level, options: debug/info/warn/error\n") fmt.Printf(" --log-level string set the log level, options: debug/info/warn/error\n")
return return
} }
// check and define the IP version
var version *xdb.Version = nil
if len(ipVersion) < 2 {
slog.Error("please specify the ip version with flag --version, ipv4 or ipv6 ?")
return
} else if v, err := xdb.VersionFromName(ipVersion); err != nil {
slog.Error("failed to parse version name", "error", err)
} else {
version = v
}
// check and apply the log level // check and apply the log level
err = applyLogLevel(logLevel) err = applyLogLevel(logLevel)
if err != nil { if err != nil {
@ -206,14 +218,6 @@ func genDb() {
return return
} }
// check and define the IP version
var version *xdb.Version = nil
if v, err := xdb.VersionFromName(ipVersion); err != nil {
slog.Error("failed to parse version name", "error", err)
} else {
version = v
}
// make the binary file // make the binary file
tStart := time.Now() tStart := time.Now()
maker, err := xdb.NewMaker(version, indexPolicy, srcFile, dstFile, fields) maker, err := xdb.NewMaker(version, indexPolicy, srcFile, dstFile, fields)
@ -245,12 +249,10 @@ func genDb() {
func testSearch() { func testSearch() {
var err error var err error
var dbFile, ipVersion = "", "v4" var dbFile = ""
var fErr = iterateFlags(func(key string, val string) error { var fErr = iterateFlags(func(key string, val string) error {
if key == "db" { if key == "db" {
dbFile = val dbFile = val
} else if key == "version" {
ipVersion = val
} else { } else {
return fmt.Errorf("undefined option '%s=%s'\n", key, val) return fmt.Errorf("undefined option '%s=%s'\n", key, val)
} }
@ -265,16 +267,34 @@ func testSearch() {
fmt.Printf("%s search [command options]\n", os.Args[0]) fmt.Printf("%s search [command options]\n", os.Args[0])
fmt.Printf("options:\n") fmt.Printf("options:\n")
fmt.Printf(" --db string ip2region binary xdb file path\n") fmt.Printf(" --db string ip2region binary xdb file path\n")
fmt.Printf(" --version string IP version, options: ipv4/ipv6\n")
return return
} }
// check and parse the IP version // detect the version from the xdb header
header, err := xdb.LoadXdbHeaderFromFile(dbFile)
if err != nil {
slog.Error("failed to load xdb header", "error", err)
return
}
var version *xdb.Version = nil var version *xdb.Version = nil
if v, err := xdb.VersionFromName(ipVersion); err != nil { versionNo := binary.LittleEndian.Uint16(header[0:])
slog.Error("failed to parse version name", "error", err) if versionNo == 2 {
// old xdb file
version = xdb.V4
} else if versionNo == 3 {
ipNo := int(binary.LittleEndian.Uint16(header[16:]))
if ipNo == xdb.V4.Id {
version = xdb.V4
} else if ipNo == xdb.V6.Id {
version = xdb.V6
} else {
slog.Error("invalid ip version", "id", ipNo)
return
}
} else { } else {
version = v slog.Error("invalid xdb version", "versionNo", versionNo, "xdbFile", dbFile)
return
} }
searcher, err := xdb.NewSearcher(version, dbFile) searcher, err := xdb.NewSearcher(version, dbFile)
@ -287,10 +307,13 @@ func testSearch() {
fmt.Printf("test program exited, thanks for trying\n") fmt.Printf("test program exited, thanks for trying\n")
}() }()
fmt.Println(`ip2region xdb search test program, commands: fmt.Printf(`ip2region xdb search test program,
loadIndex : load the vector index for search speedup. source xdb: %s (%s)
clearIndex: clear the vector index. commands:
quit : exit the test program`) loadIndex : load the vector index for search speedup.
clearIndex: clear the vector index.
quit : exit the test program
`, dbFile, version.Name)
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
for { for {
fmt.Print("ip2region>> ") fmt.Print("ip2region>> ")

View File

@ -60,7 +60,7 @@ import (
) )
const ( const (
VersionNo = 3 VersionNo = 3 // since 2025/09/01 (IPv6 supporting)
HeaderInfoLength = 256 HeaderInfoLength = 256
VectorIndexRows = 256 VectorIndexRows = 256
VectorIndexCols = 256 VectorIndexCols = 256
@ -183,6 +183,11 @@ func (m *Maker) loadSegments() error {
var iErr = IterateSegments(m.srcHandle, func(l string) { var iErr = IterateSegments(m.srcHandle, func(l string) {
slog.Debug("loaded", "segment", l) slog.Debug("loaded", "segment", l)
}, func(seg *Segment) error { }, func(seg *Segment) error {
// ip version check
if len(seg.StartIP) != m.version.Bytes {
return fmt.Errorf("invalid ip segment(%s expected)", m.version.Name)
}
// check the continuity of the data segment // check the continuity of the data segment
if err := seg.AfterCheck(last); err != nil { if err := seg.AfterCheck(last); err != nil {
return err return err

View File

@ -90,6 +90,11 @@ func (s *Searcher) ClearVectorIndex() {
// Search find the region for the specified ip address // Search find the region for the specified ip address
func (s *Searcher) Search(ip []byte) (string, int, error) { func (s *Searcher) Search(ip []byte) (string, int, error) {
// version check
if len(ip) != s.version.Bytes {
return "", 0, fmt.Errorf("invalid ip address(%s expected)", s.version.Name)
}
// locate the segment index block based on the vector index // locate the segment index block based on the vector index
var ioCount = 0 var ioCount = 0
var il0, il1, bytes, tBytes = int(ip[0]), int(ip[1]), len(ip), len(ip) << 1 var il0, il1, bytes, tBytes = int(ip[0]), int(ip[1]), len(ip), len(ip) << 1
@ -180,3 +185,42 @@ func (s *Searcher) Search(ip []byte) (string, int, error) {
return string(regionBuff), ioCount, nil return string(regionBuff), ioCount, nil
} }
// LoadXdbHeader load the header info from the specified handle
func LoadXdbHeader(handle *os.File) ([]byte, error) {
_, err := handle.Seek(0, 0)
if err != nil {
return nil, fmt.Errorf("seek to the header: %w", err)
}
var buff = make([]byte, HeaderInfoLength)
rLen, err := handle.Read(buff)
if err != nil {
return nil, err
}
if rLen != len(buff) {
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
}
return buff, nil
}
// LoadXdbHeaderFromFile load header info from the specified db file path
func LoadXdbHeaderFromFile(dbFile string) ([]byte, error) {
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
if err != nil {
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
}
defer func(handle *os.File) {
_ = handle.Close()
}(handle)
header, err := LoadXdbHeader(handle)
if err != nil {
return nil, err
}
return header, nil
}

View File

@ -132,6 +132,10 @@ func IterateSegments(handle *os.File, before func(l string), cb func(seg *Segmen
return fmt.Errorf("check end ip `%s`: %s", ps[1], err) return fmt.Errorf("check end ip `%s`: %s", ps[1], err)
} }
if len(sip) != len(eip) {
return fmt.Errorf("invalid ip segment line `%s`, sip/eip version not match", l)
}
if IPCompare(sip, eip) > 0 { if IPCompare(sip, eip) > 0 {
return fmt.Errorf("start ip(%s) should not be greater than end ip(%s)", ps[0], ps[1]) return fmt.Errorf("start ip(%s) should not be greater than end ip(%s)", ps[0], ps[1])
} }

View File

@ -11,6 +11,8 @@ import (
type Version struct { type Version struct {
Id int Id int
Name string
Bytes int
SegmentIndexSize int SegmentIndexSize int
} }
@ -18,15 +20,19 @@ var (
VX = &Version{} VX = &Version{}
V4 = &Version{ V4 = &Version{
Id: 4, Id: 4,
Name: "IPv4",
Bytes: 4,
SegmentIndexSize: 14, // 4 + 4 + 2 + 4 SegmentIndexSize: 14, // 4 + 4 + 2 + 4
} }
V6 = &Version{ V6 = &Version{
Id: 6, Id: 6,
Name: "IPv6",
Bytes: 16,
SegmentIndexSize: 38, // 16 + 16 + 2 + 4 SegmentIndexSize: 38, // 16 + 16 + 2 + 4
} }
) )
func VersionFromData(ip string) (*Version, error) { func VersionFromIP(ip string) (*Version, error) {
bytes, err := ParseIP(ip) bytes, err := ParseIP(ip)
if err != nil { if err != nil {
return VX, fmt.Errorf("parse ip fail: %w", err) return VX, fmt.Errorf("parse ip fail: %w", err)