0x00. 简介

GeoIP库可以根据IP地址(支持IPv4 和 IPv6), 定位该IP所在的 洲、经纬度、国家、省市、ASN 等信息。

GeoIP目前已经升级到GeoIP2,GeoIP2有两个版本,一个免费版(GeoLite2),一个收费版本(GeoIP2, 200$起步)。

收费版本的准确率稍高一些,更新频率为每周二更新一次, 免费版是每月第一个周二更新一次。

两者对比可以参考官网说明 https://www.maxmind.com/en/geoip2-city-accuracy-comparison

对于大部分项目来说免费版已经足够使用了.

除了GeoIP外, 其实还有 ip2location、Quova等也提供类似的功能, 但都是收费的.

 

0x01. 资源下载

很多linux版本支持这个库, 可以使用yum 或 apt 进行下载, windows上使用的话就需要自己编译了.

源码下载:

https://dev.maxmind.com/geoip/geoip2/downloadable/

GeoIP2提供了多种语言的API接口供选择.

这里我需要使用C语言接口, 所以下载C语言版的源码.

https://github.com/maxmind/libmaxminddb/releases

GeoIP数据库下载:

https://dev.maxmind.com/geoip/geoip2/geolite2/

可以看到官网提供三种库,2种格式, 首先 官网API是需要使用二进制库文件, CSV格式的库可以导入其他程序 或 供你简单浏览。

三种库的区别可以从名字上就可以看出来:

  City       精确到城市(大小70M左右),

  Country 精确到国家(4M左右),

  ASN       用于产看IP地址的拥有者(7M左右). 需要注意的是 City 和 Country 库中不含ASN信息

对于ASN的理解可以通过知乎了解一下 https://www.zhihu.com/question/21024981

根据业务需求选择. 这里我们下载精确到城市的数据库文件.

https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz

由于数据库经常更新, 官网还提供了更新的方案:

https://dev.maxmind.com/geoip/geoipupdate/#For_Free_GeoLite2_Databases

0x02.接口说明

http://maxmind.github.io/libmaxminddb/

/* ------------------数据库的关闭与打开
* 这里 MMDB_open 的 flags 参数需要说明一下,
* 目前代码实现db文件都是使用mmap()映射到内存的,
* 所以flags其实设置什么都无所谓,所以默认使用 MMDB_MODE_MMAP就好.
*/
int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb);
void MMDB_close(MMDB_s *const mmdb); /* ------------------数据搜索
* 网络字节序地址搜索API, 即传入sockaddr参数是网络字节序的地址 */
MMDB_lookup_result_s MMDB_lookup_sockaddr(MMDB_s *const mmdb,
const struct sockaddr *const sockaddr, int *const mmdb_error); /* 字符串IP地址搜索API, 即传入ipstr参数的null结尾字符串,如"114.240.52.162"
* 这个API其实就是调用 getaddrinfo() 将字符串转换为网络地址,
* 然后再调用 MMDB_lookup_sockaddr() 实现的,
* 所以不要将网络地址转成字符串,再调用这个函数, 直接使用MMDB_lookup_sockaddr()
* gai_error参数 是用来返回 getaddrinfo() 错误码的.
*/
MMDB_lookup_result_s MMDB_lookup_string(MMDB_s *const mmdb, const char *const ipstr,
int *const gai_error, int *const mmdb_error); /* ------------------从搜索到数据中提取指定数据
* 下面 3 个函数意义相同, 只不过传入参数的方法不同.
* 3个函数前2个参数相同, 第一个是 搜索结果中搜到的entry,具体看例子程序
* 第二个参数是 获取数据的存放处, 剩下的参数都是搜索用的数据.
* 其实这3个函数实现是层层调用的关系:
* MMDB_get_value()是可变参数函数, 函数内部把 可变参数 用 va_list 封装起来,
* 接着调用 MMDB_vget_value(), 这个 vget函数把所有可变参数 再分离开来,
* 放到一个 字符串指针数组里面, 再把这个数组传递给 MMDB_aget_value()函数,
* 最终就是 aget 函数完成搜索功能.
* 所以如果考虑到性能, 那个最好直接调用 aget 函数.
* 另外, 需要注意 传入的参数 最后一个必须是 NULL, 否则会导致程序崩溃
*/
int MMDB_get_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, ...);
int MMDB_vget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, va_list va_path);
int MMDB_aget_value(MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, const char *const *const path); /* ------------------从搜索到数据中提取全部数据
* 调用完MMDB_lookup_sockaddr/MMDB_lookup_string 后获取所有该IP地址相关信息
* 具体使用看官方例子, 用处不大, 主要是可以用来了解一下结果数据的组成
*/
int MMDB_get_entry_data_list(MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list);
int MMDB_dump_entry_data_list(FILE *const stream,MMDB_entry_data_list_s *const entry_data_list, int indent);
void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list);
int MMDB_get_metadata_as_entry_data_list(MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list); /* ------------------其他, MMDB_read_node例子可以看源码的 read_node_t.c,个人觉得没什么用 */
const char *MMDB_strerror(int error_code);
const char *MMDB_lib_version(void);
int MMDB_read_node(MMDB_s *const mmdb, uint32_t node_number, MMDB_search_node_s *const node);

0x03. 例子

例子1:使用 City 或 Country 库 查询IP所属位置信息

/* this file must be utf8 encode */
#include <errno.h>
#include <maxminddb.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char **argv)
{
char *filename = "./GeoLite2-City.mmdb";
char *ip_address = "114.240.52.162"; MMDB_s mmdb;
MMDB_entry_data_s entry_data;
MMDB_lookup_result_s result;
int gai_error, mmdb_error; int status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS != status) {
fprintf(stderr, "Can't open %s - %s\n", filename, MMDB_strerror(status)); if (MMDB_IO_ERROR == status) {
fprintf(stderr, "IO error: %s\n", strerror(errno));
}
exit();
} result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
if ( != gai_error) {
fprintf(stderr, "Error from getaddrinfo for %s - %s\n", ip_address, gai_strerror(gai_error));
exit();
} if (MMDB_SUCCESS != mmdb_error) {
fprintf(stderr, "Got an error from libmaxminddb: %s\n", MMDB_strerror(mmdb_error));
exit();
} if (result.found_entry) {
/* 获取国家名称(简体中文) */
status = MMDB_get_value(&result.entry, &entry_data, "country", "names", "zh-CN", NULL);
/* 获取国家简称(CN/AU/JP等) */
// status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL); /* 获取城市名称(简体中文) */
// status = MMDB_get_value(&result.entry, &entry_data, "city", "names", "zh-CN", NULL); if (MMDB_SUCCESS == status) {/* MMDB_get_value 成功 */
if (entry_data.has_data) {/* 找到了想要的数据 */
if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
/* entry_data.utf8_string 返回的不是null-terminated 字符串,需要根据长度自己截取数据 */
fwrite(entry_data.utf8_string, entry_data.data_size, , stdout);
printf("\n");
}
else {
printf("data_type = %d\n", entry_data.type);
}
}
else {
fprintf(stderr, "MMDB_get_value not found\n");
}
}
else {
fprintf(stderr, "MMDB_get_value failed,%s\n", MMDB_strerror(status));
}
} MMDB_close(&mmdb);
exit(EXIT_SUCCESS);
}

例子2: 使用ASN库查询IP地址的ASN

/* this file must be utf8 encode */
#include <errno.h>
#include <maxminddb.h>
#include <stdlib.h>
#include <string.h> int main(int argc, char **argv)
{
char *filename = "./GeoLite2-ASN.mmdb";
char *ip_address = "114.240.52.162"; MMDB_s mmdb;
MMDB_entry_data_s entry_data;
MMDB_lookup_result_s result;
int gai_error, mmdb_error; int status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS != status) {
fprintf(stderr, "Can't open %s - %s\n", filename, MMDB_strerror(status)); if (MMDB_IO_ERROR == status) {
fprintf(stderr, "IO error: %s\n", strerror(errno));
}
exit();
} result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
if ( != gai_error) {
fprintf(stderr, "Error from getaddrinfo for %s - %s\n", ip_address, gai_strerror(gai_error));
exit();
} if (MMDB_SUCCESS != mmdb_error) {
fprintf(stderr, "Got an error from libmaxminddb: %s\n", MMDB_strerror(mmdb_error));
exit();
} /* ASN DB INFO Example
{
"autonomous_system_number":
4808 <uint32>
"autonomous_system_organization":
"China Unicom Beijing Province Network" <utf8_string>
}
*/
if (result.found_entry) {
/* 获取ASN */
status = MMDB_get_value(&result.entry, &entry_data, "autonomous_system_organization", NULL); if (MMDB_SUCCESS == status) {/* MMDB_get_value 成功 */
if (entry_data.has_data) {/* 找到了想要的数据 */
if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
/* entry_data.utf8_string 返回的不是null-terminated 字符串,需要根据长度自己截取数据 */
fwrite(entry_data.utf8_string, entry_data.data_size, , stdout);
printf("\n");
}
else {
printf("data_type = %d\n", entry_data.type);
}
}
else {
fprintf(stderr, "MMDB_get_value not found\n");
}
}
else {
fprintf(stderr, "MMDB_get_value failed,%s\n", MMDB_strerror(status));
}
} MMDB_close(&mmdb);
exit(EXIT_SUCCESS);
}

0x04. 题外话

从源代码来看, 官方提供的库并不适合高速大量查询, 所以有很多第三方实现,例如nginx等都自己实现了数据库的搜索模块.

GeoIP的使用-C语言版的更多相关文章

  1. libnode 0.4.0 发布,C++ 语言版的 Node.js

    libnode 0.4.0 支持 Windows ,提升了性能,libuv 更新到 0.10.17 版本,libj 更新到 0.8.2 版本. libnode 是 C++ 语言版的 Node.js,和 ...

  2. md5加密算法c语言版

    from: http://blog.sina.com.cn/s/blog_693de6100101kcu6.html 注:以下是md5加密算法c语言版(16/32位) ---------------- ...

  3. 基于gSOAP使用头文件的C语言版web service开发过程例子

    基于gSOAP使用头文件的C语言版web service开发过程例子 一服务端 1 打开VS2005,创建一个工程,命名为calcServer. 2 添加一个头文件calc.h,编辑内容如下: 1// ...

  4. Windows 8.1 with Update 镜像下载(增OEM单语言版)

    该系统已有更新的版本,请转至<Windows 8.1 with update 官方最新镜像汇总>下载. 2014年4月9日凌晨,微软向MSDN订阅用户开放了Windows 8.1 with ...

  5. 数据结构C语言版 有向图的十字链表存储表示和实现

    /*1wangxiaobo@163.com 数据结构C语言版 有向图的十字链表存储表示和实现 P165 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h> ...

  6. 数据结构C语言版 表插入排序 静态表

    数据结构C语言版 表插入排序.txt两个人吵架,先说对不起的人,并不是认输了,并不是原谅了.他只是比对方更珍惜这份感情./*  数据结构C语言版 表插入排序  算法10.3 P267-P270  编译 ...

  7. 数据结构C语言版 弗洛伊德算法实现

    /* 数据结构C语言版 弗洛伊德算法  P191 编译环境:Dev-C++ 4.9.9.2 */ #include <stdio.h>#include <limits.h> # ...

  8. VS2015如何新建C++或者C语言版的lib文件

    当我们不想公开我们的代码的时候,可以把我们的代码封装成静态数据连接库,即lib文件.下面介绍下如何生成lib文件. 以VS2015为例,一种是C++版的lib文件,一种是C语言版的lib文件. 一.按 ...

  9. hbase rest api接口链接管理【golang语言版】

    # go-hbase-resthbase rest api接口链接管理[golang语言版]关于hbase的rest接口的详细信息可以到官网查看[http://hbase.apache.org/boo ...

随机推荐

  1. Linux系统下root密码遗忘等系统故障的修复方法 - 运维总结

    IDC机房有一台centos系统的服务器,由于这台服务器的系统装了好长时间,且root密码中间更新过几次,后面去机房现场维护时,登陆密码遗忘了,悲催啊~没办法,只能开机进入“单用户模式”进行密码重置了 ...

  2. VIJOS-P1232 核电站问题

    VIJOS-P1232 核电站问题 JDOJ 1373 https://neooj.com/oldoj/problem.php?id=1373 题目描述         一个核电站有N个放核物质的坑, ...

  3. C# 一般处理程序

    public class Three : IHttpHandler{ public void ProcessRequest(HttpContext context) { context.Respons ...

  4. 5-ESP8266 SDK开发基础入门篇--了解一下操作系统

    对于操作系统不知道有没有害怕接触的... 先说一下操作系统是什么意思,其实咱的电脑就运行了操作系统,手机,等等... 操作系统和任务分不开,所谓任务就是一个一个的执行各个功能的函数,,,操作系统呢就是 ...

  5. [RN] windows7 安装 Realm Studio 后,打开报错 A JavaScript error occurred in the main process

    windows7  安装 Realm Studio 后,打开报错 报错如下: A JavaScript error occurred in the main process Uncaught Exce ...

  6. linux下python解释器的sys.path路径如何添加

    一. 使用pycharm时, pycharm会自动把我们新建的每个项目都加入到sys.path路径中, 我们在使用过程中根本不涉及 项目路径的处理, 但是当项目部署到linux上时, 问题就来了, l ...

  7. requests--重定向,序列化

    重定向 默认情况下,除了 HEAD, Requests 会自动处理所有重定向.可以使用响应对象的 history 方法来追踪重定向. Response.history 是一个 Response 对象的 ...

  8. SPOJ31428 FIBONOMIAL(斐波那契数列)

    神鱼推题,必是好题. 前几天刚做过[BJOI2019]勘破神机,于是就会这题了.(BJ人民强啊……%鱼) 首先要求是 $$\sum\limits_{i=0}^nx^if_i$$ 应该很明显能想到把 $ ...

  9. [LeetCode] 390. Elimination Game 淘汰游戏

    There is a list of sorted integers from 1 to n. Starting from left to right, remove the first number ...

  10. 分布式共识算法 (三) Raft算法

    系列目录 分布式共识算法 (一) 背景 分布式共识算法 (二) Paxos算法 分布式共识算法 (三) Raft算法 分布式共识算法 (四) BTF算法 一.引子 1.1 介绍 Raft 是一种为了管 ...