commit 3acb551f94a52b6d6580a3092a3e9343b728fa9d Author: sre Date: Mon May 9 10:53:02 2022 +0800 改造 支持域名 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f26eab2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.idea +go.sum +main.exe +server.exe +log.txt +logs +*.log +uploads +gin.log +cert +challenge \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee150d6 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# ip地址归属地查询 +http://127.0.0.1:8080?address=sre.ink +## bench +```bash +goos: windows +goarch: amd64 +pkg: IPRegion/ip2region +cpu: AMD Ryzen 9 5900HX with Radeon Graphics +BenchmarkBtreeSearch +BenchmarkBtreeSearch-16 215227 5310 ns/op +BenchmarkMemorySearch +BenchmarkMemorySearch-16 5435004 220.3 ns/op +BenchmarkBinarySearch +BenchmarkBinarySearch-16 34378 34766 ns/op +PASS +``` \ No newline at end of file diff --git a/getIPInfo.go b/getIPInfo.go new file mode 100644 index 0000000..69c5de8 --- /dev/null +++ b/getIPInfo.go @@ -0,0 +1,43 @@ +package main + +import ( + "IPRegion/ip2region" + "IPRegion/util" + "fmt" + //"github.com/lionsoul2014/ip2region/binding/golang/ip2region" +) + +func GetIPInfo(address, searchType string) (ipInfo ip2region.IpInfo, err error) { + //https://gitee.com/lionsoul/ip2region/raw/master/data/ip2region.db + region, err := ip2region.New("ip2region.db") + defer region.Close() + if err != nil { + fmt.Println(err) + return + } + var ipAddress string + if util.MatchIP(address) { + ipAddress = address + } else { + ipAddress, err = util.GetIPByDomain(address) + if err != nil { + //提供非法ip 返回空结构体 + ipAddress = "266.266.266.266" + } + } + switch searchType { + case "Memory": + ip, err := region.MemorySearch(ipAddress) + return ip, err + case "Binary": + ip, err := region.BinarySearch(ipAddress) + return ip, err + case "Btree": + ip, err := region.BtreeSearch(ipAddress) + return ip, err + default: + ip, err := region.MemorySearch(ipAddress) + return ip, err + } + +} diff --git a/global/const.go b/global/const.go new file mode 100644 index 0000000..65fbf3d --- /dev/null +++ b/global/const.go @@ -0,0 +1,3 @@ +package global + +const SearchType = "Memory" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..56ea97e --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module IPRegion + +go 1.18 + +require github.com/lionsoul2014/ip2region v2.2.0-release+incompatible diff --git a/ip2region.db b/ip2region.db new file mode 100644 index 0000000..c43f884 Binary files /dev/null and b/ip2region.db differ diff --git a/ip2region/ip2Region.go b/ip2region/ip2Region.go new file mode 100644 index 0000000..f290921 --- /dev/null +++ b/ip2region/ip2Region.go @@ -0,0 +1,347 @@ +package ip2region + +import ( + "errors" + "io/ioutil" + "os" + "strconv" + "strings" +) + +const ( + INDEX_BLOCK_LENGTH = 12 + TOTAL_HEADER_LENGTH = 8192 +) + +var err error +var ipInfo IpInfo + +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 + + dbBinStr []byte + dbFile string +} + +type IpInfo struct { + CityId int64 + Country string + Region string + Province string + City string + ISP string +} + +func (ip IpInfo) String() string { + return strconv.FormatInt(ip.CityId, 10) + "|" + ip.Country + "|" + ip.Region + "|" + ip.Province + "|" + ip.City + "|" + ip.ISP +} + +func getIpInfo(cityId int64, line []byte) IpInfo { + + lineSlice := strings.Split(string(line), "|") + ipInfo := IpInfo{} + length := len(lineSlice) + ipInfo.CityId = cityId + if length < 5 { + for i := 0; i <= 5-length; i++ { + lineSlice = append(lineSlice, "") + } + } + + ipInfo.Country = lineSlice[0] + ipInfo.Region = lineSlice[1] + ipInfo.Province = lineSlice[2] + ipInfo.City = lineSlice[3] + ipInfo.ISP = lineSlice[4] + return ipInfo +} + +func New(path string) (*Ip2Region, error) { + + file, err := os.Open(path) + if err != nil { + return nil, err + } + + return &Ip2Region{ + dbFile: path, + dbFileHandler: file, + }, nil +} + +func (this *Ip2Region) Close() { + this.dbFileHandler.Close() +} + +func (this *Ip2Region) MemorySearch(ipStr string) (ipInfo IpInfo, err error) { + ipInfo = IpInfo{} + + if this.totalBlocks == 0 { + this.dbBinStr, err = ioutil.ReadFile(this.dbFile) + + if err != nil { + + return ipInfo, err + } + + this.firstIndexPtr = getLong(this.dbBinStr, 0) + this.lastIndexPtr = getLong(this.dbBinStr, 4) + this.totalBlocks = (this.lastIndexPtr-this.firstIndexPtr)/INDEX_BLOCK_LENGTH + 1 + } + + ip, err := ip2long(ipStr) + if err != nil { + return ipInfo, err + } + + h := this.totalBlocks + var dataPtr, l int64 + for l <= h { + + m := (l + h) >> 1 + p := this.firstIndexPtr + m*INDEX_BLOCK_LENGTH + sip := getLong(this.dbBinStr, p) + if ip < sip { + h = m - 1 + } else { + eip := getLong(this.dbBinStr, p+4) + if ip > eip { + l = m + 1 + } else { + dataPtr = getLong(this.dbBinStr, p+8) + break + } + } + } + if dataPtr == 0 { + return ipInfo, errors.New("not found") + } + + dataLen := ((dataPtr >> 24) & 0xFF) + dataPtr = (dataPtr & 0x00FFFFFF) + ipInfo = getIpInfo(getLong(this.dbBinStr, dataPtr), this.dbBinStr[(dataPtr)+4:dataPtr+dataLen]) + return ipInfo, nil + +} + +func (this *Ip2Region) BinarySearch(ipStr string) (ipInfo IpInfo, err error) { + ipInfo = IpInfo{} + if this.totalBlocks == 0 { + this.dbFileHandler.Seek(0, 0) + superBlock := make([]byte, 8) + this.dbFileHandler.Read(superBlock) + this.firstIndexPtr = getLong(superBlock, 0) + this.lastIndexPtr = getLong(superBlock, 4) + this.totalBlocks = (this.lastIndexPtr-this.firstIndexPtr)/INDEX_BLOCK_LENGTH + 1 + } + + var l, dataPtr, p int64 + + h := this.totalBlocks + + ip, err := ip2long(ipStr) + + if err != nil { + return + } + + for l <= h { + m := (l + h) >> 1 + + p = m * INDEX_BLOCK_LENGTH + + _, err = this.dbFileHandler.Seek(this.firstIndexPtr+p, 0) + if err != nil { + return + } + + buffer := make([]byte, INDEX_BLOCK_LENGTH) + _, err = this.dbFileHandler.Read(buffer) + + if err != nil { + + } + sip := getLong(buffer, 0) + if ip < sip { + h = m - 1 + } else { + eip := getLong(buffer, 4) + if ip > eip { + l = m + 1 + } else { + dataPtr = getLong(buffer, 8) + break + } + } + + } + + if dataPtr == 0 { + err = errors.New("not found") + return + } + + dataLen := ((dataPtr >> 24) & 0xFF) + dataPtr = (dataPtr & 0x00FFFFFF) + + this.dbFileHandler.Seek(dataPtr, 0) + data := make([]byte, dataLen) + this.dbFileHandler.Read(data) + ipInfo = getIpInfo(getLong(data, 0), data[4:dataLen]) + err = nil + return +} + +func (this *Ip2Region) BtreeSearch(ipStr string) (ipInfo IpInfo, err error) { + ipInfo = IpInfo{} + ip, err := ip2long(ipStr) + + if this.headerLen == 0 { + this.dbFileHandler.Seek(8, 0) + + buffer := make([]byte, TOTAL_HEADER_LENGTH) + this.dbFileHandler.Read(buffer) + var idx int64 + for i := 0; i < TOTAL_HEADER_LENGTH; i += 8 { + startIp := getLong(buffer, int64(i)) + dataPar := getLong(buffer, int64(i+4)) + if dataPar == 0 { + break + } + + this.headerSip = append(this.headerSip, startIp) + this.headerPtr = append(this.headerPtr, dataPar) + idx++ + } + + this.headerLen = idx + } + + var l, sptr, eptr int64 + h := this.headerLen + + for l <= h { + m := int64(l+h) >> 1 + if m < this.headerLen { + if ip == this.headerSip[m] { + if m > 0 { + sptr = this.headerPtr[m-1] + eptr = this.headerPtr[m] + } else { + sptr = this.headerPtr[m] + eptr = this.headerPtr[m+1] + } + break + } + if ip < this.headerSip[m] { + if m == 0 { + sptr = this.headerPtr[m] + eptr = this.headerPtr[m+1] + break + } else if ip > this.headerSip[m-1] { + sptr = this.headerPtr[m-1] + eptr = this.headerPtr[m] + break + } + h = m - 1 + } else { + if m == this.headerLen-1 { + sptr = this.headerPtr[m-1] + eptr = this.headerPtr[m] + break + } else if ip <= this.headerSip[m+1] { + sptr = this.headerPtr[m] + eptr = this.headerPtr[m+1] + break + } + l = m + 1 + } + } + + } + + if sptr == 0 { + err = errors.New("not found") + return + } + + blockLen := eptr - sptr + this.dbFileHandler.Seek(sptr, 0) + index := make([]byte, blockLen+INDEX_BLOCK_LENGTH) + this.dbFileHandler.Read(index) + var dataptr int64 + h = blockLen / INDEX_BLOCK_LENGTH + l = 0 + + for l <= h { + m := int64(l+h) >> 1 + p := m * INDEX_BLOCK_LENGTH + sip := getLong(index, p) + if ip < sip { + h = m - 1 + } else { + eip := getLong(index, p+4) + if ip > eip { + l = m + 1 + } else { + dataptr = getLong(index, p+8) + break + } + } + } + + if dataptr == 0 { + err = errors.New("not found") + return + } + + dataLen := (dataptr >> 24) & 0xFF + dataPtr := dataptr & 0x00FFFFFF + + this.dbFileHandler.Seek(dataPtr, 0) + data := make([]byte, dataLen) + this.dbFileHandler.Read(data) + ipInfo = getIpInfo(getLong(data, 0), data[4:]) + return +} + +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_test.go b/ip2region/ip2Region_test.go new file mode 100644 index 0000000..81b65f8 --- /dev/null +++ b/ip2region/ip2Region_test.go @@ -0,0 +1,49 @@ +package ip2region + +import ( + "testing" +) + +func BenchmarkBtreeSearch(B *testing.B) { + region, err := New("../ip2region.db ") + if err != nil { + B.Error(err) + } + for i := 0; i < B.N; i++ { + region.BtreeSearch("127.0.0.1") + } + +} + +func BenchmarkMemorySearch(B *testing.B) { + region, err := New("../ip2region.db ") + if err != nil { + B.Error(err) + } + for i := 0; i < B.N; i++ { + region.MemorySearch("127.0.0.1") + } + +} + +func BenchmarkBinarySearch(B *testing.B) { + region, err := New("../ip2region.db ") + if err != nil { + B.Error(err) + } + for i := 0; i < B.N; i++ { + region.BinarySearch("127.0.0.1") + } + +} + +func TestIp2long(t *testing.T) { + ip, err := ip2long("127.0.0.1") + if err != nil { + t.Error(err) + } + if ip != 2130706433 { + t.Error("result error") + } + t.Log(ip) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..20c9ebe --- /dev/null +++ b/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "IPRegion/global" + "IPRegion/ip2region" + "encoding/json" + "fmt" + "net/http" +) + +func main() { + http.HandleFunc("/", IPHandle) + http.ListenAndServe(":8080", nil) +} + +func IPHandle(w http.ResponseWriter, req *http.Request) { + address := req.FormValue("address") + var ipInfo ip2region.IpInfo + if address != "" { + ipInfo, _ = GetIPInfo(address, global.SearchType) + } + res, _ := json.Marshal(ipInfo) + fmt.Fprintln(w, string(res)) + +} diff --git a/resJSON.go b/resJSON.go new file mode 100644 index 0000000..5da1285 --- /dev/null +++ b/resJSON.go @@ -0,0 +1,10 @@ +package main + +type IpInfo struct { + CityId int64 + Country string + Region string + Province string + City string + ISP string +} diff --git a/util/getIPByDomain.go b/util/getIPByDomain.go new file mode 100644 index 0000000..2b49933 --- /dev/null +++ b/util/getIPByDomain.go @@ -0,0 +1,13 @@ +package util + +import ( + "net" +) + +func GetIPByDomain(domain string) (string, error) { + addr, err := net.ResolveIPAddr("ip", domain) + if err != nil { + return "", err + } + return addr.String(), nil +} diff --git a/util/regx.go b/util/regx.go new file mode 100644 index 0000000..0f65a15 --- /dev/null +++ b/util/regx.go @@ -0,0 +1,12 @@ +package util + +import "regexp" + +func MatchIP(ip string) bool { + ipReg := `^((0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|1\d\d|2[0-4]\d|25[0-5])$` + match, _ := regexp.MatchString(ipReg, ip) + if match { + return true + } + return false +}