zoukankan      html  css  js  c++  java
  • GeoIP的使用-C语言版

    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
    ", filename, MMDB_strerror(status));
    
            if (MMDB_IO_ERROR == status) {
                fprintf(stderr, "IO error: %s
    ", strerror(errno));
            }
            exit(1);
        }
    
        result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
        if (0 != gai_error) {
            fprintf(stderr, "Error from getaddrinfo for %s - %s
    ", ip_address, gai_strerror(gai_error));
            exit(2);
        }
    
        if (MMDB_SUCCESS != mmdb_error) {
            fprintf(stderr, "Got an error from libmaxminddb: %s
    ", MMDB_strerror(mmdb_error));
            exit(3);
        }
    
        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, 1, stdout); printf(" "); } else { printf("data_type = %d ", entry_data.type); } } else { fprintf(stderr, "MMDB_get_value not found "); } } else { fprintf(stderr, "MMDB_get_value failed,%s ", 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
    ", filename, MMDB_strerror(status));
    
            if (MMDB_IO_ERROR == status) {
                fprintf(stderr, "IO error: %s
    ", strerror(errno));
            }
            exit(1);
        }
    
        result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error);
        if (0 != gai_error) {
            fprintf(stderr, "Error from getaddrinfo for %s - %s
    ", ip_address, gai_strerror(gai_error));
            exit(2);
        }
    
        if (MMDB_SUCCESS != mmdb_error) {
            fprintf(stderr, "Got an error from libmaxminddb: %s
    ", MMDB_strerror(mmdb_error));
            exit(3);
        }
    
    /* 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, 1, stdout);
                        printf("
    ");
                    }
                    else {
                        printf("data_type = %d
    ", entry_data.type);
                    }
                }
                else {
                    fprintf(stderr, "MMDB_get_value not found
    ");
                }
            }
            else {
                fprintf(stderr, "MMDB_get_value failed,%s
    ", MMDB_strerror(status));
            }
        }
    
    
        MMDB_close(&mmdb);
        exit(EXIT_SUCCESS);
    }

    0x04. 题外话

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

  • 相关阅读:
    TODO C++ lambda表达式
    C++ Map实践
    【转】C++ typedef typename 作用
    C++ Vector实践
    再学引用
    设置table中的td一连串内容自动换行
    JavaScript中基本数据类型和引用数据类型的区别
    “浏览器模式”和“文档模式”之间的区别
    浏览器模式与文档模式区别
    HTML5中的data-*属性和jQuery中的.data()方法使用
  • 原文地址:https://www.cnblogs.com/LubinLew/p/GeoIP.html
Copyright © 2011-2022 走看看