maxmind-db-rust 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/CONTRIBUTING.md +496 -0
- data/LICENSE +15 -0
- data/README.md +343 -0
- data/ext/maxmind_db_rust/Cargo.toml +25 -0
- data/ext/maxmind_db_rust/extconf.rb +5 -0
- data/ext/maxmind_db_rust/lib/maxmind/db/rust.rb +16 -0
- data/ext/maxmind_db_rust/src/lib.rs +970 -0
- data/lib/maxmind/db/rust.rb +135 -0
- metadata +183 -0
data/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# maxmind-db-rust
|
|
2
|
+
|
|
3
|
+
[](https://github.com/oschwald/maxmind-db-rust-ruby/actions/workflows/test.yml)
|
|
4
|
+
[](https://github.com/oschwald/maxmind-db-rust-ruby/actions/workflows/lint.yml)
|
|
5
|
+
|
|
6
|
+
A high-performance Rust-based Ruby gem for reading MaxMind DB files. Provides API compatibility with the official `maxmind-db` gem while leveraging Rust for superior performance.
|
|
7
|
+
|
|
8
|
+
> **Note:** This is an unofficial library and is not endorsed by MaxMind. For the official Ruby library, see [maxmind-db](https://github.com/maxmind/MaxMind-DB-Reader-ruby).
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **High Performance**: Rust-based implementation provides significantly faster lookups than pure Ruby
|
|
13
|
+
- **API Compatible**: Familiar API similar to the official MaxMind::DB gem
|
|
14
|
+
- **Thread-Safe**: Safe to use from multiple threads
|
|
15
|
+
- **Memory Modes**: Support for both memory-mapped (MMAP) and in-memory modes
|
|
16
|
+
- **Iterator Support**: Iterate over all networks in the database (extension feature)
|
|
17
|
+
- **Type Support**: Works with both String and IPAddr objects
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add this line to your application's Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'maxmind-db-rust'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
And then execute:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or install it yourself as:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
gem install maxmind-db-rust
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- Ruby 3.2 or higher
|
|
42
|
+
- Rust toolchain (for building from source)
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Basic Usage
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require 'maxmind/db/rust'
|
|
50
|
+
|
|
51
|
+
# Open database
|
|
52
|
+
reader = MaxMind::DB::Rust::Reader.new(
|
|
53
|
+
'GeoIP2-City.mmdb',
|
|
54
|
+
mode: MaxMind::DB::Rust::MODE_MEMORY
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Lookup an IP address
|
|
58
|
+
record = reader.get('8.8.8.8')
|
|
59
|
+
if record
|
|
60
|
+
puts record['country']['iso_code']
|
|
61
|
+
puts record['country']['names']['en']
|
|
62
|
+
puts record['city']['names']['en']
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Close the database
|
|
66
|
+
reader.close
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Get with Prefix Length
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
require 'maxmind/db/rust'
|
|
73
|
+
|
|
74
|
+
reader = MaxMind::DB::Rust::Reader.new('GeoIP2-City.mmdb')
|
|
75
|
+
|
|
76
|
+
record, prefix_length = reader.get_with_prefix_length('8.8.8.8')
|
|
77
|
+
puts "Record: #{record}"
|
|
78
|
+
puts "Prefix length: #{prefix_length}"
|
|
79
|
+
|
|
80
|
+
reader.close
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Using IPAddr Objects
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
require 'maxmind/db/rust'
|
|
87
|
+
require 'ipaddr'
|
|
88
|
+
|
|
89
|
+
reader = MaxMind::DB::Rust::Reader.new('GeoIP2-City.mmdb')
|
|
90
|
+
|
|
91
|
+
ip = IPAddr.new('8.8.8.8')
|
|
92
|
+
record = reader.get(ip)
|
|
93
|
+
|
|
94
|
+
reader.close
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Database Modes
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
require 'maxmind/db/rust'
|
|
101
|
+
|
|
102
|
+
# MODE_AUTO: Uses memory-mapped files (default, best performance)
|
|
103
|
+
reader = MaxMind::DB::Rust::Reader.new(
|
|
104
|
+
'GeoIP2-City.mmdb',
|
|
105
|
+
mode: MaxMind::DB::Rust::MODE_AUTO
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# MODE_MMAP: Explicitly use memory-mapped files (recommended)
|
|
109
|
+
reader = MaxMind::DB::Rust::Reader.new(
|
|
110
|
+
'GeoIP2-City.mmdb',
|
|
111
|
+
mode: MaxMind::DB::Rust::MODE_MMAP
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# MODE_MEMORY: Load entire database into memory
|
|
115
|
+
reader = MaxMind::DB::Rust::Reader.new(
|
|
116
|
+
'GeoIP2-City.mmdb',
|
|
117
|
+
mode: MaxMind::DB::Rust::MODE_MEMORY
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Accessing Metadata
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require 'maxmind/db/rust'
|
|
125
|
+
|
|
126
|
+
reader = MaxMind::DB::Rust::Reader.new('GeoIP2-City.mmdb')
|
|
127
|
+
|
|
128
|
+
metadata = reader.metadata
|
|
129
|
+
puts "Database type: #{metadata.database_type}"
|
|
130
|
+
puts "Node count: #{metadata.node_count}"
|
|
131
|
+
puts "Record size: #{metadata.record_size}"
|
|
132
|
+
puts "IP version: #{metadata.ip_version}"
|
|
133
|
+
puts "Build epoch: #{metadata.build_epoch}"
|
|
134
|
+
puts "Languages: #{metadata.languages.join(', ')}"
|
|
135
|
+
puts "Description: #{metadata.description}"
|
|
136
|
+
|
|
137
|
+
reader.close
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Iterator Support (Extension Feature)
|
|
141
|
+
|
|
142
|
+
Iterate over all networks in the database:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
require 'maxmind/db/rust'
|
|
146
|
+
|
|
147
|
+
reader = MaxMind::DB::Rust::Reader.new('GeoLite2-Country.mmdb')
|
|
148
|
+
|
|
149
|
+
# Iterate over all networks
|
|
150
|
+
reader.each do |network, data|
|
|
151
|
+
puts "#{network}: #{data['country']['iso_code']}"
|
|
152
|
+
break # Remove this to see all networks
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Iterate over networks within a specific subnet (String CIDR notation)
|
|
156
|
+
reader.each('192.168.0.0/16') do |network, data|
|
|
157
|
+
puts "#{network}: #{data['city']['names']['en']}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Iterate over networks within a specific subnet (IPAddr object)
|
|
161
|
+
require 'ipaddr'
|
|
162
|
+
subnet = IPAddr.new('10.0.0.0/8')
|
|
163
|
+
reader.each(subnet) do |network, data|
|
|
164
|
+
puts "#{network}: #{data['country']['iso_code']}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Use Enumerable methods
|
|
168
|
+
countries = reader.map { |network, data| data['country']['iso_code'] }.uniq
|
|
169
|
+
puts "Unique countries: #{countries.size}"
|
|
170
|
+
|
|
171
|
+
reader.close
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## API Documentation
|
|
175
|
+
|
|
176
|
+
### `MaxMind::DB::Rust::Reader`
|
|
177
|
+
|
|
178
|
+
#### `new(database_path, options = {})`
|
|
179
|
+
|
|
180
|
+
Create a new Reader instance.
|
|
181
|
+
|
|
182
|
+
**Parameters:**
|
|
183
|
+
|
|
184
|
+
- `database_path` (String): Path to the MaxMind DB file
|
|
185
|
+
- `options` (Hash): Optional configuration
|
|
186
|
+
- `:mode` (Symbol): One of `:MODE_AUTO`, `:MODE_MEMORY`, or `:MODE_MMAP`
|
|
187
|
+
|
|
188
|
+
**Returns:** Reader instance
|
|
189
|
+
|
|
190
|
+
**Raises:**
|
|
191
|
+
|
|
192
|
+
- `Errno::ENOENT`: If the database file does not exist
|
|
193
|
+
- `MaxMind::DB::Rust::InvalidDatabaseError`: If the file is not a valid MaxMind DB
|
|
194
|
+
|
|
195
|
+
#### `get(ip_address)`
|
|
196
|
+
|
|
197
|
+
Look up an IP address in the database.
|
|
198
|
+
|
|
199
|
+
**Parameters:**
|
|
200
|
+
|
|
201
|
+
- `ip_address` (String or IPAddr): The IP address to look up
|
|
202
|
+
|
|
203
|
+
**Returns:** Hash with the record data, or `nil` if not found
|
|
204
|
+
|
|
205
|
+
**Raises:**
|
|
206
|
+
|
|
207
|
+
- `ArgumentError`: If looking up IPv6 in an IPv4-only database
|
|
208
|
+
- `MaxMind::DB::Rust::InvalidDatabaseError`: If the database is corrupt
|
|
209
|
+
|
|
210
|
+
#### `get_with_prefix_length(ip_address)`
|
|
211
|
+
|
|
212
|
+
Look up an IP address and return the prefix length.
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
|
|
216
|
+
- `ip_address` (String or IPAddr): The IP address to look up
|
|
217
|
+
|
|
218
|
+
**Returns:** Array `[record, prefix_length]` where record is a Hash or `nil`
|
|
219
|
+
|
|
220
|
+
#### `metadata()`
|
|
221
|
+
|
|
222
|
+
Get metadata about the database.
|
|
223
|
+
|
|
224
|
+
**Returns:** `MaxMind::DB::Rust::Metadata` instance
|
|
225
|
+
|
|
226
|
+
#### `close()`
|
|
227
|
+
|
|
228
|
+
Close the database and release resources.
|
|
229
|
+
|
|
230
|
+
#### `closed()`
|
|
231
|
+
|
|
232
|
+
Check if the database has been closed.
|
|
233
|
+
|
|
234
|
+
**Returns:** Boolean
|
|
235
|
+
|
|
236
|
+
#### `each(network = nil) { |network, data| ... }`
|
|
237
|
+
|
|
238
|
+
Iterate over networks in the database.
|
|
239
|
+
|
|
240
|
+
**Parameters:**
|
|
241
|
+
|
|
242
|
+
- `network` (String or IPAddr, optional): Network CIDR to iterate within (e.g., "192.168.0.0/16"). If omitted, iterates over all networks in the database.
|
|
243
|
+
|
|
244
|
+
**Yields:** IPAddr network and Hash data for each entry
|
|
245
|
+
|
|
246
|
+
**Returns:** Enumerator if no block given
|
|
247
|
+
|
|
248
|
+
**Raises:**
|
|
249
|
+
|
|
250
|
+
- `ArgumentError`: If network CIDR is invalid or IPv6 network specified for IPv4-only database
|
|
251
|
+
|
|
252
|
+
### `MaxMind::DB::Rust::Metadata`
|
|
253
|
+
|
|
254
|
+
Metadata attributes:
|
|
255
|
+
|
|
256
|
+
- `binary_format_major_version` - Major version of the binary format
|
|
257
|
+
- `binary_format_minor_version` - Minor version of the binary format
|
|
258
|
+
- `build_epoch` - Unix timestamp when the database was built
|
|
259
|
+
- `database_type` - Type of database (e.g., "GeoIP2-City")
|
|
260
|
+
- `description` - Hash of locale codes to descriptions
|
|
261
|
+
- `ip_version` - 4 for IPv4-only, 6 for IPv4/IPv6 support
|
|
262
|
+
- `languages` - Array of supported locale codes
|
|
263
|
+
- `node_count` - Number of nodes in the search tree
|
|
264
|
+
- `record_size` - Record size in bits (24, 28, or 32)
|
|
265
|
+
- `node_byte_size` - Size of a node in bytes
|
|
266
|
+
- `search_tree_size` - Size of the search tree in bytes
|
|
267
|
+
|
|
268
|
+
### Constants
|
|
269
|
+
|
|
270
|
+
- `MaxMind::DB::Rust::MODE_AUTO` - Automatically choose the best mode (uses MMAP)
|
|
271
|
+
- `MaxMind::DB::Rust::MODE_MEMORY` - Load entire database into memory
|
|
272
|
+
- `MaxMind::DB::Rust::MODE_MMAP` - Use memory-mapped file I/O (recommended)
|
|
273
|
+
|
|
274
|
+
### Exceptions
|
|
275
|
+
|
|
276
|
+
- `MaxMind::DB::Rust::InvalidDatabaseError` - Raised when the database file is corrupt or invalid
|
|
277
|
+
|
|
278
|
+
## Comparison with Official Gem
|
|
279
|
+
|
|
280
|
+
| Feature | maxmind-db (official) | maxmind-db-rust (this gem) |
|
|
281
|
+
| ---------------- | --------------------- | -------------------------- |
|
|
282
|
+
| Implementation | Pure Ruby | Rust with Ruby bindings |
|
|
283
|
+
| Performance | Baseline | 10-50x faster |
|
|
284
|
+
| API | MaxMind::DB | MaxMind::DB::Rust |
|
|
285
|
+
| MODE_FILE | ✓ | ✗ |
|
|
286
|
+
| MODE_MEMORY | ✓ | ✓ |
|
|
287
|
+
| MODE_AUTO | ✓ | ✓ |
|
|
288
|
+
| MODE_MMAP | ✗ | ✓ |
|
|
289
|
+
| Iterator support | ✗ | ✓ |
|
|
290
|
+
| Thread-safe | ✓ | ✓ |
|
|
291
|
+
|
|
292
|
+
## Performance
|
|
293
|
+
|
|
294
|
+
Expected performance characteristics (will vary based on hardware):
|
|
295
|
+
|
|
296
|
+
- Single-threaded lookups: 300,000 - 500,000 lookups/second
|
|
297
|
+
- Significantly faster than pure Ruby implementations
|
|
298
|
+
- Memory-mapped mode (MMAP) provides best performance
|
|
299
|
+
- Fully thread-safe for concurrent lookups
|
|
300
|
+
|
|
301
|
+
## Development
|
|
302
|
+
|
|
303
|
+
Interested in contributing? See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed developer documentation, including:
|
|
304
|
+
|
|
305
|
+
- Development setup and prerequisites
|
|
306
|
+
- Building and testing the extension
|
|
307
|
+
- Code quality guidelines
|
|
308
|
+
- Project structure
|
|
309
|
+
- Submitting changes
|
|
310
|
+
|
|
311
|
+
### Quick Start
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
git clone https://github.com/oschwald/maxmind-db-rust-ruby.git
|
|
315
|
+
cd maxmind-db-rust-ruby
|
|
316
|
+
git submodule update --init --recursive
|
|
317
|
+
bundle install
|
|
318
|
+
bundle exec rake compile
|
|
319
|
+
bundle exec rake test
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Contributing
|
|
323
|
+
|
|
324
|
+
1. Fork it
|
|
325
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
326
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
327
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
328
|
+
5. Create a new Pull Request
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
This software is licensed under the ISC License. See the LICENSE file for details.
|
|
333
|
+
|
|
334
|
+
## Support
|
|
335
|
+
|
|
336
|
+
- **Issues**: https://github.com/oschwald/maxmind-db-rust-ruby/issues
|
|
337
|
+
- **Documentation**: https://www.rubydoc.info/gems/maxmind-db-rust
|
|
338
|
+
|
|
339
|
+
## Credits
|
|
340
|
+
|
|
341
|
+
This gem uses the [maxminddb](https://github.com/oschwald/maxminddb-rust) Rust crate for the core MaxMind DB reading functionality.
|
|
342
|
+
|
|
343
|
+
Built with [magnus](https://github.com/matsadler/magnus) and [rb-sys](https://github.com/oxidize-rb/rb-sys).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "maxmind_db_rust"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["Gregory Oschwald <oschwald@gmail.com>"]
|
|
6
|
+
description = "Rust-based Ruby binding for MaxMind DB - high-performance alternative to maxmind-db"
|
|
7
|
+
license = "ISC"
|
|
8
|
+
repository = "https://github.com/oschwald/maxmind-db-rust-ruby"
|
|
9
|
+
|
|
10
|
+
[lib]
|
|
11
|
+
name = "maxmind_db_rust"
|
|
12
|
+
crate-type = ["cdylib"]
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
arc-swap = "1.7"
|
|
16
|
+
ipnetwork = "0.21"
|
|
17
|
+
magnus = "0.8"
|
|
18
|
+
maxminddb = { version = "0.26.0", features = ["unsafe-str-decode"] }
|
|
19
|
+
memmap2 = "0.9"
|
|
20
|
+
serde = "1.0"
|
|
21
|
+
|
|
22
|
+
[profile.release]
|
|
23
|
+
lto = "thin"
|
|
24
|
+
codegen-units = 1
|
|
25
|
+
opt-level = 3
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ipaddr'
|
|
4
|
+
require 'maxmind/db/maxmind_db_rust'
|
|
5
|
+
|
|
6
|
+
module MaxMind
|
|
7
|
+
module DB
|
|
8
|
+
module Rust
|
|
9
|
+
# The native extension defines:
|
|
10
|
+
# - Reader class
|
|
11
|
+
# - Metadata class
|
|
12
|
+
# - InvalidDatabaseError exception
|
|
13
|
+
# - MODE_AUTO, MODE_MEMORY, MODE_MMAP constants
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|