Files
go-utils/geoip/qqwry/qqwry.go
2023-10-26 19:51:23 +08:00

220 lines
4.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package qqwry
import (
"encoding/binary"
"errors"
"github.com/tx7do/go-utils/geoip"
"net"
"strings"
"sync"
"github.com/tx7do/go-utils/geoip/qqwry/assets"
)
type Client struct {
data []byte
dataLen uint32
ipCache sync.Map
IPNum int64
startPos uint32
endPos uint32
}
func NewClient() *Client {
cli := &Client{
ipCache: sync.Map{},
}
cli.init()
return cli
}
func (c *Client) init() {
c.startPos, c.endPos = c.readHeader()
c.IPNum = int64((c.endPos-c.startPos)/ipRecordLength + 1)
}
// parseIp 解析IP
func (c *Client) parseIp(queryIp string) (uint32, error) {
ip := net.ParseIP(queryIp).To4()
if ip == nil {
return 0, errors.New("ip is not ipv4")
}
ip32 := binary.BigEndian.Uint32(ip)
return ip32, nil
}
// readHeader 读取文件头
func (c *Client) readHeader() (uint32, uint32) {
startPos := binary.LittleEndian.Uint32(assets.QQWryDat[:4])
endPos := binary.LittleEndian.Uint32(assets.QQWryDat[4:8])
return startPos, endPos
}
// readMode 获取偏移值类型
func (c *Client) readMode(offset uint32) byte {
return assets.QQWryDat[offset]
}
// readIpRecord 读取IP记录 前4字节起始IP后3字节偏移量
func (c *Client) readIpRecord(offset uint32) (ip32 uint32, ipOffset uint32) {
buf := assets.QQWryDat[offset : offset+ipRecordLength]
ip32 = binary.LittleEndian.Uint32(buf[:4])
ipOffset = byte3ToUInt32(buf[4:])
return ip32, ipOffset
}
// locateIP 定位IP
func (c *Client) locateIP(ip32 uint32) int32 {
var _ip32 uint32
var _ipOffset uint32
var offset uint32
var mid uint32
i := c.startPos
j := c.endPos
for {
mid = getMiddleOffset(i, j)
_ip32, _ipOffset = c.readIpRecord(mid)
if j-i == ipRecordLength {
offset = _ipOffset
_ip32, _ipOffset = c.readIpRecord(mid + ipRecordLength)
if ip32 < _ip32 {
break
} else {
offset = 0
break
}
}
if _ip32 > ip32 {
j = mid
} else if _ip32 < ip32 {
i = mid
} else if _ip32 == ip32 {
offset = _ipOffset
break
}
}
return int32(offset)
}
// readArea 读取区域
func (c *Client) readArea(offset uint32) []byte {
mode := c.readMode(offset)
if mode == redirectMode1 || mode == redirectMode2 {
areaOffset := c.readUInt24(int32(offset) + 1)
if areaOffset == 0 {
return []byte{}
}
return c.readString(areaOffset)
}
return c.readString(offset)
}
// readString 获取字符串
func (c *Client) readString(offset uint32) []byte {
data := make([]byte, 0, 30)
for i := offset; i < uint32(len(assets.QQWryDat)); i++ {
if assets.QQWryDat[i] == 0 {
data = assets.QQWryDat[offset:i]
break
}
}
return data
}
func (c *Client) readUInt24(offset int32) uint32 {
i := uint32(assets.QQWryDat[offset+0]) & 0xFF
i |= (uint32(assets.QQWryDat[offset+1]) << 8) & 0xFF00
i |= (uint32(assets.QQWryDat[offset+2]) << 16) & 0xFF0000
return i
}
func (c *Client) Query(queryIp string) (res geoip.Result, err error) {
res.IP = queryIp
res.Country = "中国"
ip32, err := c.parseIp(queryIp)
if err != nil {
return
}
offset := c.locateIP(ip32)
if offset <= 0 {
err = errors.New("ip not found")
return
}
//读取第一个字节判断是否是标志字节
offset += 4
mode := c.readMode(uint32(offset))
var _area []byte
var area string
var ispPos uint32
switch mode {
case redirectMode1:
posC := c.readUInt24(offset + 1)
mode = c.readMode(posC)
posCA := posC
if mode == redirectMode2 {
posCA = c.readUInt24(int32(posC) + 1)
posC += 4
}
_area = c.readString(posCA)
if mode != redirectMode2 {
posC += uint32(len(area) + 1)
}
ispPos = posC
case redirectMode2:
posCA := c.readUInt24(offset + 1)
_area = c.readString(posCA)
ispPos = uint32(offset) + 4
default:
posCA := offset + 0
_area = c.readString(uint32(posCA))
ispPos = uint32(offset) + uint32(len(area)) + 1
}
if len(_area) != 0 {
area = strings.TrimSpace(gb18030Decode(_area))
areas := SpiltAddress(area)
if len(areas) == 2 {
res.Province = areas[0]
res.City = areas[1]
} else if len(areas) == 1 {
res.City = areas[0]
} else {
res.City = area
}
}
ispMode := assets.QQWryDat[ispPos]
if ispMode == redirectMode1 || ispMode == redirectMode2 {
ispPos = c.readUInt24(int32(ispPos + 1))
}
if ispPos > 0 {
var _isp []byte
_isp = c.readString(ispPos)
res.ISP = strings.TrimSpace(gb18030Decode(_isp))
if res.ISP != "" {
if strings.Contains(res.ISP, "CZ88.NET") {
res.ISP = ""
}
}
}
return
}