1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-12 17:14:46 +02:00

geoip_maxmind: use our own mmdb implementation

This is mainly due to licensing. The libmaxminddb library uses the
Apache license, which meant if we would compile it in by default it
would effectively transform our "GPLv2 or later" to "GPLv3 or later".
Our implementation is ISC licensed, so we can include and enable it
by default and keep things at "GPLv2 or later". This is also why we
used geoip_classic in the first place as default and compiled in,
and not the mmdb variant.

The mmdb.c is based on the specification, using the Go implementation
as a reference during development (ISC licensed), initially implemented
with the help of Claude Opus 4.6. After that substantial changes were
made to make it match UnrealIRCd's style and to make things less error
prone: C style changes, allocation and zero termination of strings in
the library, auto-NULL in variadic functions so the caller cannot
forget NULL there (similar to our unreal_log/do_unreal_log), using
enums as the return type instead of int (similar to curl), adding
doxygen docs, etc.

This also means the old mmdb library dependency has been dropped,
including from configure/autoconf.

At the moment we still use the geoip classic library by default,
including those DB files. The idea is we will switch over sometime
later after this current new MMDB stuff has received more testing.

This also makes us more flexible, since .mmdb files have become the
de-facto standard for pretty much all geoip vendors.
This commit is contained in:
Bram Matthys
2026-03-22 10:24:02 +01:00
parent 89bce01c31
commit 172ace9750
9 changed files with 1456 additions and 279 deletions
+170
View File
@@ -0,0 +1,170 @@
/*
* mmdb.h - Minimal MMDB (MaxMind DB) reader library
*
* Written from the MaxMind DB file format specification
* (https://maxmind.github.io/MaxMind-DB/).
*
* This C implementation was written by the UnrealIRCd team,
* using the Go MMDB reader oschwald/maxminddb-golang by
* Gregory J. Oschwald as a reference during development.
*
* Copyright (c) 2015 Gregory J. Oschwald (Go implementation)
* Copyright (c) 2026 UnrealIRCd team
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that
* the above copyright notice and this permission notice appear in
* all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MMDB_H
#define MMDB_H
#include <stdint.h>
#include <stddef.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#endif
/** Status/error codes returned by mmdb functions */
typedef enum {
MMDB_OK = 0, /**< Success */
MMDB_ERR_OPEN, /**< Could not open or mmap the file */
MMDB_ERR_INVALID_DB, /**< Not a valid MMDB file */
MMDB_ERR_CORRUPT, /**< Search tree or data section corruption */
MMDB_ERR_NODATA, /**< IP found but requested path doesn't exist */
MMDB_ERR_TYPE, /**< Type mismatch (asked for string, got uint, etc) */
MMDB_ERR_IPV6_IN_V4, /**< Tried to look up an IPv6 address in an IPv4-only db */
MMDB_ERR_BADARG, /**< Invalid argument (e.g. unparseable IP address) */
} MMDB_Status;
/** Database metadata */
typedef struct {
uint32_t node_count; /**< Number of nodes in the search tree */
uint16_t record_size; /**< Size of each record in bits */
uint16_t ip_version; /**< IP version the database covers (4 or 6) */
uint64_t build_epoch; /**< Unix timestamp when the database was built */
char database_type[128]; /**< Database type string (e.g. "GeoLite2-Country") */
} MMDB_Metadata;
/** Database handle */
typedef struct {
uint8_t *data; /**< mmap'd (or malloc'd) file contents */
size_t data_size; /**< Total file size */
size_t data_section_offset; /**< Offset where data section starts */
size_t data_section_size; /**< Size of data section */
MMDB_Metadata metadata; /**< Parsed database metadata */
uint32_t ipv4_start_node; /**< Cached start node for IPv4 lookups in IPv6 dbs */
int ipv4_start_bit_depth; /**< Bit depth at ipv4_start_node */
int is_mmap; /**< 1 if data was mmap'd, 0 if malloc'd */
} MMDB_DB;
/** Lookup result */
typedef struct {
MMDB_DB *db; /**< Database this result belongs to */
size_t offset; /**< Offset into data section, or 0 if not found */
int has_data; /**< 1 if IP was found and has data */
} MMDB_Result;
/** Open an MMDB database file.
* Uses mmap where available, falls back to malloc+read.
* @param db Database handle to initialize
* @param filename Path to the .mmdb file
* @returns MMDB_OK on success, or an error code
*/
MMDB_Status mmdb_open(MMDB_DB *db, const char *filename);
/** Close the database and release resources.
* Safe to call on an already-closed or zero-initialized handle.
* @param db Database handle to close
*/
void mmdb_close(MMDB_DB *db);
/** Look up an IP address given as a string (IPv4 or IPv6).
* On success, check result->has_data to see if the IP was
* actually found in the database.
* @param db Database handle
* @param ip_str IP address string (e.g. "1.2.3.4" or "2001:db8::1")
* @param result Lookup result (output)
* @returns MMDB_OK on success, or an error code
*/
MMDB_Status mmdb_lookup(MMDB_DB *db, const char *ip_str, MMDB_Result *result);
/** Look up an IP address given as a sockaddr.
* Supports sockaddr_in (IPv4) and sockaddr_in6 (IPv6).
* @param db Database handle
* @param sa Socket address to look up
* @param result Lookup result (output)
* @returns MMDB_OK on success, or an error code
*/
MMDB_Status mmdb_lookup_sockaddr(MMDB_DB *db, const struct sockaddr *sa,
MMDB_Result *result);
/** Retrieve a string value from a lookup result by path.
* Returns a malloc'd, null-terminated copy. Caller must free().
* On error, *out is set to NULL.
* @param result Lookup result from mmdb_lookup()
* @param out Receives a malloc'd null-terminated string (output)
* @param ... Path of map keys (NULL sentinel is added automatically)
* @returns MMDB_OK on success, or an error code
* @note Example: mmdb_get_str(&result, &val, "country", "iso_code");
*/
MMDB_Status mmdb_do_get_str(MMDB_Result *result, char **out, ...);
#define mmdb_get_str(result, out, ...) mmdb_do_get_str(result, out, __VA_ARGS__, NULL)
/** Retrieve a uint32 value from a lookup result by path.
* Also accepts uint16 values (promoted to uint32).
* On error, *out is set to 0.
* @param result Lookup result from mmdb_lookup()
* @param out Receives the uint32 value (output)
* @param ... Path of map keys (NULL sentinel is added automatically)
* @returns MMDB_OK on success, or an error code
* @note Example: mmdb_get_uint32(&result, &asn, "autonomous_system_number");
*/
MMDB_Status mmdb_do_get_uint32(MMDB_Result *result, uint32_t *out, ...);
#define mmdb_get_uint32(result, out, ...) mmdb_do_get_uint32(result, out, __VA_ARGS__, NULL)
/** Retrieve a boolean value from a lookup result by path.
* On error, *out is set to 0.
* @param result Lookup result from mmdb_lookup()
* @param out Receives the boolean value (0 or 1) (output)
* @param ... Path of map keys (NULL sentinel is added automatically)
* @returns MMDB_OK on success, or an error code
* @note Example: mmdb_get_bool(&result, &is_vpn, "is_anonymous_vpn");
*/
MMDB_Status mmdb_do_get_bool(MMDB_Result *result, int *out, ...);
#define mmdb_get_bool(result, out, ...) mmdb_do_get_bool(result, out, __VA_ARGS__, NULL)
/** Retrieve a double (float64) value from a lookup result by path.
* Also accepts float32 values (promoted to double).
* On error, *out is set to 0.
* @param result Lookup result from mmdb_lookup()
* @param out Receives the double value (output)
* @param ... Path of map keys (NULL sentinel is added automatically)
* @returns MMDB_OK on success, or an error code
* @note Example: mmdb_get_double(&result, &lat, "location", "latitude");
*/
MMDB_Status mmdb_do_get_double(MMDB_Result *result, double *out, ...);
#define mmdb_get_double(result, out, ...) mmdb_do_get_double(result, out, __VA_ARGS__, NULL)
/** Return a human-readable error string for a status code.
* @param err Status code to describe
* @returns Static string describing the error (never NULL)
*/
const char *mmdb_strerror(MMDB_Status err);
#endif /* MMDB_H */