Documentation exists for the old MaxMind GeoIP databases but I found myself needing to add some RFC1918 addresses into the DB for a logstash configuration. This was a bit of a pain so I figured I’d share for others who want to do the same. This script is setup to use the MaxMind::DB::Reader::XS and MaxMind::DB::Writer::Tree perl modules. (I gave up perl 20 years ago. Every time you think you’re out, they pull you back in!) Anyway, the XS module requires you to build the libmaxminddb library on your machine but you can probably get away with the standard reader. Anything you put into the custom_ranges hash will get inserted or updated in the default DB. This script takes a while to run (23min on my machine) but so far so good. Consider it beta. YMMV.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
#!/usr/bin/env perl use strict; use warnings; use feature qw( say ); use local::lib 'local'; use MaxMind::DB::Reader::XS; use MaxMind::DB::Writer::Tree; use Net::Works::Network; use Data::Dumper; use LWP::Simple; use Archive::Tar; my $download = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz'; my $local = 'GeoLite2-City.mmdb'; my $output = '/etc/logstash/GeoLite2-Custom.mmdb'; my $tar = Archive::Tar->new; # Check if local downlaod is fresh if(-e $local) { my @lstat = stat($local); } if((! -e $local) || ($lstat[9] + (60*60*24*6) < time)) { say "Getting updated database file..."; getstore($download, $local . ".tgz"); $tar->read($local . '.tgz'); for my $target (grep(/GeoLite2-City.mmdb$/, $tar->list_files())) { $tar->extract_file( $target, $local ); } } my $reader = MaxMind::DB::Reader->new( file => $local); my %types = ( names => 'map', city => 'map', continent => 'map', registered_country => 'map', represented_country => 'map', country => 'map', location => 'map', postal => 'map', traits => 'map', geoname_id => 'uint32', type => 'utf8_string', en => 'utf8_string', de => 'utf8_string', es => 'utf8_string', fr => 'utf8_string', ja => 'utf8_string', 'pt-BR' => 'utf8_string', ru => 'utf8_string', 'zh-CN' => 'utf8_string', locales => [ 'array', 'utf8_string' ], code => 'utf8_string', geoname_id => 'uint32', ip_address => 'utf8_string', subdivisions => [ 'array' , 'map' ], iso_code => 'utf8_string', environments => [ 'array', 'utf8_string' ], expires => 'uint32', name => 'utf8_string', time_zone => 'utf8_string', accuracy_radius => 'uint32', latitude => 'float', longitude => 'float', metro_code => 'uint32', time_zone => 'utf8_string', is_in_european_union => 'utf8_string', is_satellite_provider => 'utf8_string', is_anonymous_proxy => 'utf8_string', ); my $tree = MaxMind::DB::Writer::Tree->new( database_type => 'GeoLite2-City', description => { en => 'GeoLite2 City database' }, ip_version => 4, map_key_type_callback => sub { $types{ $_[0] } }, merge_strategy => 'recurse', record_size => 28, remove_reserved_networks => 0, ); $reader->iterate_search_tree( sub { my $ip_as_integer = shift; my $mask_length = shift; my $data = shift; my $net_address; if ($ip_as_integer > 2**32-1) { return; } my $address = Net::Works::Address->new_from_integer( integer => $ip_as_integer ); $net_address = join '/', $address->as_ipv4_string, $mask_length - 96; if($mask_length > 127) { return; } #say join '/', $address->as_ipv4_string, $mask_length - 96; #say Dumper($data); $tree->insert_network( $net_address, $data ); } ); my %custom_ranges = ( '172.16.0.0/12' => { city => { geoname_id => 4704482, names => { en => "Dallas", }, }, continent => { code => "NA", geoname_id => 6255149, names => { en => "North America", }, }, country => { geoname_id => 6252001, iso_code => "US", names => { en => "United States", }, }, location => { accuracy_radius => 2, latitude => 32.776706, longitude => -96.805047, metro_code => 623, time_zone => "America/Chicago", }, postal => { code => 75202, }, registered_country => { geoname_id => 6252001, iso_code => "US", names => { en => "United States", }, }, subdivisions => [ { geoname_id => 4736286, iso_code => "TX", names => { en => "Texas", }, } ], }, ); for my $range ( keys %custom_ranges ) { my $metadata = $custom_ranges{$range}; my $network = Net::Works::Network->new_from_string ( string => $range ); $tree->insert_network($network, $metadata); } open my $fh, '>:raw', $output; $tree->write_tree( $fh ); close $fh; |
2 comments
Hi there,
Just want to thank you for posting this. It’s helped me massively, and allowed me to build an internal GeoIP database for a UK rail company who have many internal sites (over 200 of them).
You’re absolutely right about how long it takes to build the mmdb 😀
A
Thank you. The most complete example I have found.