├── README.md ├── dist.ini ├── resty └── maxminddb.lua └── test.lua /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # A lua client for maxmind db 4 | 5 | to get ip location with ip database offerd by maxmind 6 | 7 | 8 | # Installation 9 | 10 | 1. install libmaxminddb from this [repo](https://github.com/maxmind/libmaxminddb) 11 | 2. download ip database from [maxmind](https://www.maxmind.com/en/geoip2-databases) 12 | 13 | # Demo code: 14 | 15 | ```lua 16 | local maxminddb = require("resty.maxminddb") 17 | local geo_file = '/home/lua/common_data/GeoIP2-Country-20161004.mmdb' 18 | local ip_db = maxminddb.new(geo_file) 19 | 20 | local client_ip = ngx.req.get_uri_args()['ip'] 21 | local out = {} 22 | if not client_ip then 23 | return nil 24 | end 25 | local res,err = ip_db:get_area_code(client_ip) 26 | ngx.print( res ) 27 | ``` 28 | 29 | 30 | # Bug Reports 31 | 32 | Please report bugs by filing an issue with our GitHub issue tracker at 33 | https://github.com/lilien1010/libmaxminddb/issues 34 | or if the bug is casued by libmaxminddb tracker at 35 | https://github.com/maxmind/libmaxminddb/issues 36 | 37 | # Copyright and License 38 | 39 | Licensed under the Apache License, Version 2.0 (the "License"); 40 | you may not use this file except in compliance with the License. 41 | You may obtain a copy of the License at 42 | 43 | http://www.apache.org/licenses/LICENSE-2.0 44 | 45 | Unless required by applicable law or agreed to in writing, software 46 | distributed under the License is distributed on an "AS IS" BASIS, 47 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 48 | See the License for the specific language governing permissions and 49 | limitations under the License. 50 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-maxminddb 2 | abstract = a lua client to get ip location maxminddb 3 | version = 1.1.1 4 | author = lilien1010(Lien) 5 | license = 3bsd 6 | repo_link = https://github.com/lilien1010/lua-resty-maxminddb 7 | is_original = yes 8 | lib_dir = resty/ -------------------------------------------------------------------------------- /resty/maxminddb.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | a lua interface (build with luajit ffi) for maxminddb to get country code from ip address 3 | ]] 4 | 5 | local json = require('cjson'); 6 | local json_encode = json.encode 7 | local json_decode = json.decode 8 | local ht_print = ngx.print 9 | local ngx_log = ngx.log 10 | local ngx_ERR = ngx.ERR 11 | local ngx_CRIT = ngx.CRIT 12 | local ngx_INFO = ngx.INFO 13 | 14 | local ngx_sub = ngx.re.sub; 15 | local ngx_gsub = ngx.re.gsub; 16 | local ngx_unescape_uri = ngx.unescape_uri 17 | local ngx_find = ngx.re.find 18 | local string_sub = string.sub 19 | local string_len = string.len 20 | local string_byte = string.byte 21 | local ngx_read_body = ngx.req.read_body 22 | local ngx_decode_base64 = ngx.decode_base64 23 | local ngx_now = ngx.now 24 | local ngx_exit = ngx.exit 25 | 26 | 27 | 28 | 29 | 30 | local ffi = require 'ffi' 31 | local ffi_new = ffi.new 32 | local ffi_str = ffi.string 33 | local ffi_copy = ffi.copy 34 | local ffi_cast = ffi.cast 35 | 36 | ffi.cdef[[ 37 | 38 | typedef unsigned int mmdb_uint128_t __attribute__ ((__mode__(TI))); 39 | 40 | typedef struct MMDB_entry_s { 41 | struct MMDB_s *mmdb; 42 | uint32_t offset; 43 | } MMDB_entry_s; 44 | 45 | typedef struct MMDB_lookup_result_s { 46 | bool found_entry; 47 | MMDB_entry_s entry; 48 | uint16_t netmask; 49 | } MMDB_lookup_result_s; 50 | 51 | typedef struct MMDB_entry_data_s { 52 | bool has_data; 53 | union { 54 | uint32_t pointer; 55 | const char *utf8_string; 56 | double double_value; 57 | const uint8_t *bytes; 58 | uint16_t uint16; 59 | uint32_t uint32; 60 | int32_t int32; 61 | uint64_t uint64; 62 | mmdb_uint128_t uint128; 63 | bool boolean; 64 | float float_value; 65 | }; 66 | 67 | uint32_t offset; 68 | uint32_t offset_to_next; 69 | uint32_t data_size; 70 | uint32_t type; 71 | } MMDB_entry_data_s; 72 | 73 | typedef struct MMDB_entry_data_list_s { 74 | MMDB_entry_data_s entry_data; 75 | struct MMDB_entry_data_list_s *next; 76 | } MMDB_entry_data_list_s; 77 | 78 | typedef struct MMDB_description_s { 79 | const char *language; 80 | const char *description; 81 | } MMDB_description_s; 82 | 83 | typedef struct MMDB_metadata_s { 84 | uint32_t node_count; 85 | uint16_t record_size; 86 | uint16_t ip_version; 87 | const char *database_type; 88 | struct { 89 | size_t count; 90 | const char **names; 91 | } languages; 92 | uint16_t binary_format_major_version; 93 | uint16_t binary_format_minor_version; 94 | uint64_t build_epoch; 95 | struct { 96 | size_t count; 97 | MMDB_description_s **descriptions; 98 | } description; 99 | } MMDB_metadata_s; 100 | 101 | typedef struct MMDB_ipv4_start_node_s { 102 | uint16_t netmask; 103 | uint32_t node_value; 104 | } MMDB_ipv4_start_node_s; 105 | 106 | typedef struct MMDB_s { 107 | uint32_t flags; 108 | const char *filename; 109 | ssize_t file_size; 110 | const uint8_t *file_content; 111 | const uint8_t *data_section; 112 | uint32_t data_section_size; 113 | const uint8_t *metadata_section; 114 | uint32_t metadata_section_size; 115 | uint16_t full_record_byte_size; 116 | uint16_t depth; 117 | MMDB_ipv4_start_node_s ipv4_start_node; 118 | MMDB_metadata_s metadata; 119 | } MMDB_s; 120 | 121 | typedef char * pchar; 122 | 123 | MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb, const char *const ipstr, int *const gai_error,int *const mmdb_error); 124 | int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb); 125 | int MMDB_aget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, const char *const *const path); 126 | char *MMDB_strerror(int error_code); 127 | ]] 128 | 129 | local MMDB_SUCCESS = 0 130 | local MMDB_DATA_TYPE_POINTER = 1 131 | local MMDB_DATA_TYPE_UTF8_STRING= 2 132 | local MMDB_DATA_TYPE_DOUBLE = 3 133 | local MMDB_DATA_TYPE_BYTES = 4 134 | 135 | 136 | -- you should install the libmaxminddb to your system 137 | local maxm = ffi.load('libmaxminddb.so') 138 | --https://github.com/maxmind/libmaxminddb 139 | 140 | 141 | local _M ={} 142 | 143 | local mt = { __index = _M } 144 | 145 | function _M.new(maxmind_country_geoip2_file) 146 | 147 | local mmdb = ffi_new('MMDB_s') 148 | local file_name_ip2 = ffi_new('char[?]',#maxmind_country_geoip2_file,maxmind_country_geoip2_file) 149 | local maxmind_reday = maxm.MMDB_open(file_name_ip2,0,mmdb) 150 | 151 | return setmetatable({ mmdb=mmdb }, mt); 152 | end 153 | 154 | 155 | 156 | function _M:lookup(ip) 157 | 158 | local ip_str = ffi_cast('const char *',ffi_new('char[?]',#ip+1,ip)) 159 | local gai_error = ffi_new('int[1]') 160 | local mmdb_error = ffi_new('int[1]') 161 | 162 | local result = maxm.MMDB_lookup_string(self.mmdb,ip_str,gai_error,mmdb_error) 163 | 164 | if mmdb_error[0] ~= MMDB_SUCCESS then 165 | return nil,'fail when lookup' 166 | end 167 | 168 | if gai_error[0] ~= MMDB_SUCCESS then 169 | return nil,'ga error' 170 | end 171 | 172 | 173 | if true~=result.found_entry then 174 | ngx_log(ngx_ERR, "stream lua mmdb lookup: entry not found") 175 | return nil,'not found' 176 | end 177 | 178 | local lookup = ffi_new('const char*[3]') 179 | lookup[0] = ffi_cast('const char *',ffi_new('char[?]',8,'country')) 180 | lookup[1] = ffi_cast('const char *',ffi_new('char[?]',9,'iso_code')) 181 | 182 | 183 | local entry_data = ffi_new('MMDB_entry_data_s[1]') 184 | lookup = ffi_cast('const char *const *const',lookup) 185 | local mmdb_error = maxm.MMDB_aget_value(result.entry, entry_data, lookup); 186 | 187 | if mmdb_error ~= MMDB_SUCCESS then 188 | return nil,'no value' 189 | end 190 | 191 | if true ~= entry_data[0].has_data then 192 | ngx_log(ngx_ERR, "stream lua mmdb lookup: entry has no data") 193 | return nil,'no data' 194 | end 195 | 196 | local country = '' 197 | if entry_data[0].type == MMDB_DATA_TYPE_UTF8_STRING then 198 | return ffi_str(entry_data[0].utf8_string,entry_data[0].data_size) 199 | end 200 | 201 | if entry_data[0].type ==MMDB_DATA_TYPE_BYTES then 202 | return ffi_str(ffi_cast('char * ',entry_data[0].bytes),entry_data[0].data_size) 203 | end 204 | 205 | return nil,'no data' 206 | end 207 | 208 | -- https://www.maxmind.com/en/geoip2-databases you should download the mmdb file from maxmind 209 | 210 | 211 | function _M:get_area_code(ip) 212 | 213 | local succ,country,err = pcall(self.lookup,self,ip) 214 | 215 | if succ==true then 216 | return country 217 | else 218 | return nil,err 219 | end 220 | end 221 | 222 | 223 | return _M; 224 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local json_encode = json.encode 4 | local json_decode = json.decode 5 | local ngx_log = ngx.log 6 | local ngx_say = ngx.say 7 | local ngx_exit = ngx.exit 8 | local ngx_ERR = ngx.ERR 9 | local ngx_CRIT = ngx.CRIT 10 | local ngx_INFO = ngx.INFO 11 | local ngx_now = ngx.now 12 | local ngx_time = ngx.time 13 | 14 | local string_len = string.len 15 | 16 | local string_sub = string.sub; 17 | local string_len = string.len; 18 | local tonumber = tonumber; 19 | 20 | local ngx_find = ngx.re.find 21 | local ngx_sub = ngx.re.sub 22 | 23 | local string_match = string.match 24 | local ngx_match = ngx.re.match 25 | 26 | 27 | local maxminddb = require("resty.maxminddb") 28 | local geo_file = '/home/lua/common_data/GeoIP2-Country-20161004.mmdb' 29 | local ipDB = maxminddb.new(geo_file) 30 | function _M:iplocation5(args) 31 | 32 | local ip = ngx.req.get_uri_args()['ip'] 33 | local out = {} 34 | if not ip then 35 | return nil 36 | end 37 | local res,err = ipDB:get_area_code(ip) 38 | ngx.print( json_encode({res}) ) 39 | end 40 | 41 | return _M 42 | --------------------------------------------------------------------------------