├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── anonymous_ip.go ├── asn.go ├── city.go ├── common.go ├── connection_type.go ├── continent.go ├── country.go ├── domain.go ├── geoip2_test.go ├── go.mod ├── isp.go ├── location.go ├── metadata.go ├── postal.go ├── reader.go ├── reader_anonymous_ip.go ├── reader_asn.go ├── reader_city.go ├── reader_connection_type.go ├── reader_country.go ├── reader_domain.go ├── reader_isp.go ├── reader_test.go ├── subdivision.go ├── traits.go └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | testdata 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "testdata/maxmind"] 2 | path = testdata/maxmind 3 | url = https://github.com/maxmind/MaxMind-DB 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Aleksey Lin (https://incsw.in) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/IncSW/geoip2?style=flat-square)](https://goreportcard.com/report/github.com/IncSW/geoip2) 3 | [![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/IncSW/geoip2?tab=doc) 4 | 5 | # GeoIP2 Reader for Go 6 | 7 | This library reads MaxMind GeoIP2 databases. 8 | 9 | Inspired by [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang). 10 | 11 | ## Installation 12 | 13 | `go get github.com/IncSW/geoip2` 14 | 15 | ## Quick Start 16 | 17 | ```go 18 | import "github.com/IncSW/geoip2" 19 | 20 | reader, err := geoip2.NewCityReaderFromFile("path/to/GeoIP2-City.mmdb") 21 | if err != nil { 22 | panic(err) 23 | } 24 | record, err := reader.Lookup(net.ParseIP("81.2.69.142")) 25 | if err != nil { 26 | panic(err) 27 | } 28 | println(record.Continent.Names["zh-CN"]) // 欧洲 29 | println(record.City.Names["pt-BR"]) // Wimbledon 30 | if len(record.Subdivisions) != 0 { 31 | println(record.Subdivisions[0].Names["en"]) // England 32 | } 33 | println(record.Country.Names["ru"]) // Великобритания 34 | println(record.Country.ISOCode) // GB 35 | println(record.Location.TimeZone) // Europe/London 36 | println(record.Country.GeoNameID) // 2635167, https://www.geonames.org/2635167 37 | ``` 38 | 39 | ## Performance 40 | 41 | ### [IncSW/geoip2](https://github.com/IncSW/geoip2) 42 | ``` 43 | city-24 342847 2981 ns/op 2032 B/op 12 allocs/op 44 | city_parallel-24 4477626 269 ns/op 2032 B/op 12 allocs/op 45 | isp-24 3539738 336 ns/op 64 B/op 1 allocs/op 46 | isp_parallel-24 46938070 25.7 ns/op 64 B/op 1 allocs/op 47 | connection_type-24 8759110 133 ns/op 0 B/op 0 allocs/op 48 | connection_type_parallel-24 142261742 8.34 ns/op 0 B/op 0 allocs/op 49 | ``` 50 | 51 | ### [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang) 52 | ``` 53 | city-24 109092 10717 ns/op 2848 B/op 103 allocs/op 54 | city_parallel-24 662510 1718 ns/op 2848 B/op 103 allocs/op 55 | isp-24 1688287 705 ns/op 112 B/op 4 allocs/op 56 | isp_parallel-24 14285560 84.4 ns/op 112 B/op 4 allocs/op 57 | connection_type-24 3883234 305 ns/op 32 B/op 2 allocs/op 58 | connection_type_parallel-24 34284831 32.1 ns/op 32 B/op 2 allocs/op 59 | ``` 60 | 61 | ## Supported databases types 62 | 63 | ### Country 64 | - GeoIP2-Country 65 | - GeoLite2-Country 66 | - DBIP-Country 67 | - DBIP-Country-Lite 68 | 69 | ### City 70 | - GeoIP2-City 71 | - GeoLite2-City 72 | - GeoIP2-Enterprise 73 | - DBIP-City-Lite 74 | 75 | ### ISP 76 | - GeoIP2-ISP 77 | 78 | ### ASN 79 | - GeoLite2-ASN 80 | - DBIP-ASN-Lite 81 | - DBIP-ASN-Lite (compat=GeoLite2-ASN) 82 | 83 | ### Connection Type 84 | - GeoIP2-Connection-Type 85 | 86 | ### Anonymous IP 87 | - GeoIP2-Anonymous-IP 88 | 89 | ### Domain 90 | - GeoIP2-Domain 91 | 92 | ## MMDB files for tests 93 | 94 | MMDB files for tests are organised in their respective directories based on the source within the `testdata` repository root directory. 95 | 96 | ### MaxMind 97 | 98 | These are obtained using a submodule into a `testdata/maxmind` directory. 99 | 100 | ``` 101 | git submodule init 102 | git submodule update 103 | ``` 104 | 105 | ### DB-IP 106 | 107 | You must obtain these files manually and place them into the `testdata/dbip` directory. 108 | 109 | ## License 110 | 111 | [MIT License](LICENSE). 112 | -------------------------------------------------------------------------------- /anonymous_ip.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import "errors" 4 | 5 | func readAnonymousIPMap(result *AnonymousIP, buffer []byte, mapSize uint, offset uint) (uint, error) { 6 | var key []byte 7 | var err error 8 | for i := uint(0); i < mapSize; i++ { 9 | key, offset, err = readMapKey(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch b2s(key) { 14 | case "is_anonymous": 15 | result.IsAnonymous, offset, err = readBool(buffer, offset) 16 | if err != nil { 17 | return 0, err 18 | } 19 | case "is_anonymous_vpn": 20 | result.IsAnonymousVPN, offset, err = readBool(buffer, offset) 21 | if err != nil { 22 | return 0, err 23 | } 24 | case "is_hosting_provider": 25 | result.IsHostingProvider, offset, err = readBool(buffer, offset) 26 | if err != nil { 27 | return 0, err 28 | } 29 | case "is_public_proxy": 30 | result.IsPublicProxy, offset, err = readBool(buffer, offset) 31 | if err != nil { 32 | return 0, err 33 | } 34 | case "is_tor_exit_node": 35 | result.IsTorExitNode, offset, err = readBool(buffer, offset) 36 | if err != nil { 37 | return 0, err 38 | } 39 | case "is_residential_proxy": 40 | result.IsResidentialProxy, offset, err = readBool(buffer, offset) 41 | if err != nil { 42 | return 0, err 43 | } 44 | default: 45 | return 0, errors.New("unknown anonymous ip key: " + string(key)) 46 | } 47 | } 48 | return offset, nil 49 | } 50 | -------------------------------------------------------------------------------- /asn.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import "errors" 4 | 5 | func readASNMap(result *ASN, buffer []byte, mapSize uint, offset uint) (uint, error) { 6 | var key []byte 7 | var err error 8 | for i := uint(0); i < mapSize; i++ { 9 | key, offset, err = readMapKey(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch b2s(key) { 14 | case "autonomous_system_number": 15 | result.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset) 16 | if err != nil { 17 | return 0, err 18 | } 19 | case "autonomous_system_organization": 20 | result.AutonomousSystemOrganization, offset, err = readString(buffer, offset) 21 | if err != nil { 22 | return 0, err 23 | } 24 | default: 25 | return 0, errors.New("unknown asn key: " + string(key)) 26 | } 27 | } 28 | return offset, nil 29 | } 30 | -------------------------------------------------------------------------------- /city.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readCity(city *City, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readCityMap(city, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid city pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readCityMap(city, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid city type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readCityMap(city *City, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "geoname_id": 48 | city.GeoNameID, offset, err = readUInt32(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "names": 53 | city.Names, offset, err = readStringMap(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | case "confidence": 58 | city.Confidence, offset, err = readUInt16(buffer, offset) 59 | if err != nil { 60 | return 0, err 61 | } 62 | default: 63 | return 0, errors.New("unknown city key: " + string(key)) 64 | } 65 | } 66 | return offset, nil 67 | } 68 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "math" 7 | "net" 8 | "strconv" 9 | "unsafe" 10 | ) 11 | 12 | func getNetworkString(ip net.IP, mask uint) string { 13 | bitLen := 128 14 | for i := 0; i < len(ip); i++ { 15 | if ip[i] != 0 { 16 | if i == 10 { 17 | bitLen = 32 18 | } 19 | break 20 | } 21 | } 22 | cidrMask := net.CIDRMask(int(mask), bitLen) 23 | ipNet := net.IPNet{ 24 | IP: ip.Mask(cidrMask), 25 | Mask: cidrMask, 26 | } 27 | return ipNet.String() 28 | } 29 | 30 | func readControl(buffer []byte, offset uint) (byte, uint, uint, error) { 31 | controlByte := buffer[offset] 32 | offset++ 33 | dataType := controlByte >> 5 34 | if dataType == dataTypeExtended { 35 | dataType = buffer[offset] + 7 36 | offset++ 37 | } 38 | size := uint(controlByte & 0x1f) 39 | if dataType == dataTypeExtended || size < 29 { 40 | return dataType, size, offset, nil 41 | } 42 | bytesToRead := size - 28 43 | newOffset := offset + bytesToRead 44 | if newOffset > uint(len(buffer)) { 45 | return 0, 0, 0, errors.New("invalid offset") 46 | } 47 | size = uint(bytesToUInt64(buffer[offset:newOffset])) 48 | switch bytesToRead { 49 | case 1: 50 | size += 29 51 | case 2: 52 | size += 285 53 | default: 54 | size += 65821 55 | } 56 | return dataType, size, newOffset, nil 57 | } 58 | 59 | func readPointer(buffer []byte, size uint, offset uint) (uint, uint, error) { 60 | pointerSize := ((size >> 3) & 0x3) + 1 61 | newOffset := offset + pointerSize 62 | if newOffset > uint(len(buffer)) { 63 | return 0, 0, errors.New("invalid offset") 64 | } 65 | prefix := uint64(0) 66 | if pointerSize != 4 { 67 | prefix = uint64(size) & 0x7 68 | } 69 | unpacked := uint(bytesToUInt64WithPrefix(prefix, buffer[offset:newOffset])) 70 | pointerValueOffset := uint(0) 71 | switch pointerSize { 72 | case 2: 73 | pointerValueOffset = 2048 74 | case 3: 75 | pointerValueOffset = 526336 76 | case 4: 77 | pointerValueOffset = 0 78 | } 79 | return unpacked + pointerValueOffset, newOffset, nil 80 | } 81 | 82 | func readFloat64(buffer []byte, offset uint) (float64, uint, error) { 83 | dataType, size, offset, err := readControl(buffer, offset) 84 | if err != nil { 85 | return 0, 0, err 86 | } 87 | switch dataType { 88 | case dataTypeFloat64: 89 | newOffset := offset + size 90 | return bytesToFloat64(buffer[offset:newOffset]), newOffset, nil 91 | case dataTypePointer: 92 | pointer, newOffset, err := readPointer(buffer, size, offset) 93 | if err != nil { 94 | return 0, 0, err 95 | } 96 | dataType, size, offset, err := readControl(buffer, pointer) 97 | if err != nil { 98 | return 0, 0, err 99 | } 100 | if dataType != dataTypeFloat64 { 101 | return 0, 0, errors.New("invalid float64 pointer type: " + strconv.Itoa(int(dataType))) 102 | } 103 | return bytesToFloat64(buffer[offset : offset+size]), newOffset, nil 104 | default: 105 | return 0, 0, errors.New("invalid float64 type: " + strconv.Itoa(int(dataType))) 106 | } 107 | } 108 | 109 | func readUInt16(buffer []byte, offset uint) (uint16, uint, error) { 110 | dataType, size, offset, err := readControl(buffer, offset) 111 | if err != nil { 112 | return 0, 0, err 113 | } 114 | switch dataType { 115 | case dataTypeUint16: 116 | newOffset := offset + size 117 | return uint16(bytesToUInt64(buffer[offset:newOffset])), newOffset, nil 118 | case dataTypePointer: 119 | pointer, newOffset, err := readPointer(buffer, size, offset) 120 | if err != nil { 121 | return 0, 0, err 122 | } 123 | dataType, size, offset, err := readControl(buffer, pointer) 124 | if err != nil { 125 | return 0, 0, err 126 | } 127 | if dataType != dataTypeUint16 { 128 | return 0, 0, errors.New("invalid uint16 pointer type: " + strconv.Itoa(int(dataType))) 129 | } 130 | return uint16(bytesToUInt64(buffer[offset : offset+size])), newOffset, nil 131 | default: 132 | return 0, 0, errors.New("invalid uint16 type: " + strconv.Itoa(int(dataType))) 133 | } 134 | } 135 | 136 | func readUInt32(buffer []byte, offset uint) (uint32, uint, error) { 137 | dataType, size, offset, err := readControl(buffer, offset) 138 | if err != nil { 139 | return 0, 0, err 140 | } 141 | switch dataType { 142 | case dataTypeUint32: 143 | newOffset := offset + size 144 | return uint32(bytesToUInt64(buffer[offset:newOffset])), newOffset, nil 145 | case dataTypePointer: 146 | pointer, newOffset, err := readPointer(buffer, size, offset) 147 | if err != nil { 148 | return 0, 0, err 149 | } 150 | dataType, size, offset, err := readControl(buffer, pointer) 151 | if err != nil { 152 | return 0, 0, err 153 | } 154 | if dataType != dataTypeUint32 { 155 | return 0, 0, errors.New("invalid uint32 pointer type: " + strconv.Itoa(int(dataType))) 156 | } 157 | return uint32(bytesToUInt64(buffer[offset : offset+size])), newOffset, nil 158 | default: 159 | return 0, 0, errors.New("invalid uint32 type: " + strconv.Itoa(int(dataType))) 160 | } 161 | } 162 | 163 | func readBool(buffer []byte, offset uint) (bool, uint, error) { 164 | dataType, size, offset, err := readControl(buffer, offset) 165 | if err != nil { 166 | return false, 0, err 167 | } 168 | switch dataType { 169 | case dataTypeBool: 170 | return size != 0, offset, nil 171 | case dataTypePointer: 172 | pointer, newOffset, err := readPointer(buffer, size, offset) 173 | if err != nil { 174 | return false, 0, err 175 | } 176 | dataType, size, _, err := readControl(buffer, pointer) 177 | if err != nil { 178 | return false, 0, err 179 | } 180 | if dataType != dataTypeBool { 181 | return false, 0, errors.New("invalid bool pointer type: " + strconv.Itoa(int(dataType))) 182 | } 183 | return size != 0, newOffset, nil 184 | default: 185 | return false, 0, errors.New("invalid bool type: " + strconv.Itoa(int(dataType))) 186 | } 187 | } 188 | 189 | func readString(buffer []byte, offset uint) (string, uint, error) { 190 | dataType, size, offset, err := readControl(buffer, offset) 191 | if err != nil { 192 | return "", 0, err 193 | } 194 | switch dataType { 195 | case dataTypeString: 196 | newOffset := offset + size 197 | return b2s(buffer[offset:newOffset]), newOffset, nil 198 | case dataTypePointer: 199 | pointer, newOffset, err := readPointer(buffer, size, offset) 200 | if err != nil { 201 | return "", 0, err 202 | } 203 | dataType, size, offset, err := readControl(buffer, pointer) 204 | if err != nil { 205 | return "", 0, err 206 | } 207 | if dataType != dataTypeString { 208 | return "", 0, errors.New("invalid string pointer type: " + strconv.Itoa(int(dataType))) 209 | } 210 | return b2s(buffer[offset : offset+size]), newOffset, nil 211 | default: 212 | return "", 0, errors.New("invalid string type: " + strconv.Itoa(int(dataType))) 213 | } 214 | } 215 | 216 | func readStringMap(buffer []byte, offset uint) (map[string]string, uint, error) { 217 | dataType, size, offset, err := readControl(buffer, offset) 218 | if err != nil { 219 | return nil, 0, err 220 | } 221 | switch dataType { 222 | case dataTypeMap: 223 | return readStringMapMap(buffer, size, offset) 224 | case dataTypePointer: 225 | pointer, newOffset, err := readPointer(buffer, size, offset) 226 | if err != nil { 227 | return nil, 0, err 228 | } 229 | dataType, size, offset, err := readControl(buffer, pointer) 230 | if err != nil { 231 | return nil, 0, err 232 | } 233 | if dataType != dataTypeMap { 234 | return nil, 0, errors.New("invalid stringMap pointer type: " + strconv.Itoa(int(dataType))) 235 | } 236 | value, _, err := readStringMapMap(buffer, size, offset) 237 | if err != nil { 238 | return nil, 0, err 239 | } 240 | return value, newOffset, nil 241 | default: 242 | return nil, 0, errors.New("invalid stringMap type: " + strconv.Itoa(int(dataType))) 243 | } 244 | } 245 | 246 | func readStringMapMap(buffer []byte, mapSize uint, offset uint) (map[string]string, uint, error) { 247 | var key []byte 248 | var err error 249 | var dataType byte 250 | var size uint 251 | result := map[string]string{} 252 | for i := uint(0); i < mapSize; i++ { 253 | key, offset, err = readMapKey(buffer, offset) 254 | if err != nil { 255 | return nil, 0, err 256 | } 257 | dataType, size, offset, err = readControl(buffer, offset) 258 | if err != nil { 259 | return nil, 0, err 260 | } 261 | switch dataType { 262 | case dataTypePointer: 263 | pointer, newOffset, err := readPointer(buffer, size, offset) 264 | if err != nil { 265 | return nil, 0, err 266 | } 267 | dataType, size, valueOffset, err := readControl(buffer, pointer) 268 | if err != nil { 269 | return nil, 0, err 270 | } 271 | if dataType != dataTypeString { 272 | return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType))) 273 | } 274 | offset = newOffset 275 | result[b2s(key)] = b2s(buffer[valueOffset : valueOffset+size]) 276 | case dataTypeString: 277 | newOffset := offset + size 278 | value := b2s(buffer[offset:newOffset]) 279 | offset = newOffset 280 | result[b2s(key)] = value 281 | default: 282 | return nil, 0, errors.New("invalid data type of key " + string(key) + ": " + strconv.Itoa(int(dataType))) 283 | } 284 | } 285 | return result, offset, nil 286 | } 287 | 288 | func readMapKey(buffer []byte, offset uint) ([]byte, uint, error) { 289 | dataType, size, offset, err := readControl(buffer, offset) 290 | if err != nil { 291 | return nil, 0, err 292 | } 293 | if dataType == dataTypePointer { 294 | pointer, newOffset, err := readPointer(buffer, size, offset) 295 | if err != nil { 296 | return nil, 0, err 297 | } 298 | dataType, size, offset, err := readControl(buffer, pointer) 299 | if err != nil { 300 | return nil, 0, err 301 | } 302 | if dataType != dataTypeString { 303 | return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType))) 304 | } 305 | return buffer[offset : offset+size], newOffset, nil 306 | } 307 | if dataType != dataTypeString { 308 | return nil, 0, errors.New("map key must be a string, got: " + strconv.Itoa(int(dataType))) 309 | } 310 | newOffset := offset + size 311 | if newOffset > uint(len(buffer)) { 312 | return nil, 0, errors.New("invalid offset") 313 | } 314 | return buffer[offset:newOffset], newOffset, nil 315 | } 316 | 317 | func readStringSlice(buffer []byte, sliceSize uint, offset uint) ([]string, uint, error) { 318 | var err error 319 | var value string 320 | result := make([]string, sliceSize) 321 | for i := uint(0); i < sliceSize; i++ { 322 | value, offset, err = readString(buffer, offset) 323 | if err != nil { 324 | return nil, 0, err 325 | } 326 | result[i] = value 327 | } 328 | return result, offset, nil 329 | } 330 | 331 | func bytesToUInt64(buffer []byte) uint64 { 332 | switch len(buffer) { 333 | case 1: 334 | return uint64(buffer[0]) 335 | case 2: 336 | return uint64(buffer[0])<<8 | uint64(buffer[1]) 337 | case 3: 338 | return (uint64(buffer[0])<<8|uint64(buffer[1]))<<8 | uint64(buffer[2]) 339 | case 4: 340 | return ((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8 | uint64(buffer[3]) 341 | case 5: 342 | return (((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8 | uint64(buffer[4]) 343 | case 6: 344 | return ((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8 | uint64(buffer[5]) 345 | case 7: 346 | return (((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8 | uint64(buffer[6]) 347 | case 8: 348 | return ((((((uint64(buffer[0])<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8|uint64(buffer[6]))<<8 | uint64(buffer[7]) 349 | } 350 | return 0 351 | } 352 | 353 | func bytesToUInt64WithPrefix(prefix uint64, buffer []byte) uint64 { 354 | switch len(buffer) { 355 | case 0: 356 | return prefix 357 | case 1: 358 | return prefix<<8 | uint64(buffer[0]) 359 | case 2: 360 | return (prefix<<8|uint64(buffer[0]))<<8 | uint64(buffer[1]) 361 | case 3: 362 | return ((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8 | uint64(buffer[2]) 363 | case 4: 364 | return (((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8 | uint64(buffer[3]) 365 | case 5: 366 | return ((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8 | uint64(buffer[4]) 367 | case 6: 368 | return (((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8 | uint64(buffer[5]) 369 | case 7: 370 | return ((((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8 | uint64(buffer[6]) 371 | case 8: 372 | return (((((((prefix<<8|uint64(buffer[0]))<<8|uint64(buffer[1]))<<8|uint64(buffer[2]))<<8|uint64(buffer[3]))<<8|uint64(buffer[4]))<<8|uint64(buffer[5]))<<8|uint64(buffer[6]))<<8 | uint64(buffer[7]) 373 | } 374 | return 0 375 | } 376 | 377 | func bytesToFloat32(buffer []byte) float32 { 378 | bits := binary.BigEndian.Uint32(buffer) 379 | return math.Float32frombits(bits) 380 | } 381 | 382 | func bytesToFloat64(buffer []byte) float64 { 383 | bits := binary.BigEndian.Uint64(buffer) 384 | return math.Float64frombits(bits) 385 | } 386 | 387 | func b2s(value []byte) string { 388 | return *(*string)(unsafe.Pointer(&value)) 389 | } 390 | -------------------------------------------------------------------------------- /connection_type.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import "errors" 4 | 5 | func readConnectionTypeMap(result *ConnectionType, buffer []byte, mapSize uint, offset uint) (uint, error) { 6 | var key []byte 7 | var err error 8 | for i := uint(0); i < mapSize; i++ { 9 | key, offset, err = readMapKey(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch b2s(key) { 14 | case "connection_type": 15 | result.ConnectionType, offset, err = readString(buffer, offset) 16 | if err != nil { 17 | return 0, err 18 | } 19 | default: 20 | return 0, errors.New("unknown connectionType key: " + string(key)) 21 | } 22 | } 23 | return offset, nil 24 | } 25 | -------------------------------------------------------------------------------- /continent.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readContinent(continent *Continent, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readContinentMap(continent, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid continent pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readContinentMap(continent, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid continent type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readContinentMap(continent *Continent, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "geoname_id": 48 | continent.GeoNameID, offset, err = readUInt32(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "code": 53 | continent.Code, offset, err = readString(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | case "names": 58 | continent.Names, offset, err = readStringMap(buffer, offset) 59 | if err != nil { 60 | return 0, err 61 | } 62 | default: 63 | return 0, errors.New("unknown continent key: " + string(key)) 64 | } 65 | } 66 | return offset, nil 67 | } 68 | -------------------------------------------------------------------------------- /country.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readCountry(country *Country, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readCountryMap(country, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid country pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readCountryMap(country, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid country type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readCountryMap(country *Country, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "geoname_id": 48 | country.GeoNameID, offset, err = readUInt32(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "iso_code": 53 | country.ISOCode, offset, err = readString(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | case "names": 58 | country.Names, offset, err = readStringMap(buffer, offset) 59 | if err != nil { 60 | return 0, err 61 | } 62 | case "is_in_european_union": 63 | country.IsInEuropeanUnion, offset, err = readBool(buffer, offset) 64 | if err != nil { 65 | return 0, err 66 | } 67 | case "type": 68 | country.Type, offset, err = readString(buffer, offset) 69 | if err != nil { 70 | return 0, err 71 | } 72 | case "confidence": 73 | country.Confidence, offset, err = readUInt16(buffer, offset) 74 | if err != nil { 75 | return 0, err 76 | } 77 | default: 78 | return 0, errors.New("unknown country key: " + string(key)) 79 | } 80 | } 81 | return offset, nil 82 | } 83 | -------------------------------------------------------------------------------- /domain.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import "errors" 4 | 5 | func readDomainMap(result *Domain, buffer []byte, mapSize uint, offset uint) (uint, error) { 6 | var key []byte 7 | var err error 8 | for i := uint(0); i < mapSize; i++ { 9 | key, offset, err = readMapKey(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch b2s(key) { 14 | case "domain": 15 | result.Domain, offset, err = readString(buffer, offset) 16 | if err != nil { 17 | return 0, err 18 | } 19 | default: 20 | return 0, errors.New("unknown domain key: " + string(key)) 21 | } 22 | } 23 | return offset, nil 24 | } 25 | -------------------------------------------------------------------------------- /geoip2_test.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "net" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestReader(t *testing.T) { 10 | ip := net.ParseIP("81.2.69.160") 11 | 12 | countryReader, err := NewCountryReaderFromFile("testdata/maxmind/test-data/GeoIP2-Country-Test.mmdb") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | _, err = countryReader.Lookup(ip) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | countryLiteReader, err := NewCountryReaderFromFile("testdata/maxmind/test-data/GeoLite2-Country-Test.mmdb") 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | _, err = countryLiteReader.Lookup(ip) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | cityReader, err := NewCityReaderFromFile("testdata/maxmind/test-data/GeoIP2-City-Test.mmdb") 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | _, err = cityReader.Lookup(ip) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | cityLiteReader, err := NewCityReaderFromFile("testdata/maxmind/test-data/GeoLite2-City-Test.mmdb") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | _, err = cityLiteReader.Lookup(ip) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | ispReader, err := NewISPReaderFromFile("testdata/maxmind/test-data/GeoIP2-ISP-Test.mmdb") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | _, err = ispReader.Lookup(ip) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | ip = net.ParseIP("2.125.160.216") 56 | connectionTypeReader, err := NewConnectionTypeReaderFromFile("testdata/maxmind/test-data/GeoIP2-Connection-Type-Test.mmdb") 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | _, err = connectionTypeReader.Lookup(ip) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | ip = net.ParseIP("81.128.69.160") 66 | asnReader, err := NewASNReaderFromFile("testdata/maxmind/test-data/GeoLite2-ASN-Test.mmdb") 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | _, err = asnReader.Lookup(ip) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | } 75 | 76 | func BenchmarkGeoIP2(b *testing.B) { 77 | ip := net.ParseIP("1.128.0.0") 78 | b.ReportAllocs() 79 | 80 | b.Run("country", func(b *testing.B) { 81 | reader, err := NewCountryReaderFromFile("testdata/maxmind/test-data/GeoIP2-Country-Test.mmdb") 82 | if err != nil { 83 | b.Fatal(err) 84 | } 85 | b.Run("sync", func(b *testing.B) { 86 | for i := 0; i < b.N; i++ { 87 | _, _ = reader.Lookup(ip) 88 | } 89 | }) 90 | b.Run("parallel", func(b *testing.B) { 91 | b.RunParallel(func(pb *testing.PB) { 92 | for pb.Next() { 93 | _, _ = reader.Lookup(ip) 94 | } 95 | }) 96 | }) 97 | }) 98 | 99 | b.Run("city", func(b *testing.B) { 100 | reader, err := NewCityReaderFromFile("testdata/maxmind/test-data/GeoIP2-City-Test.mmdb") 101 | if err != nil { 102 | b.Fatal(err) 103 | } 104 | b.Run("sync", func(b *testing.B) { 105 | for i := 0; i < b.N; i++ { 106 | _, _ = reader.Lookup(ip) 107 | } 108 | }) 109 | b.Run("parallel", func(b *testing.B) { 110 | b.RunParallel(func(pb *testing.PB) { 111 | for pb.Next() { 112 | _, _ = reader.Lookup(ip) 113 | } 114 | }) 115 | }) 116 | }) 117 | 118 | b.Run("isp", func(b *testing.B) { 119 | reader, err := NewISPReaderFromFile("testdata/maxmind/test-data/GeoIP2-ISP-Test.mmdb") 120 | if err != nil { 121 | b.Fatal(err) 122 | } 123 | b.Run("sync", func(b *testing.B) { 124 | for i := 0; i < b.N; i++ { 125 | _, _ = reader.Lookup(ip) 126 | } 127 | }) 128 | b.Run("parallel", func(b *testing.B) { 129 | b.RunParallel(func(pb *testing.PB) { 130 | for pb.Next() { 131 | _, _ = reader.Lookup(ip) 132 | } 133 | }) 134 | }) 135 | }) 136 | 137 | b.Run("connection_type", func(b *testing.B) { 138 | reader, err := NewConnectionTypeReaderFromFile("testdata/maxmind/test-data/GeoIP2-Connection-Type-Test.mmdb") 139 | if err != nil { 140 | b.Fatal(err) 141 | } 142 | b.Run("sync", func(b *testing.B) { 143 | for i := 0; i < b.N; i++ { 144 | _, _ = reader.Lookup(ip) 145 | } 146 | }) 147 | b.Run("parallel", func(b *testing.B) { 148 | b.RunParallel(func(pb *testing.PB) { 149 | for pb.Next() { 150 | _, _ = reader.Lookup(ip) 151 | } 152 | }) 153 | }) 154 | }) 155 | 156 | b.Run("asn", func(b *testing.B) { 157 | reader, err := NewASNReaderFromFile("testdata/maxmind/test-data/GeoLite2-ASN-Test.mmdb") 158 | if err != nil { 159 | b.Fatal(err) 160 | } 161 | b.Run("sync", func(b *testing.B) { 162 | for i := 0; i < b.N; i++ { 163 | asn, err := reader.Lookup(ip) 164 | if err != nil { 165 | b.Fatal(err) 166 | } 167 | runtime.KeepAlive(asn.Network) 168 | } 169 | }) 170 | b.Run("parallel", func(b *testing.B) { 171 | b.RunParallel(func(pb *testing.PB) { 172 | for pb.Next() { 173 | asn, err := reader.Lookup(ip) 174 | if err != nil { 175 | b.Fatal(err) 176 | } 177 | runtime.KeepAlive(asn.Network) 178 | } 179 | }) 180 | }) 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/IncSW/geoip2 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /isp.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import "errors" 4 | 5 | func readISPMap(result *ISP, buffer []byte, mapSize uint, offset uint) (uint, error) { 6 | var key []byte 7 | var err error 8 | for i := uint(0); i < mapSize; i++ { 9 | key, offset, err = readMapKey(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch b2s(key) { 14 | case "autonomous_system_number": 15 | result.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset) 16 | if err != nil { 17 | return 0, err 18 | } 19 | case "autonomous_system_organization": 20 | result.AutonomousSystemOrganization, offset, err = readString(buffer, offset) 21 | if err != nil { 22 | return 0, err 23 | } 24 | case "isp": 25 | result.ISP, offset, err = readString(buffer, offset) 26 | if err != nil { 27 | return 0, err 28 | } 29 | case "organization": 30 | result.Organization, offset, err = readString(buffer, offset) 31 | if err != nil { 32 | return 0, err 33 | } 34 | case "mobile_country_code": 35 | result.MobileCountryCode, offset, err = readString(buffer, offset) 36 | if err != nil { 37 | return 0, err 38 | } 39 | case "mobile_network_code": 40 | result.MobileNetworkCode, offset, err = readString(buffer, offset) 41 | if err != nil { 42 | return 0, err 43 | } 44 | default: 45 | return 0, errors.New("unknown isp key: " + string(key)) 46 | } 47 | } 48 | return offset, nil 49 | } 50 | -------------------------------------------------------------------------------- /location.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readLocation(location *Location, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readLocationMap(location, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid location pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readLocationMap(location, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid location type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readLocationMap(location *Location, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "latitude": 48 | location.Latitude, offset, err = readFloat64(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "longitude": 53 | location.Longitude, offset, err = readFloat64(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | case "accuracy_radius": 58 | location.AccuracyRadius, offset, err = readUInt16(buffer, offset) 59 | if err != nil { 60 | return 0, err 61 | } 62 | case "time_zone": 63 | location.TimeZone, offset, err = readString(buffer, offset) 64 | if err != nil { 65 | return 0, err 66 | } 67 | case "metro_code": 68 | location.MetroCode, offset, err = readUInt16(buffer, offset) 69 | if err != nil { 70 | return 0, err 71 | } 72 | default: 73 | return 0, errors.New("unknown location key: " + string(key)) 74 | } 75 | } 76 | return offset, nil 77 | } 78 | -------------------------------------------------------------------------------- /metadata.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | type Metadata struct { 9 | NodeCount uint32 // node_count This is an unsigned 32-bit integer indicating the number of nodes in the search tree. 10 | RecordSize uint16 // record_size This is an unsigned 16-bit integer. It indicates the number of bits in a record in the search tree. Note that each node consists of two records. 11 | IPVersion uint16 // ip_version This is an unsigned 16-bit integer which is always 4 or 6. It indicates whether the database contains IPv4 or IPv6 address data. 12 | DatabaseType string // database_type This is a string that indicates the structure of each data record associated with an IP address. The actual definition of these structures is left up to the database creator. Names starting with “GeoIP” are reserved for use by MaxMind (and “GeoIP” is a trademark anyway). 13 | Languages []string // languages An array of strings, each of which is a locale code. A given record may contain data items that have been localized to some or all of these locales. Records should not contain localized data for locales not included in this array. This is an optional key, as this may not be relevant for all types of data. 14 | BinaryFormatMajorVersion uint16 // binary_format_major_version This is an unsigned 16-bit integer indicating the major version number for the database’s binary format. 15 | BinaryFormatMinorVersion uint16 // binary_format_minor_version This is an unsigned 16-bit integer indicating the minor version number for the database’s binary format. 16 | BuildEpoch uint64 // build_epoch This is an unsigned 64-bit integer that contains the database build timestamp as a Unix epoch value. 17 | Description map[string]string // description This key will always point to a map. The keys of that map will be language codes, and the values will be a description in that language as a UTF-8 string. The codes may include additional information such as script or country identifiers, like “zh-TW” or “mn-Cyrl-MN”. The additional identifiers will be separated by a dash character (“-“). 18 | } 19 | 20 | var metadataStartMarker = []byte("\xAB\xCD\xEFMaxMind.com") 21 | 22 | func readMetadata(buffer []byte) (*Metadata, error) { 23 | dataType, metadataSize, offset, err := readControl(buffer, 0) 24 | if err != nil { 25 | return nil, err 26 | } 27 | if dataType != dataTypeMap { 28 | return nil, errors.New("invalid metadata type: " + strconv.Itoa(int(dataType))) 29 | } 30 | var key []byte 31 | metadata := &Metadata{} 32 | for i := uint(0); i < metadataSize; i++ { 33 | key, offset, err = readMapKey(buffer, offset) 34 | if err != nil { 35 | return nil, err 36 | } 37 | size := uint(0) 38 | dataType, size, offset, err = readControl(buffer, offset) 39 | if err != nil { 40 | return nil, err 41 | } 42 | newOffset := uint(0) 43 | switch b2s(key) { 44 | case "binary_format_major_version": 45 | if dataType != dataTypeUint16 { 46 | return nil, errors.New("invalid binary_format_major_version type: " + strconv.Itoa(int(dataType))) 47 | } 48 | newOffset = offset + size 49 | metadata.BinaryFormatMajorVersion = uint16(bytesToUInt64(buffer[offset:newOffset])) 50 | case "binary_format_minor_version": 51 | if dataType != dataTypeUint16 { 52 | return nil, errors.New("invalid binary_format_minor_version type: " + strconv.Itoa(int(dataType))) 53 | } 54 | newOffset = offset + size 55 | metadata.BinaryFormatMinorVersion = uint16(bytesToUInt64(buffer[offset:newOffset])) 56 | case "build_epoch": 57 | if dataType != dataTypeUint64 { 58 | return nil, errors.New("invalid build_epoch type: " + strconv.Itoa(int(dataType))) 59 | } 60 | newOffset = offset + size 61 | metadata.BuildEpoch = bytesToUInt64(buffer[offset:newOffset]) 62 | case "database_type": 63 | if dataType != dataTypeString { 64 | return nil, errors.New("invalid database_type type: " + strconv.Itoa(int(dataType))) 65 | } 66 | newOffset = offset + size 67 | metadata.DatabaseType = b2s(buffer[offset:newOffset]) 68 | case "description": 69 | if dataType != dataTypeMap { 70 | return nil, errors.New("invalid description type: " + strconv.Itoa(int(dataType))) 71 | } 72 | metadata.Description, newOffset, err = readStringMapMap(buffer, size, offset) 73 | if err != nil { 74 | return nil, err 75 | } 76 | case "ip_version": 77 | if dataType != dataTypeUint16 { 78 | return nil, errors.New("invalid ip_version type: " + strconv.Itoa(int(dataType))) 79 | } 80 | newOffset = offset + size 81 | metadata.IPVersion = uint16(bytesToUInt64(buffer[offset:newOffset])) 82 | case "languages": 83 | if dataType != dataTypeSlice { 84 | return nil, errors.New("invalid languages type: " + strconv.Itoa(int(dataType))) 85 | } 86 | metadata.Languages, newOffset, err = readStringSlice(buffer, size, offset) 87 | if err != nil { 88 | return nil, err 89 | } 90 | case "node_count": 91 | if dataType != dataTypeUint32 { 92 | return nil, errors.New("invalid node_count type: " + strconv.Itoa(int(dataType))) 93 | } 94 | newOffset = offset + size 95 | metadata.NodeCount = uint32(bytesToUInt64(buffer[offset:newOffset])) 96 | case "record_size": 97 | if dataType != dataTypeUint16 { 98 | return nil, errors.New("invalid record_size type: " + strconv.Itoa(int(dataType))) 99 | } 100 | newOffset = offset + size 101 | metadata.RecordSize = uint16(bytesToUInt64(buffer[offset:newOffset])) 102 | default: 103 | return nil, errors.New("unknown key: " + string(key) + ", type: " + strconv.Itoa(int(dataType))) 104 | } 105 | offset = newOffset 106 | } 107 | return metadata, nil 108 | } 109 | -------------------------------------------------------------------------------- /postal.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readPostal(postal *Postal, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readPostalMap(postal, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid postal pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readPostalMap(postal, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid postal type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readPostalMap(postal *Postal, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "code": 48 | postal.Code, offset, err = readString(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "confidence": 53 | postal.Confidence, offset, err = readUInt16(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | default: 58 | return 0, errors.New("unknown postal key: " + string(key)) 59 | } 60 | } 61 | return offset, nil 62 | } 63 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | var ErrNotFound = errors.New("not found") 11 | 12 | type reader struct { 13 | metadata *Metadata 14 | buffer []byte 15 | decoderBuffer []byte 16 | nodeBuffer []byte 17 | ipV4Start uint 18 | ipV4StartBitDepth uint 19 | nodeOffsetMult uint 20 | } 21 | 22 | func (r *reader) getOffsetWithPrefix(ip net.IP) (uint, uint, error) { 23 | pointer, prefix, err := r.lookupPointer(ip) 24 | if err != nil { 25 | return 0, 0, err 26 | } 27 | offset := pointer - uint(r.metadata.NodeCount) - uint(dataSectionSeparatorSize) 28 | if offset >= uint(len(r.buffer)) { 29 | return 0, 0, errors.New("the MaxMind DB search tree is corrupt: " + strconv.Itoa(int(pointer))) 30 | } 31 | return offset, prefix, nil 32 | } 33 | 34 | func (r *reader) getOffset(ip net.IP) (uint, error) { 35 | offset, _, err := r.getOffsetWithPrefix(ip) 36 | if err != nil { 37 | return 0, err 38 | } 39 | return offset, nil 40 | } 41 | 42 | func (r *reader) lookupPointer(ip net.IP) (uint, uint, error) { 43 | if ip == nil { 44 | return 0, 0, errors.New("IP cannot be nil") 45 | } 46 | ipV4 := ip.To4() 47 | if ipV4 != nil { 48 | ip = ipV4 49 | } 50 | if len(ip) == 16 && r.metadata.IPVersion == 4 { 51 | return 0, 0, errors.New("cannot look up an IPv6 address in an IPv4-only database") 52 | } 53 | bitCount := uint(len(ip)) * 8 54 | node := uint(0) 55 | if bitCount == 32 { 56 | node = r.ipV4Start 57 | } 58 | nodeCount := uint(r.metadata.NodeCount) 59 | i := uint(0) 60 | for ; i < bitCount && node < nodeCount; i++ { 61 | bit := 1 & (ip[i>>3] >> (7 - (i % 8))) 62 | offset := node * r.nodeOffsetMult 63 | if bit == 0 { 64 | node = r.readLeft(offset) 65 | } else { 66 | node = r.readRight(offset) 67 | } 68 | } 69 | if node == nodeCount { 70 | return 0, 0, ErrNotFound 71 | } else if node > nodeCount { 72 | return node, i, nil 73 | } 74 | return 0, 0, errors.New("invalid node in search tree") 75 | } 76 | 77 | func (r *reader) readLeft(nodeNumber uint) uint { 78 | switch r.metadata.RecordSize { 79 | case 28: 80 | return ((uint(r.nodeBuffer[nodeNumber+3]) & 0xF0) << 20) | (uint(r.nodeBuffer[nodeNumber]) << 16) | (uint(r.nodeBuffer[nodeNumber+1]) << 8) | uint(r.nodeBuffer[nodeNumber+2]) 81 | case 24: 82 | return (uint(r.nodeBuffer[nodeNumber]) << 16) | (uint(r.nodeBuffer[nodeNumber+1]) << 8) | uint(r.nodeBuffer[nodeNumber+2]) 83 | default: // case 32: 84 | return (uint(r.nodeBuffer[nodeNumber]) << 24) | (uint(r.nodeBuffer[nodeNumber+1]) << 16) | (uint(r.nodeBuffer[nodeNumber+2]) << 8) | uint(r.nodeBuffer[nodeNumber+3]) 85 | } 86 | } 87 | 88 | func (r *reader) readRight(nodeNumber uint) uint { 89 | switch r.metadata.RecordSize { 90 | case 28: 91 | return ((uint(r.nodeBuffer[nodeNumber+3]) & 0x0F) << 24) | (uint(r.nodeBuffer[nodeNumber+4]) << 16) | (uint(r.nodeBuffer[nodeNumber+5]) << 8) | uint(r.nodeBuffer[nodeNumber+6]) 92 | case 24: 93 | return (uint(r.nodeBuffer[nodeNumber+3]) << 16) | (uint(r.nodeBuffer[nodeNumber+4]) << 8) | uint(r.nodeBuffer[nodeNumber+5]) 94 | default: // case 32: 95 | return (uint(r.nodeBuffer[nodeNumber+4]) << 24) | (uint(r.nodeBuffer[nodeNumber+5]) << 16) | (uint(r.nodeBuffer[nodeNumber+6]) << 8) | uint(r.nodeBuffer[nodeNumber+7]) 96 | } 97 | } 98 | 99 | func newReader(buffer []byte) (*reader, error) { 100 | if len(buffer) == 0 { 101 | return nil, errors.New("buffer is empty") 102 | } 103 | 104 | metadataStart := bytes.LastIndex(buffer, metadataStartMarker) 105 | metadata, err := readMetadata(buffer[metadataStart+len(metadataStartMarker):]) 106 | if err != nil { 107 | return nil, err 108 | } 109 | nodeOffsetMult := uint(metadata.RecordSize) / 4 110 | searchTreeSize := uint(metadata.NodeCount) * nodeOffsetMult 111 | dataSectionStart := searchTreeSize + dataSectionSeparatorSize 112 | if dataSectionStart > uint(metadataStart) { 113 | return nil, errors.New("the MaxMind DB contains invalid metadata") 114 | } 115 | reader := &reader{ 116 | metadata: metadata, 117 | buffer: buffer, 118 | decoderBuffer: buffer[searchTreeSize+dataSectionSeparatorSize : metadataStart], 119 | nodeBuffer: buffer[:searchTreeSize], 120 | nodeOffsetMult: nodeOffsetMult, 121 | } 122 | if metadata.IPVersion == 6 { 123 | node := uint(0) 124 | i := uint(0) 125 | for ; i < 96 && node < uint(metadata.NodeCount); i++ { 126 | node = reader.readLeft(node * nodeOffsetMult) 127 | } 128 | reader.ipV4Start = node 129 | reader.ipV4StartBitDepth = i 130 | } 131 | return reader, nil 132 | } 133 | -------------------------------------------------------------------------------- /reader_anonymous_ip.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type AnonymousIPReader struct { 11 | *reader 12 | } 13 | 14 | func (r *AnonymousIPReader) Lookup(ip net.IP) (*AnonymousIP, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return nil, err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return nil, err 22 | } 23 | result := &AnonymousIP{} 24 | switch dataType { 25 | case dataTypeMap: 26 | _, err = readAnonymousIPMap(result, r.decoderBuffer, size, offset) 27 | if err != nil { 28 | return nil, err 29 | } 30 | case dataTypePointer: 31 | pointer, _, err := readPointer(r.decoderBuffer, size, offset) 32 | if err != nil { 33 | return nil, err 34 | } 35 | dataType, size, offset, err := readControl(r.decoderBuffer, pointer) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if dataType != dataTypeMap { 40 | return nil, errors.New("invalid Anonymous-IP pointer type: " + strconv.Itoa(int(dataType))) 41 | } 42 | _, err = readAnonymousIPMap(result, r.decoderBuffer, size, offset) 43 | if err != nil { 44 | return nil, err 45 | } 46 | default: 47 | return nil, errors.New("invalid Anonymous-IP type: " + strconv.Itoa(int(dataType))) 48 | } 49 | return result, nil 50 | } 51 | 52 | func NewAnonymousIPReader(buffer []byte) (*AnonymousIPReader, error) { 53 | reader, err := newReader(buffer) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if reader.metadata.DatabaseType != "GeoIP2-Anonymous-IP" { 58 | return nil, errors.New("wrong MaxMind DB Anonymous-IP type: " + reader.metadata.DatabaseType) 59 | } 60 | return &AnonymousIPReader{ 61 | reader: reader, 62 | }, nil 63 | } 64 | 65 | func NewAnonymousIPReaderFromFile(filename string) (*AnonymousIPReader, error) { 66 | buffer, err := ioutil.ReadFile(filename) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return NewAnonymousIPReader(buffer) 71 | } 72 | -------------------------------------------------------------------------------- /reader_asn.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type ASNReader struct { 11 | *reader 12 | } 13 | 14 | func (r *ASNReader) Lookup(ip net.IP) (*ASN, error) { 15 | offset, prefix, err := r.getOffsetWithPrefix(ip) 16 | if err != nil { 17 | return nil, err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return nil, err 22 | } 23 | result := &ASN{} 24 | result.Network = getNetworkString(ip, prefix) 25 | switch dataType { 26 | case dataTypeMap: 27 | _, err = readASNMap(result, r.decoderBuffer, size, offset) 28 | if err != nil { 29 | return nil, err 30 | } 31 | case dataTypePointer: 32 | pointer, _, err := readPointer(r.decoderBuffer, size, offset) 33 | if err != nil { 34 | return nil, err 35 | } 36 | dataType, size, offset, err := readControl(r.decoderBuffer, pointer) 37 | if err != nil { 38 | return nil, err 39 | } 40 | if dataType != dataTypeMap { 41 | return nil, errors.New("invalid ASN pointer type: " + strconv.Itoa(int(dataType))) 42 | } 43 | _, err = readASNMap(result, r.decoderBuffer, size, offset) 44 | if err != nil { 45 | return nil, err 46 | } 47 | default: 48 | return nil, errors.New("invalid ASN type: " + strconv.Itoa(int(dataType))) 49 | } 50 | return result, nil 51 | } 52 | 53 | func NewASNReader(buffer []byte) (*ASNReader, error) { 54 | reader, err := newReader(buffer) 55 | if err != nil { 56 | return nil, err 57 | } 58 | if reader.metadata.DatabaseType != "GeoLite2-ASN" && 59 | reader.metadata.DatabaseType != "DBIP-ASN-Lite" && 60 | reader.metadata.DatabaseType != "DBIP-ASN-Lite (compat=GeoLite2-ASN)" { 61 | return nil, errors.New("wrong MaxMind DB ASN type: " + reader.metadata.DatabaseType) 62 | } 63 | return &ASNReader{ 64 | reader: reader, 65 | }, nil 66 | } 67 | 68 | func NewASNReaderFromFile(filename string) (*ASNReader, error) { 69 | buffer, err := ioutil.ReadFile(filename) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return NewASNReader(buffer) 74 | } 75 | -------------------------------------------------------------------------------- /reader_city.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type CityReader struct { 11 | *reader 12 | } 13 | 14 | func (r *CityReader) Lookup(ip net.IP) (*CityResult, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return nil, err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return nil, err 22 | } 23 | if dataType != dataTypeMap { 24 | return nil, errors.New("invalid City type: " + strconv.Itoa(int(dataType))) 25 | } 26 | var key []byte 27 | result := &CityResult{} 28 | for i := uint(0); i < size; i++ { 29 | key, offset, err = readMapKey(r.decoderBuffer, offset) 30 | if err != nil { 31 | return nil, err 32 | } 33 | switch b2s(key) { 34 | case "city": 35 | offset, err = readCity(&result.City, r.decoderBuffer, offset) 36 | if err != nil { 37 | return nil, err 38 | } 39 | case "continent": 40 | offset, err = readContinent(&result.Continent, r.decoderBuffer, offset) 41 | if err != nil { 42 | return nil, err 43 | } 44 | case "country": 45 | offset, err = readCountry(&result.Country, r.decoderBuffer, offset) 46 | if err != nil { 47 | return nil, err 48 | } 49 | case "location": 50 | offset, err = readLocation(&result.Location, r.decoderBuffer, offset) 51 | if err != nil { 52 | return nil, err 53 | } 54 | case "postal": 55 | offset, err = readPostal(&result.Postal, r.decoderBuffer, offset) 56 | if err != nil { 57 | return nil, err 58 | } 59 | case "registered_country": 60 | offset, err = readCountry(&result.RegisteredCountry, r.decoderBuffer, offset) 61 | if err != nil { 62 | return nil, err 63 | } 64 | case "represented_country": 65 | offset, err = readCountry(&result.RepresentedCountry, r.decoderBuffer, offset) 66 | if err != nil { 67 | return nil, err 68 | } 69 | case "subdivisions": 70 | result.Subdivisions, offset, err = readSubdivisions(r.decoderBuffer, offset) 71 | if err != nil { 72 | return nil, err 73 | } 74 | case "traits": 75 | offset, err = readTraits(&result.Traits, r.decoderBuffer, offset) 76 | if err != nil { 77 | return nil, err 78 | } 79 | default: 80 | return nil, errors.New("unknown City response key: " + string(key) + ", type: " + strconv.Itoa(int(dataType))) 81 | } 82 | } 83 | return result, nil 84 | } 85 | 86 | func NewCityReader(buffer []byte) (*CityReader, error) { 87 | reader, err := newReader(buffer) 88 | if err != nil { 89 | return nil, err 90 | } 91 | if reader.metadata.DatabaseType != "GeoIP2-City" && 92 | reader.metadata.DatabaseType != "GeoLite2-City" && 93 | reader.metadata.DatabaseType != "GeoIP2-Enterprise" && 94 | reader.metadata.DatabaseType != "DBIP-City-Lite" { 95 | return nil, errors.New("wrong MaxMind DB City type: " + reader.metadata.DatabaseType) 96 | } 97 | return &CityReader{ 98 | reader: reader, 99 | }, nil 100 | } 101 | 102 | func NewCityReaderFromFile(filename string) (*CityReader, error) { 103 | buffer, err := ioutil.ReadFile(filename) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return NewCityReader(buffer) 108 | } 109 | 110 | func NewEnterpriseReader(buffer []byte) (*CityReader, error) { 111 | return NewCityReader(buffer) 112 | } 113 | 114 | func NewEnterpriseReaderFromFile(filename string) (*CityReader, error) { 115 | return NewCityReaderFromFile(filename) 116 | } 117 | -------------------------------------------------------------------------------- /reader_connection_type.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type ConnectionTypeReader struct { 11 | *reader 12 | } 13 | 14 | func (r *ConnectionTypeReader) Lookup(ip net.IP) (string, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return "", err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return "", err 22 | } 23 | result := &ConnectionType{} 24 | switch dataType { 25 | case dataTypeMap: 26 | _, err = readConnectionTypeMap(result, r.decoderBuffer, size, offset) 27 | if err != nil { 28 | return "", err 29 | } 30 | case dataTypePointer: 31 | pointer, _, err := readPointer(r.decoderBuffer, size, offset) 32 | if err != nil { 33 | return "", err 34 | } 35 | dataType, size, offset, err := readControl(r.decoderBuffer, pointer) 36 | if err != nil { 37 | return "", err 38 | } 39 | if dataType != dataTypeMap { 40 | return "", errors.New("invalid Connection-Type pointer type: " + strconv.Itoa(int(dataType))) 41 | } 42 | _, err = readConnectionTypeMap(result, r.decoderBuffer, size, offset) 43 | if err != nil { 44 | return "", err 45 | } 46 | default: 47 | return "", errors.New("invalid Connection-Type type: " + strconv.Itoa(int(dataType))) 48 | } 49 | return result.ConnectionType, nil 50 | } 51 | 52 | func NewConnectionTypeReader(buffer []byte) (*ConnectionTypeReader, error) { 53 | reader, err := newReader(buffer) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if reader.metadata.DatabaseType != "GeoIP2-Connection-Type" { 58 | return nil, errors.New("wrong MaxMind DB Connection-Type type: " + reader.metadata.DatabaseType) 59 | } 60 | return &ConnectionTypeReader{ 61 | reader: reader, 62 | }, nil 63 | } 64 | 65 | func NewConnectionTypeReaderFromFile(filename string) (*ConnectionTypeReader, error) { 66 | buffer, err := ioutil.ReadFile(filename) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return NewConnectionTypeReader(buffer) 71 | } 72 | -------------------------------------------------------------------------------- /reader_country.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type CountryReader struct { 11 | *reader 12 | } 13 | 14 | func (r *CountryReader) Lookup(ip net.IP) (*CountryResult, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return nil, err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return nil, err 22 | } 23 | if dataType != dataTypeMap { 24 | return nil, errors.New("invalid Country type: " + strconv.Itoa(int(dataType))) 25 | } 26 | var key []byte 27 | result := &CountryResult{} 28 | for i := uint(0); i < size; i++ { 29 | key, offset, err = readMapKey(r.decoderBuffer, offset) 30 | if err != nil { 31 | return nil, err 32 | } 33 | switch b2s(key) { 34 | case "continent": 35 | offset, err = readContinent(&result.Continent, r.decoderBuffer, offset) 36 | if err != nil { 37 | return nil, err 38 | } 39 | case "country": 40 | offset, err = readCountry(&result.Country, r.decoderBuffer, offset) 41 | if err != nil { 42 | return nil, err 43 | } 44 | case "registered_country": 45 | offset, err = readCountry(&result.RegisteredCountry, r.decoderBuffer, offset) 46 | if err != nil { 47 | return nil, err 48 | } 49 | case "represented_country": 50 | offset, err = readCountry(&result.RepresentedCountry, r.decoderBuffer, offset) 51 | if err != nil { 52 | return nil, err 53 | } 54 | case "traits": 55 | offset, err = readTraits(&result.Traits, r.decoderBuffer, offset) 56 | if err != nil { 57 | return nil, err 58 | } 59 | default: 60 | return nil, errors.New("unknown Country response key: " + string(key) + ", type: " + strconv.Itoa(int(dataType))) 61 | } 62 | } 63 | return result, nil 64 | } 65 | 66 | func NewCountryReader(buffer []byte) (*CountryReader, error) { 67 | reader, err := newReader(buffer) 68 | if err != nil { 69 | return nil, err 70 | } 71 | if reader.metadata.DatabaseType != "GeoIP2-Country" && 72 | reader.metadata.DatabaseType != "GeoLite2-Country" && 73 | reader.metadata.DatabaseType != "DBIP-Country" && 74 | reader.metadata.DatabaseType != "DBIP-Country-Lite" { 75 | return nil, errors.New("wrong MaxMind DB Country type: " + reader.metadata.DatabaseType) 76 | } 77 | return &CountryReader{ 78 | reader: reader, 79 | }, nil 80 | } 81 | 82 | func NewCountryReaderFromFile(filename string) (*CountryReader, error) { 83 | buffer, err := ioutil.ReadFile(filename) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return NewCountryReader(buffer) 88 | } 89 | -------------------------------------------------------------------------------- /reader_domain.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type DomainReader struct { 11 | *reader 12 | } 13 | 14 | func (r *DomainReader) Lookup(ip net.IP) (string, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return "", err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return "", err 22 | } 23 | result := &Domain{} 24 | switch dataType { 25 | case dataTypeMap: 26 | _, err = readDomainMap(result, r.decoderBuffer, size, offset) 27 | if err != nil { 28 | return "", err 29 | } 30 | case dataTypePointer: 31 | pointer, _, err := readPointer(r.decoderBuffer, size, offset) 32 | if err != nil { 33 | return "", err 34 | } 35 | dataType, size, offset, err := readControl(r.decoderBuffer, pointer) 36 | if err != nil { 37 | return "", err 38 | } 39 | if dataType != dataTypeMap { 40 | return "", errors.New("invalid Domain pointer type: " + strconv.Itoa(int(dataType))) 41 | } 42 | _, err = readDomainMap(result, r.decoderBuffer, size, offset) 43 | if err != nil { 44 | return "", err 45 | } 46 | default: 47 | return "", errors.New("invalid Domain type: " + strconv.Itoa(int(dataType))) 48 | } 49 | return result.Domain, nil 50 | } 51 | 52 | func NewDomainReader(buffer []byte) (*DomainReader, error) { 53 | reader, err := newReader(buffer) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if reader.metadata.DatabaseType != "GeoIP2-Domain" { 58 | return nil, errors.New("wrong MaxMind DB Domain type: " + reader.metadata.DatabaseType) 59 | } 60 | return &DomainReader{ 61 | reader: reader, 62 | }, nil 63 | } 64 | 65 | func NewDomainReaderFromFile(filename string) (*DomainReader, error) { 66 | buffer, err := ioutil.ReadFile(filename) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return NewDomainReader(buffer) 71 | } 72 | -------------------------------------------------------------------------------- /reader_isp.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | type ISPReader struct { 11 | *reader 12 | } 13 | 14 | func (r *ISPReader) Lookup(ip net.IP) (*ISP, error) { 15 | offset, err := r.getOffset(ip) 16 | if err != nil { 17 | return nil, err 18 | } 19 | dataType, size, offset, err := readControl(r.decoderBuffer, offset) 20 | if err != nil { 21 | return nil, err 22 | } 23 | result := &ISP{} 24 | switch dataType { 25 | case dataTypeMap: 26 | _, err = readISPMap(result, r.decoderBuffer, size, offset) 27 | if err != nil { 28 | return nil, err 29 | } 30 | case dataTypePointer: 31 | pointer, _, err := readPointer(r.decoderBuffer, size, offset) 32 | if err != nil { 33 | return nil, err 34 | } 35 | dataType, size, offset, err := readControl(r.decoderBuffer, pointer) 36 | if err != nil { 37 | return nil, err 38 | } 39 | if dataType != dataTypeMap { 40 | return nil, errors.New("invalid ISP pointer type: " + strconv.Itoa(int(dataType))) 41 | } 42 | _, err = readISPMap(result, r.decoderBuffer, size, offset) 43 | if err != nil { 44 | return nil, err 45 | } 46 | default: 47 | return nil, errors.New("invalid ISP type: " + strconv.Itoa(int(dataType))) 48 | } 49 | return result, nil 50 | } 51 | 52 | func NewISPReader(buffer []byte) (*ISPReader, error) { 53 | reader, err := newReader(buffer) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if reader.metadata.DatabaseType != "GeoIP2-ISP" { 58 | return nil, errors.New("wrong MaxMind DB ISP type: " + reader.metadata.DatabaseType) 59 | } 60 | return &ISPReader{ 61 | reader: reader, 62 | }, nil 63 | } 64 | 65 | func NewISPReaderFromFile(filename string) (*ISPReader, error) { 66 | buffer, err := ioutil.ReadFile(filename) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return NewISPReader(buffer) 71 | } 72 | -------------------------------------------------------------------------------- /reader_test.go: -------------------------------------------------------------------------------- 1 | // Test DB 2 | // https://github.com/maxmind/MaxMind-DB 3 | // https://db-ip.com/db/lite.php 4 | package geoip2 5 | 6 | import ( 7 | "net" 8 | "testing" 9 | ) 10 | 11 | func TestAnonymousIP(t *testing.T) { 12 | reader, err := NewAnonymousIPReaderFromFile("testdata/maxmind/test-data/GeoIP2-Anonymous-IP-Test.mmdb") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | record, err := reader.Lookup(net.ParseIP("81.2.69.0")) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | if record.IsAnonymous != true { 22 | t.Fatal() 23 | } 24 | if record.IsAnonymousVPN != true { 25 | t.Fatal() 26 | } 27 | if record.IsHostingProvider != true { 28 | t.Fatal() 29 | } 30 | if record.IsPublicProxy != true { 31 | t.Fatal() 32 | } 33 | if record.IsTorExitNode != true { 34 | t.Fatal() 35 | } 36 | 37 | record, err = reader.Lookup(net.ParseIP("186.30.236.0")) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | if record.IsAnonymous != true { 42 | t.Fatal() 43 | } 44 | if record.IsAnonymousVPN != false { 45 | t.Fatal() 46 | } 47 | if record.IsHostingProvider != false { 48 | t.Fatal() 49 | } 50 | if record.IsPublicProxy != true { 51 | t.Fatal() 52 | } 53 | if record.IsTorExitNode != false { 54 | t.Fatal() 55 | } 56 | } 57 | 58 | func TestReaderZeroLength(t *testing.T) { 59 | _, err := newReader([]byte{}) 60 | if err == nil { 61 | t.Fatal() 62 | } 63 | } 64 | 65 | func TestCity(t *testing.T) { 66 | reader, err := NewCityReaderFromFile("testdata/maxmind/test-data/GeoIP2-City-Test.mmdb") 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | record, err := reader.Lookup(net.ParseIP("81.2.69.142")) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if record.City.GeoNameID != 2643743 { 76 | t.Fatal() 77 | } 78 | if record.City.Names["de"] != "London" || 79 | record.City.Names["es"] != "Londres" { 80 | t.Fatal() 81 | } 82 | if record.Location.AccuracyRadius != 10 { 83 | t.Fatal() 84 | } 85 | if record.Location.Latitude != 51.5142 { 86 | t.Fatal() 87 | } 88 | if record.Location.Longitude != -0.0931 { 89 | t.Fatal() 90 | } 91 | if record.Location.TimeZone != "Europe/London" { 92 | t.Fatal() 93 | } 94 | if len(record.Subdivisions) != 1 { 95 | t.Fatal() 96 | } 97 | if record.Subdivisions[0].GeoNameID != 6269131 { 98 | t.Fatal() 99 | } 100 | if record.Subdivisions[0].ISOCode != "ENG" { 101 | t.Fatal() 102 | } 103 | if record.Subdivisions[0].Names["en"] != "England" || 104 | record.Subdivisions[0].Names["pt-BR"] != "Inglaterra" { 105 | t.Fatal() 106 | } 107 | 108 | record, err = reader.Lookup(net.ParseIP("2a02:ff80::")) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | if record.City.GeoNameID != 0 { 113 | t.Fatal() 114 | } 115 | if record.Country.IsInEuropeanUnion != true { 116 | t.Fatal() 117 | } 118 | if record.Location.AccuracyRadius != 100 { 119 | t.Fatal() 120 | } 121 | if record.Location.Latitude != 51.5 { 122 | t.Fatal() 123 | } 124 | if record.Location.Longitude != 10.5 { 125 | t.Fatal() 126 | } 127 | if record.Location.TimeZone != "Europe/Berlin" { 128 | t.Fatal() 129 | } 130 | if len(record.Subdivisions) != 0 { 131 | t.Fatal() 132 | } 133 | } 134 | 135 | func TestConnectionType(t *testing.T) { 136 | reader, err := NewConnectionTypeReaderFromFile("testdata/maxmind/test-data/GeoIP2-Connection-Type-Test.mmdb") 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | 141 | record, err := reader.Lookup(net.ParseIP("1.0.0.0")) 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | if record != "Cable/DSL" { 146 | t.Fatal() 147 | } 148 | 149 | record, err = reader.Lookup(net.ParseIP("1.0.1.0")) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if record != "Cellular" { 154 | t.Fatal() 155 | } 156 | } 157 | 158 | func TestCountry(t *testing.T) { 159 | reader, err := NewCountryReaderFromFile("testdata/maxmind/test-data/GeoIP2-Country-Test.mmdb") 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | record, err := reader.Lookup(net.ParseIP("74.209.24.0")) 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | if record.Continent.GeoNameID != 6255149 { 169 | t.Fatal() 170 | } 171 | if record.Continent.Code != "NA" { 172 | t.Fatal() 173 | } 174 | if record.Continent.Names["es"] != "Norteamérica" || 175 | record.Continent.Names["ru"] != "Северная Америка" { 176 | t.Fatal() 177 | } 178 | if record.Country.GeoNameID != 6252001 { 179 | t.Fatal() 180 | } 181 | if record.Country.ISOCode != "US" { 182 | t.Fatal() 183 | } 184 | if record.Country.Names["fr"] != "États-Unis" || 185 | record.Country.Names["pt-BR"] != "Estados Unidos" { 186 | t.Fatal() 187 | } 188 | if record.Country.IsInEuropeanUnion != false { 189 | t.Fatal() 190 | } 191 | if record.RegisteredCountry.GeoNameID != 6252001 { 192 | t.Fatal() 193 | } 194 | if record.RepresentedCountry.GeoNameID != 0 { 195 | t.Fatal() 196 | } 197 | if record.Traits.IsAnonymousProxy != true { 198 | t.Fatal() 199 | } 200 | if record.Traits.IsSatelliteProvider != true { 201 | t.Fatal() 202 | } 203 | 204 | record, err = reader.Lookup(net.ParseIP("2a02:ffc0::")) 205 | if err != nil { 206 | t.Fatal(err) 207 | } 208 | if record.Continent.GeoNameID != 6255148 { 209 | t.Fatal() 210 | } 211 | if record.Continent.Code != "EU" { 212 | t.Fatal() 213 | } 214 | if record.Continent.Names["en"] != "Europe" || 215 | record.Continent.Names["zh-CN"] != "欧洲" { 216 | t.Fatal() 217 | } 218 | if record.Country.GeoNameID != 2411586 { 219 | t.Fatal() 220 | } 221 | if record.Country.ISOCode != "GI" { 222 | t.Fatal() 223 | } 224 | if record.Country.Names["en"] != "Gibraltar" || 225 | record.Country.Names["ja"] != "ジブラルタル" { 226 | t.Fatal() 227 | } 228 | if record.Country.IsInEuropeanUnion != false { 229 | t.Fatal() 230 | } 231 | if record.RegisteredCountry.GeoNameID != 2411586 { 232 | t.Fatal() 233 | } 234 | if record.RepresentedCountry.GeoNameID != 0 { 235 | t.Fatal() 236 | } 237 | if record.Traits.IsAnonymousProxy != false { 238 | t.Fatal() 239 | } 240 | } 241 | 242 | func TestDomain(t *testing.T) { 243 | reader, err := NewDomainReaderFromFile("testdata/maxmind/test-data/GeoIP2-Domain-Test.mmdb") 244 | if err != nil { 245 | t.Fatal(err) 246 | } 247 | 248 | record, err := reader.Lookup(net.ParseIP("1.2.0.0")) 249 | if err != nil { 250 | t.Fatal(err) 251 | } 252 | if record != "maxmind.com" { 253 | t.Fatal() 254 | } 255 | 256 | record, err = reader.Lookup(net.ParseIP("186.30.236.0")) 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | if record != "replaced.com" { 261 | t.Fatal() 262 | } 263 | } 264 | 265 | func TestEnterprise(t *testing.T) { 266 | reader, err := NewEnterpriseReaderFromFile("testdata/maxmind/test-data/GeoIP2-Enterprise-Test.mmdb") 267 | if err != nil { 268 | t.Fatal(err) 269 | } 270 | 271 | record, err := reader.Lookup(net.ParseIP("74.209.24.0")) 272 | if err != nil { 273 | t.Fatal(err) 274 | } 275 | if record.City.Confidence != 11 { 276 | t.Fatal() 277 | } 278 | if record.Country.Confidence != 99 { 279 | t.Fatal() 280 | } 281 | if record.Postal.Code != "12037" { 282 | t.Fatal() 283 | } 284 | if record.Postal.Confidence != 11 { 285 | t.Fatal() 286 | } 287 | if len(record.Subdivisions) != 1 { 288 | t.Fatal() 289 | } 290 | if record.Subdivisions[0].Confidence != 93 { 291 | t.Fatal() 292 | } 293 | if record.Traits.AutonomousSystemNumber != 14671 { 294 | t.Fatal() 295 | } 296 | if record.Traits.AutonomousSystemOrganization != "FairPoint Communications" { 297 | t.Fatal() 298 | } 299 | if record.Traits.ISP != "Fairpoint Communications" { 300 | t.Fatal() 301 | } 302 | if record.Traits.Organization != "Fairpoint Communications" { 303 | t.Fatal() 304 | } 305 | if record.Traits.ConnectionType != "Cable/DSL" { 306 | t.Fatal() 307 | } 308 | if record.Traits.Domain != "frpt.net" { 309 | t.Fatal() 310 | } 311 | if record.Traits.StaticIPScore != 0.34 { 312 | t.Fatal() 313 | } 314 | if record.Traits.UserType != "residential" { 315 | t.Fatal() 316 | } 317 | 318 | record, err = reader.Lookup(net.ParseIP("81.2.69.160")) 319 | if err != nil { 320 | t.Fatal(err) 321 | } 322 | if record.Traits.ISP != "Andrews & Arnold Ltd" { 323 | t.Fatal() 324 | } 325 | if record.Traits.Organization != "STONEHOUSE office network" { 326 | t.Fatal() 327 | } 328 | if record.Traits.ConnectionType != "Corporate" { 329 | t.Fatal() 330 | } 331 | if record.Traits.Domain != "in-addr.arpa" { 332 | t.Fatal() 333 | } 334 | if record.Traits.StaticIPScore != 0.34 { 335 | t.Fatal() 336 | } 337 | if record.Traits.UserType != "government" { 338 | t.Fatal() 339 | } 340 | } 341 | 342 | func TestISP(t *testing.T) { 343 | reader, err := NewISPReaderFromFile("testdata/maxmind/test-data/GeoIP2-ISP-Test.mmdb") 344 | if err != nil { 345 | t.Fatal(err) 346 | } 347 | 348 | record, err := reader.Lookup(net.ParseIP("1.128.0.0")) 349 | if err != nil { 350 | t.Fatal(err) 351 | } 352 | if record.AutonomousSystemNumber != 1221 { 353 | t.Fatal() 354 | } 355 | if record.AutonomousSystemOrganization != "Telstra Pty Ltd" { 356 | t.Fatal() 357 | } 358 | if record.ISP != "Telstra Internet" { 359 | t.Fatal() 360 | } 361 | if record.Organization != "Telstra Internet" { 362 | t.Fatal() 363 | } 364 | 365 | record, err = reader.Lookup(net.ParseIP("4.0.0.0")) 366 | if err != nil { 367 | t.Fatal(err) 368 | } 369 | if record.AutonomousSystemNumber != 0 { 370 | t.Fatal() 371 | } 372 | if record.AutonomousSystemOrganization != "" { 373 | t.Fatal() 374 | } 375 | if record.ISP != "Level 3 Communications" { 376 | t.Fatal() 377 | } 378 | if record.Organization != "Level 3 Communications" { 379 | t.Fatal() 380 | } 381 | } 382 | 383 | func TestASN(t *testing.T) { 384 | reader, err := NewASNReaderFromFile("testdata/maxmind/test-data/GeoLite2-ASN-Test.mmdb") 385 | if err != nil { 386 | t.Fatal(err) 387 | } 388 | 389 | record, err := reader.Lookup(net.ParseIP("1.128.0.0")) 390 | if err != nil { 391 | t.Fatal(err) 392 | } 393 | if record.AutonomousSystemNumber != 1221 { 394 | t.Fatal() 395 | } 396 | if record.AutonomousSystemOrganization != "Telstra Pty Ltd" { 397 | t.Fatal() 398 | } 399 | if record.Network != "1.128.0.0/11" { 400 | t.Fatal() 401 | } 402 | 403 | record, err = reader.Lookup(net.ParseIP("2600:6000::")) 404 | if err != nil { 405 | t.Fatal(err) 406 | } 407 | if record.AutonomousSystemNumber != 237 { 408 | t.Fatal() 409 | } 410 | if record.AutonomousSystemOrganization != "Merit Network Inc." { 411 | t.Fatal() 412 | } 413 | if record.Network != "2600:6000::/20" { 414 | t.Fatal() 415 | } 416 | 417 | } 418 | 419 | func TestDBIPCity(t *testing.T) { 420 | reader, err := NewCityReaderFromFile("testdata/dbip/dbip-city-lite.mmdb") 421 | if err != nil { 422 | t.Fatal(err) 423 | } 424 | record, err := reader.Lookup(net.ParseIP("66.30.184.198")) 425 | if err != nil { 426 | t.Fatal(err) 427 | } 428 | if record.City.GeoNameID != 0 { 429 | t.Fatal() 430 | } 431 | if record.City.Names["en"] != "Medfield" { 432 | t.Fatal() 433 | } 434 | if record.Location.Latitude != 42.1876 { 435 | t.Fatal() 436 | } 437 | if record.Location.Longitude != -71.3065 { 438 | t.Fatal() 439 | } 440 | if len(record.Subdivisions) != 1 { 441 | t.Fatal() 442 | } 443 | if record.Subdivisions[0].Names["en"] != "Massachusetts" { 444 | t.Fatal() 445 | } 446 | } 447 | 448 | func TestDBIPCountry(t *testing.T) { 449 | reader, err := NewCountryReaderFromFile("testdata/dbip/dbip-country-lite.mmdb") 450 | if err != nil { 451 | t.Fatal(err) 452 | } 453 | record, err := reader.Lookup(net.ParseIP("66.30.184.198")) 454 | if err != nil { 455 | t.Fatal(err) 456 | } 457 | if record.Continent.GeoNameID != 6255149 { 458 | t.Fatal() 459 | } 460 | if record.Continent.Code != "NA" { 461 | t.Fatal() 462 | } 463 | if record.Continent.Names["en"] != "North America" || 464 | record.Continent.Names["ru"] != "Северная Америка" { 465 | t.Fatal() 466 | } 467 | if record.Country.GeoNameID != 6252001 { 468 | t.Fatal() 469 | } 470 | if record.Country.ISOCode != "US" { 471 | t.Fatal() 472 | } 473 | if record.Country.Names["fr"] != "États-Unis" || 474 | record.Country.Names["pt-BR"] != "Estados Unidos" { 475 | t.Fatal() 476 | } 477 | if record.Country.IsInEuropeanUnion { 478 | t.Fatal() 479 | } 480 | } 481 | 482 | func TestDBIPASN(t *testing.T) { 483 | reader, err := NewASNReaderFromFile("testdata/dbip/dbip-asn-lite.mmdb") 484 | if err != nil { 485 | t.Fatal(err) 486 | } 487 | record, err := reader.Lookup(net.ParseIP("66.30.184.198")) 488 | if err != nil { 489 | t.Fatal(err) 490 | } 491 | if record.AutonomousSystemNumber != 7922 { 492 | t.Fatal() 493 | } 494 | if record.AutonomousSystemOrganization != "Comcast Cable Communications, LLC" { 495 | t.Fatal() 496 | } 497 | if record.Network != "66.30.0.0/15" { 498 | t.Fatal() 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /subdivision.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readSubdivisions(buffer []byte, offset uint) ([]Subdivision, uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return nil, 0, err 12 | } 13 | switch dataType { 14 | case dataTypeSlice: 15 | return readSubdivisionsSlice(buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return nil, 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return nil, 0, err 24 | } 25 | if dataType != dataTypeSlice { 26 | return nil, 0, errors.New("invalid subdivisions pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | subdivisions, _, err := readSubdivisionsSlice(buffer, size, offset) 29 | if err != nil { 30 | return nil, 0, err 31 | } 32 | return subdivisions, newOffset, nil 33 | default: 34 | return nil, 0, errors.New("invalid subdivisions type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readSubdivisionsSlice(buffer []byte, subdivisionsSize uint, offset uint) ([]Subdivision, uint, error) { 39 | var err error 40 | subdivisions := make([]Subdivision, subdivisionsSize) 41 | for i := uint(0); i < subdivisionsSize; i++ { 42 | offset, err = readSubdivision(&subdivisions[i], buffer, offset) 43 | if err != nil { 44 | return nil, 0, err 45 | } 46 | } 47 | return subdivisions, offset, nil 48 | } 49 | 50 | func readSubdivision(subdivision *Subdivision, buffer []byte, offset uint) (uint, error) { 51 | dataType, size, offset, err := readControl(buffer, offset) 52 | if err != nil { 53 | return 0, err 54 | } 55 | switch dataType { 56 | case dataTypeMap: 57 | return readSubdivisionMap(subdivision, buffer, size, offset) 58 | case dataTypePointer: 59 | pointer, newOffset, err := readPointer(buffer, size, offset) 60 | if err != nil { 61 | return 0, err 62 | } 63 | dataType, size, offset, err := readControl(buffer, pointer) 64 | if err != nil { 65 | return 0, err 66 | } 67 | if dataType != dataTypeMap { 68 | return 0, errors.New("invalid subdivision pointer type: " + strconv.Itoa(int(dataType))) 69 | } 70 | _, err = readSubdivisionMap(subdivision, buffer, size, offset) 71 | if err != nil { 72 | return 0, err 73 | } 74 | return newOffset, nil 75 | default: 76 | return 0, errors.New("invalid subdivision type: " + strconv.Itoa(int(dataType))) 77 | } 78 | } 79 | 80 | func readSubdivisionMap(subdivision *Subdivision, buffer []byte, mapSize uint, offset uint) (uint, error) { 81 | var key []byte 82 | var err error 83 | for i := uint(0); i < mapSize; i++ { 84 | key, offset, err = readMapKey(buffer, offset) 85 | if err != nil { 86 | return 0, err 87 | } 88 | switch b2s(key) { 89 | case "geoname_id": 90 | subdivision.GeoNameID, offset, err = readUInt32(buffer, offset) 91 | if err != nil { 92 | return 0, err 93 | } 94 | case "iso_code": 95 | subdivision.ISOCode, offset, err = readString(buffer, offset) 96 | if err != nil { 97 | return 0, err 98 | } 99 | case "names": 100 | subdivision.Names, offset, err = readStringMap(buffer, offset) 101 | if err != nil { 102 | return 0, err 103 | } 104 | case "confidence": 105 | subdivision.Confidence, offset, err = readUInt16(buffer, offset) 106 | if err != nil { 107 | return 0, err 108 | } 109 | default: 110 | return 0, errors.New("unknown subdivision key: " + string(key)) 111 | } 112 | } 113 | return offset, nil 114 | } 115 | -------------------------------------------------------------------------------- /traits.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | ) 7 | 8 | func readTraits(traits *Traits, buffer []byte, offset uint) (uint, error) { 9 | dataType, size, offset, err := readControl(buffer, offset) 10 | if err != nil { 11 | return 0, err 12 | } 13 | switch dataType { 14 | case dataTypeMap: 15 | return readTraitsMap(traits, buffer, size, offset) 16 | case dataTypePointer: 17 | pointer, newOffset, err := readPointer(buffer, size, offset) 18 | if err != nil { 19 | return 0, err 20 | } 21 | dataType, size, offset, err := readControl(buffer, pointer) 22 | if err != nil { 23 | return 0, err 24 | } 25 | if dataType != dataTypeMap { 26 | return 0, errors.New("invalid traits pointer type: " + strconv.Itoa(int(dataType))) 27 | } 28 | _, err = readTraitsMap(traits, buffer, size, offset) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return newOffset, nil 33 | default: 34 | return 0, errors.New("invalid traits type: " + strconv.Itoa(int(dataType))) 35 | } 36 | } 37 | 38 | func readTraitsMap(traits *Traits, buffer []byte, mapSize uint, offset uint) (uint, error) { 39 | var key []byte 40 | var err error 41 | for i := uint(0); i < mapSize; i++ { 42 | key, offset, err = readMapKey(buffer, offset) 43 | if err != nil { 44 | return 0, err 45 | } 46 | switch b2s(key) { 47 | case "is_anonymous_proxy": 48 | traits.IsAnonymousProxy, offset, err = readBool(buffer, offset) 49 | if err != nil { 50 | return 0, err 51 | } 52 | case "is_satellite_provider": 53 | traits.IsSatelliteProvider, offset, err = readBool(buffer, offset) 54 | if err != nil { 55 | return 0, err 56 | } 57 | case "is_legitimate_proxy": 58 | traits.IsLegitimateProxy, offset, err = readBool(buffer, offset) 59 | if err != nil { 60 | return 0, err 61 | } 62 | case "static_ip_score": 63 | traits.StaticIPScore, offset, err = readFloat64(buffer, offset) 64 | if err != nil { 65 | return 0, err 66 | } 67 | case "autonomous_system_number": 68 | traits.AutonomousSystemNumber, offset, err = readUInt32(buffer, offset) 69 | if err != nil { 70 | return 0, err 71 | } 72 | case "autonomous_system_organization": 73 | traits.AutonomousSystemOrganization, offset, err = readString(buffer, offset) 74 | if err != nil { 75 | return 0, err 76 | } 77 | case "isp": 78 | traits.ISP, offset, err = readString(buffer, offset) 79 | if err != nil { 80 | return 0, err 81 | } 82 | case "organization": 83 | traits.Organization, offset, err = readString(buffer, offset) 84 | if err != nil { 85 | return 0, err 86 | } 87 | case "connection_type": 88 | traits.ConnectionType, offset, err = readString(buffer, offset) 89 | if err != nil { 90 | return 0, err 91 | } 92 | case "domain": 93 | traits.Domain, offset, err = readString(buffer, offset) 94 | if err != nil { 95 | return 0, err 96 | } 97 | case "user_type": 98 | traits.UserType, offset, err = readString(buffer, offset) 99 | if err != nil { 100 | return 0, err 101 | } 102 | case "mobile_country_code": 103 | traits.MobileCountryCode, offset, err = readString(buffer, offset) 104 | if err != nil { 105 | return 0, err 106 | } 107 | case "mobile_network_code": 108 | traits.MobileNetworkCode, offset, err = readString(buffer, offset) 109 | if err != nil { 110 | return 0, err 111 | } 112 | default: 113 | return 0, errors.New("unknown traits key: " + string(key)) 114 | } 115 | } 116 | return offset, nil 117 | } 118 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | const ( 4 | dataTypeExtended = 0 5 | dataTypePointer = 1 6 | dataTypeString = 2 7 | dataTypeFloat64 = 3 8 | dataTypeBytes = 4 9 | dataTypeUint16 = 5 10 | dataTypeUint32 = 6 11 | dataTypeMap = 7 12 | dataTypeInt32 = 8 13 | dataTypeUint64 = 9 14 | dataTypeUint128 = 10 15 | dataTypeSlice = 11 16 | dataTypeDataCacheContainer = 12 17 | dataTypeEndMarker = 13 18 | dataTypeBool = 14 19 | dataTypeFloat32 = 15 20 | 21 | dataSectionSeparatorSize = 16 22 | ) 23 | 24 | type Continent struct { 25 | GeoNameID uint32 26 | Code string 27 | Names map[string]string 28 | } 29 | 30 | type Country struct { 31 | ISOCode string 32 | Names map[string]string 33 | Type string // [RepresentedCountry] 34 | GeoNameID uint32 35 | Confidence uint16 // Enterprise [Country, RegisteredCountry] 36 | IsInEuropeanUnion bool 37 | } 38 | 39 | type Subdivision struct { 40 | ISOCode string 41 | Names map[string]string 42 | GeoNameID uint32 43 | Confidence uint16 // Enterprise 44 | } 45 | 46 | type City struct { 47 | Names map[string]string 48 | GeoNameID uint32 49 | Confidence uint16 // Enterprise 50 | } 51 | 52 | type Location struct { 53 | Latitude float64 54 | Longitude float64 55 | TimeZone string 56 | AccuracyRadius uint16 57 | MetroCode uint16 58 | } 59 | 60 | type Postal struct { 61 | Code string 62 | Confidence uint16 // Enterprise 63 | } 64 | 65 | type Traits struct { 66 | StaticIPScore float64 // Enterprise 67 | ISP string // Enterprise 68 | Organization string // Enterprise 69 | ConnectionType string // Enterprise 70 | Domain string // Enterprise 71 | UserType string // Enterprise 72 | AutonomousSystemOrganization string // Enterprise 73 | AutonomousSystemNumber uint32 // Enterprise 74 | IsLegitimateProxy bool // Enterprise 75 | MobileCountryCode string // Enterprise 76 | MobileNetworkCode string // Enterprise 77 | IsAnonymousProxy bool 78 | IsSatelliteProvider bool 79 | } 80 | 81 | type CountryResult struct { 82 | Continent Continent 83 | Country Country 84 | RegisteredCountry Country 85 | RepresentedCountry Country 86 | Traits Traits 87 | } 88 | 89 | type CityResult struct { 90 | Continent Continent 91 | Country Country 92 | Subdivisions []Subdivision 93 | City City 94 | Location Location 95 | Postal Postal 96 | RegisteredCountry Country 97 | RepresentedCountry Country 98 | Traits Traits 99 | } 100 | 101 | type ISP struct { 102 | AutonomousSystemNumber uint32 103 | AutonomousSystemOrganization string 104 | ISP string 105 | Organization string 106 | MobileCountryCode string 107 | MobileNetworkCode string 108 | } 109 | 110 | type ConnectionType struct { 111 | ConnectionType string 112 | } 113 | 114 | type AnonymousIP struct { 115 | IsAnonymous bool 116 | IsAnonymousVPN bool 117 | IsHostingProvider bool 118 | IsPublicProxy bool 119 | IsTorExitNode bool 120 | IsResidentialProxy bool 121 | } 122 | 123 | type ASN struct { 124 | AutonomousSystemNumber uint32 125 | AutonomousSystemOrganization string 126 | Network string 127 | } 128 | 129 | type Domain struct { 130 | Domain string 131 | } 132 | --------------------------------------------------------------------------------