diff --git a/analyse.go b/analyse.go index 10fcab7..d61d496 100644 --- a/analyse.go +++ b/analyse.go @@ -1,53 +1,47 @@ package goip import ( + "go.dtapp.net/goip/geoip" + "go.dtapp.net/goip/ip2region" + "go.dtapp.net/goip/ip2region_v2" + "go.dtapp.net/goip/ipv6wry" + "go.dtapp.net/goip/qqwry" + "net" "strconv" ) -var ( - ipv4 = "IPV4" - ipv6 = "IPV6" -) - type AnalyseResult struct { - IP string `json:"ip,omitempty"` // 输入的ip地址 - Country string `json:"country,omitempty"` // 国家或地区 - Province string `json:"province,omitempty"` // 省份 - City string `json:"city,omitempty"` // 城市 - Area string `json:"area,omitempty"` // 区域 - Isp string `json:"isp,omitempty"` // 运营商 + Ip string `json:"ip,omitempty"` + QqwryInfo qqwry.QueryResult `json:"qqwry_info"` + Ip2regionInfo ip2region.QueryResult `json:"ip2region_info"` + Ip2regionV2info ip2region_v2.QueryResult `json:"ip2regionv2_info"` + GeoipInfo geoip.QueryCityResult `json:"geoip_info"` + Ipv6wryInfo ipv6wry.QueryResult `json:"ipv6wry_info"` } func (c *Client) Analyse(item string) AnalyseResult { isIp := c.isIpv4OrIpv6(item) + ipByte := net.ParseIP(item) switch isIp { case ipv4: - info := c.V4db.Find(item) - search, err := c.V4Region.MemorySearch(item) - if err != nil { - return AnalyseResult{ - IP: info.IP, - Country: info.Country, - Area: info.Area, - } - } else { - return AnalyseResult{ - IP: search.IP, - Country: search.Country, - Province: search.Province, - City: search.City, - Isp: info.Area, - } + qqeryInfo, _ := c.QueryQqWry(ipByte) + ip2regionInfo, _ := c.QueryIp2Region(ipByte) + ip2regionV2Info, _ := c.QueryIp2RegionV2(ipByte) + geoipInfo, _ := c.QueryGeoIp(ipByte) + return AnalyseResult{ + Ip: ipByte.String(), + QqwryInfo: qqeryInfo, + Ip2regionInfo: ip2regionInfo, + Ip2regionV2info: ip2regionV2Info, + GeoipInfo: geoipInfo, } case ipv6: - info := c.V6db.Find(item) + geoipInfo, _ := c.QueryGeoIp(ipByte) + ipv6Info, _ := c.QueryIpv6wry(ipByte) return AnalyseResult{ - IP: info.IP, - Country: info.Country, - Province: info.Province, - City: info.City, - Area: info.Area, - Isp: info.Isp, + Ip: ipByte.String(), + GeoipInfo: geoipInfo, + Ipv6wryInfo: ipv6Info, } default: return AnalyseResult{} diff --git a/client.go b/client.go new file mode 100644 index 0000000..20f3527 --- /dev/null +++ b/client.go @@ -0,0 +1,39 @@ +package goip + +import ( + "go.dtapp.net/goip/geoip" + "go.dtapp.net/goip/ip2region" + "go.dtapp.net/goip/ip2region_v2" + "go.dtapp.net/goip/ipv6wry" + "go.dtapp.net/goip/qqwry" +) + +type Client struct { + ip2regionV2Client *ip2region_v2.Client + ip2regionClient *ip2region.Client + qqwryClient *qqwry.Client + geoIpClient *geoip.Client + ipv6wryClient *ipv6wry.Client +} + +// NewIp 实例化 +func NewIp() *Client { + + c := &Client{} + + c.ip2regionV2Client, _ = ip2region_v2.New() + + c.ip2regionClient = ip2region.New() + + c.qqwryClient = qqwry.New() + + c.geoIpClient, _ = geoip.New() + + c.ipv6wryClient = ipv6wry.New() + + return c +} + +func (c *Client) Close() { + c.geoIpClient.Close() +} diff --git a/const.go b/const.go index adc4612..611662b 100644 --- a/const.go +++ b/const.go @@ -1,3 +1,3 @@ package goip -const Version = "1.0.32" +const Version = "1.0.33" diff --git a/geoip/client.go b/geoip/client.go index 1e220fe..3bb9d01 100644 --- a/geoip/client.go +++ b/geoip/client.go @@ -1,9 +1,19 @@ package geoip import ( + _ "embed" "github.com/oschwald/geoip2-golang" ) +//go:embed GeoLite2-ASN.mmdb +var asnBuff []byte + +//go:embed GeoLite2-City.mmdb +var cityBuff []byte + +//go:embed GeoLite2-Country.mmdb +var countryBuff []byte + type Client struct { asnDb *geoip2.Reader cityDb *geoip2.Reader diff --git a/geoip/query.go b/geoip/query.go index 38b1ad3..e9b34a8 100644 --- a/geoip/query.go +++ b/geoip/query.go @@ -5,15 +5,6 @@ import ( "net" ) -//go:embed GeoLite2-ASN.mmdb -var asnBuff []byte - -//go:embed GeoLite2-City.mmdb -var cityBuff []byte - -//go:embed GeoLite2-Country.mmdb -var countryBuff []byte - // QueryCityResult 返回 type QueryCityResult struct { Ip string `json:"ip,omitempty"` // ip diff --git a/goip.go b/goip.go deleted file mode 100644 index 3c0faf5..0000000 --- a/goip.go +++ /dev/null @@ -1,80 +0,0 @@ -package goip - -import ( - "go.dtapp.net/goip/geoip" - "go.dtapp.net/goip/ip2region" - "go.dtapp.net/goip/ip2region_v2" - v4 "go.dtapp.net/goip/v4" - v6 "go.dtapp.net/goip/v6" - "log" - "strings" -) - -type Client struct { - V4Region ip2region.Ip2Region // IPV4 - V4db v4.Pointer // IPV4 - V6db v6.Pointer // IPV6 - ip2regionV2Client *ip2region_v2.Client - geoIpClient *geoip.Client - GeoIpLicenseKey string -} - -// NewIp 实例化 -func NewIp() *Client { - - c := &Client{} - - v4Num := c.V4db.InitIPV4Data() - log.Printf("IPV4 库加载完成 共加载:%d 条 IP 记录\n", v4Num) - - v6Num := c.V6db.InitIPV4Data() - log.Printf("IPV6 库加载完成 共加载:%d 条 IP 记录\n", v6Num) - - c.ip2regionV2Client, _ = ip2region_v2.New() - - c.geoIpClient, _ = geoip.New() - - return c -} - -func (c *Client) Close() { - c.geoIpClient.Close() -} - -func (c *Client) Ipv4(ip string) (res v4.Result, resInfo ip2region.IpInfo) { - res = c.V4db.Find(ip) - resInfo, _ = c.V4Region.MemorySearch(ip) - return res, resInfo -} - -func (c *Client) Ipv6(ip string) (res v6.Result) { - res = c.V6db.Find(ip) - return res -} - -func (c *Client) isIpv4OrIpv6(ip string) string { - if len(ip) < 7 { - return "" - } - arrIpv4 := strings.Split(ip, ".") - if len(arrIpv4) == 4 { - //. 判断IPv4 - for _, val := range arrIpv4 { - if !c.CheckIpv4(val) { - return "" - } - } - return ipv4 - } - arrIpv6 := strings.Split(ip, ":") - if len(arrIpv6) == 8 { - // 判断Ipv6 - for _, val := range arrIpv6 { - if !c.CheckIpv6(val) { - return "Neither" - } - } - return ipv6 - } - return "" -} diff --git a/ip.go b/ip.go index 28613e9..158c4bb 100644 --- a/ip.go +++ b/ip.go @@ -9,6 +9,7 @@ import ( // GetInsideIp 内网ip func GetInsideIp(ctx context.Context) string { + conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { panic(err) @@ -49,27 +50,25 @@ func Ips(ctx context.Context) (map[string]string, error) { var respGetOutsideIp struct { Data struct { - Ip string `json:"ip"` + Ip string `json:"ip,omitempty"` } `json:"data"` } // GetOutsideIp 外网ip -func GetOutsideIp(ctx context.Context) (ip string) { - ip = "0.0.0.0" - get := gorequest.NewHttp() - get.SetUri("https://api.dtapp.net/ip") - response, err := get.Get(ctx) +func GetOutsideIp(ctx context.Context) string { + // 请求 + getHttp := gorequest.NewHttp() + getHttp.SetUri("https://api.dtapp.net/ip") + response, err := getHttp.Get(ctx) if err != nil { - return + return "0.0.0.0" } + // 解析 err = json.Unmarshal(response.ResponseBody, &respGetOutsideIp) if err != nil { - return - } - if respGetOutsideIp.Data.Ip == "" { - return + return "0.0.0.0" } - ip = respGetOutsideIp.Data.Ip + respGetOutsideIp.Data.Ip = "0.0.0.0" return respGetOutsideIp.Data.Ip } diff --git a/ip2region/client.go b/ip2region/client.go new file mode 100644 index 0000000..19c13c6 --- /dev/null +++ b/ip2region/client.go @@ -0,0 +1,103 @@ +package ip2region + +import ( + _ "embed" + "errors" + "go.dtapp.net/gostring" + "os" + "strconv" + "strings" +) + +const ( + IndexBlockLength = 12 +) + +//go:embed ip2region.db +var dbBuff []byte + +type Client struct { + // db file handler + dbFileHandler *os.File + + //header block info + + headerSip []int64 + headerPtr []int64 + headerLen int64 + + // super block index info + firstIndexPtr int64 + lastIndexPtr int64 + totalBlocks int64 + + // for memory mode only + // the original db binary string + + dbFile string +} + +func New() *Client { + + c := &Client{} + + return c +} + +// 获取Ip信息 +func getIpInfo(ipStr string, cityId int64, line []byte) (result QueryResult) { + + lineSlice := strings.Split(string(line), "|") + length := len(lineSlice) + result.CityId = cityId + if length < 5 { + for i := 0; i <= 5-length; i++ { + lineSlice = append(lineSlice, "") + } + } + + if lineSlice[0] != "0" { + result.Country = gostring.SpaceAndLineBreak(lineSlice[0]) + } + if lineSlice[1] != "0" { + result.Region = gostring.SpaceAndLineBreak(lineSlice[1]) + } + if lineSlice[2] != "0" { + result.Province = gostring.SpaceAndLineBreak(lineSlice[2]) + } + if lineSlice[3] != "0" { + result.City = gostring.SpaceAndLineBreak(lineSlice[3]) + } + if lineSlice[4] != "0" { + result.Isp = gostring.SpaceAndLineBreak(lineSlice[4]) + } + + result.Ip = ipStr + return result +} + +func getLong(b []byte, offset int64) int64 { + + val := int64(b[offset]) | + int64(b[offset+1])<<8 | + int64(b[offset+2])<<16 | + int64(b[offset+3])<<24 + + return val + +} + +func ip2long(IpStr string) (int64, error) { + bits := strings.Split(IpStr, ".") + if len(bits) != 4 { + return 0, errors.New("ip format error") + } + + var sum int64 + for i, n := range bits { + bit, _ := strconv.ParseInt(n, 10, 64) + sum += bit << uint(24-8*i) + } + + return sum, nil +} diff --git a/ip2region/ip2region.go b/ip2region/ip2region.go deleted file mode 100644 index 00785f2..0000000 --- a/ip2region/ip2region.go +++ /dev/null @@ -1,168 +0,0 @@ -package ip2region - -import ( - _ "embed" - "errors" - "go.dtapp.net/gostring" - "net" - "os" - "strconv" - "strings" -) - -const ( - IndexBlockLength = 12 -) - -type Ip2Region struct { - // db file handler - dbFileHandler *os.File - - //header block info - - headerSip []int64 - headerPtr []int64 - headerLen int64 - - // super block index info - firstIndexPtr int64 - lastIndexPtr int64 - totalBlocks int64 - - // for memory mode only - // the original db binary string - - dbFile string -} - -//go:embed ip2region.db -var dbBinStr []byte - -type IpInfo struct { - IP string `json:"ip,omitempty"` // 输入的ip地址 - CityID int64 `json:"city_id,omitempty"` // 城市ID - Country string `json:"country,omitempty"` // 国家 - Region string `json:"region,omitempty"` // 区域 - Province string `json:"province,omitempty"` // 省份 - City string `json:"city,omitempty"` // 城市 - ISP string `json:"isp,omitempty"` // 运营商 -} - -func (ip IpInfo) String() string { - return ip.IP + "|" + strconv.FormatInt(ip.CityID, 10) + "|" + ip.Country + "|" + ip.Region + "|" + ip.Province + "|" + ip.City + "|" + ip.ISP -} - -// 获取Ip信息 -func getIpInfo(ipStr string, cityId int64, line []byte) (ipInfo IpInfo) { - - lineSlice := strings.Split(string(line), "|") - length := len(lineSlice) - ipInfo.CityID = cityId - if length < 5 { - for i := 0; i <= 5-length; i++ { - lineSlice = append(lineSlice, "") - } - } - - if lineSlice[0] != "0" { - ipInfo.Country = gostring.SpaceAndLineBreak(lineSlice[0]) - } - if lineSlice[1] != "0" { - ipInfo.Region = gostring.SpaceAndLineBreak(lineSlice[1]) - } - if lineSlice[2] != "0" { - ipInfo.Province = gostring.SpaceAndLineBreak(lineSlice[2]) - } - if lineSlice[3] != "0" { - ipInfo.City = gostring.SpaceAndLineBreak(lineSlice[3]) - } - if lineSlice[4] != "0" { - ipInfo.ISP = gostring.SpaceAndLineBreak(lineSlice[4]) - } - - ipInfo.IP = ipStr - return ipInfo -} - -// MemorySearch memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内 -func (r *Ip2Region) MemorySearch(ipStr string) (ipInfo IpInfo, err error) { - - ipInfo.IP = ipStr - if net.ParseIP(ipStr).To4() == nil { - if net.ParseIP(ipStr).To16() == nil { - return ipInfo, err - } - } - - if r.totalBlocks == 0 { - - if err != nil { - - return ipInfo, err - } - - r.firstIndexPtr = getLong(dbBinStr, 0) - r.lastIndexPtr = getLong(dbBinStr, 4) - r.totalBlocks = (r.lastIndexPtr-r.firstIndexPtr)/IndexBlockLength + 1 - } - - ip, err := ip2long(ipStr) - if err != nil { - return ipInfo, err - } - - h := r.totalBlocks - var dataPtr, l int64 - for l <= h { - - m := (l + h) >> 1 - p := r.firstIndexPtr + m*IndexBlockLength - sip := getLong(dbBinStr, p) - if ip < sip { - h = m - 1 - } else { - eip := getLong(dbBinStr, p+4) - if ip > eip { - l = m + 1 - } else { - dataPtr = getLong(dbBinStr, p+8) - break - } - } - } - if dataPtr == 0 { - return ipInfo, errors.New("not found") - } - - dataLen := (dataPtr >> 24) & 0xFF - dataPtr = dataPtr & 0x00FFFFFF - ipInfo = getIpInfo(ipStr, getLong(dbBinStr, dataPtr), dbBinStr[(dataPtr)+4:dataPtr+dataLen]) - return ipInfo, nil - -} - -func getLong(b []byte, offset int64) int64 { - - val := int64(b[offset]) | - int64(b[offset+1])<<8 | - int64(b[offset+2])<<16 | - int64(b[offset+3])<<24 - - return val - -} - -func ip2long(IpStr string) (int64, error) { - bits := strings.Split(IpStr, ".") - if len(bits) != 4 { - return 0, errors.New("ip format error") - } - - var sum int64 - for i, n := range bits { - bit, _ := strconv.ParseInt(n, 10, 64) - sum += bit << uint(24-8*i) - } - - return sum, nil -} diff --git a/ip2region/qqery.go b/ip2region/qqery.go new file mode 100644 index 0000000..9e359ef --- /dev/null +++ b/ip2region/qqery.go @@ -0,0 +1,73 @@ +package ip2region + +import ( + "errors" + "net" + "strconv" +) + +type QueryResult struct { + Ip string `json:"ip,omitempty"` // ip + CityId int64 `json:"city_id,omitempty"` // 城市代码 + Country string `json:"country,omitempty"` // 国家 + Region string `json:"region,omitempty"` // 区域 + Province string `json:"province,omitempty"` // 省份 + City string `json:"city,omitempty"` // 城市 + Isp string `json:"isp,omitempty"` // 运营商 +} + +func (ip QueryResult) String() string { + return ip.Ip + "|" + strconv.FormatInt(ip.CityId, 10) + "|" + ip.Country + "|" + ip.Region + "|" + ip.Province + "|" + ip.City + "|" + ip.Isp +} + +// Query memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内 +func (c *Client) Query(ipAddress net.IP) (result QueryResult, err error) { + + result.Ip = ipAddress.String() + + if c.totalBlocks == 0 { + + if err != nil { + + return result, err + } + + c.firstIndexPtr = getLong(dbBuff, 0) + c.lastIndexPtr = getLong(dbBuff, 4) + c.totalBlocks = (c.lastIndexPtr-c.firstIndexPtr)/IndexBlockLength + 1 + } + + ip, err := ip2long(result.Ip) + if err != nil { + return result, err + } + + h := c.totalBlocks + var dataPtr, l int64 + for l <= h { + + m := (l + h) >> 1 + p := c.firstIndexPtr + m*IndexBlockLength + sip := getLong(dbBuff, p) + if ip < sip { + h = m - 1 + } else { + eip := getLong(dbBuff, p+4) + if ip > eip { + l = m + 1 + } else { + dataPtr = getLong(dbBuff, p+8) + break + } + } + } + if dataPtr == 0 { + return result, errors.New("not found") + } + + dataLen := (dataPtr >> 24) & 0xFF + dataPtr = dataPtr & 0x00FFFFFF + result = getIpInfo(result.Ip, getLong(dbBuff, dataPtr), dbBuff[(dataPtr)+4:dataPtr+dataLen]) + return result, nil + +} diff --git a/ip2region_v2/client.go b/ip2region_v2/client.go new file mode 100644 index 0000000..9213643 --- /dev/null +++ b/ip2region_v2/client.go @@ -0,0 +1,26 @@ +package ip2region_v2 + +import _ "embed" + +//go:embed ip2region.xdb +var cBuff []byte + +type Client struct { + db *Searcher +} + +func New() (*Client, error) { + + var err error + c := &Client{} + + // 1、从 dbPath 加载整个 xdb 到内存 + + // 2、用全局的 cBuff 创建完全基于内存的查询对象。 + c.db, err = NewWithBuffer(cBuff) + if err != nil { + return nil, err + } + + return c, err +} diff --git a/ip2region_v2/query.go b/ip2region_v2/query.go index 1e2c2f2..a7dc513 100644 --- a/ip2region_v2/query.go +++ b/ip2region_v2/query.go @@ -6,50 +6,27 @@ import ( "net" ) -//go:embed ip2region.xdb -var cBuff []byte - -type Client struct { - db *Searcher -} - -func New() (*Client, error) { - - var err error - c := &Client{} - - // 1、从 dbPath 加载整个 xdb 到内存 - - // 2、用全局的 cBuff 创建完全基于内存的查询对象。 - c.db, err = NewWithBuffer(cBuff) - if err != nil { - return nil, err - } - - return c, err -} - -// Result 返回 -type Result struct { - Ip string `json:"ip,omitempty"` // 查询的ip地址 +// QueryResult 返回 +type QueryResult struct { + Ip string `json:"ip,omitempty"` // ip Country string `json:"country,omitempty"` // 国家 Province string `json:"province,omitempty"` // 省份 City string `json:"city,omitempty"` // 城市 Operator string `json:"operator,omitempty"` // 运营商 } -func (c *Client) Query(ipAddress net.IP) (result Result, err error) { +func (c *Client) Query(ipAddress net.IP) (result QueryResult, err error) { // 备注:并发使用,用整个 xdb 缓存创建的 searcher 对象可以安全用于并发。 str, err := c.db.SearchByStr(ipAddress.String()) if err != nil { - return Result{}, err + return QueryResult{}, err } split := gostring.Split(str, "|") if len(split) <= 0 { - return Result{}, err + return QueryResult{}, err } result.Ip = ipAddress.String() diff --git a/ipv6wry/client.go b/ipv6wry/client.go new file mode 100644 index 0000000..f07ca91 --- /dev/null +++ b/ipv6wry/client.go @@ -0,0 +1,138 @@ +package ipv6wry + +import ( + _ "embed" + "encoding/binary" + "log" +) + +var ( + header []byte + country []byte + area []byte + v6ip uint64 + offset uint32 + start uint32 + end uint32 +) + +//go:embed ipv6wry.db +var datBuff []byte + +type Client struct { + Offset uint32 + ItemLen uint32 + IndexLen uint32 +} + +func New() *Client { + + c := &Client{} + + buf := datBuff[0:8] + start := binary.LittleEndian.Uint32(buf[:4]) + end := binary.LittleEndian.Uint32(buf[4:]) + + num := int64((end-start)/7 + 1) + log.Printf("ipv6wry.db 共加载:%d 条ip记录\n", num) + + return c +} + +// ReadData 从文件中读取数据 +func (c *Client) readData(length uint32) (rs []byte) { + end := c.Offset + length + dataNum := uint32(len(datBuff)) + if c.Offset > dataNum { + return nil + } + + if end > dataNum { + end = dataNum + } + rs = datBuff[c.Offset:end] + c.Offset = end + return rs +} + +func (c *Client) getAddr() ([]byte, []byte) { + mode := c.readData(1)[0] + if mode == 0x01 { + // [IP][0x01][国家和地区信息的绝对偏移地址] + c.Offset = byteToUInt32(c.readData(3)) + return c.getAddr() + } + // [IP][0x02][信息的绝对偏移][...] or [IP][国家][...] + _offset := c.Offset - 1 + c1 := c.readArea(_offset) + if mode == 0x02 { + c.Offset = 4 + _offset + } else { + c.Offset = _offset + uint32(1+len(c1)) + } + c2 := c.readArea(c.Offset) + return c1, c2 +} + +func (c *Client) readArea(offset uint32) []byte { + c.Offset = offset + mode := c.readData(1)[0] + if mode == 0x01 || mode == 0x02 { + return c.readArea(byteToUInt32(c.readData(3))) + } + c.Offset = offset + return c.readString() +} + +func (c *Client) readString() []byte { + data := make([]byte, 0) + for { + buf := c.readData(1) + if buf[0] == 0 { + break + } + data = append(data, buf[0]) + } + return data +} + +func (c *Client) searchIndex(ip uint64) uint32 { + + c.ItemLen = 8 + c.IndexLen = 11 + + header = datBuff[8:24] + start = binary.LittleEndian.Uint32(header[8:]) + counts := binary.LittleEndian.Uint32(header[:8]) + end = start + counts*c.IndexLen + + buf := make([]byte, c.IndexLen) + + for { + mid := start + c.IndexLen*(((end-start)/c.IndexLen)>>1) + buf = datBuff[mid : mid+c.IndexLen] + _ip := binary.LittleEndian.Uint64(buf[:c.ItemLen]) + + if end-start == c.IndexLen { + if ip >= binary.LittleEndian.Uint64(datBuff[end:end+c.ItemLen]) { + buf = datBuff[end : end+c.IndexLen] + } + return byteToUInt32(buf[c.ItemLen:]) + } + + if _ip > ip { + end = mid + } else if _ip < ip { + start = mid + } else if _ip == ip { + return byteToUInt32(buf[c.ItemLen:]) + } + } +} + +func byteToUInt32(data []byte) uint32 { + i := uint32(data[0]) & 0xff + i |= (uint32(data[1]) << 8) & 0xff00 + i |= (uint32(data[2]) << 16) & 0xff0000 + return i +} diff --git a/v6/download.go b/ipv6wry/download.go similarity index 99% rename from v6/download.go rename to ipv6wry/download.go index d3c6fa0..9a3339d 100644 --- a/v6/download.go +++ b/ipv6wry/download.go @@ -1,4 +1,4 @@ -package v6 +package ipv6wry import ( "github.com/saracen/go7z" diff --git a/v6/ipv6wry.db b/ipv6wry/ipv6wry.db similarity index 100% rename from v6/ipv6wry.db rename to ipv6wry/ipv6wry.db diff --git a/ipv6wry/query.go b/ipv6wry/query.go new file mode 100644 index 0000000..332629b --- /dev/null +++ b/ipv6wry/query.go @@ -0,0 +1,82 @@ +package ipv6wry + +import ( + "go.dtapp.net/gostring" + "math/big" + "net" + "strings" +) + +// QueryResult 返回 +type QueryResult struct { + Ip string `json:"ip,omitempty"` // ip + Country string `json:"country,omitempty"` // 国家 + Province string `json:"province,omitempty"` // 省份 + City string `json:"city,omitempty"` // 城市 + Area string `json:"area,omitempty"` // 区域 + Isp string `json:"isp,omitempty"` // 运营商 +} + +// Query ip地址查询对应归属地信息 +func (c *Client) Query(ipAddress net.IP) (result QueryResult) { + + result.Ip = ipAddress.String() + + c.Offset = 0 + + tp := big.NewInt(0) + op := big.NewInt(0) + tp.SetBytes(ipAddress.To16()) + op.SetString("18446744073709551616", 10) + op.Div(tp, op) + tp.SetString("FFFFFFFFFFFFFFFF", 16) + op.And(op, tp) + + v6ip = op.Uint64() + offset = c.searchIndex(v6ip) + c.Offset = offset + + country, area = c.getAddr() + + // 解析地区数据 + info := strings.Split(string(country), "\t") + if len(info) > 0 { + i := 1 + for { + if i > len(info) { + break + } + switch i { + case 1: + result.Country = info[i-1] + result.Country = gostring.SpaceAndLineBreak(result.Country) + case 2: + result.Province = info[i-1] + result.Province = gostring.SpaceAndLineBreak(result.Province) + case 3: + result.City = info[i-1] + result.City = gostring.SpaceAndLineBreak(result.City) + case 4: + result.Area = info[i-1] + result.Area = gostring.SpaceAndLineBreak(result.Area) + } + i++ // 自增 + } + } else { + result.Country = string(country) + result.Country = gostring.SpaceAndLineBreak(result.Country) + } + // 运营商 + result.Isp = string(area) + + // Delete ZX (防止不相关的信息产生干扰) + if result.Isp == "ZX" || result.Isp == "" { + result.Isp = "" + } else { + result.Isp = " " + result.Isp + } + + result.Isp = gostring.SpaceAndLineBreak(result.Isp) + + return result +} diff --git a/is.go b/is.go new file mode 100644 index 0000000..da49c9c --- /dev/null +++ b/is.go @@ -0,0 +1,35 @@ +package goip + +import "strings" + +var ( + ipv4 = "IPV4" + ipv6 = "IPV6" +) + +func (c *Client) isIpv4OrIpv6(ip string) string { + if len(ip) < 7 { + return "" + } + arrIpv4 := strings.Split(ip, ".") + if len(arrIpv4) == 4 { + //. 判断IPv4 + for _, val := range arrIpv4 { + if !c.CheckIpv4(val) { + return "" + } + } + return ipv4 + } + arrIpv6 := strings.Split(ip, ":") + if len(arrIpv6) == 8 { + // 判断Ipv6 + for _, val := range arrIpv6 { + if !c.CheckIpv6(val) { + return "Neither" + } + } + return ipv6 + } + return "" +} diff --git a/qqwry/client.go b/qqwry/client.go new file mode 100644 index 0000000..3b51445 --- /dev/null +++ b/qqwry/client.go @@ -0,0 +1,139 @@ +package qqwry + +import ( + _ "embed" + "encoding/binary" + "log" +) + +var ( + header []byte + country []byte + area []byte + offset uint32 + start uint32 + end uint32 +) + +//go:embed qqwry.dat +var datBuff []byte + +type Client struct { + Offset uint32 + ItemLen uint32 + IndexLen uint32 +} + +func New() *Client { + + c := &Client{} + + buf := datBuff[0:8] + start := binary.LittleEndian.Uint32(buf[:4]) + end := binary.LittleEndian.Uint32(buf[4:]) + + num := int64((end-start)/7 + 1) + log.Printf("qqwry.dat 共加载:%d 条ip记录\n", num) + + return c +} + +// ReadData 从文件中读取数据 +func (c *Client) readData(length uint32) (rs []byte) { + end := c.Offset + length + dataNum := uint32(len(datBuff)) + if c.Offset > dataNum { + return nil + } + + if end > dataNum { + end = dataNum + } + rs = datBuff[c.Offset:end] + c.Offset = end + return rs +} + +// 获取地址信息 +func (c *Client) getAddr() ([]byte, []byte) { + mode := c.readData(1)[0] + if mode == 0x01 { + // [IP][0x01][国家和地区信息的绝对偏移地址] + c.Offset = byteToUInt32(c.readData(3)) + return c.getAddr() + } + // [IP][0x02][信息的绝对偏移][...] or [IP][国家][...] + _offset := c.Offset - 1 + c1 := c.readArea(_offset) + if mode == 0x02 { + c.Offset = 4 + _offset + } else { + c.Offset = _offset + uint32(1+len(c1)) + } + c2 := c.readArea(c.Offset) + return c1, c2 +} + +// 读取区 +func (c *Client) readArea(offset uint32) []byte { + c.Offset = offset + mode := c.readData(1)[0] + if mode == 0x01 || mode == 0x02 { + return c.readArea(byteToUInt32(c.readData(3))) + } + c.Offset = offset + return c.readString() +} + +// 读取字符串 +func (c *Client) readString() []byte { + data := make([]byte, 0) + for { + buf := c.readData(1) + if buf[0] == 0 { + break + } + data = append(data, buf[0]) + } + return data +} + +// 搜索索引 +func (c *Client) searchIndex(ip uint32) uint32 { + c.ItemLen = 4 + c.IndexLen = 7 + header = datBuff[0:8] + start = binary.LittleEndian.Uint32(header[:4]) + end = binary.LittleEndian.Uint32(header[4:]) + + buf := make([]byte, c.IndexLen) + + for { + mid := start + c.IndexLen*(((end-start)/c.IndexLen)>>1) + buf = datBuff[mid : mid+c.IndexLen] + _ip := binary.LittleEndian.Uint32(buf[:c.ItemLen]) + + if end-start == c.IndexLen { + if ip >= binary.LittleEndian.Uint32(datBuff[end:end+c.ItemLen]) { + buf = datBuff[end : end+c.IndexLen] + } + return byteToUInt32(buf[c.ItemLen:]) + } + + if _ip > ip { + end = mid + } else if _ip < ip { + start = mid + } else if _ip == ip { + return byteToUInt32(buf[c.ItemLen:]) + } + } +} + +// 字节转UInt32 +func byteToUInt32(data []byte) uint32 { + i := uint32(data[0]) & 0xff + i |= (uint32(data[1]) << 8) & 0xff00 + i |= (uint32(data[2]) << 16) & 0xff0000 + return i +} diff --git a/v4/download.go b/qqwry/download.go similarity index 98% rename from v4/download.go rename to qqwry/download.go index 08e7386..6126330 100644 --- a/v4/download.go +++ b/qqwry/download.go @@ -1,4 +1,4 @@ -package v4 +package qqwry import ( "bytes" diff --git a/v4/qqwry.dat b/qqwry/qqwry.dat similarity index 100% rename from v4/qqwry.dat rename to qqwry/qqwry.dat diff --git a/qqwry/query.go b/qqwry/query.go new file mode 100644 index 0000000..2e1d7bb --- /dev/null +++ b/qqwry/query.go @@ -0,0 +1,53 @@ +package qqwry + +import ( + "encoding/binary" + "errors" + "go.dtapp.net/gostring" + "golang.org/x/text/encoding/simplifiedchinese" + "net" +) + +// QueryResult 返回 +type QueryResult struct { + Ip string `json:"ip,omitempty"` // ip + Country string `json:"country,omitempty"` // 国家或地区 + Area string `json:"area,omitempty"` // 区域 +} + +// Query ip地址查询对应归属地信息 +func (c *Client) Query(ipAddress net.IP) (result QueryResult, err error) { + + c.Offset = 0 + + // 偏移 + offset = c.searchIndex(binary.BigEndian.Uint32(ipAddress.To4())) + if offset <= 0 { + return QueryResult{}, errors.New("搜索失败") + } + + result.Ip = ipAddress.String() + + c.Offset = offset + c.ItemLen + + country, area = c.getAddr() + + enc := simplifiedchinese.GBK.NewDecoder() + + result.Country, _ = enc.String(string(country)) + + result.Country = gostring.SpaceAndLineBreak(result.Country) + + result.Area, _ = enc.String(string(area)) + + // Delete CZ88.NET (防止不相关的信息产生干扰) + if result.Area == " CZ88.NET" || result.Area == "" { + result.Area = "" + } else { + result.Area = " " + result.Area + } + + result.Area = gostring.SpaceAndLineBreak(result.Area) + + return result, nil +} diff --git a/query.go b/query.go index fd4aa0a..d6f6763 100644 --- a/query.go +++ b/query.go @@ -3,7 +3,10 @@ package goip import ( "errors" "go.dtapp.net/goip/geoip" + "go.dtapp.net/goip/ip2region" "go.dtapp.net/goip/ip2region_v2" + "go.dtapp.net/goip/ipv6wry" + "go.dtapp.net/goip/qqwry" "net" ) @@ -11,51 +14,46 @@ var ( QueryIncorrect = errors.New("ip地址不正确") ) -// QueryQqWryResult 返回 -type QueryQqWryResult struct { - Ip string `json:"ip,omitempty"` // 查询的ip地址 - Country string `json:"country,omitempty"` // 国家或地区 - Area string `json:"area,omitempty"` // 区域 -} - // QueryQqWry 纯真IP库 // https://www.cz88.net/ -func (c *Client) QueryQqWry(ipAddress net.IP) (result QueryQqWryResult, err error) { +func (c *Client) QueryQqWry(ipAddress net.IP) (result qqwry.QueryResult, err error) { if ipAddress.To4() == nil { return result, QueryIncorrect } - resp := c.V4db.Query(ipAddress) - return QueryQqWryResult{ - Ip: resp.IP, - Country: resp.Country, - Area: resp.Area, - }, nil + + query, err := c.qqwryClient.Query(ipAddress) + if err != nil { + return qqwry.QueryResult{}, err + } + + return query, err } // QueryIp2Region ip2region // https://github.com/lionsoul2014/ip2region -func (c *Client) QueryIp2Region(ipAddress net.IP) (result QueryQqWryResult, err error) { +func (c *Client) QueryIp2Region(ipAddress net.IP) (result ip2region.QueryResult, err error) { if ipAddress.To4() == nil { return result, QueryIncorrect } - resp := c.V4db.Query(ipAddress) - return QueryQqWryResult{ - Ip: resp.IP, - Country: resp.Country, - Area: resp.Area, - }, nil + + query, err := c.ip2regionClient.Query(ipAddress) + if err != nil { + return ip2region.QueryResult{}, err + } + + return query, err } // QueryIp2RegionV2 ip2region // https://github.com/lionsoul2014/ip2region -func (c *Client) QueryIp2RegionV2(ipAddress net.IP) (result ip2region_v2.Result, err error) { +func (c *Client) QueryIp2RegionV2(ipAddress net.IP) (result ip2region_v2.QueryResult, err error) { if ipAddress.To4() == nil { return result, QueryIncorrect } query, err := c.ip2regionV2Client.Query(ipAddress) if err != nil { - return ip2region_v2.Result{}, err + return ip2region_v2.QueryResult{}, err } return query, nil @@ -75,3 +73,15 @@ func (c *Client) QueryGeoIp(ipAddress net.IP) (result geoip.QueryCityResult, err return query, nil } + +// QueryIpv6wry ip2region +// https://ip.zxinc.org +func (c *Client) QueryIpv6wry(ipAddress net.IP) (result ipv6wry.QueryResult, err error) { + if ipAddress.To16() == nil { + return result, QueryIncorrect + } + + query := c.ipv6wryClient.Query(ipAddress) + + return query, nil +} diff --git a/v4/ipv4.go b/v4/ipv4.go deleted file mode 100644 index f7b81d1..0000000 --- a/v4/ipv4.go +++ /dev/null @@ -1,223 +0,0 @@ -package v4 - -import ( - _ "embed" - "encoding/binary" - "go.dtapp.net/gostring" - "golang.org/x/text/encoding/simplifiedchinese" - "log" - "net" -) - -var ( - header []byte - country []byte - area []byte - offset uint32 - start uint32 - end uint32 -) - -//go:embed qqwry.dat -var dat []byte - -type Pointer struct { - Offset uint32 - ItemLen uint32 - IndexLen uint32 -} - -// Result 返回 -type Result struct { - IP string `json:"ip,omitempty"` // 输入的ip地址 - Country string `json:"country,omitempty"` // 国家或地区 - Area string `json:"area,omitempty"` // 区域 -} - -// InitIPV4Data 加载 -func (q *Pointer) InitIPV4Data() int64 { - buf := dat[0:8] - start := binary.LittleEndian.Uint32(buf[:4]) - end := binary.LittleEndian.Uint32(buf[4:]) - - return int64((end-start)/7 + 1) -} - -// ReadData 从文件中读取数据 -func (q *Pointer) readData(length uint32) (rs []byte) { - end := q.Offset + length - dataNum := uint32(len(dat)) - if q.Offset > dataNum { - return nil - } - - if end > dataNum { - end = dataNum - } - rs = dat[q.Offset:end] - q.Offset = end - return rs -} - -// Find ip地址查询对应归属地信息 -func (q *Pointer) Find(ipStr string) (res Result) { - - // 赋值 - res.IP = ipStr - if net.ParseIP(ipStr).To4() == nil { - log.Println("不是ip地址") - // 不是ip地址 - return res - } - - q.Offset = 0 - - // 偏移 - offset = q.searchIndex(binary.BigEndian.Uint32(net.ParseIP(ipStr).To4())) - if offset <= 0 { - return - } - - q.Offset = offset + q.ItemLen - - country, area = q.getAddr() - - enc := simplifiedchinese.GBK.NewDecoder() - - res.Country, _ = enc.String(string(country)) - res.Country = gostring.SpaceAndLineBreak(res.Country) - - res.Area, _ = enc.String(string(area)) - - // Delete CZ88.NET (防止不相关的信息产生干扰) - if res.Area == " CZ88.NET" || res.Area == "" { - res.Area = "" - } else { - res.Area = " " + res.Area - } - - res.Area = gostring.SpaceAndLineBreak(res.Area) - - return -} - -// Query ip地址查询对应归属地信息 -func (q *Pointer) Query(ipAddress net.IP) (res Result) { - - q.Offset = 0 - - // 偏移 - offset = q.searchIndex(binary.BigEndian.Uint32(ipAddress.To4())) - if offset <= 0 { - return - } - - q.Offset = offset + q.ItemLen - - country, area = q.getAddr() - - enc := simplifiedchinese.GBK.NewDecoder() - - res.Country, _ = enc.String(string(country)) - - res.Country = gostring.SpaceAndLineBreak(res.Country) - - res.Area, _ = enc.String(string(area)) - - // Delete CZ88.NET (防止不相关的信息产生干扰) - if res.Area == " CZ88.NET" || res.Area == "" { - res.Area = "" - } else { - res.Area = " " + res.Area - } - - res.Area = gostring.SpaceAndLineBreak(res.Area) - - res.IP = ipAddress.String() - - return -} - -// 获取地址信息 -func (q *Pointer) getAddr() ([]byte, []byte) { - mode := q.readData(1)[0] - if mode == 0x01 { - // [IP][0x01][国家和地区信息的绝对偏移地址] - q.Offset = byteToUInt32(q.readData(3)) - return q.getAddr() - } - // [IP][0x02][信息的绝对偏移][...] or [IP][国家][...] - _offset := q.Offset - 1 - c1 := q.readArea(_offset) - if mode == 0x02 { - q.Offset = 4 + _offset - } else { - q.Offset = _offset + uint32(1+len(c1)) - } - c2 := q.readArea(q.Offset) - return c1, c2 -} - -// 读取区 -func (q *Pointer) readArea(offset uint32) []byte { - q.Offset = offset - mode := q.readData(1)[0] - if mode == 0x01 || mode == 0x02 { - return q.readArea(byteToUInt32(q.readData(3))) - } - q.Offset = offset - return q.readString() -} - -// 读取字符串 -func (q *Pointer) readString() []byte { - data := make([]byte, 0) - for { - buf := q.readData(1) - if buf[0] == 0 { - break - } - data = append(data, buf[0]) - } - return data -} - -// 搜索索引 -func (q *Pointer) searchIndex(ip uint32) uint32 { - q.ItemLen = 4 - q.IndexLen = 7 - header = dat[0:8] - start = binary.LittleEndian.Uint32(header[:4]) - end = binary.LittleEndian.Uint32(header[4:]) - - buf := make([]byte, q.IndexLen) - - for { - mid := start + q.IndexLen*(((end-start)/q.IndexLen)>>1) - buf = dat[mid : mid+q.IndexLen] - _ip := binary.LittleEndian.Uint32(buf[:q.ItemLen]) - - if end-start == q.IndexLen { - if ip >= binary.LittleEndian.Uint32(dat[end:end+q.ItemLen]) { - buf = dat[end : end+q.IndexLen] - } - return byteToUInt32(buf[q.ItemLen:]) - } - - if _ip > ip { - end = mid - } else if _ip < ip { - start = mid - } else if _ip == ip { - return byteToUInt32(buf[q.ItemLen:]) - } - } -} - -// 字节转UInt32 -func byteToUInt32(data []byte) uint32 { - i := uint32(data[0]) & 0xff - i |= (uint32(data[1]) << 8) & 0xff00 - i |= (uint32(data[2]) << 16) & 0xff0000 - return i -} diff --git a/v6/ipv6.go b/v6/ipv6.go deleted file mode 100644 index 67f4ed9..0000000 --- a/v6/ipv6.go +++ /dev/null @@ -1,213 +0,0 @@ -package v6 - -import ( - _ "embed" - "encoding/binary" - "go.dtapp.net/gostring" - "math/big" - "net" - "strings" -) - -var ( - header []byte - country []byte - area []byte - v6ip uint64 - offset uint32 - start uint32 - end uint32 -) - -type Result struct { - IP string `json:"ip,omitempty"` // 输入的ip地址 - Country string `json:"country,omitempty"` // 国家 - Province string `json:"province,omitempty"` // 省份 - City string `json:"city,omitempty"` // 城市 - Area string `json:"area,omitempty"` // 区域 - Isp string `json:"isp,omitempty"` // 运营商 -} - -//go:embed ipv6wry.db -var dat []byte - -type Pointer struct { - Offset uint32 - ItemLen uint32 - IndexLen uint32 -} - -// InitIPV4Data 加载 -func (q *Pointer) InitIPV4Data() int64 { - buf := dat[0:8] - start := binary.LittleEndian.Uint32(buf[:4]) - end := binary.LittleEndian.Uint32(buf[4:]) - - return int64((end-start)/7 + 1) -} - -// ReadData 从文件中读取数据 -func (q *Pointer) readData(length uint32) (rs []byte) { - end := q.Offset + length - dataNum := uint32(len(dat)) - if q.Offset > dataNum { - return nil - } - - if end > dataNum { - end = dataNum - } - rs = dat[q.Offset:end] - q.Offset = end - return rs -} - -// Find ip地址查询对应归属地信息 -func (q *Pointer) Find(ipStr string) (res Result) { - - res = Result{} - res.IP = ipStr - if net.ParseIP(ipStr).To16() == nil { - return Result{} - } - - q.Offset = 0 - - tp := big.NewInt(0) - op := big.NewInt(0) - tp.SetBytes(net.ParseIP(ipStr).To16()) - op.SetString("18446744073709551616", 10) - op.Div(tp, op) - tp.SetString("FFFFFFFFFFFFFFFF", 16) - op.And(op, tp) - - v6ip = op.Uint64() - offset = q.searchIndex(v6ip) - q.Offset = offset - - country, area = q.getAddr() - - // 解析地区数据 - info := strings.Split(string(country), "\t") - if len(info) > 0 { - i := 1 - for { - if i > len(info) { - break - } - switch i { - case 1: - res.Country = info[i-1] - res.Country = gostring.SpaceAndLineBreak(res.Country) - case 2: - res.Province = info[i-1] - res.Province = gostring.SpaceAndLineBreak(res.Province) - case 3: - res.City = info[i-1] - res.City = gostring.SpaceAndLineBreak(res.City) - case 4: - res.Area = info[i-1] - res.Area = gostring.SpaceAndLineBreak(res.Area) - } - i++ // 自增 - } - } else { - res.Country = string(country) - res.Country = gostring.SpaceAndLineBreak(res.Country) - } - // 运营商 - res.Isp = string(area) - - // Delete ZX (防止不相关的信息产生干扰) - if res.Isp == "ZX" || res.Isp == "" { - res.Isp = "" - } else { - res.Isp = " " + res.Isp - } - - res.Isp = gostring.SpaceAndLineBreak(res.Isp) - - return -} - -func (q *Pointer) getAddr() ([]byte, []byte) { - mode := q.readData(1)[0] - if mode == 0x01 { - // [IP][0x01][国家和地区信息的绝对偏移地址] - q.Offset = byteToUInt32(q.readData(3)) - return q.getAddr() - } - // [IP][0x02][信息的绝对偏移][...] or [IP][国家][...] - _offset := q.Offset - 1 - c1 := q.readArea(_offset) - if mode == 0x02 { - q.Offset = 4 + _offset - } else { - q.Offset = _offset + uint32(1+len(c1)) - } - c2 := q.readArea(q.Offset) - return c1, c2 -} - -func (q *Pointer) readArea(offset uint32) []byte { - q.Offset = offset - mode := q.readData(1)[0] - if mode == 0x01 || mode == 0x02 { - return q.readArea(byteToUInt32(q.readData(3))) - } - q.Offset = offset - return q.readString() -} - -func (q *Pointer) readString() []byte { - data := make([]byte, 0) - for { - buf := q.readData(1) - if buf[0] == 0 { - break - } - data = append(data, buf[0]) - } - return data -} - -func (q *Pointer) searchIndex(ip uint64) uint32 { - - q.ItemLen = 8 - q.IndexLen = 11 - - header = dat[8:24] - start = binary.LittleEndian.Uint32(header[8:]) - counts := binary.LittleEndian.Uint32(header[:8]) - end = start + counts*q.IndexLen - - buf := make([]byte, q.IndexLen) - - for { - mid := start + q.IndexLen*(((end-start)/q.IndexLen)>>1) - buf = dat[mid : mid+q.IndexLen] - _ip := binary.LittleEndian.Uint64(buf[:q.ItemLen]) - - if end-start == q.IndexLen { - if ip >= binary.LittleEndian.Uint64(dat[end:end+q.ItemLen]) { - buf = dat[end : end+q.IndexLen] - } - return byteToUInt32(buf[q.ItemLen:]) - } - - if _ip > ip { - end = mid - } else if _ip < ip { - start = mid - } else if _ip == ip { - return byteToUInt32(buf[q.ItemLen:]) - } - } -} - -func byteToUInt32(data []byte) uint32 { - i := uint32(data[0]) & 0xff - i |= (uint32(data[1]) << 8) & 0xff00 - i |= (uint32(data[2]) << 16) & 0xff0000 - return i -}