├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── go.yml │ └── golangci-lint.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── LICENSE ├── README.md ├── example_test.go ├── go.mod ├── go.sum ├── reader.go └── reader_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | schedule: 9 | - cron: '0 12 * * 5' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | # We must fetch at least the immediate parents so that if this is 21 | # a pull request then we can checkout the head. 22 | fetch-depth: 2 23 | 24 | # If this run was triggered by a pull request event, then checkout 25 | # the head of the pull request instead of the merge commit. 26 | - run: git checkout HEAD^2 27 | if: ${{ github.event_name == 'pull_request' }} 28 | 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v3 32 | # Override language selection by uncommenting this and choosing your languages 33 | # with: 34 | # languages: go, javascript, csharp, python, cpp, java 35 | 36 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 37 | # If this step fails, then you should remove it and run the build manually (see below) 38 | - name: Autobuild 39 | uses: github/codeql-action/autobuild@v3 40 | 41 | # ℹ️ Command-line programs to run using the OS shell. 42 | # 📚 https://git.io/JvXDl 43 | 44 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 45 | # and modify them (or add more) to build your code if your project 46 | # uses a compiled language 47 | 48 | #- run: | 49 | # make bootstrap 50 | # make release 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v3 54 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | strategy: 10 | matrix: 11 | go-version: [1.23.x, 1.24.x] 12 | platform: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.platform }} 14 | steps: 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: ${{ matrix.go-version }} 19 | id: go 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | 26 | - name: Get dependencies 27 | run: go get -v -t -d ./... 28 | 29 | - name: Build 30 | run: go build -v . 31 | 32 | - name: Test 33 | run: go test -race -v ./... 34 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | golangci: 7 | name: lint 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: golangci-lint 12 | uses: golangci/golangci-lint-action@v8 13 | with: 14 | version: latest 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.out 3 | *.test 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test-data"] 2 | path = test-data 3 | url = https://github.com/maxmind/MaxMind-DB.git 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | go: "1.23" 4 | tests: true 5 | allow-parallel-runners: true 6 | linters: 7 | default: all 8 | disable: 9 | - cyclop 10 | - depguard 11 | - err113 12 | - exhaustive 13 | - exhaustruct 14 | - forcetypeassert 15 | - funlen 16 | - gochecknoglobals 17 | - gocognit 18 | - godox 19 | - gosmopolitan 20 | - inamedparam 21 | - interfacebloat 22 | - mnd 23 | - nlreturn 24 | - nonamedreturns 25 | - paralleltest 26 | - testpackage 27 | - thelper 28 | - varnamelen 29 | - wrapcheck 30 | - wsl 31 | settings: 32 | errorlint: 33 | errorf: true 34 | asserts: true 35 | comparison: true 36 | exhaustive: 37 | default-signifies-exhaustive: true 38 | forbidigo: 39 | forbid: 40 | - pattern: Geoip 41 | msg: you should use `GeoIP` 42 | - pattern: geoIP 43 | msg: you should use `geoip` 44 | - pattern: Maxmind 45 | msg: you should use `MaxMind` 46 | - pattern: ^maxMind 47 | msg: you should use `maxmind` 48 | - pattern: Minfraud 49 | msg: you should use `MinFraud` 50 | - pattern: ^minFraud 51 | msg: you should use `minfraud` 52 | - pattern: ^math.Max$ 53 | msg: you should use the max built-in instead. 54 | - pattern: ^math.Min$ 55 | msg: you should use the min built-in instead. 56 | - pattern: ^os.IsNotExist 57 | msg: As per their docs, new code should use errors.Is(err, fs.ErrNotExist). 58 | - pattern: ^os.IsExist 59 | msg: As per their docs, new code should use errors.Is(err, fs.ErrExist) 60 | gosec: 61 | excludes: 62 | - G115 63 | govet: 64 | disable: 65 | - shadow 66 | enable-all: true 67 | lll: 68 | line-length: 120 69 | tab-width: 4 70 | misspell: 71 | locale: US 72 | extra-words: 73 | - typo: marshall 74 | correction: marshal 75 | - typo: marshalling 76 | correction: marshaling 77 | - typo: marshalls 78 | correction: marshals 79 | - typo: unmarshall 80 | correction: unmarshal 81 | - typo: unmarshalling 82 | correction: unmarshaling 83 | - typo: unmarshalls 84 | correction: unmarshals 85 | nolintlint: 86 | require-explanation: true 87 | require-specific: true 88 | allow-no-explanation: 89 | - lll 90 | - misspell 91 | allow-unused: false 92 | revive: 93 | severity: warning 94 | enable-all-rules: true 95 | rules: 96 | - name: add-constant 97 | disabled: true 98 | - name: cognitive-complexity 99 | disabled: true 100 | - name: confusing-naming 101 | disabled: true 102 | - name: confusing-results 103 | disabled: true 104 | - name: cyclomatic 105 | disabled: true 106 | - name: deep-exit 107 | disabled: true 108 | - name: flag-parameter 109 | disabled: true 110 | - name: function-length 111 | disabled: true 112 | - name: function-result-limit 113 | disabled: true 114 | - name: line-length-limit 115 | disabled: true 116 | - name: max-public-structs 117 | disabled: true 118 | - name: nested-structs 119 | disabled: true 120 | - name: unchecked-type-assertion 121 | disabled: true 122 | - name: unhandled-error 123 | disabled: true 124 | tagliatelle: 125 | case: 126 | rules: 127 | avro: snake 128 | bson: snake 129 | env: upperSnake 130 | envconfig: upperSnake 131 | json: snake 132 | mapstructure: snake 133 | xml: snake 134 | yaml: snake 135 | unparam: 136 | check-exported: true 137 | exclusions: 138 | generated: lax 139 | presets: 140 | - comments 141 | - common-false-positives 142 | - legacy 143 | - std-error-handling 144 | rules: 145 | - linters: 146 | - govet 147 | - revive 148 | path: _test.go 149 | text: 'fieldalignment:' 150 | paths: 151 | - third_party$ 152 | - builtin$ 153 | - examples$ 154 | formatters: 155 | enable: 156 | - gci 157 | - gofmt 158 | - gofumpt 159 | - goimports 160 | - golines 161 | settings: 162 | gci: 163 | sections: 164 | - standard 165 | - default 166 | - prefix(github.com/oschwald/geoip2-golang) 167 | gofumpt: 168 | extra-rules: true 169 | exclusions: 170 | generated: lax 171 | paths: 172 | - third_party$ 173 | - builtin$ 174 | - examples$ 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, Gregory J. Oschwald 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeoIP2 Reader for Go # 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/oschwald/geoip2-golang)](https://pkg.go.dev/github.com/oschwald/geoip2-golang) 4 | 5 | This library reads MaxMind [GeoLite2](http://dev.maxmind.com/geoip/geoip2/geolite2/) 6 | and [GeoIP2](http://www.maxmind.com/en/geolocation_landing) databases. 7 | 8 | This library is built using 9 | [the Go maxminddb reader](https://github.com/oschwald/maxminddb-golang). 10 | All data for the database record is decoded using this library. If you only 11 | need several fields, you may get superior performance by using maxminddb's 12 | `Lookup` directly with a result struct that only contains the required fields. 13 | (See [example_test.go](https://github.com/oschwald/maxminddb-golang/blob/main/example_test.go) 14 | in the maxminddb repository for an example of this.) 15 | 16 | ## Installation ## 17 | 18 | ``` 19 | go get github.com/oschwald/geoip2-golang 20 | ``` 21 | 22 | ## Usage ## 23 | 24 | [See GoDoc](http://godoc.org/github.com/oschwald/geoip2-golang) for 25 | documentation and examples. 26 | 27 | ## Example ## 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "log" 35 | "net" 36 | 37 | "github.com/oschwald/geoip2-golang" 38 | ) 39 | 40 | func main() { 41 | db, err := geoip2.Open("GeoIP2-City.mmdb") 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | defer db.Close() 46 | // If you are using strings that may be invalid, check that ip is not nil 47 | ip := net.ParseIP("81.2.69.142") 48 | record, err := db.City(ip) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names["pt-BR"]) 53 | if len(record.Subdivisions) > 0 { 54 | fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names["en"]) 55 | } 56 | fmt.Printf("Russian country name: %v\n", record.Country.Names["ru"]) 57 | fmt.Printf("ISO country code: %v\n", record.Country.IsoCode) 58 | fmt.Printf("Time zone: %v\n", record.Location.TimeZone) 59 | fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude) 60 | // Output: 61 | // Portuguese (BR) city name: Londres 62 | // English subdivision name: England 63 | // Russian country name: Великобритания 64 | // ISO country code: GB 65 | // Time zone: Europe/London 66 | // Coordinates: 51.5142, -0.0931 67 | } 68 | 69 | ``` 70 | 71 | ## Testing ## 72 | 73 | Make sure you checked out test data submodule: 74 | 75 | ``` 76 | git submodule init 77 | git submodule update 78 | ``` 79 | 80 | Execute test suite: 81 | 82 | ``` 83 | go test 84 | ``` 85 | 86 | ## Contributing ## 87 | 88 | Contributions welcome! Please fork the repository and open a pull request 89 | with your changes. 90 | 91 | ## License ## 92 | 93 | This is free software, licensed under the ISC license. 94 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | ) 8 | 9 | // Example provides a basic example of using the API. Use of the Country 10 | // method is analogous to that of the City method. 11 | func Example() { 12 | db, err := Open("test-data/test-data/GeoIP2-City-Test.mmdb") 13 | if err != nil { 14 | log.Panic(err) 15 | } 16 | defer db.Close() 17 | // If you are using strings that may be invalid, check that ip is not nil 18 | ip := net.ParseIP("81.2.69.142") 19 | record, err := db.City(ip) 20 | if err != nil { 21 | log.Panic(err) 22 | } 23 | fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names["pt-BR"]) 24 | fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names["en"]) 25 | fmt.Printf("Russian country name: %v\n", record.Country.Names["ru"]) 26 | fmt.Printf("ISO country code: %v\n", record.Country.IsoCode) 27 | fmt.Printf("Time zone: %v\n", record.Location.TimeZone) 28 | fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude) 29 | // Output: 30 | // Portuguese (BR) city name: Londres 31 | // English subdivision name: England 32 | // Russian country name: Великобритания 33 | // ISO country code: GB 34 | // Time zone: Europe/London 35 | // Coordinates: 51.5142, -0.0931 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oschwald/geoip2-golang 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/oschwald/maxminddb-golang v1.13.1 7 | github.com/stretchr/testify v1.10.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | golang.org/x/sys v0.21.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= 4 | github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 10 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | // Package geoip2 provides an easy-to-use API for the MaxMind GeoIP2 and 2 | // GeoLite2 databases; this package does not support GeoIP Legacy databases. 3 | // 4 | // The structs provided by this package match the internal structure of 5 | // the data in the MaxMind databases. 6 | // 7 | // See github.com/oschwald/maxminddb-golang for more advanced used cases. 8 | package geoip2 9 | 10 | import ( 11 | "fmt" 12 | "net" 13 | 14 | "github.com/oschwald/maxminddb-golang" 15 | ) 16 | 17 | // The Enterprise struct corresponds to the data in the GeoIP2 Enterprise 18 | // database. 19 | type Enterprise struct { 20 | Continent struct { 21 | Names map[string]string `maxminddb:"names"` 22 | Code string `maxminddb:"code"` 23 | GeoNameID uint `maxminddb:"geoname_id"` 24 | } `maxminddb:"continent"` 25 | City struct { 26 | Names map[string]string `maxminddb:"names"` 27 | GeoNameID uint `maxminddb:"geoname_id"` 28 | Confidence uint8 `maxminddb:"confidence"` 29 | } `maxminddb:"city"` 30 | Postal struct { 31 | Code string `maxminddb:"code"` 32 | Confidence uint8 `maxminddb:"confidence"` 33 | } `maxminddb:"postal"` 34 | Subdivisions []struct { 35 | Names map[string]string `maxminddb:"names"` 36 | IsoCode string `maxminddb:"iso_code"` 37 | GeoNameID uint `maxminddb:"geoname_id"` 38 | Confidence uint8 `maxminddb:"confidence"` 39 | } `maxminddb:"subdivisions"` 40 | RepresentedCountry struct { 41 | Names map[string]string `maxminddb:"names"` 42 | IsoCode string `maxminddb:"iso_code"` 43 | Type string `maxminddb:"type"` 44 | GeoNameID uint `maxminddb:"geoname_id"` 45 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 46 | } `maxminddb:"represented_country"` 47 | Country struct { 48 | Names map[string]string `maxminddb:"names"` 49 | IsoCode string `maxminddb:"iso_code"` 50 | GeoNameID uint `maxminddb:"geoname_id"` 51 | Confidence uint8 `maxminddb:"confidence"` 52 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 53 | } `maxminddb:"country"` 54 | RegisteredCountry struct { 55 | Names map[string]string `maxminddb:"names"` 56 | IsoCode string `maxminddb:"iso_code"` 57 | GeoNameID uint `maxminddb:"geoname_id"` 58 | Confidence uint8 `maxminddb:"confidence"` 59 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 60 | } `maxminddb:"registered_country"` 61 | Traits struct { 62 | AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 63 | ConnectionType string `maxminddb:"connection_type"` 64 | Domain string `maxminddb:"domain"` 65 | ISP string `maxminddb:"isp"` 66 | MobileCountryCode string `maxminddb:"mobile_country_code"` 67 | MobileNetworkCode string `maxminddb:"mobile_network_code"` 68 | Organization string `maxminddb:"organization"` 69 | UserType string `maxminddb:"user_type"` 70 | AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 71 | StaticIPScore float64 `maxminddb:"static_ip_score"` 72 | IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 73 | IsAnycast bool `maxminddb:"is_anycast"` 74 | IsLegitimateProxy bool `maxminddb:"is_legitimate_proxy"` 75 | IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 76 | } `maxminddb:"traits"` 77 | Location struct { 78 | TimeZone string `maxminddb:"time_zone"` 79 | Latitude float64 `maxminddb:"latitude"` 80 | Longitude float64 `maxminddb:"longitude"` 81 | MetroCode uint `maxminddb:"metro_code"` 82 | AccuracyRadius uint16 `maxminddb:"accuracy_radius"` 83 | } `maxminddb:"location"` 84 | } 85 | 86 | // The City struct corresponds to the data in the GeoIP2/GeoLite2 City 87 | // databases. 88 | type City struct { 89 | City struct { 90 | Names map[string]string `maxminddb:"names"` 91 | GeoNameID uint `maxminddb:"geoname_id"` 92 | } `maxminddb:"city"` 93 | Postal struct { 94 | Code string `maxminddb:"code"` 95 | } `maxminddb:"postal"` 96 | Continent struct { 97 | Names map[string]string `maxminddb:"names"` 98 | Code string `maxminddb:"code"` 99 | GeoNameID uint `maxminddb:"geoname_id"` 100 | } `maxminddb:"continent"` 101 | Subdivisions []struct { 102 | Names map[string]string `maxminddb:"names"` 103 | IsoCode string `maxminddb:"iso_code"` 104 | GeoNameID uint `maxminddb:"geoname_id"` 105 | } `maxminddb:"subdivisions"` 106 | RepresentedCountry struct { 107 | Names map[string]string `maxminddb:"names"` 108 | IsoCode string `maxminddb:"iso_code"` 109 | Type string `maxminddb:"type"` 110 | GeoNameID uint `maxminddb:"geoname_id"` 111 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 112 | } `maxminddb:"represented_country"` 113 | Country struct { 114 | Names map[string]string `maxminddb:"names"` 115 | IsoCode string `maxminddb:"iso_code"` 116 | GeoNameID uint `maxminddb:"geoname_id"` 117 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 118 | } `maxminddb:"country"` 119 | RegisteredCountry struct { 120 | Names map[string]string `maxminddb:"names"` 121 | IsoCode string `maxminddb:"iso_code"` 122 | GeoNameID uint `maxminddb:"geoname_id"` 123 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 124 | } `maxminddb:"registered_country"` 125 | Location struct { 126 | TimeZone string `maxminddb:"time_zone"` 127 | Latitude float64 `maxminddb:"latitude"` 128 | Longitude float64 `maxminddb:"longitude"` 129 | MetroCode uint `maxminddb:"metro_code"` 130 | AccuracyRadius uint16 `maxminddb:"accuracy_radius"` 131 | } `maxminddb:"location"` 132 | Traits struct { 133 | IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 134 | IsAnycast bool `maxminddb:"is_anycast"` 135 | IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 136 | } `maxminddb:"traits"` 137 | } 138 | 139 | // The Country struct corresponds to the data in the GeoIP2/GeoLite2 140 | // Country databases. 141 | type Country struct { 142 | Continent struct { 143 | Names map[string]string `maxminddb:"names"` 144 | Code string `maxminddb:"code"` 145 | GeoNameID uint `maxminddb:"geoname_id"` 146 | } `maxminddb:"continent"` 147 | Country struct { 148 | Names map[string]string `maxminddb:"names"` 149 | IsoCode string `maxminddb:"iso_code"` 150 | GeoNameID uint `maxminddb:"geoname_id"` 151 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 152 | } `maxminddb:"country"` 153 | RegisteredCountry struct { 154 | Names map[string]string `maxminddb:"names"` 155 | IsoCode string `maxminddb:"iso_code"` 156 | GeoNameID uint `maxminddb:"geoname_id"` 157 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 158 | } `maxminddb:"registered_country"` 159 | RepresentedCountry struct { 160 | Names map[string]string `maxminddb:"names"` 161 | IsoCode string `maxminddb:"iso_code"` 162 | Type string `maxminddb:"type"` 163 | GeoNameID uint `maxminddb:"geoname_id"` 164 | IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 165 | } `maxminddb:"represented_country"` 166 | Traits struct { 167 | IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 168 | IsAnycast bool `maxminddb:"is_anycast"` 169 | IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 170 | } `maxminddb:"traits"` 171 | } 172 | 173 | // The AnonymousIP struct corresponds to the data in the GeoIP2 174 | // Anonymous IP database. 175 | type AnonymousIP struct { 176 | IsAnonymous bool `maxminddb:"is_anonymous"` 177 | IsAnonymousVPN bool `maxminddb:"is_anonymous_vpn"` 178 | IsHostingProvider bool `maxminddb:"is_hosting_provider"` 179 | IsPublicProxy bool `maxminddb:"is_public_proxy"` 180 | IsResidentialProxy bool `maxminddb:"is_residential_proxy"` 181 | IsTorExitNode bool `maxminddb:"is_tor_exit_node"` 182 | } 183 | 184 | // The ASN struct corresponds to the data in the GeoLite2 ASN database. 185 | type ASN struct { 186 | AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 187 | AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 188 | } 189 | 190 | // The ConnectionType struct corresponds to the data in the GeoIP2 191 | // Connection-Type database. 192 | type ConnectionType struct { 193 | ConnectionType string `maxminddb:"connection_type"` 194 | } 195 | 196 | // The Domain struct corresponds to the data in the GeoIP2 Domain database. 197 | type Domain struct { 198 | Domain string `maxminddb:"domain"` 199 | } 200 | 201 | // The ISP struct corresponds to the data in the GeoIP2 ISP database. 202 | type ISP struct { 203 | AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 204 | ISP string `maxminddb:"isp"` 205 | MobileCountryCode string `maxminddb:"mobile_country_code"` 206 | MobileNetworkCode string `maxminddb:"mobile_network_code"` 207 | Organization string `maxminddb:"organization"` 208 | AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 209 | } 210 | 211 | type databaseType int 212 | 213 | const ( 214 | isAnonymousIP = 1 << iota 215 | isASN 216 | isCity 217 | isConnectionType 218 | isCountry 219 | isDomain 220 | isEnterprise 221 | isISP 222 | ) 223 | 224 | // Reader holds the maxminddb.Reader struct. It can be created using the 225 | // Open and FromBytes functions. 226 | type Reader struct { 227 | mmdbReader *maxminddb.Reader 228 | databaseType databaseType 229 | } 230 | 231 | // InvalidMethodError is returned when a lookup method is called on a 232 | // database that it does not support. For instance, calling the ISP method 233 | // on a City database. 234 | type InvalidMethodError struct { 235 | Method string 236 | DatabaseType string 237 | } 238 | 239 | func (e InvalidMethodError) Error() string { 240 | return fmt.Sprintf(`geoip2: the %s method does not support the %s database`, 241 | e.Method, e.DatabaseType) 242 | } 243 | 244 | // UnknownDatabaseTypeError is returned when an unknown database type is 245 | // opened. 246 | type UnknownDatabaseTypeError struct { 247 | DatabaseType string 248 | } 249 | 250 | func (e UnknownDatabaseTypeError) Error() string { 251 | return fmt.Sprintf(`geoip2: reader does not support the %q database type`, 252 | e.DatabaseType) 253 | } 254 | 255 | // Open takes a string path to a file and returns a Reader struct or an error. 256 | // The database file is opened using a memory map. Use the Close method on the 257 | // Reader object to return the resources to the system. 258 | func Open(file string) (*Reader, error) { 259 | reader, err := maxminddb.Open(file) 260 | if err != nil { 261 | return nil, err 262 | } 263 | dbType, err := getDBType(reader) 264 | return &Reader{reader, dbType}, err 265 | } 266 | 267 | // FromBytes takes a byte slice corresponding to a GeoIP2/GeoLite2 database 268 | // file and returns a Reader struct or an error. Note that the byte slice is 269 | // used directly; any modification of it after opening the database will result 270 | // in errors while reading from the database. 271 | func FromBytes(bytes []byte) (*Reader, error) { 272 | reader, err := maxminddb.FromBytes(bytes) 273 | if err != nil { 274 | return nil, err 275 | } 276 | dbType, err := getDBType(reader) 277 | return &Reader{reader, dbType}, err 278 | } 279 | 280 | func getDBType(reader *maxminddb.Reader) (databaseType, error) { 281 | switch reader.Metadata.DatabaseType { 282 | case "GeoIP2-Anonymous-IP": 283 | return isAnonymousIP, nil 284 | case "DBIP-ASN-Lite (compat=GeoLite2-ASN)", 285 | "GeoLite2-ASN": 286 | return isASN, nil 287 | // We allow City lookups on Country for back compat 288 | case "DBIP-City-Lite", 289 | "DBIP-Country-Lite", 290 | "DBIP-Country", 291 | "DBIP-Location (compat=City)", 292 | "GeoLite2-City", 293 | "GeoIP2-City", 294 | "GeoIP2-City-Africa", 295 | "GeoIP2-City-Asia-Pacific", 296 | "GeoIP2-City-Europe", 297 | "GeoIP2-City-North-America", 298 | "GeoIP2-City-South-America", 299 | "GeoIP2-Precision-City", 300 | "GeoLite2-Country", 301 | "GeoIP2-Country": 302 | return isCity | isCountry, nil 303 | case "GeoIP2-Connection-Type": 304 | return isConnectionType, nil 305 | case "GeoIP2-Domain": 306 | return isDomain, nil 307 | case "DBIP-ISP (compat=Enterprise)", 308 | "DBIP-Location-ISP (compat=Enterprise)", 309 | "GeoIP2-Enterprise": 310 | return isEnterprise | isCity | isCountry, nil 311 | case "GeoIP2-ISP", "GeoIP2-Precision-ISP": 312 | return isISP | isASN, nil 313 | default: 314 | return 0, UnknownDatabaseTypeError{reader.Metadata.DatabaseType} 315 | } 316 | } 317 | 318 | // Enterprise takes an IP address as a net.IP struct and returns an Enterprise 319 | // struct and/or an error. This is intended to be used with the GeoIP2 320 | // Enterprise database. 321 | func (r *Reader) Enterprise(ipAddress net.IP) (*Enterprise, error) { 322 | if isEnterprise&r.databaseType == 0 { 323 | return nil, InvalidMethodError{"Enterprise", r.Metadata().DatabaseType} 324 | } 325 | var enterprise Enterprise 326 | err := r.mmdbReader.Lookup(ipAddress, &enterprise) 327 | return &enterprise, err 328 | } 329 | 330 | // City takes an IP address as a net.IP struct and returns a City struct 331 | // and/or an error. Although this can be used with other databases, this 332 | // method generally should be used with the GeoIP2 or GeoLite2 City databases. 333 | func (r *Reader) City(ipAddress net.IP) (*City, error) { 334 | if isCity&r.databaseType == 0 { 335 | return nil, InvalidMethodError{"City", r.Metadata().DatabaseType} 336 | } 337 | var city City 338 | err := r.mmdbReader.Lookup(ipAddress, &city) 339 | return &city, err 340 | } 341 | 342 | // Country takes an IP address as a net.IP struct and returns a Country struct 343 | // and/or an error. Although this can be used with other databases, this 344 | // method generally should be used with the GeoIP2 or GeoLite2 Country 345 | // databases. 346 | func (r *Reader) Country(ipAddress net.IP) (*Country, error) { 347 | if isCountry&r.databaseType == 0 { 348 | return nil, InvalidMethodError{"Country", r.Metadata().DatabaseType} 349 | } 350 | var country Country 351 | err := r.mmdbReader.Lookup(ipAddress, &country) 352 | return &country, err 353 | } 354 | 355 | // AnonymousIP takes an IP address as a net.IP struct and returns a 356 | // AnonymousIP struct and/or an error. 357 | func (r *Reader) AnonymousIP(ipAddress net.IP) (*AnonymousIP, error) { 358 | if isAnonymousIP&r.databaseType == 0 { 359 | return nil, InvalidMethodError{"AnonymousIP", r.Metadata().DatabaseType} 360 | } 361 | var anonIP AnonymousIP 362 | err := r.mmdbReader.Lookup(ipAddress, &anonIP) 363 | return &anonIP, err 364 | } 365 | 366 | // ASN takes an IP address as a net.IP struct and returns a ASN struct and/or 367 | // an error. 368 | func (r *Reader) ASN(ipAddress net.IP) (*ASN, error) { 369 | if isASN&r.databaseType == 0 { 370 | return nil, InvalidMethodError{"ASN", r.Metadata().DatabaseType} 371 | } 372 | var val ASN 373 | err := r.mmdbReader.Lookup(ipAddress, &val) 374 | return &val, err 375 | } 376 | 377 | // ConnectionType takes an IP address as a net.IP struct and returns a 378 | // ConnectionType struct and/or an error. 379 | func (r *Reader) ConnectionType(ipAddress net.IP) (*ConnectionType, error) { 380 | if isConnectionType&r.databaseType == 0 { 381 | return nil, InvalidMethodError{"ConnectionType", r.Metadata().DatabaseType} 382 | } 383 | var val ConnectionType 384 | err := r.mmdbReader.Lookup(ipAddress, &val) 385 | return &val, err 386 | } 387 | 388 | // Domain takes an IP address as a net.IP struct and returns a 389 | // Domain struct and/or an error. 390 | func (r *Reader) Domain(ipAddress net.IP) (*Domain, error) { 391 | if isDomain&r.databaseType == 0 { 392 | return nil, InvalidMethodError{"Domain", r.Metadata().DatabaseType} 393 | } 394 | var val Domain 395 | err := r.mmdbReader.Lookup(ipAddress, &val) 396 | return &val, err 397 | } 398 | 399 | // ISP takes an IP address as a net.IP struct and returns a ISP struct and/or 400 | // an error. 401 | func (r *Reader) ISP(ipAddress net.IP) (*ISP, error) { 402 | if isISP&r.databaseType == 0 { 403 | return nil, InvalidMethodError{"ISP", r.Metadata().DatabaseType} 404 | } 405 | var val ISP 406 | err := r.mmdbReader.Lookup(ipAddress, &val) 407 | return &val, err 408 | } 409 | 410 | // Metadata takes no arguments and returns a struct containing metadata about 411 | // the MaxMind database in use by the Reader. 412 | func (r *Reader) Metadata() maxminddb.Metadata { 413 | return r.mmdbReader.Metadata 414 | } 415 | 416 | // Close unmaps the database file from virtual memory and returns the 417 | // resources to the system. 418 | func (r *Reader) Close() error { 419 | return r.mmdbReader.Close() 420 | } 421 | -------------------------------------------------------------------------------- /reader_test.go: -------------------------------------------------------------------------------- 1 | package geoip2 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestReader(t *testing.T) { 13 | reader, err := Open("test-data/test-data/GeoIP2-City-Test.mmdb") 14 | require.NoError(t, err) 15 | 16 | defer reader.Close() 17 | 18 | record, err := reader.City(net.ParseIP("81.2.69.160")) 19 | require.NoError(t, err) 20 | 21 | m := reader.Metadata() 22 | assert.Equal(t, uint(2), m.BinaryFormatMajorVersion) 23 | assert.Equal(t, uint(0), m.BinaryFormatMinorVersion) 24 | assert.NotZero(t, m.BuildEpoch) 25 | assert.Equal(t, "GeoIP2-City", m.DatabaseType) 26 | assert.Equal(t, 27 | map[string]string{ 28 | "en": "GeoIP2 City Test Database (fake GeoIP2 data, for example purposes only)", 29 | "zh": "小型数据库", 30 | }, 31 | m.Description, 32 | ) 33 | assert.Equal(t, uint(6), m.IPVersion) 34 | assert.Equal(t, []string{"en", "zh"}, m.Languages) 35 | assert.NotZero(t, m.NodeCount) 36 | assert.Equal(t, uint(28), m.RecordSize) 37 | 38 | assert.Equal(t, uint(2643743), record.City.GeoNameID) 39 | assert.Equal(t, 40 | map[string]string{ 41 | "de": "London", 42 | "en": "London", 43 | "es": "Londres", 44 | "fr": "Londres", 45 | "ja": "ロンドン", 46 | "pt-BR": "Londres", 47 | "ru": "Лондон", 48 | }, 49 | record.City.Names, 50 | ) 51 | assert.Equal(t, uint(6255148), record.Continent.GeoNameID) 52 | assert.Equal(t, "EU", record.Continent.Code) 53 | assert.Equal(t, 54 | map[string]string{ 55 | "de": "Europa", 56 | "en": "Europe", 57 | "es": "Europa", 58 | "fr": "Europe", 59 | "ja": "ヨーロッパ", 60 | "pt-BR": "Europa", 61 | "ru": "Европа", 62 | "zh-CN": "欧洲", 63 | }, 64 | record.Continent.Names, 65 | ) 66 | 67 | assert.Equal(t, uint(2635167), record.Country.GeoNameID) 68 | assert.False(t, record.Country.IsInEuropeanUnion) 69 | assert.Equal(t, "GB", record.Country.IsoCode) 70 | assert.Equal(t, 71 | map[string]string{ 72 | "de": "Vereinigtes Königreich", 73 | "en": "United Kingdom", 74 | "es": "Reino Unido", 75 | "fr": "Royaume-Uni", 76 | "ja": "イギリス", 77 | "pt-BR": "Reino Unido", 78 | "ru": "Великобритания", 79 | "zh-CN": "英国", 80 | }, 81 | record.Country.Names, 82 | ) 83 | 84 | assert.Equal(t, uint16(100), record.Location.AccuracyRadius) 85 | assert.InEpsilon(t, 51.5142, record.Location.Latitude, 1e-10) 86 | assert.InEpsilon(t, -0.0931, record.Location.Longitude, 1e-10) 87 | assert.Equal(t, "Europe/London", record.Location.TimeZone) 88 | 89 | assert.Equal(t, uint(6269131), record.Subdivisions[0].GeoNameID) 90 | assert.Equal(t, "ENG", record.Subdivisions[0].IsoCode) 91 | assert.Equal(t, 92 | map[string]string{ 93 | "en": "England", 94 | "pt-BR": "Inglaterra", 95 | "fr": "Angleterre", 96 | "es": "Inglaterra", 97 | }, 98 | record.Subdivisions[0].Names, 99 | ) 100 | 101 | assert.Equal(t, uint(6252001), record.RegisteredCountry.GeoNameID) 102 | assert.False(t, record.RegisteredCountry.IsInEuropeanUnion) 103 | assert.Equal(t, "US", record.RegisteredCountry.IsoCode) 104 | assert.Equal(t, 105 | map[string]string{ 106 | "de": "USA", 107 | "en": "United States", 108 | "es": "Estados Unidos", 109 | "fr": "États-Unis", 110 | "ja": "アメリカ合衆国", 111 | "pt-BR": "Estados Unidos", 112 | "ru": "США", 113 | "zh-CN": "美国", 114 | }, 115 | record.RegisteredCountry.Names, 116 | ) 117 | 118 | assert.False(t, record.RepresentedCountry.IsInEuropeanUnion) 119 | } 120 | 121 | func TestIsAnycast(t *testing.T) { 122 | for _, test := range []string{"Country", "City", "Enterprise"} { 123 | t.Run(test, func(t *testing.T) { 124 | reader, err := Open("test-data/test-data/GeoIP2-" + test + "-Test.mmdb") 125 | require.NoError(t, err) 126 | defer reader.Close() 127 | 128 | record, err := reader.City(net.ParseIP("214.1.1.0")) 129 | require.NoError(t, err) 130 | 131 | assert.True(t, record.Traits.IsAnycast) 132 | }) 133 | } 134 | } 135 | 136 | func TestMetroCode(t *testing.T) { 137 | reader, err := Open("test-data/test-data/GeoIP2-City-Test.mmdb") 138 | require.NoError(t, err) 139 | defer reader.Close() 140 | 141 | record, err := reader.City(net.ParseIP("216.160.83.56")) 142 | require.NoError(t, err) 143 | 144 | assert.Equal(t, uint(819), record.Location.MetroCode) 145 | } 146 | 147 | func TestAnonymousIP(t *testing.T) { 148 | reader, err := Open("test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb") 149 | require.NoError(t, err) 150 | defer reader.Close() 151 | 152 | record, err := reader.AnonymousIP(net.ParseIP("1.2.0.0")) 153 | require.NoError(t, err) 154 | 155 | assert.True(t, record.IsAnonymous) 156 | 157 | assert.True(t, record.IsAnonymousVPN) 158 | assert.False(t, record.IsHostingProvider) 159 | assert.False(t, record.IsPublicProxy) 160 | assert.False(t, record.IsTorExitNode) 161 | assert.False(t, record.IsResidentialProxy) 162 | } 163 | 164 | func TestASN(t *testing.T) { 165 | reader, err := Open("test-data/test-data/GeoLite2-ASN-Test.mmdb") 166 | require.NoError(t, err) 167 | defer reader.Close() 168 | 169 | record, err := reader.ASN(net.ParseIP("1.128.0.0")) 170 | require.NoError(t, err) 171 | 172 | assert.Equal(t, uint(1221), record.AutonomousSystemNumber) 173 | 174 | assert.Equal(t, "Telstra Pty Ltd", record.AutonomousSystemOrganization) 175 | } 176 | 177 | func TestConnectionType(t *testing.T) { 178 | reader, err := Open("test-data/test-data/GeoIP2-Connection-Type-Test.mmdb") 179 | require.NoError(t, err) 180 | 181 | defer reader.Close() 182 | 183 | record, err := reader.ConnectionType(net.ParseIP("1.0.1.0")) 184 | require.NoError(t, err) 185 | 186 | assert.Equal(t, "Cellular", record.ConnectionType) 187 | } 188 | 189 | func TestCountry(t *testing.T) { 190 | reader, err := Open("test-data/test-data/GeoIP2-Country-Test.mmdb") 191 | require.NoError(t, err) 192 | 193 | defer reader.Close() 194 | 195 | record, err := reader.Country(net.ParseIP("81.2.69.160")) 196 | require.NoError(t, err) 197 | 198 | assert.False(t, record.Country.IsInEuropeanUnion) 199 | assert.False(t, record.RegisteredCountry.IsInEuropeanUnion) 200 | assert.False(t, record.RepresentedCountry.IsInEuropeanUnion) 201 | } 202 | 203 | func TestDomain(t *testing.T) { 204 | reader, err := Open("test-data/test-data/GeoIP2-Domain-Test.mmdb") 205 | require.NoError(t, err) 206 | defer reader.Close() 207 | 208 | record, err := reader.Domain(net.ParseIP("1.2.0.0")) 209 | require.NoError(t, err) 210 | assert.Equal(t, "maxmind.com", record.Domain) 211 | } 212 | 213 | func TestEnterprise(t *testing.T) { 214 | reader, err := Open("test-data/test-data/GeoIP2-Enterprise-Test.mmdb") 215 | require.NoError(t, err) 216 | 217 | defer reader.Close() 218 | 219 | record, err := reader.Enterprise(net.ParseIP("74.209.24.0")) 220 | require.NoError(t, err) 221 | 222 | assert.Equal(t, uint8(11), record.City.Confidence) 223 | 224 | assert.Equal(t, uint(14671), record.Traits.AutonomousSystemNumber) 225 | assert.Equal(t, "FairPoint Communications", record.Traits.AutonomousSystemOrganization) 226 | assert.Equal(t, "Cable/DSL", record.Traits.ConnectionType) 227 | assert.Equal(t, "frpt.net", record.Traits.Domain) 228 | assert.InEpsilon(t, float64(0.34), record.Traits.StaticIPScore, 1e-10) 229 | 230 | record, err = reader.Enterprise(net.ParseIP("149.101.100.0")) 231 | require.NoError(t, err) 232 | 233 | assert.Equal(t, uint(6167), record.Traits.AutonomousSystemNumber) 234 | 235 | assert.Equal(t, "CELLCO-PART", record.Traits.AutonomousSystemOrganization) 236 | assert.Equal(t, "Verizon Wireless", record.Traits.ISP) 237 | assert.Equal(t, "310", record.Traits.MobileCountryCode) 238 | assert.Equal(t, "004", record.Traits.MobileNetworkCode) 239 | } 240 | 241 | func TestISP(t *testing.T) { 242 | reader, err := Open("test-data/test-data/GeoIP2-ISP-Test.mmdb") 243 | require.NoError(t, err) 244 | defer reader.Close() 245 | 246 | record, err := reader.ISP(net.ParseIP("149.101.100.0")) 247 | require.NoError(t, err) 248 | 249 | assert.Equal(t, uint(6167), record.AutonomousSystemNumber) 250 | 251 | assert.Equal(t, "CELLCO-PART", record.AutonomousSystemOrganization) 252 | assert.Equal(t, "Verizon Wireless", record.ISP) 253 | assert.Equal(t, "310", record.MobileCountryCode) 254 | assert.Equal(t, "004", record.MobileNetworkCode) 255 | assert.Equal(t, "Verizon Wireless", record.Organization) 256 | } 257 | 258 | // This ensures the compiler does not optimize away the function call. 259 | var cityResult *City 260 | 261 | func BenchmarkCity(b *testing.B) { 262 | db, err := Open("GeoLite2-City.mmdb") 263 | if err != nil { 264 | b.Fatal(err) 265 | } 266 | defer db.Close() 267 | 268 | //nolint:gosec // this is just a benchmark 269 | r := rand.New(rand.NewSource(0)) 270 | 271 | var city *City 272 | 273 | ip := make(net.IP, 4) 274 | for range b.N { 275 | randomIPv4Address(r, ip) 276 | city, err = db.City(ip) 277 | if err != nil { 278 | b.Fatal(err) 279 | } 280 | } 281 | cityResult = city 282 | } 283 | 284 | // This ensures the compiler does not optimize away the function call. 285 | var asnResult *ASN 286 | 287 | func BenchmarkASN(b *testing.B) { 288 | db, err := Open("GeoLite2-ASN.mmdb") 289 | if err != nil { 290 | b.Fatal(err) 291 | } 292 | defer db.Close() 293 | 294 | //nolint:gosec // this is just a benchmark 295 | r := rand.New(rand.NewSource(0)) 296 | 297 | var asn *ASN 298 | 299 | ip := make(net.IP, 4) 300 | for range b.N { 301 | randomIPv4Address(r, ip) 302 | asn, err = db.ASN(ip) 303 | if err != nil { 304 | b.Fatal(err) 305 | } 306 | } 307 | asnResult = asn 308 | } 309 | 310 | func randomIPv4Address(r *rand.Rand, ip net.IP) { 311 | num := r.Uint32() 312 | ip[0] = byte(num >> 24) 313 | ip[1] = byte(num >> 16) 314 | ip[2] = byte(num >> 8) 315 | ip[3] = byte(num) 316 | } 317 | --------------------------------------------------------------------------------