251 lines
4.9 KiB
Go
251 lines
4.9 KiB
Go
|
package ipdb
|
||
|
|
||
|
import (
|
||
|
"os"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"encoding/json"
|
||
|
"io/ioutil"
|
||
|
"net"
|
||
|
"strings"
|
||
|
"reflect"
|
||
|
"unsafe"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const IPv4 = 0x01
|
||
|
const IPv6 = 0x02
|
||
|
|
||
|
var (
|
||
|
ErrFileSize = errors.New("IP Database file size error.")
|
||
|
ErrMetaData = errors.New("IP Database metadata error.")
|
||
|
ErrReadFull = errors.New("IP Database ReadFull error.")
|
||
|
|
||
|
ErrDatabaseError = errors.New("database error")
|
||
|
|
||
|
ErrIPFormat = errors.New("Query IP Format error.")
|
||
|
|
||
|
ErrNoSupportLanguage = errors.New("language not support")
|
||
|
ErrNoSupportIPv4 = errors.New("IPv4 not support")
|
||
|
ErrNoSupportIPv6 = errors.New("IPv6 not support")
|
||
|
|
||
|
ErrDataNotExists = errors.New("data is not exists")
|
||
|
)
|
||
|
|
||
|
type MetaData struct {
|
||
|
Build int64 `json:"build"`
|
||
|
IPVersion uint16 `json:"ip_version"`
|
||
|
Languages map[string]int `json:"languages"`
|
||
|
NodeCount int `json:"node_count"`
|
||
|
TotalSize int `json:"total_size"`
|
||
|
Fields []string `json:"fields"`
|
||
|
}
|
||
|
|
||
|
type reader struct {
|
||
|
fileSize int
|
||
|
nodeCount int
|
||
|
v4offset int
|
||
|
|
||
|
meta MetaData
|
||
|
data []byte
|
||
|
|
||
|
refType map[string]string
|
||
|
}
|
||
|
|
||
|
func newReader(name string, obj interface{}) (*reader, error) {
|
||
|
var err error
|
||
|
var fileInfo os.FileInfo
|
||
|
fileInfo, err = os.Stat(name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
fileSize := int(fileInfo.Size())
|
||
|
|
||
|
body, err := ioutil.ReadFile(name)
|
||
|
if err != nil {
|
||
|
return nil, ErrReadFull
|
||
|
}
|
||
|
var meta MetaData
|
||
|
metaLength := int(binary.BigEndian.Uint32(body[0:4]))
|
||
|
if err := json.Unmarshal(body[4:4+metaLength], &meta); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if len(meta.Languages) == 0 || len(meta.Fields) == 0 {
|
||
|
return nil, ErrMetaData
|
||
|
}
|
||
|
if fileSize != (4+metaLength+meta.TotalSize) {
|
||
|
return nil, ErrFileSize
|
||
|
}
|
||
|
|
||
|
var dm map[string]string
|
||
|
if obj != nil {
|
||
|
t := reflect.TypeOf(obj).Elem()
|
||
|
dm = make(map[string]string, t.NumField())
|
||
|
for i := 0; i < t.NumField(); i++ {
|
||
|
k := t.Field(i).Tag.Get("json")
|
||
|
dm[k] = t.Field(i).Name
|
||
|
}
|
||
|
}
|
||
|
|
||
|
db := &reader{
|
||
|
fileSize: fileSize,
|
||
|
nodeCount: meta.NodeCount,
|
||
|
|
||
|
meta:meta,
|
||
|
refType: dm,
|
||
|
|
||
|
data: body[4+metaLength:],
|
||
|
}
|
||
|
|
||
|
if db.v4offset == 0 {
|
||
|
node := 0
|
||
|
for i := 0; i < 96 && node < db.nodeCount; i++ {
|
||
|
if i >= 80 {
|
||
|
node = db.readNode(node, 1)
|
||
|
} else {
|
||
|
node = db.readNode(node, 0)
|
||
|
}
|
||
|
}
|
||
|
db.v4offset = node
|
||
|
}
|
||
|
|
||
|
return db, nil
|
||
|
}
|
||
|
|
||
|
func (db *reader) Find(addr, language string) ([]string, error) {
|
||
|
return db.find1(addr, language)
|
||
|
}
|
||
|
|
||
|
func (db *reader) FindMap(addr, language string) (map[string]string, error) {
|
||
|
|
||
|
data, err := db.find1(addr, language)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
info := make(map[string]string, len(db.meta.Fields))
|
||
|
for k, v := range data {
|
||
|
info[db.meta.Fields[k]] = v
|
||
|
}
|
||
|
|
||
|
return info, nil
|
||
|
}
|
||
|
|
||
|
func (db *reader) find0(addr string) ([]byte, error) {
|
||
|
|
||
|
var err error
|
||
|
var node int
|
||
|
ipv := net.ParseIP(addr)
|
||
|
if ip := ipv.To4(); ip != nil {
|
||
|
if !db.IsIPv4Support() {
|
||
|
return nil, ErrNoSupportIPv4
|
||
|
}
|
||
|
|
||
|
node, err = db.search(ip, 32)
|
||
|
} else if ip := ipv.To16(); ip != nil {
|
||
|
if !db.IsIPv6Support() {
|
||
|
return nil, ErrNoSupportIPv6
|
||
|
}
|
||
|
|
||
|
node, err = db.search(ip, 128)
|
||
|
} else {
|
||
|
return nil, ErrIPFormat
|
||
|
}
|
||
|
|
||
|
if err != nil || node < 0 {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
body, err := db.resolve(node)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return body, nil
|
||
|
}
|
||
|
|
||
|
func (db *reader) find1(addr, language string) ([]string, error) {
|
||
|
|
||
|
off, ok := db.meta.Languages[language]
|
||
|
if !ok {
|
||
|
return nil, ErrNoSupportLanguage
|
||
|
}
|
||
|
|
||
|
body, err := db.find0(addr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
str := (*string)(unsafe.Pointer(&body))
|
||
|
tmp := strings.Split(*str, "\t")
|
||
|
|
||
|
if (off + len(db.meta.Fields)) > len(tmp) {
|
||
|
return nil, ErrDatabaseError
|
||
|
}
|
||
|
|
||
|
return tmp[off:off+len(db.meta.Fields)], nil
|
||
|
}
|
||
|
|
||
|
func (db *reader) search(ip net.IP, bitCount int) (int, error) {
|
||
|
|
||
|
var node int
|
||
|
|
||
|
if bitCount == 32 {
|
||
|
node = db.v4offset
|
||
|
} else {
|
||
|
node = 0;
|
||
|
}
|
||
|
|
||
|
for i := 0; i < bitCount; i++ {
|
||
|
if node > db.nodeCount {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
node = db.readNode(node, ((0xFF & int(ip[i >> 3])) >> uint(7 - (i % 8))) & 1)
|
||
|
}
|
||
|
|
||
|
if node > db.nodeCount {
|
||
|
return node, nil
|
||
|
}
|
||
|
|
||
|
return -1, ErrDataNotExists
|
||
|
}
|
||
|
|
||
|
func (db *reader) readNode(node, index int) int {
|
||
|
off := node * 8 + index * 4
|
||
|
return int(binary.BigEndian.Uint32(db.data[off:off+4]))
|
||
|
}
|
||
|
|
||
|
func (db *reader) resolve(node int) ([]byte, error) {
|
||
|
resolved := node - db.nodeCount + db.nodeCount * 8
|
||
|
if resolved >= db.fileSize {
|
||
|
return nil, ErrDatabaseError
|
||
|
}
|
||
|
|
||
|
size := int(binary.BigEndian.Uint16(db.data[resolved:resolved+2]))
|
||
|
if (resolved+2+size) > len(db.data) {
|
||
|
return nil, ErrDatabaseError
|
||
|
}
|
||
|
bytes := db.data[resolved+2:resolved+2+size]
|
||
|
|
||
|
return bytes, nil
|
||
|
}
|
||
|
|
||
|
func (db *reader) IsIPv4Support() bool {
|
||
|
return (int(db.meta.IPVersion) & IPv4) == IPv4
|
||
|
}
|
||
|
|
||
|
func (db *reader) IsIPv6Support() bool {
|
||
|
return (int(db.meta.IPVersion) & IPv6) == IPv6
|
||
|
}
|
||
|
|
||
|
func (db *reader) Build() time.Time {
|
||
|
return time.Unix(db.meta.Build, 0).In(time.UTC)
|
||
|
}
|
||
|
|
||
|
func (db *reader) Languages() []string {
|
||
|
ls := make([]string, 0, len(db.meta.Languages))
|
||
|
for k := range db.meta.Languages {
|
||
|
ls = append(ls, k)
|
||
|
}
|
||
|
return ls
|
||
|
}
|