forked from wangtuanjie/ip17mon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ip17mon.go
186 lines (159 loc) · 3.65 KB
/
ip17mon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package ip17mon
import (
"bytes"
"encoding/binary"
"errors"
"io/ioutil"
"net"
)
const Null = "N/A"
var (
ErrInvalidIp = errors.New("invalid ip format")
std *Locator
)
// Init defaut locator with dataFile
func Init(dataFile string) (err error) {
if std != nil {
return
}
std, err = NewLocator(dataFile)
return
}
// Init defaut locator with data
func InitWithData(data []byte) {
if std != nil {
return
}
std = NewLocatorWithData(data)
return
}
// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func Find(ipstr string) (*LocationInfo, error) {
return std.Find(ipstr)
}
// Find locationInfo by uint32
func FindByUint(ip uint32) *LocationInfo {
return std.FindByUint(ip)
}
//-----------------------------------------------------------------------------
// New locator with dataFile
func NewLocator(dataFile string) (loc *Locator, err error) {
data, err := ioutil.ReadFile(dataFile)
if err != nil {
return
}
loc = NewLocatorWithData(data)
return
}
// New locator with data
func NewLocatorWithData(data []byte) (loc *Locator) {
loc = new(Locator)
loc.init(data)
return
}
type Locator struct {
textData []byte
indexData1 []uint32
indexData2 []int
indexData3 []int
index []int
}
type LocationInfo struct {
Country string
Region string
City string
Isp string
}
// Find locationInfo by ip string
// It will return err when ipstr is not a valid format
func (loc *Locator) Find(ipstr string) (info *LocationInfo, err error) {
ip := net.ParseIP(ipstr)
if ip == nil {
err = ErrInvalidIp
return
}
info = loc.FindByUint(binary.BigEndian.Uint32([]byte(ip.To4())))
return
}
// Find locationInfo by uint32
func (loc *Locator) FindByUint(ip uint32) (info *LocationInfo) {
end := len(loc.indexData1) - 1
if ip>>24 != 0xff {
end = loc.index[(ip>>24)+1]
}
idx := loc.findIndexOffset(ip, loc.index[ip>>24], end)
off := loc.indexData2[idx]
return newLocationInfo(loc.textData[off : off+loc.indexData3[idx]])
}
// binary search
func (loc *Locator) findIndexOffset(ip uint32, start, end int) int {
for start < end {
mid := (start + end) / 2
if ip > loc.indexData1[mid] {
start = mid + 1
} else {
end = mid
}
}
if loc.indexData1[end] >= ip {
return end
}
return start
}
func (loc *Locator) init(data []byte) {
textoff := int(binary.BigEndian.Uint32(data[:4]))
loc.textData = data[textoff-1024:]
loc.index = make([]int, 256)
for i := 0; i < 256; i++ {
off := 4 + i*4
loc.index[i] = int(binary.LittleEndian.Uint32(data[off : off+4]))
}
nidx := (textoff - 4 - 1024 - 1024) / 8
loc.indexData1 = make([]uint32, nidx)
loc.indexData2 = make([]int, nidx)
loc.indexData3 = make([]int, nidx)
for i := 0; i < nidx; i++ {
off := 4 + 1024 + i*8
loc.indexData1[i] = binary.BigEndian.Uint32(data[off : off+4])
loc.indexData2[i] = int(uint32(data[off+4]) | uint32(data[off+5])<<8 | uint32(data[off+6])<<16)
loc.indexData3[i] = int(data[off+7])
}
return
}
func newLocationInfo(str []byte) *LocationInfo {
var info *LocationInfo
fields := bytes.Split(str, []byte("\t"))
switch len(fields) {
case 4:
// free version
info = &LocationInfo{
Country: string(fields[0]),
Region: string(fields[1]),
City: string(fields[2]),
}
case 5:
// pay version
info = &LocationInfo{
Country: string(fields[0]),
Region: string(fields[1]),
City: string(fields[2]),
Isp: string(fields[4]),
}
default:
panic("unexpected ip info:" + string(str))
}
if len(info.Country) == 0 {
info.Country = Null
}
if len(info.Region) == 0 {
info.Region = Null
}
if len(info.City) == 0 {
info.City = Null
}
if len(info.Isp) == 0 {
info.Isp = Null
}
return info
}