From mdounin at mdounin.ru Wed Dec 3 16:31:05 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:05 +0300 Subject: [PATCH 1 of 6] GeoIP: added missing empty line Message-ID: # HG changeset patch # User Maxim Dounin # Date 1764678500 -10800 # Tue Dec 02 15:28:20 2025 +0300 # Node ID e83601fadc0a6b7859b6af64626a2b1fb9134ad8 # Parent ff8c3ef06fd552b19750c0af4a28051708a91c40 GeoIP: added missing empty line. diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -889,6 +889,7 @@ ngx_http_geoip_proxy(ngx_conf_t *cf, ngx return NGX_CONF_OK; } + static ngx_int_t ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net, ngx_cidr_t *cidr) { From mdounin at mdounin.ru Wed Dec 3 16:31:06 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:06 +0300 Subject: [PATCH 2 of 6] GeoIP: removed unused ngx_http_geoip_var_t structure In-Reply-To: References: Message-ID: <354839370549e47835b0.1764779466@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1764679196 -10800 # Tue Dec 02 15:39:56 2025 +0300 # Node ID 354839370549e47835b001649a0f32c76517677c # Parent e83601fadc0a6b7859b6af64626a2b1fb9134ad8 GeoIP: removed unused ngx_http_geoip_var_t structure. It was never used, since introduction of the module in 2985:31af2d1a742e, and hence removed. diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -32,12 +32,6 @@ typedef struct { } ngx_http_geoip_conf_t; -typedef struct { - ngx_str_t *name; - uintptr_t data; -} ngx_http_geoip_var_t; - - typedef const char *(*ngx_http_geoip_variable_handler_pt)(GeoIP *, u_long addr); diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -30,12 +30,6 @@ typedef struct { } ngx_stream_geoip_conf_t; -typedef struct { - ngx_str_t *name; - uintptr_t data; -} ngx_stream_geoip_var_t; - - typedef const char *(*ngx_stream_geoip_variable_handler_pt)(GeoIP *, u_long addr); From mdounin at mdounin.ru Wed Dec 3 16:31:07 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:07 +0300 Subject: [PATCH 3 of 6] GeoIP: moved ngx_http_geoip_addr() to a better place In-Reply-To: References: Message-ID: <3fb3fd326c10849df11f.1764779467@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1764576361 -10800 # Mon Dec 01 11:06:01 2025 +0300 # Node ID 3fb3fd326c10849df11fa637bf8de2eedec6fb46 # Parent 354839370549e47835b001649a0f32c76517677c GeoIP: moved ngx_http_geoip_addr() to a better place. Per existing style, helper functions are defined after functions that use them, and with appropriate prototypes. diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -72,6 +72,13 @@ static ngx_int_t ngx_http_geoip_city_int ngx_http_variable_value_t *v, uintptr_t data); static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r); +static u_long ngx_http_geoip_addr(ngx_http_request_t *r, + ngx_http_geoip_conf_t *gcf); +#if (NGX_HAVE_GEOIP_V6) +static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, + ngx_http_geoip_conf_t *gcf); +#endif + static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf); static void *ngx_http_geoip_create_conf(ngx_conf_t *cf); static char *ngx_http_geoip_init_conf(ngx_conf_t *cf, void *conf); @@ -230,107 +237,6 @@ static ngx_http_variable_t ngx_http_geo }; -static u_long -ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) -{ - ngx_addr_t addr; - ngx_table_elt_t *xfwd; - struct sockaddr_in *sin; - - addr.sockaddr = r->connection->sockaddr; - addr.socklen = r->connection->socklen; - /* addr.name = r->connection->addr_text; */ - - xfwd = r->headers_in.x_forwarded_for; - - if (xfwd != NULL && gcf->proxies != NULL) { - (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, - gcf->proxies, gcf->proxy_recursive); - } - -#if (NGX_HAVE_INET6) - - if (addr.sockaddr->sa_family == AF_INET6) { - u_char *p; - in_addr_t inaddr; - struct in6_addr *inaddr6; - - inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; - - if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - p = inaddr6->s6_addr; - - inaddr = (in_addr_t) p[12] << 24; - inaddr += p[13] << 16; - inaddr += p[14] << 8; - inaddr += p[15]; - - return inaddr; - } - } - -#endif - - if (addr.sockaddr->sa_family != AF_INET) { - return INADDR_NONE; - } - - sin = (struct sockaddr_in *) addr.sockaddr; - return ntohl(sin->sin_addr.s_addr); -} - - -#if (NGX_HAVE_GEOIP_V6) - -static geoipv6_t -ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) -{ - ngx_addr_t addr; - ngx_table_elt_t *xfwd; - in_addr_t addr4; - struct in6_addr addr6; - struct sockaddr_in *sin; - struct sockaddr_in6 *sin6; - - addr.sockaddr = r->connection->sockaddr; - addr.socklen = r->connection->socklen; - /* addr.name = r->connection->addr_text; */ - - xfwd = r->headers_in.x_forwarded_for; - - if (xfwd != NULL && gcf->proxies != NULL) { - (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, - gcf->proxies, gcf->proxy_recursive); - } - - switch (addr.sockaddr->sa_family) { - - case AF_INET: - /* Produce IPv4-mapped IPv6 address. */ - sin = (struct sockaddr_in *) addr.sockaddr; - addr4 = ntohl(sin->sin_addr.s_addr); - - ngx_memzero(&addr6, sizeof(struct in6_addr)); - addr6.s6_addr[10] = 0xff; - addr6.s6_addr[11] = 0xff; - addr6.s6_addr[12] = addr4 >> 24; - addr6.s6_addr[13] = addr4 >> 16; - addr6.s6_addr[14] = addr4 >> 8; - addr6.s6_addr[15] = addr4; - return addr6; - - case AF_INET6: - sin6 = (struct sockaddr_in6 *) addr.sockaddr; - return sin6->sin6_addr; - - default: - return in6addr_any; - } -} - -#endif - - static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) @@ -612,6 +518,107 @@ ngx_http_geoip_get_city_record(ngx_http_ } +static u_long +ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + ngx_table_elt_t *xfwd; + struct sockaddr_in *sin; + + addr.sockaddr = r->connection->sockaddr; + addr.socklen = r->connection->socklen; + /* addr.name = r->connection->addr_text; */ + + xfwd = r->headers_in.x_forwarded_for; + + if (xfwd != NULL && gcf->proxies != NULL) { + (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, + gcf->proxies, gcf->proxy_recursive); + } + +#if (NGX_HAVE_INET6) + + if (addr.sockaddr->sa_family == AF_INET6) { + u_char *p; + in_addr_t inaddr; + struct in6_addr *inaddr6; + + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + p = inaddr6->s6_addr; + + inaddr = (in_addr_t) p[12] << 24; + inaddr += p[13] << 16; + inaddr += p[14] << 8; + inaddr += p[15]; + + return inaddr; + } + } + +#endif + + if (addr.sockaddr->sa_family != AF_INET) { + return INADDR_NONE; + } + + sin = (struct sockaddr_in *) addr.sockaddr; + return ntohl(sin->sin_addr.s_addr); +} + + +#if (NGX_HAVE_GEOIP_V6) + +static geoipv6_t +ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + ngx_table_elt_t *xfwd; + in_addr_t addr4; + struct in6_addr addr6; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + addr.sockaddr = r->connection->sockaddr; + addr.socklen = r->connection->socklen; + /* addr.name = r->connection->addr_text; */ + + xfwd = r->headers_in.x_forwarded_for; + + if (xfwd != NULL && gcf->proxies != NULL) { + (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, + gcf->proxies, gcf->proxy_recursive); + } + + switch (addr.sockaddr->sa_family) { + + case AF_INET: + /* Produce IPv4-mapped IPv6 address. */ + sin = (struct sockaddr_in *) addr.sockaddr; + addr4 = ntohl(sin->sin_addr.s_addr); + + ngx_memzero(&addr6, sizeof(struct in6_addr)); + addr6.s6_addr[10] = 0xff; + addr6.s6_addr[11] = 0xff; + addr6.s6_addr[12] = addr4 >> 24; + addr6.s6_addr[13] = addr4 >> 16; + addr6.s6_addr[14] = addr4 >> 8; + addr6.s6_addr[15] = addr4; + return addr6; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addr.sockaddr; + return sin6->sin6_addr; + + default: + return in6addr_any; + } +} + +#endif + + static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf) { diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -72,6 +72,13 @@ static ngx_int_t ngx_stream_geoip_city_i ngx_stream_variable_value_t *v, uintptr_t data); static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s); +static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s, + ngx_stream_geoip_conf_t *gcf); +#if (NGX_HAVE_GEOIP_V6) +static geoipv6_t ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, + ngx_stream_geoip_conf_t *gcf); +#endif + static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf); static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf); static char *ngx_stream_geoip_country(ngx_conf_t *cf, ngx_command_t *cmd, @@ -208,91 +215,6 @@ static ngx_stream_variable_t ngx_stream }; -static u_long -ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) -{ - ngx_addr_t addr; - struct sockaddr_in *sin; - - addr.sockaddr = s->connection->sockaddr; - addr.socklen = s->connection->socklen; - /* addr.name = s->connection->addr_text; */ - -#if (NGX_HAVE_INET6) - - if (addr.sockaddr->sa_family == AF_INET6) { - u_char *p; - in_addr_t inaddr; - struct in6_addr *inaddr6; - - inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; - - if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - p = inaddr6->s6_addr; - - inaddr = (in_addr_t) p[12] << 24; - inaddr += p[13] << 16; - inaddr += p[14] << 8; - inaddr += p[15]; - - return inaddr; - } - } - -#endif - - if (addr.sockaddr->sa_family != AF_INET) { - return INADDR_NONE; - } - - sin = (struct sockaddr_in *) addr.sockaddr; - return ntohl(sin->sin_addr.s_addr); -} - - -#if (NGX_HAVE_GEOIP_V6) - -static geoipv6_t -ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) -{ - ngx_addr_t addr; - in_addr_t addr4; - struct in6_addr addr6; - struct sockaddr_in *sin; - struct sockaddr_in6 *sin6; - - addr.sockaddr = s->connection->sockaddr; - addr.socklen = s->connection->socklen; - /* addr.name = s->connection->addr_text; */ - - switch (addr.sockaddr->sa_family) { - - case AF_INET: - /* Produce IPv4-mapped IPv6 address. */ - sin = (struct sockaddr_in *) addr.sockaddr; - addr4 = ntohl(sin->sin_addr.s_addr); - - ngx_memzero(&addr6, sizeof(struct in6_addr)); - addr6.s6_addr[10] = 0xff; - addr6.s6_addr[11] = 0xff; - addr6.s6_addr[12] = addr4 >> 24; - addr6.s6_addr[13] = addr4 >> 16; - addr6.s6_addr[14] = addr4 >> 8; - addr6.s6_addr[15] = addr4; - return addr6; - - case AF_INET6: - sin6 = (struct sockaddr_in6 *) addr.sockaddr; - return sin6->sin6_addr; - - default: - return in6addr_any; - } -} - -#endif - - static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) @@ -574,6 +496,91 @@ ngx_stream_geoip_get_city_record(ngx_str } +static u_long +ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + struct sockaddr_in *sin; + + addr.sockaddr = s->connection->sockaddr; + addr.socklen = s->connection->socklen; + /* addr.name = s->connection->addr_text; */ + +#if (NGX_HAVE_INET6) + + if (addr.sockaddr->sa_family == AF_INET6) { + u_char *p; + in_addr_t inaddr; + struct in6_addr *inaddr6; + + inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + + if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { + p = inaddr6->s6_addr; + + inaddr = (in_addr_t) p[12] << 24; + inaddr += p[13] << 16; + inaddr += p[14] << 8; + inaddr += p[15]; + + return inaddr; + } + } + +#endif + + if (addr.sockaddr->sa_family != AF_INET) { + return INADDR_NONE; + } + + sin = (struct sockaddr_in *) addr.sockaddr; + return ntohl(sin->sin_addr.s_addr); +} + + +#if (NGX_HAVE_GEOIP_V6) + +static geoipv6_t +ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + in_addr_t addr4; + struct in6_addr addr6; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + addr.sockaddr = s->connection->sockaddr; + addr.socklen = s->connection->socklen; + /* addr.name = s->connection->addr_text; */ + + switch (addr.sockaddr->sa_family) { + + case AF_INET: + /* Produce IPv4-mapped IPv6 address. */ + sin = (struct sockaddr_in *) addr.sockaddr; + addr4 = ntohl(sin->sin_addr.s_addr); + + ngx_memzero(&addr6, sizeof(struct in6_addr)); + addr6.s6_addr[10] = 0xff; + addr6.s6_addr[11] = 0xff; + addr6.s6_addr[12] = addr4 >> 24; + addr6.s6_addr[13] = addr4 >> 16; + addr6.s6_addr[14] = addr4 >> 8; + addr6.s6_addr[15] = addr4; + return addr6; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addr.sockaddr; + return sin6->sin6_addr; + + default: + return in6addr_any; + } +} + +#endif + + static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf) { From mdounin at mdounin.ru Wed Dec 3 16:31:08 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:08 +0300 Subject: [PATCH 4 of 6] GeoIP: introduced ngx_http_geoip_sockaddr() helper In-Reply-To: References: Message-ID: <9be164cc80e638557db1.1764779468@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1764576372 -10800 # Mon Dec 01 11:06:12 2025 +0300 # Node ID 9be164cc80e638557db1f1c6614202b540359ae6 # Parent 3fb3fd326c10849df11fa637bf8de2eedec6fb46 GeoIP: introduced ngx_http_geoip_sockaddr() helper. Such a helper function will be needed for further use with libmaxminddb, where sockaddr is used for both IPv4 and IPv6 lookups. It also reduces amount of code duplication. Stream module counterpart, where direct use of s->connection->sockaddr is sufficient, is adjusted similarly to match the code, but without introducing a helper function. diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -78,6 +78,8 @@ static u_long ngx_http_geoip_addr(ngx_ht static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf); #endif +static struct sockaddr *ngx_http_geoip_sockaddr(ngx_http_request_t *r, + ngx_http_geoip_conf_t *gcf); static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf); static void *ngx_http_geoip_create_conf(ngx_conf_t *cf); @@ -521,29 +523,19 @@ ngx_http_geoip_get_city_record(ngx_http_ static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { - ngx_addr_t addr; - ngx_table_elt_t *xfwd; + struct sockaddr *sockaddr; struct sockaddr_in *sin; - addr.sockaddr = r->connection->sockaddr; - addr.socklen = r->connection->socklen; - /* addr.name = r->connection->addr_text; */ - - xfwd = r->headers_in.x_forwarded_for; - - if (xfwd != NULL && gcf->proxies != NULL) { - (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, - gcf->proxies, gcf->proxy_recursive); - } + sockaddr = ngx_http_geoip_sockaddr(r, gcf); #if (NGX_HAVE_INET6) - if (addr.sockaddr->sa_family == AF_INET6) { + if (sockaddr->sa_family == AF_INET6) { u_char *p; in_addr_t inaddr; struct in6_addr *inaddr6; - inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + inaddr6 = &((struct sockaddr_in6 *) sockaddr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; @@ -559,11 +551,11 @@ ngx_http_geoip_addr(ngx_http_request_t * #endif - if (addr.sockaddr->sa_family != AF_INET) { + if (sockaddr->sa_family != AF_INET) { return INADDR_NONE; } - sin = (struct sockaddr_in *) addr.sockaddr; + sin = (struct sockaddr_in *) sockaddr; return ntohl(sin->sin_addr.s_addr); } @@ -573,13 +565,48 @@ ngx_http_geoip_addr(ngx_http_request_t * static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { - ngx_addr_t addr; - ngx_table_elt_t *xfwd; in_addr_t addr4; struct in6_addr addr6; + struct sockaddr *sockaddr; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; + sockaddr = ngx_http_geoip_sockaddr(r, gcf); + + switch (sockaddr->sa_family) { + + case AF_INET: + /* Produce IPv4-mapped IPv6 address. */ + sin = (struct sockaddr_in *) sockaddr; + addr4 = ntohl(sin->sin_addr.s_addr); + + ngx_memzero(&addr6, sizeof(struct in6_addr)); + addr6.s6_addr[10] = 0xff; + addr6.s6_addr[11] = 0xff; + addr6.s6_addr[12] = addr4 >> 24; + addr6.s6_addr[13] = addr4 >> 16; + addr6.s6_addr[14] = addr4 >> 8; + addr6.s6_addr[15] = addr4; + return addr6; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sockaddr; + return sin6->sin6_addr; + + default: + return in6addr_any; + } +} + +#endif + + +static struct sockaddr * +ngx_http_geoip_sockaddr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) +{ + ngx_addr_t addr; + ngx_table_elt_t *xfwd; + addr.sockaddr = r->connection->sockaddr; addr.socklen = r->connection->socklen; /* addr.name = r->connection->addr_text; */ @@ -591,33 +618,9 @@ ngx_http_geoip_addr_v6(ngx_http_request_ gcf->proxies, gcf->proxy_recursive); } - switch (addr.sockaddr->sa_family) { - - case AF_INET: - /* Produce IPv4-mapped IPv6 address. */ - sin = (struct sockaddr_in *) addr.sockaddr; - addr4 = ntohl(sin->sin_addr.s_addr); - - ngx_memzero(&addr6, sizeof(struct in6_addr)); - addr6.s6_addr[10] = 0xff; - addr6.s6_addr[11] = 0xff; - addr6.s6_addr[12] = addr4 >> 24; - addr6.s6_addr[13] = addr4 >> 16; - addr6.s6_addr[14] = addr4 >> 8; - addr6.s6_addr[15] = addr4; - return addr6; - - case AF_INET6: - sin6 = (struct sockaddr_in6 *) addr.sockaddr; - return sin6->sin6_addr; - - default: - return in6addr_any; - } + return addr.sockaddr; } -#endif - static ngx_int_t ngx_http_geoip_add_variables(ngx_conf_t *cf) diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -499,21 +499,19 @@ ngx_stream_geoip_get_city_record(ngx_str static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) { - ngx_addr_t addr; + struct sockaddr *sockaddr; struct sockaddr_in *sin; - addr.sockaddr = s->connection->sockaddr; - addr.socklen = s->connection->socklen; - /* addr.name = s->connection->addr_text; */ + sockaddr = s->connection->sockaddr; #if (NGX_HAVE_INET6) - if (addr.sockaddr->sa_family == AF_INET6) { + if (sockaddr->sa_family == AF_INET6) { u_char *p; in_addr_t inaddr; struct in6_addr *inaddr6; - inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr; + inaddr6 = &((struct sockaddr_in6 *) sockaddr)->sin6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; @@ -529,11 +527,11 @@ ngx_stream_geoip_addr(ngx_stream_session #endif - if (addr.sockaddr->sa_family != AF_INET) { + if (sockaddr->sa_family != AF_INET) { return INADDR_NONE; } - sin = (struct sockaddr_in *) addr.sockaddr; + sin = (struct sockaddr_in *) sockaddr; return ntohl(sin->sin_addr.s_addr); } @@ -543,21 +541,19 @@ ngx_stream_geoip_addr(ngx_stream_session static geoipv6_t ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) { - ngx_addr_t addr; in_addr_t addr4; struct in6_addr addr6; + struct sockaddr *sockaddr; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; - addr.sockaddr = s->connection->sockaddr; - addr.socklen = s->connection->socklen; - /* addr.name = s->connection->addr_text; */ + sockaddr = s->connection->sockaddr; - switch (addr.sockaddr->sa_family) { + switch (sockaddr->sa_family) { case AF_INET: /* Produce IPv4-mapped IPv6 address. */ - sin = (struct sockaddr_in *) addr.sockaddr; + sin = (struct sockaddr_in *) sockaddr; addr4 = ntohl(sin->sin_addr.s_addr); ngx_memzero(&addr6, sizeof(struct in6_addr)); @@ -570,7 +566,7 @@ ngx_stream_geoip_addr_v6(ngx_stream_sess return addr6; case AF_INET6: - sin6 = (struct sockaddr_in6 *) addr.sockaddr; + sin6 = (struct sockaddr_in6 *) sockaddr; return sin6->sin6_addr; default: From mdounin at mdounin.ru Wed Dec 3 16:31:09 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:09 +0300 Subject: [PATCH 5 of 6] GeoIP: simplified handling of IPv6 databases In-Reply-To: References: Message-ID: <9d4db0c2b512cb8d5483.1764779469@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1764578741 -10800 # Mon Dec 01 11:45:41 2025 +0300 # Node ID 9d4db0c2b512cb8d548345db2852c2d6a000d49c # Parent 9be164cc80e638557db1f1c6614202b540359ae6 GeoIP: simplified handling of IPv6 databases. diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -32,32 +32,6 @@ typedef struct { } ngx_http_geoip_conf_t; -typedef const char *(*ngx_http_geoip_variable_handler_pt)(GeoIP *, - u_long addr); - - -ngx_http_geoip_variable_handler_pt ngx_http_geoip_country_functions[] = { - GeoIP_country_code_by_ipnum, - GeoIP_country_code3_by_ipnum, - GeoIP_country_name_by_ipnum, -}; - - -#if (NGX_HAVE_GEOIP_V6) - -typedef const char *(*ngx_http_geoip_variable_handler_v6_pt)(GeoIP *, - geoipv6_t addr); - - -ngx_http_geoip_variable_handler_v6_pt ngx_http_geoip_country_v6_functions[] = { - GeoIP_country_code_by_ipnum_v6, - GeoIP_country_code3_by_ipnum_v6, - GeoIP_country_name_by_ipnum_v6, -}; - -#endif - - static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r, @@ -243,13 +217,6 @@ static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - ngx_http_geoip_variable_handler_pt handler = - ngx_http_geoip_country_functions[data]; -#if (NGX_HAVE_GEOIP_V6) - ngx_http_geoip_variable_handler_v6_pt handler_v6 = - ngx_http_geoip_country_v6_functions[data]; -#endif - const char *val; ngx_http_geoip_conf_t *gcf; @@ -260,12 +227,35 @@ ngx_http_geoip_country_variable(ngx_http } #if (NGX_HAVE_GEOIP_V6) - val = gcf->country_v6 - ? handler_v6(gcf->country, ngx_http_geoip_addr_v6(r, gcf)) - : handler(gcf->country, ngx_http_geoip_addr(r, gcf)); -#else - val = handler(gcf->country, ngx_http_geoip_addr(r, gcf)); + if (gcf->country_v6) { + if (data == NGX_GEOIP_COUNTRY_CODE) { + val = GeoIP_country_code_by_ipnum_v6(gcf->country, + ngx_http_geoip_addr_v6(r, gcf)); + + } else if (data == NGX_GEOIP_COUNTRY_CODE3) { + val = GeoIP_country_code3_by_ipnum_v6(gcf->country, + ngx_http_geoip_addr_v6(r, gcf)); + + } else { /* NGX_GEOIP_COUNTRY_NAME */ + val = GeoIP_country_name_by_ipnum_v6(gcf->country, + ngx_http_geoip_addr_v6(r, gcf)); + } + } else #endif + { + if (data == NGX_GEOIP_COUNTRY_CODE) { + val = GeoIP_country_code_by_ipnum(gcf->country, + ngx_http_geoip_addr(r, gcf)); + + } else if (data == NGX_GEOIP_COUNTRY_CODE3) { + val = GeoIP_country_code3_by_ipnum(gcf->country, + ngx_http_geoip_addr(r, gcf)); + + } else { /* NGX_GEOIP_COUNTRY_NAME */ + val = GeoIP_country_name_by_ipnum(gcf->country, + ngx_http_geoip_addr(r, gcf)); + } + } if (val == NULL) { goto not_found; @@ -302,14 +292,14 @@ ngx_http_geoip_org_variable(ngx_http_req } #if (NGX_HAVE_GEOIP_V6) - val = gcf->org_v6 - ? GeoIP_name_by_ipnum_v6(gcf->org, - ngx_http_geoip_addr_v6(r, gcf)) - : GeoIP_name_by_ipnum(gcf->org, - ngx_http_geoip_addr(r, gcf)); -#else - val = GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf)); + if (gcf->org_v6) { + val = GeoIP_name_by_ipnum_v6(gcf->org, ngx_http_geoip_addr_v6(r, gcf)); + + } else #endif + { + val = GeoIP_name_by_ipnum(gcf->org, ngx_http_geoip_addr(r, gcf)); + } if (val == NULL) { goto not_found; @@ -506,14 +496,12 @@ ngx_http_geoip_get_city_record(ngx_http_ if (gcf->city) { #if (NGX_HAVE_GEOIP_V6) - return gcf->city_v6 - ? GeoIP_record_by_ipnum_v6(gcf->city, - ngx_http_geoip_addr_v6(r, gcf)) - : GeoIP_record_by_ipnum(gcf->city, - ngx_http_geoip_addr(r, gcf)); -#else + if (gcf->city_v6) { + return GeoIP_record_by_ipnum_v6(gcf->city, + ngx_http_geoip_addr_v6(r, gcf)); + } +#endif return GeoIP_record_by_ipnum(gcf->city, ngx_http_geoip_addr(r, gcf)); -#endif } return NULL; diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -30,34 +30,6 @@ typedef struct { } ngx_stream_geoip_conf_t; -typedef const char *(*ngx_stream_geoip_variable_handler_pt)(GeoIP *, - u_long addr); - - -ngx_stream_geoip_variable_handler_pt ngx_stream_geoip_country_functions[] = { - GeoIP_country_code_by_ipnum, - GeoIP_country_code3_by_ipnum, - GeoIP_country_name_by_ipnum, -}; - - -#if (NGX_HAVE_GEOIP_V6) - -typedef const char *(*ngx_stream_geoip_variable_handler_v6_pt)(GeoIP *, - geoipv6_t addr); - - -ngx_stream_geoip_variable_handler_v6_pt - ngx_stream_geoip_country_v6_functions[] = -{ - GeoIP_country_code_by_ipnum_v6, - GeoIP_country_code3_by_ipnum_v6, - GeoIP_country_name_by_ipnum_v6, -}; - -#endif - - static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_geoip_org_variable(ngx_stream_session_t *s, @@ -219,13 +191,6 @@ static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - ngx_stream_geoip_variable_handler_pt handler = - ngx_stream_geoip_country_functions[data]; -#if (NGX_HAVE_GEOIP_V6) - ngx_stream_geoip_variable_handler_v6_pt handler_v6 = - ngx_stream_geoip_country_v6_functions[data]; -#endif - const char *val; ngx_stream_geoip_conf_t *gcf; @@ -236,12 +201,35 @@ ngx_stream_geoip_country_variable(ngx_st } #if (NGX_HAVE_GEOIP_V6) - val = gcf->country_v6 - ? handler_v6(gcf->country, ngx_stream_geoip_addr_v6(s, gcf)) - : handler(gcf->country, ngx_stream_geoip_addr(s, gcf)); -#else - val = handler(gcf->country, ngx_stream_geoip_addr(s, gcf)); + if (gcf->country_v6) { + if (data == NGX_GEOIP_COUNTRY_CODE) { + val = GeoIP_country_code_by_ipnum_v6(gcf->country, + ngx_stream_geoip_addr_v6(s, gcf)); + + } else if (data == NGX_GEOIP_COUNTRY_CODE3) { + val = GeoIP_country_code3_by_ipnum_v6(gcf->country, + ngx_stream_geoip_addr_v6(s, gcf)); + + } else { /* NGX_GEOIP_COUNTRY_NAME */ + val = GeoIP_country_name_by_ipnum_v6(gcf->country, + ngx_stream_geoip_addr_v6(s, gcf)); + } + } else #endif + { + if (data == NGX_GEOIP_COUNTRY_CODE) { + val = GeoIP_country_code_by_ipnum(gcf->country, + ngx_stream_geoip_addr(s, gcf)); + + } else if (data == NGX_GEOIP_COUNTRY_CODE3) { + val = GeoIP_country_code3_by_ipnum(gcf->country, + ngx_stream_geoip_addr(s, gcf)); + + } else { /* NGX_GEOIP_COUNTRY_NAME */ + val = GeoIP_country_name_by_ipnum(gcf->country, + ngx_stream_geoip_addr(s, gcf)); + } + } if (val == NULL) { goto not_found; @@ -278,14 +266,15 @@ ngx_stream_geoip_org_variable(ngx_stream } #if (NGX_HAVE_GEOIP_V6) - val = gcf->org_v6 - ? GeoIP_name_by_ipnum_v6(gcf->org, - ngx_stream_geoip_addr_v6(s, gcf)) - : GeoIP_name_by_ipnum(gcf->org, - ngx_stream_geoip_addr(s, gcf)); -#else - val = GeoIP_name_by_ipnum(gcf->org, ngx_stream_geoip_addr(s, gcf)); + if (gcf->org_v6) { + val = GeoIP_name_by_ipnum_v6(gcf->org, + ngx_stream_geoip_addr_v6(s, gcf)); + + } else #endif + { + val = GeoIP_name_by_ipnum(gcf->org, ngx_stream_geoip_addr(s, gcf)); + } if (val == NULL) { goto not_found; @@ -482,14 +471,12 @@ ngx_stream_geoip_get_city_record(ngx_str if (gcf->city) { #if (NGX_HAVE_GEOIP_V6) - return gcf->city_v6 - ? GeoIP_record_by_ipnum_v6(gcf->city, - ngx_stream_geoip_addr_v6(s, gcf)) - : GeoIP_record_by_ipnum(gcf->city, - ngx_stream_geoip_addr(s, gcf)); -#else + if (gcf->city_v6) { + return GeoIP_record_by_ipnum_v6(gcf->city, + ngx_stream_geoip_addr_v6(s, gcf)); + } +#endif return GeoIP_record_by_ipnum(gcf->city, ngx_stream_geoip_addr(s, gcf)); -#endif } return NULL; From mdounin at mdounin.ru Wed Dec 3 16:31:10 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:31:10 +0300 Subject: [PATCH 6 of 6] GeoIP: support for GeoIP2 databases in the MMDB format In-Reply-To: References: Message-ID: <234a79a752d46a21ecac.1764779470@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1764579291 -10800 # Mon Dec 01 11:54:51 2025 +0300 # Node ID 234a79a752d46a21ecaccddfda660ca2cbfe15f4 # Parent 9d4db0c2b512cb8d548345db2852c2d6a000d49c GeoIP: support for GeoIP2 databases in the MMDB format. With this change, the ngx_http_geoip_module can be compiled with the GeoIP library (legacy), or the libmaxminddb library, or both. Usage of specific libraries can be disabled during compilation with new configure options, "--without-geoip" and "--without-libmaxminddb". Existing directives (geoip_country, geoip_org, geoip_city) support both file formats, and existing variables are mapped to the appropriate data paths in the MaxMind GeoIP2 databases, for example: geoip_country /path/to/GeoLite2-Country.mmdb; Notable exceptions: the $geoip_country_code3, $geoip_city_country_code3, and $geoip_area_code variables are no longer available if GeoIP2 databases are used, because the underlying fields are not available in the new databases. Additionally, the geoip_set directive is introduced. It can be used to access arbitrary data paths within an MMDB database, for example: geoip_set $country /path/to/GeoLite2-Country.mmdb country.names.en; geoip_set $asn /path/to/GeoLite2-ASN.mmdb autonomous_system_number; The last parameter is the data path to look up in the database, similarly to the one accepted by the mmdblookup utility (but with dots between path components). diff --git a/auto/lib/geoip/conf b/auto/lib/geoip/conf --- a/auto/lib/geoip/conf +++ b/auto/lib/geoip/conf @@ -1,10 +1,15 @@ # Copyright (C) Igor Sysoev +# Copyright (C) Maxim Dounin # Copyright (C) Nginx, Inc. +ngx_geoip_found=no + +if [ $GEOIP_LEGACY != DISABLED ]; then + ngx_feature="GeoIP library" - ngx_feature_name= + ngx_feature_name="NGX_HAVE_GEOIP_LEGACY" ngx_feature_run=no ngx_feature_incs="#include " ngx_feature_path= @@ -12,84 +17,160 @@ ngx_feature_test="GeoIP_open(NULL, 0)" . auto/feature + if [ $ngx_found = no ]; then -if [ $ngx_found = no ]; then + # FreeBSD port + + ngx_feature="GeoIP library in /usr/local/" + ngx_feature_path="/usr/local/include" - # FreeBSD port + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lGeoIP" + else + ngx_feature_libs="-L/usr/local/lib -lGeoIP" + fi - ngx_feature="GeoIP library in /usr/local/" - ngx_feature_path="/usr/local/include" + . auto/feature + fi + + if [ $ngx_found = no ]; then + + # NetBSD port - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lGeoIP" - else - ngx_feature_libs="-L/usr/local/lib -lGeoIP" + ngx_feature="GeoIP library in /usr/pkg/" + ngx_feature_path="/usr/pkg/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lGeoIP" + else + ngx_feature_libs="-L/usr/pkg/lib -lGeoIP" + fi + + . auto/feature fi - . auto/feature -fi - + if [ $ngx_found = no ]; then -if [ $ngx_found = no ]; then + # MacPorts - # NetBSD port + ngx_feature="GeoIP library in /opt/local/" + ngx_feature_path="/opt/local/include" - ngx_feature="GeoIP library in /usr/pkg/" - ngx_feature_path="/usr/pkg/include" + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lGeoIP" + else + ngx_feature_libs="-L/opt/local/lib -lGeoIP" + fi - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lGeoIP" - else - ngx_feature_libs="-L/usr/pkg/lib -lGeoIP" + . auto/feature fi - . auto/feature + if [ $ngx_found = yes ]; then + + CORE_INCS="$CORE_INCS $ngx_feature_path" + + if [ $USE_GEOIP = YES ]; then + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + fi + + NGX_LIB_GEOIP=$ngx_feature_libs + + ngx_feature="GeoIP IPv6 support" + ngx_feature_name="NGX_HAVE_GEOIP_V6" + ngx_feature_run=no + ngx_feature_incs="#include + #include " + #ngx_feature_path= + #ngx_feature_libs= + ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);" + . auto/feature + + ngx_geoip_found=yes + fi fi -if [ $ngx_found = no ]; then +if [ $GEOIP_MMDB != DISABLED ]; then - # MacPorts + ngx_feature="libmaxminddb" + ngx_feature_name="NGX_HAVE_GEOIP_MMDB" + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lmaxminddb" + ngx_feature_test="MMDB_open(NULL, 0, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then - ngx_feature="GeoIP library in /opt/local/" - ngx_feature_path="/opt/local/include" + # FreeBSD port + + ngx_feature="libmaxminddb in /usr/local/" + ngx_feature_path="/usr/local/include" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lGeoIP" - else - ngx_feature_libs="-L/opt/local/lib -lGeoIP" + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lmaxminddb" + else + ngx_feature_libs="-L/usr/local/lib -lmaxminddb" + fi + + . auto/feature fi - . auto/feature + if [ $ngx_found = no ]; then + + # NetBSD port + + ngx_feature="libmaxminddb in /usr/pkg/" + ngx_feature_path="/usr/pkg/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lmaxminddb" + else + ngx_feature_libs="-L/usr/pkg/lib -lmaxminddb" + fi + + . auto/feature + fi + + if [ $ngx_found = no ]; then + + # MacPorts + + ngx_feature="libmaxminddb in /opt/local/" + ngx_feature_path="/opt/local/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lmaxminddb" + else + ngx_feature_libs="-L/opt/local/lib -lmaxminddb" + fi + + . auto/feature + fi + + if [ $ngx_found = yes ]; then + + ngx_geoip_found=yes + + CORE_INCS="$CORE_INCS $ngx_feature_path" + + if [ $USE_GEOIP = YES ]; then + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" + fi + + NGX_LIB_GEOIP="$NGX_LIB_GEOIP $ngx_feature_libs" + fi fi -if [ $ngx_found = yes ]; then - - CORE_INCS="$CORE_INCS $ngx_feature_path" - - if [ $USE_GEOIP = YES ]; then - CORE_LIBS="$CORE_LIBS $ngx_feature_libs" - fi - - NGX_LIB_GEOIP=$ngx_feature_libs - - ngx_feature="GeoIP IPv6 support" - ngx_feature_name="NGX_HAVE_GEOIP_V6" - ngx_feature_run=no - ngx_feature_incs="#include - #include " - #ngx_feature_path= - #ngx_feature_libs= - ngx_feature_test="printf(\"%d\", GEOIP_CITY_EDITION_REV0_V6);" - . auto/feature - -else +if [ $ngx_geoip_found != yes ]; then cat << END -$0: error: the GeoIP module requires the GeoIP library. -You can either do not enable the module or install the library. +$0: error: the GeoIP modules require the GeoIP library or +the libmaxminddb library (or both). You can either do not enable +the modules or install at least one of the libraries. END diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -165,7 +165,10 @@ NGX_PERL=perl USE_LIBXSLT=NO USE_LIBGD=NO + USE_GEOIP=NO +GEOIP_LEGACY=YES +GEOIP_MMDB=YES NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO @@ -405,6 +408,9 @@ use the \"--with-mail_ssl_module\" optio --with-libatomic) NGX_LIBATOMIC=YES ;; --with-libatomic=*) NGX_LIBATOMIC="$value" ;; + --without-geoip) GEOIP_LEGACY=DISABLED ;; + --without-libmaxminddb) GEOIP_MMDB=DISABLED ;; + --test-build-devpoll) NGX_TEST_BUILD_DEVPOLL=YES ;; --test-build-eventport) NGX_TEST_BUILD_EVENTPORT=YES ;; --test-build-epoll) NGX_TEST_BUILD_EPOLL=YES ;; @@ -601,6 +607,9 @@ cat << END --with-openssl=DIR set path to OpenSSL library sources --with-openssl-opt=OPTIONS set additional build options for OpenSSL + --without-geoip do not use GeoIP library + --without-libmaxminddb do not use libmaxminddb library + --with-debug enable debug logging END diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -1,6 +1,7 @@ /* * Copyright (C) Igor Sysoev + * Copyright (C) Maxim Dounin * Copyright (C) Nginx, Inc. */ @@ -9,27 +10,59 @@ #include #include + +#if (NGX_HAVE_GEOIP_LEGACY) + #include #include +#endif -#define NGX_GEOIP_COUNTRY_CODE 0 -#define NGX_GEOIP_COUNTRY_CODE3 1 -#define NGX_GEOIP_COUNTRY_NAME 2 +#if (NGX_HAVE_GEOIP_MMDB) + +#include + +#endif + + +#define NGX_GEOIP_COUNTRY_CODE 0 +#define NGX_GEOIP_COUNTRY_CODE3 1 +#define NGX_GEOIP_COUNTRY_NAME 2 +#define NGX_GEOIP_CONTINENT_CODE 3 +#define NGX_GEOIP_REGION 4 +#define NGX_GEOIP_REGION_NAME 5 +#define NGX_GEOIP_CITY 6 +#define NGX_GEOIP_POSTAL_CODE 7 +#define NGX_GEOIP_LATITUDE 8 +#define NGX_GEOIP_LONGITUDE 9 +#define NGX_GEOIP_DMA_CODE 10 +#define NGX_GEOIP_AREA_CODE 11 typedef struct { - GeoIP *country; - GeoIP *org; - GeoIP *city; + void *country; + void *org; + void *city; ngx_array_t *proxies; /* array of ngx_cidr_t */ + ngx_array_t *mmdb; /* array of MMDB_s */ ngx_flag_t proxy_recursive; -#if (NGX_HAVE_GEOIP_V6) unsigned country_v6:1; + unsigned country_mmdb:1; unsigned org_v6:1; + unsigned org_mmdb:1; unsigned city_v6:1; + unsigned city_mmdb:1; +} ngx_http_geoip_conf_t; + + +#if (NGX_HAVE_GEOIP_MMDB) + +typedef struct { + MMDB_s *mmdb; + const char **path; +} ngx_http_geoip_variable_t; + #endif -} ngx_http_geoip_conf_t; static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, @@ -44,14 +77,25 @@ static ngx_int_t ngx_http_geoip_city_flo ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +#if (NGX_HAVE_GEOIP_LEGACY) static GeoIPRecord *ngx_http_geoip_get_city_record(ngx_http_request_t *r); +#endif +#if (NGX_HAVE_GEOIP_MMDB) +static ngx_int_t ngx_http_geoip_mmdb_city_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_geoip_mmdb_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf); #if (NGX_HAVE_GEOIP_V6) static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf); #endif +#endif static struct sockaddr *ngx_http_geoip_sockaddr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf); @@ -64,6 +108,15 @@ static char *ngx_http_geoip_org(ngx_conf void *conf); static char *ngx_http_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +#if (NGX_HAVE_GEOIP_MMDB) +static MMDB_s *ngx_http_geoip_mmdb_open(ngx_conf_t *cf, + ngx_http_geoip_conf_t *gcf, ngx_str_t *file); +#endif +#if (NGX_HAVE_GEOIP_LEGACY) +static ngx_int_t ngx_http_geoip_mmdb_file(ngx_str_t *file); +#endif static char *ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_geoip_cidr_value(ngx_conf_t *cf, ngx_str_t *net, @@ -94,6 +147,13 @@ static ngx_command_t ngx_http_geoip_com 0, NULL }, + { ngx_string("geoip_set"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3, + ngx_http_geoip_set, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + NULL }, + { ngx_string("geoip_proxy"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_http_geoip_proxy, @@ -163,61 +223,103 @@ static ngx_http_variable_t ngx_http_geo { ngx_string("geoip_city_continent_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, continent_code), 0, 0 }, + NGX_GEOIP_CONTINENT_CODE, 0, 0 }, { ngx_string("geoip_city_country_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_code), 0, 0 }, + NGX_GEOIP_COUNTRY_CODE, 0, 0 }, { ngx_string("geoip_city_country_code3"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_code3), 0, 0 }, + NGX_GEOIP_COUNTRY_CODE3, 0, 0 }, { ngx_string("geoip_city_country_name"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, country_name), 0, 0 }, + NGX_GEOIP_COUNTRY_NAME, 0, 0 }, { ngx_string("geoip_region"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, region), 0, 0 }, + NGX_GEOIP_REGION, 0, 0 }, { ngx_string("geoip_region_name"), NULL, ngx_http_geoip_region_name_variable, - 0, 0, 0 }, + NGX_GEOIP_REGION_NAME, 0, 0 }, { ngx_string("geoip_city"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, city), 0, 0 }, + NGX_GEOIP_CITY, 0, 0 }, { ngx_string("geoip_postal_code"), NULL, ngx_http_geoip_city_variable, - offsetof(GeoIPRecord, postal_code), 0, 0 }, + NGX_GEOIP_POSTAL_CODE, 0, 0 }, { ngx_string("geoip_latitude"), NULL, ngx_http_geoip_city_float_variable, - offsetof(GeoIPRecord, latitude), 0, 0 }, + NGX_GEOIP_LATITUDE, 0, 0 }, { ngx_string("geoip_longitude"), NULL, ngx_http_geoip_city_float_variable, - offsetof(GeoIPRecord, longitude), 0, 0 }, + NGX_GEOIP_LONGITUDE, 0, 0 }, { ngx_string("geoip_dma_code"), NULL, ngx_http_geoip_city_int_variable, - offsetof(GeoIPRecord, dma_code), 0, 0 }, + NGX_GEOIP_DMA_CODE, 0, 0 }, { ngx_string("geoip_area_code"), NULL, ngx_http_geoip_city_int_variable, - offsetof(GeoIPRecord, area_code), 0, 0 }, + NGX_GEOIP_AREA_CODE, 0, 0 }, ngx_http_null_variable }; +#if (NGX_HAVE_GEOIP_LEGACY) + +static uintptr_t ngx_http_geoip_city_offsets[] = { + offsetof(GeoIPRecord, country_code), + offsetof(GeoIPRecord, country_code3), + offsetof(GeoIPRecord, country_name), + offsetof(GeoIPRecord, continent_code), + offsetof(GeoIPRecord, region), + 0, /* region name */ + offsetof(GeoIPRecord, city), + offsetof(GeoIPRecord, postal_code), + offsetof(GeoIPRecord, latitude), + offsetof(GeoIPRecord, longitude), + offsetof(GeoIPRecord, dma_code), + offsetof(GeoIPRecord, area_code) +}; + +#endif + + +#if (NGX_HAVE_GEOIP_MMDB) + +static const char *ngx_http_geoip_mmdb_paths[][5] = { + { "country", "iso_code", NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL, NULL }, /* country code3 */ + { "country", "names", "en", NULL, NULL }, + { "continent", "code", NULL, NULL, NULL }, + { "subdivisions", "0", "iso_code", NULL, NULL }, + { "subdivisions", "0", "names", "en", NULL }, + { "city", "names", "en", NULL, NULL }, + { "postal", "code", NULL, NULL, NULL }, + { "location", "latitude", NULL, NULL, NULL }, + { "location", "longitude", NULL, NULL, NULL }, + { "location", "metro_code", NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL, NULL } /* area code */ +}; + +#endif + + static ngx_int_t ngx_http_geoip_country_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) const char *val; +#endif ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); @@ -226,6 +328,21 @@ ngx_http_geoip_country_variable(ngx_http goto not_found; } +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->country_mmdb) { + ngx_http_geoip_variable_t gv; + + gv.mmdb = gcf->country; + gv.path = ngx_http_geoip_mmdb_paths[data]; + + return ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv); + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + #if (NGX_HAVE_GEOIP_V6) if (gcf->country_v6) { if (data == NGX_GEOIP_COUNTRY_CODE) { @@ -269,6 +386,8 @@ ngx_http_geoip_country_variable(ngx_http return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -281,8 +400,10 @@ static ngx_int_t ngx_http_geoip_org_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) size_t len; char *val; +#endif ngx_http_geoip_conf_t *gcf; gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); @@ -291,6 +412,32 @@ ngx_http_geoip_org_variable(ngx_http_req goto not_found; } +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->org_mmdb) { + ngx_int_t rc; + ngx_http_geoip_variable_t gv; + + static const char *org[] = { "organization", NULL }; + static const char *asorg[] = { "autonomous_system_organization", NULL }; + + gv.mmdb = gcf->org; + gv.path = org; + + rc = ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv); + + if (rc == NGX_OK && v->not_found) { + gv.path = asorg; + rc = ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv); + } + + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + #if (NGX_HAVE_GEOIP_V6) if (gcf->org_v6) { val = GeoIP_name_by_ipnum_v6(gcf->org, ngx_http_geoip_addr_v6(r, gcf)); @@ -323,6 +470,8 @@ ngx_http_geoip_org_variable(ngx_http_req return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -335,16 +484,32 @@ static ngx_int_t ngx_http_geoip_city_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) char *val; size_t len; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_http_geoip_mmdb_city_variable(r, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { goto not_found; } - val = *(char **) ((char *) gr + data); + val = *(char **) ((char *) gr + ngx_http_geoip_city_offsets[data]); if (val == NULL) { goto no_value; } @@ -373,6 +538,8 @@ no_value: not_found: +#endif + v->not_found = 1; return NGX_OK; @@ -383,9 +550,25 @@ static ngx_int_t ngx_http_geoip_region_name_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) size_t len; const char *val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_http_geoip_mmdb_city_variable(r, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { @@ -417,6 +600,8 @@ ngx_http_geoip_region_name_variable(ngx_ not_found: +#endif + v->not_found = 1; return NGX_OK; @@ -427,13 +612,28 @@ static ngx_int_t ngx_http_geoip_city_float_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) float val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_http_geoip_mmdb_city_variable(r, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5); @@ -442,7 +642,7 @@ ngx_http_geoip_city_float_variable(ngx_h return NGX_ERROR; } - val = *(float *) ((char *) gr + data); + val = *(float *) ((char *) gr + ngx_http_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; v->valid = 1; @@ -452,6 +652,14 @@ ngx_http_geoip_city_float_variable(ngx_h GeoIPRecord_delete(gr); return NGX_OK; + +not_found: + +#endif + + v->not_found = 1; + + return NGX_OK; } @@ -459,13 +667,28 @@ static ngx_int_t ngx_http_geoip_city_int_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) int val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_http_geoip_mmdb_city_variable(r, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_http_geoip_get_city_record(r); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN); @@ -474,7 +697,7 @@ ngx_http_geoip_city_int_variable(ngx_htt return NGX_ERROR; } - val = *(int *) ((char *) gr + data); + val = *(int *) ((char *) gr + ngx_http_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%d", val) - v->data; v->valid = 1; @@ -484,9 +707,19 @@ ngx_http_geoip_city_int_variable(ngx_htt GeoIPRecord_delete(gr); return NGX_OK; + +not_found: + +#endif + + v->not_found = 1; + + return NGX_OK; } +#if (NGX_HAVE_GEOIP_LEGACY) + static GeoIPRecord * ngx_http_geoip_get_city_record(ngx_http_request_t *r) { @@ -507,6 +740,172 @@ ngx_http_geoip_get_city_record(ngx_http_ return NULL; } +#endif + + +#if (NGX_HAVE_GEOIP_MMDB) + +static ngx_int_t +ngx_http_geoip_mmdb_city_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_geoip_conf_t *gcf; + ngx_http_geoip_variable_t gv; + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + if (!gcf->city_mmdb) { + return NGX_DECLINED; + } + + gv.mmdb = gcf->city; + gv.path = ngx_http_geoip_mmdb_paths[data]; + + return ngx_http_geoip_mmdb_variable(r, v, (uintptr_t) &gv); +} + + +static ngx_int_t +ngx_http_geoip_mmdb_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_geoip_variable_t *gv = (ngx_http_geoip_variable_t *) data; + + int status; + struct sockaddr *sockaddr; + MMDB_entry_data_s entry; + MMDB_lookup_result_s result; + ngx_http_geoip_conf_t *gcf; + + if (gv->path[0] == NULL) { + goto not_found; + } + + gcf = ngx_http_get_module_main_conf(r, ngx_http_geoip_module); + + sockaddr = ngx_http_geoip_sockaddr(r, gcf); + + if (sockaddr->sa_family != AF_INET +#if (NGX_HAVE_INET6) + && sockaddr->sa_family != AF_INET6 +#endif + ) + { + goto not_found; + } + + result = MMDB_lookup_sockaddr(gv->mmdb, sockaddr, &status); + + if (status != MMDB_SUCCESS) { + if (status == MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR) { + goto not_found; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "MMDB_lookup_sockaddr() failed: %s", + MMDB_strerror(status)); + return NGX_ERROR; + } + + if (!result.found_entry) { + goto not_found; + } + + status = MMDB_aget_value(&result.entry, &entry, gv->path); + + if (status != MMDB_SUCCESS) { + if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { + goto not_found; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "MMDB_aget_value() failed: %s", + MMDB_strerror(status)); + return NGX_ERROR; + } + + if (!entry.has_data) { + goto not_found; + } + + if (entry.type == MMDB_DATA_TYPE_UTF8_STRING) { + v->len = entry.data_size; + v->data = (u_char *) entry.utf8_string; + + } else if (entry.type == MMDB_DATA_TYPE_BYTES) { + v->len = entry.data_size; + v->data = (u_char *) entry.bytes; + + } else if (entry.type == MMDB_DATA_TYPE_DOUBLE + || entry.type == MMDB_DATA_TYPE_FLOAT) + { + v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN + 5); + if (v->data == NULL) { + return NGX_ERROR; + } + + if (entry.type == MMDB_DATA_TYPE_DOUBLE) { + v->len = ngx_sprintf(v->data, "%.4f", entry.double_value) + - v->data; + + } else { /* MMDB_DATA_TYPE_FLOAT */ + v->len = ngx_sprintf(v->data, "%.4f", (double) entry.float_value) + - v->data; + } + + } else if (entry.type == MMDB_DATA_TYPE_INT32 + || entry.type == MMDB_DATA_TYPE_UINT16 + || entry.type == MMDB_DATA_TYPE_UINT32 + || entry.type == MMDB_DATA_TYPE_UINT64 + || entry.type == MMDB_DATA_TYPE_BOOLEAN) + { + v->data = ngx_pnalloc(r->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + if (entry.type == MMDB_DATA_TYPE_INT32) { + v->len = ngx_sprintf(v->data, "%D", entry.int32) - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT16) { + v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.uint16) + - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT32) { + v->len = ngx_sprintf(v->data, "%uD", entry.uint32) - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT64) { + v->len = ngx_sprintf(v->data, "%uL", entry.uint64) - v->data; + + } else { /* MMDB_DATA_TYPE_BOOLEAN */ + v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.boolean) + - v->data; + } + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "MMDB_aget_value(): unexpected entry type, %d", + entry.type); + goto not_found; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + +#endif + + +#if (NGX_HAVE_GEOIP_LEGACY) static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) @@ -587,6 +986,7 @@ ngx_http_geoip_addr_v6(ngx_http_request_ } #endif +#endif static struct sockaddr * @@ -682,6 +1082,46 @@ ngx_http_geoip_country(ngx_conf_t *cf, n return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->country = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->country == NULL) { + return NGX_CONF_ERROR; + } + + gcf->country_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->country == NULL) { @@ -702,7 +1142,7 @@ ngx_http_geoip_country(ngx_conf_t *cf, n } } - switch (gcf->country->databaseType) { + switch (((GeoIP *) gcf->country)->databaseType) { case GEOIP_COUNTRY_EDITION: @@ -718,9 +1158,11 @@ ngx_http_geoip_country(ngx_conf_t *cf, n default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP database \"%V\" type:%d", - &value[1], gcf->country->databaseType); + &value[1], ((GeoIP *) gcf->country)->databaseType); return NGX_CONF_ERROR; } + +#endif } @@ -741,6 +1183,46 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->org = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->org == NULL) { + return NGX_CONF_ERROR; + } + + gcf->org_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->org == NULL) { @@ -761,7 +1243,7 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c } } - switch (gcf->org->databaseType) { + switch (((GeoIP *) gcf->org)->databaseType) { case GEOIP_ISP_EDITION: case GEOIP_ORG_EDITION: @@ -783,9 +1265,11 @@ ngx_http_geoip_org(ngx_conf_t *cf, ngx_c default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP database \"%V\" type:%d", - &value[1], gcf->org->databaseType); + &value[1], ((GeoIP *) gcf->org)->databaseType); return NGX_CONF_ERROR; } + +#endif } @@ -806,6 +1290,46 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_ return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->city = ngx_http_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->city == NULL) { + return NGX_CONF_ERROR; + } + + gcf->city_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_http_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->city == NULL) { @@ -826,7 +1350,7 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_ } } - switch (gcf->city->databaseType) { + switch (((GeoIP *) gcf->city)->databaseType) { case GEOIP_CITY_EDITION_REV0: case GEOIP_CITY_EDITION_REV1: @@ -844,12 +1368,168 @@ ngx_http_geoip_city(ngx_conf_t *cf, ngx_ default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP City database \"%V\" type:%d", - &value[1], gcf->city->databaseType); + &value[1], ((GeoIP *) gcf->city)->databaseType); + return NGX_CONF_ERROR; + } + +#endif +} + + +static char * +ngx_http_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_http_geoip_conf_t *gcf = conf; + + u_char *key; + ngx_str_t *value; + ngx_uint_t i, n; + ngx_http_variable_t *v; + ngx_http_geoip_variable_t *gv; + + value = cf->args->elts; + + if (value[1].data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + value[1].len--; + value[1].data++; + + v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE); + if (v == NULL) { + return NGX_CONF_ERROR; + } + + gv = ngx_palloc(cf->pool, sizeof(ngx_http_geoip_variable_t)); + if (gv == NULL) { + return NGX_CONF_ERROR; + } + + v->get_handler = ngx_http_geoip_mmdb_variable; + v->data = (uintptr_t) gv; + + /* database file name */ + + if (ngx_conf_full_name(cf->cycle, &value[2], 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + gv->mmdb = ngx_http_geoip_mmdb_open(cf, gcf, &value[2]); + + if (gv->mmdb == NULL) { + return NGX_CONF_ERROR; + } + + /* data path specification */ + + n = 0; + for (i = 0; i < value[3].len; i++) { + if (value[3].data[i] == '.') { + n++; + } + } + + gv->path = ngx_pcalloc(cf->pool, (n + 2) * sizeof(char *)); + if (gv->path == NULL) { return NGX_CONF_ERROR; } + + n = 0; + key = &value[3].data[0]; + + for (i = 0; i < value[3].len; i++) { + if (value[3].data[i] != '.') { + continue; + } + + value[3].data[i] = '\0'; + gv->path[n++] = (char *) key; + key = &value[3].data[i + 1]; + } + + gv->path[n++] = (char *) key; + gv->path[n] = NULL; + + return NGX_CONF_OK; + +#else + return "is not supported on this platform"; +#endif } +#if (NGX_HAVE_GEOIP_MMDB) + +static MMDB_s * +ngx_http_geoip_mmdb_open(ngx_conf_t *cf, ngx_http_geoip_conf_t *gcf, + ngx_str_t *file) +{ + int status; + MMDB_s *mmdb; + ngx_uint_t i; + + if (gcf->mmdb == NULL) { + gcf->mmdb = ngx_array_create(cf->pool, 1, sizeof(MMDB_s)); + if (gcf->mmdb == NULL) { + return NULL; + } + } + + mmdb = gcf->mmdb->elts; + for (i = 0; i < gcf->mmdb->nelts; i++) { + if (ngx_strcmp(mmdb[i].filename, file->data) == 0) { + return &mmdb[i]; + } + } + + mmdb = ngx_array_push(gcf->mmdb); + if (mmdb == NULL) { + return NULL; + } + + status = MMDB_open((char *) file->data, 0, mmdb); + + if (status != MMDB_SUCCESS) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, + (status == MMDB_IO_ERROR) ? ngx_errno : 0, + "MMDB_open(\"%V\") failed: %s", + file, MMDB_strerror(status)); + + gcf->mmdb->nelts--; + + return NULL; + } + + return mmdb; +} + +#endif + + +#if (NGX_HAVE_GEOIP_LEGACY) + +static ngx_int_t +ngx_http_geoip_mmdb_file(ngx_str_t *file) +{ + if (file->len < sizeof(".mmdb") - 1 + || ngx_strncasecmp(file->data + file->len - (sizeof(".mmdb") - 1), + (u_char *) ".mmdb", sizeof(".mmdb") - 1) + != 0) + { + return NGX_DECLINED; + } + + return NGX_OK; +} + +#endif + + static char * ngx_http_geoip_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -916,15 +1596,33 @@ ngx_http_geoip_cleanup(void *data) { ngx_http_geoip_conf_t *gcf = data; - if (gcf->country) { +#if (NGX_HAVE_GEOIP_LEGACY) + + if (gcf->country && !gcf->country_mmdb) { GeoIP_delete(gcf->country); } - if (gcf->org) { + if (gcf->org && !gcf->org_mmdb) { GeoIP_delete(gcf->org); } - if (gcf->city) { + if (gcf->city && !gcf->city_mmdb) { GeoIP_delete(gcf->city); } + +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->mmdb) { + MMDB_s *mmdb; + ngx_uint_t i; + + mmdb = gcf->mmdb->elts; + for (i = 0; i < gcf->mmdb->nelts; i++) { + MMDB_close(&mmdb[i]); + } + } + +#endif } diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -1,6 +1,7 @@ /* * Copyright (C) Igor Sysoev + * Copyright (C) Maxim Dounin * Copyright (C) Nginx, Inc. */ @@ -9,25 +10,56 @@ #include #include +#if (NGX_HAVE_GEOIP_LEGACY) + #include #include +#endif -#define NGX_GEOIP_COUNTRY_CODE 0 -#define NGX_GEOIP_COUNTRY_CODE3 1 -#define NGX_GEOIP_COUNTRY_NAME 2 +#if (NGX_HAVE_GEOIP_MMDB) + +#include + +#endif + + +#define NGX_GEOIP_COUNTRY_CODE 0 +#define NGX_GEOIP_COUNTRY_CODE3 1 +#define NGX_GEOIP_COUNTRY_NAME 2 +#define NGX_GEOIP_CONTINENT_CODE 3 +#define NGX_GEOIP_REGION 4 +#define NGX_GEOIP_REGION_NAME 5 +#define NGX_GEOIP_CITY 6 +#define NGX_GEOIP_POSTAL_CODE 7 +#define NGX_GEOIP_LATITUDE 8 +#define NGX_GEOIP_LONGITUDE 9 +#define NGX_GEOIP_DMA_CODE 10 +#define NGX_GEOIP_AREA_CODE 11 typedef struct { - GeoIP *country; - GeoIP *org; - GeoIP *city; -#if (NGX_HAVE_GEOIP_V6) + void *country; + void *org; + void *city; + ngx_array_t *mmdb; /* array of MMDB_s */ unsigned country_v6:1; + unsigned country_mmdb:1; unsigned org_v6:1; + unsigned org_mmdb:1; unsigned city_v6:1; + unsigned city_mmdb:1; +} ngx_stream_geoip_conf_t; + + +#if (NGX_HAVE_GEOIP_MMDB) + +typedef struct { + MMDB_s *mmdb; + const char **path; +} ngx_stream_geoip_variable_t; + #endif -} ngx_stream_geoip_conf_t; static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, @@ -42,14 +74,25 @@ static ngx_int_t ngx_stream_geoip_city_f ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +#if (NGX_HAVE_GEOIP_LEGACY) static GeoIPRecord *ngx_stream_geoip_get_city_record(ngx_stream_session_t *s); +#endif +#if (NGX_HAVE_GEOIP_MMDB) +static ngx_int_t ngx_stream_geoip_mmdb_city_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_geoip_mmdb_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf); #if (NGX_HAVE_GEOIP_V6) static geoipv6_t ngx_stream_geoip_addr_v6(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf); #endif +#endif static ngx_int_t ngx_stream_geoip_add_variables(ngx_conf_t *cf); static void *ngx_stream_geoip_create_conf(ngx_conf_t *cf); @@ -59,6 +102,15 @@ static char *ngx_stream_geoip_org(ngx_co void *conf); static char *ngx_stream_geoip_city(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +#if (NGX_HAVE_GEOIP_MMDB) +static MMDB_s *ngx_stream_geoip_mmdb_open(ngx_conf_t *cf, + ngx_stream_geoip_conf_t *gcf, ngx_str_t *file); +#endif +#if (NGX_HAVE_GEOIP_LEGACY) +static ngx_int_t ngx_stream_geoip_mmdb_file(ngx_str_t *file); +#endif static void ngx_stream_geoip_cleanup(void *data); @@ -85,6 +137,13 @@ static ngx_command_t ngx_stream_geoip_c 0, NULL }, + { ngx_string("geoip_set"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE3, + ngx_stream_geoip_set, + NGX_STREAM_MAIN_CONF_OFFSET, + 0, + NULL }, + ngx_null_command }; @@ -137,61 +196,103 @@ static ngx_stream_variable_t ngx_stream { ngx_string("geoip_city_continent_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, continent_code), 0, 0 }, + NGX_GEOIP_CONTINENT_CODE, 0, 0 }, { ngx_string("geoip_city_country_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_code), 0, 0 }, + NGX_GEOIP_COUNTRY_CODE, 0, 0 }, { ngx_string("geoip_city_country_code3"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_code3), 0, 0 }, + NGX_GEOIP_COUNTRY_CODE3, 0, 0 }, { ngx_string("geoip_city_country_name"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, country_name), 0, 0 }, + NGX_GEOIP_COUNTRY_NAME, 0, 0 }, { ngx_string("geoip_region"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, region), 0, 0 }, + NGX_GEOIP_REGION, 0, 0 }, { ngx_string("geoip_region_name"), NULL, ngx_stream_geoip_region_name_variable, - 0, 0, 0 }, + NGX_GEOIP_REGION_NAME, 0, 0 }, { ngx_string("geoip_city"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, city), 0, 0 }, + NGX_GEOIP_CITY, 0, 0 }, { ngx_string("geoip_postal_code"), NULL, ngx_stream_geoip_city_variable, - offsetof(GeoIPRecord, postal_code), 0, 0 }, + NGX_GEOIP_POSTAL_CODE, 0, 0 }, { ngx_string("geoip_latitude"), NULL, ngx_stream_geoip_city_float_variable, - offsetof(GeoIPRecord, latitude), 0, 0 }, + NGX_GEOIP_LATITUDE, 0, 0 }, { ngx_string("geoip_longitude"), NULL, ngx_stream_geoip_city_float_variable, - offsetof(GeoIPRecord, longitude), 0, 0 }, + NGX_GEOIP_LONGITUDE, 0, 0 }, { ngx_string("geoip_dma_code"), NULL, ngx_stream_geoip_city_int_variable, - offsetof(GeoIPRecord, dma_code), 0, 0 }, + NGX_GEOIP_DMA_CODE, 0, 0 }, { ngx_string("geoip_area_code"), NULL, ngx_stream_geoip_city_int_variable, - offsetof(GeoIPRecord, area_code), 0, 0 }, + NGX_GEOIP_AREA_CODE, 0, 0 }, ngx_stream_null_variable }; +#if (NGX_HAVE_GEOIP_LEGACY) + +static uintptr_t ngx_stream_geoip_city_offsets[] = { + offsetof(GeoIPRecord, country_code), + offsetof(GeoIPRecord, country_code3), + offsetof(GeoIPRecord, country_name), + offsetof(GeoIPRecord, continent_code), + offsetof(GeoIPRecord, region), + 0, /* region name */ + offsetof(GeoIPRecord, city), + offsetof(GeoIPRecord, postal_code), + offsetof(GeoIPRecord, latitude), + offsetof(GeoIPRecord, longitude), + offsetof(GeoIPRecord, dma_code), + offsetof(GeoIPRecord, area_code) +}; + +#endif + + +#if (NGX_HAVE_GEOIP_MMDB) + +static const char *ngx_stream_geoip_mmdb_paths[][5] = { + { "country", "iso_code", NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL, NULL }, /* country code3 */ + { "country", "names", "en", NULL, NULL }, + { "continent", "code", NULL, NULL, NULL }, + { "subdivisions", "0", "iso_code", NULL, NULL }, + { "subdivisions", "0", "names", "en", NULL }, + { "city", "names", "en", NULL, NULL }, + { "postal", "code", NULL, NULL, NULL }, + { "location", "latitude", NULL, NULL, NULL }, + { "location", "longitude", NULL, NULL, NULL }, + { "location", "metro_code", NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL, NULL } /* area code */ +}; + +#endif + + static ngx_int_t ngx_stream_geoip_country_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) const char *val; +#endif ngx_stream_geoip_conf_t *gcf; gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); @@ -200,6 +301,21 @@ ngx_stream_geoip_country_variable(ngx_st goto not_found; } +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->country_mmdb) { + ngx_stream_geoip_variable_t gv; + + gv.mmdb = gcf->country; + gv.path = ngx_stream_geoip_mmdb_paths[data]; + + return ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv); + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + #if (NGX_HAVE_GEOIP_V6) if (gcf->country_v6) { if (data == NGX_GEOIP_COUNTRY_CODE) { @@ -243,6 +359,8 @@ ngx_stream_geoip_country_variable(ngx_st return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -255,8 +373,10 @@ static ngx_int_t ngx_stream_geoip_org_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) size_t len; char *val; +#endif ngx_stream_geoip_conf_t *gcf; gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); @@ -265,6 +385,32 @@ ngx_stream_geoip_org_variable(ngx_stream goto not_found; } +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->org_mmdb) { + ngx_int_t rc; + ngx_stream_geoip_variable_t gv; + + static const char *org[] = { "organization", NULL }; + static const char *asorg[] = { "autonomous_system_organization", NULL }; + + gv.mmdb = gcf->org; + gv.path = org; + + rc = ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv); + + if (rc == NGX_OK && v->not_found) { + gv.path = asorg; + rc = ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv); + } + + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + #if (NGX_HAVE_GEOIP_V6) if (gcf->org_v6) { val = GeoIP_name_by_ipnum_v6(gcf->org, @@ -298,6 +444,8 @@ ngx_stream_geoip_org_variable(ngx_stream return NGX_OK; +#endif + not_found: v->not_found = 1; @@ -310,16 +458,32 @@ static ngx_int_t ngx_stream_geoip_city_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) char *val; size_t len; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_stream_geoip_mmdb_city_variable(s, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { goto not_found; } - val = *(char **) ((char *) gr + data); + val = *(char **) ((char *) gr + ngx_stream_geoip_city_offsets[data]); if (val == NULL) { goto no_value; } @@ -348,6 +512,8 @@ no_value: not_found: +#endif + v->not_found = 1; return NGX_OK; @@ -358,9 +524,25 @@ static ngx_int_t ngx_stream_geoip_region_name_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) size_t len; const char *val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_stream_geoip_mmdb_city_variable(s, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { @@ -392,6 +574,8 @@ ngx_stream_geoip_region_name_variable(ng not_found: +#endif + v->not_found = 1; return NGX_OK; @@ -402,13 +586,28 @@ static ngx_int_t ngx_stream_geoip_city_float_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) float val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_stream_geoip_mmdb_city_variable(s, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5); @@ -417,7 +616,7 @@ ngx_stream_geoip_city_float_variable(ngx return NGX_ERROR; } - val = *(float *) ((char *) gr + data); + val = *(float *) ((char *) gr + ngx_stream_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%.4f", val) - v->data; v->valid = 1; @@ -427,6 +626,14 @@ ngx_stream_geoip_city_float_variable(ngx GeoIPRecord_delete(gr); return NGX_OK; + +not_found: + +#endif + + v->not_found = 1; + + return NGX_OK; } @@ -434,13 +641,28 @@ static ngx_int_t ngx_stream_geoip_city_int_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { +#if (NGX_HAVE_GEOIP_LEGACY) int val; GeoIPRecord *gr; +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_int_t rc; + + rc = ngx_stream_geoip_mmdb_city_variable(s, v, data); + + if (rc != NGX_DECLINED) { + return rc; + } + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) gr = ngx_stream_geoip_get_city_record(s); if (gr == NULL) { - v->not_found = 1; - return NGX_OK; + goto not_found; } v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); @@ -449,7 +671,7 @@ ngx_stream_geoip_city_int_variable(ngx_s return NGX_ERROR; } - val = *(int *) ((char *) gr + data); + val = *(int *) ((char *) gr + ngx_stream_geoip_city_offsets[data]); v->len = ngx_sprintf(v->data, "%d", val) - v->data; v->valid = 1; @@ -459,9 +681,19 @@ ngx_stream_geoip_city_int_variable(ngx_s GeoIPRecord_delete(gr); return NGX_OK; + +not_found: + +#endif + + v->not_found = 1; + + return NGX_OK; } +#if (NGX_HAVE_GEOIP_LEGACY) + static GeoIPRecord * ngx_stream_geoip_get_city_record(ngx_stream_session_t *s) { @@ -482,6 +714,169 @@ ngx_stream_geoip_get_city_record(ngx_str return NULL; } +#endif + + +#if (NGX_HAVE_GEOIP_MMDB) + +static ngx_int_t +ngx_stream_geoip_mmdb_city_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_geoip_conf_t *gcf; + ngx_stream_geoip_variable_t gv; + + gcf = ngx_stream_get_module_main_conf(s, ngx_stream_geoip_module); + + if (!gcf->city_mmdb) { + return NGX_DECLINED; + } + + gv.mmdb = gcf->city; + gv.path = ngx_stream_geoip_mmdb_paths[data]; + + return ngx_stream_geoip_mmdb_variable(s, v, (uintptr_t) &gv); +} + + +static ngx_int_t +ngx_stream_geoip_mmdb_variable(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_geoip_variable_t *gv = (ngx_stream_geoip_variable_t *) data; + + int status; + struct sockaddr *sockaddr; + MMDB_entry_data_s entry; + MMDB_lookup_result_s result; + + if (gv->path[0] == NULL) { + goto not_found; + } + + sockaddr = s->connection->sockaddr; + + if (sockaddr->sa_family != AF_INET +#if (NGX_HAVE_INET6) + && sockaddr->sa_family != AF_INET6 +#endif + ) + { + goto not_found; + } + + result = MMDB_lookup_sockaddr(gv->mmdb, sockaddr, &status); + + if (status != MMDB_SUCCESS) { + if (status == MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR) { + goto not_found; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "MMDB_lookup_sockaddr() failed: %s", + MMDB_strerror(status)); + return NGX_ERROR; + } + + if (!result.found_entry) { + goto not_found; + } + + status = MMDB_aget_value(&result.entry, &entry, gv->path); + + if (status != MMDB_SUCCESS) { + if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { + goto not_found; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "MMDB_aget_value() failed: %s", + MMDB_strerror(status)); + return NGX_ERROR; + } + + if (!entry.has_data) { + goto not_found; + } + + if (entry.type == MMDB_DATA_TYPE_UTF8_STRING) { + v->len = entry.data_size; + v->data = (u_char *) entry.utf8_string; + + } else if (entry.type == MMDB_DATA_TYPE_BYTES) { + v->len = entry.data_size; + v->data = (u_char *) entry.bytes; + + } else if (entry.type == MMDB_DATA_TYPE_DOUBLE + || entry.type == MMDB_DATA_TYPE_FLOAT) + { + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN + 5); + if (v->data == NULL) { + return NGX_ERROR; + } + + if (entry.type == MMDB_DATA_TYPE_DOUBLE) { + v->len = ngx_sprintf(v->data, "%.4f", entry.double_value) + - v->data; + + } else { /* MMDB_DATA_TYPE_FLOAT */ + v->len = ngx_sprintf(v->data, "%.4f", (double) entry.float_value) + - v->data; + } + + } else if (entry.type == MMDB_DATA_TYPE_INT32 + || entry.type == MMDB_DATA_TYPE_UINT16 + || entry.type == MMDB_DATA_TYPE_UINT32 + || entry.type == MMDB_DATA_TYPE_UINT64 + || entry.type == MMDB_DATA_TYPE_BOOLEAN) + { + v->data = ngx_pnalloc(s->connection->pool, NGX_INT64_LEN); + if (v->data == NULL) { + return NGX_ERROR; + } + + if (entry.type == MMDB_DATA_TYPE_INT32) { + v->len = ngx_sprintf(v->data, "%D", entry.int32) - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT16) { + v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.uint16) + - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT32) { + v->len = ngx_sprintf(v->data, "%uD", entry.uint32) - v->data; + + } else if (entry.type == MMDB_DATA_TYPE_UINT64) { + v->len = ngx_sprintf(v->data, "%uL", entry.uint64) - v->data; + + } else { /* MMDB_DATA_TYPE_BOOLEAN */ + v->len = ngx_sprintf(v->data, "%uD", (uint32_t) entry.boolean) + - v->data; + } + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, + "MMDB_aget_value(): unexpected entry type, %d", + entry.type); + goto not_found; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; + +not_found: + + v->not_found = 1; + + return NGX_OK; +} + +#endif + + +#if (NGX_HAVE_GEOIP_LEGACY) static u_long ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) @@ -562,6 +957,7 @@ ngx_stream_geoip_addr_v6(ngx_stream_sess } #endif +#endif static ngx_int_t @@ -623,6 +1019,46 @@ ngx_stream_geoip_country(ngx_conf_t *cf, return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->country = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->country == NULL) { + return NGX_CONF_ERROR; + } + + gcf->country_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->country = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->country == NULL) { @@ -643,7 +1079,7 @@ ngx_stream_geoip_country(ngx_conf_t *cf, } } - switch (gcf->country->databaseType) { + switch (((GeoIP *) gcf->country)->databaseType) { case GEOIP_COUNTRY_EDITION: @@ -659,9 +1095,11 @@ ngx_stream_geoip_country(ngx_conf_t *cf, default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP database \"%V\" type:%d", - &value[1], gcf->country->databaseType); + &value[1], ((GeoIP *) gcf->country)->databaseType); return NGX_CONF_ERROR; } + +#endif } @@ -682,6 +1120,46 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->org = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->org == NULL) { + return NGX_CONF_ERROR; + } + + gcf->org_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->org = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->org == NULL) { @@ -702,7 +1180,7 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx } } - switch (gcf->org->databaseType) { + switch (((GeoIP *) gcf->org)->databaseType) { case GEOIP_ISP_EDITION: case GEOIP_ORG_EDITION: @@ -724,9 +1202,11 @@ ngx_stream_geoip_org(ngx_conf_t *cf, ngx default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP database \"%V\" type:%d", - &value[1], gcf->org->databaseType); + &value[1], ((GeoIP *) gcf->org)->databaseType); return NGX_CONF_ERROR; } + +#endif } @@ -747,6 +1227,46 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng return NGX_CONF_ERROR; } +#if (NGX_HAVE_GEOIP_MMDB) + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) != NGX_OK) { + goto legacy; + } + +#endif + + gcf->city = ngx_stream_geoip_mmdb_open(cf, gcf, &value[1]); + + if (gcf->city == NULL) { + return NGX_CONF_ERROR; + } + + gcf->city_mmdb = 1; + + if (cf->args->nelts == 3) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[2]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + +#endif + +#if (NGX_HAVE_GEOIP_LEGACY) + + if (ngx_stream_geoip_mmdb_file(&value[1]) == NGX_OK) { + return "does not support mmdb databases on this platform"; + } + +#if (NGX_HAVE_GEOIP_MMDB) + +legacy: + +#endif + gcf->city = GeoIP_open((char *) value[1].data, GEOIP_MEMORY_CACHE); if (gcf->city == NULL) { @@ -767,7 +1287,7 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng } } - switch (gcf->city->databaseType) { + switch (((GeoIP *) gcf->city)->databaseType) { case GEOIP_CITY_EDITION_REV0: case GEOIP_CITY_EDITION_REV1: @@ -785,26 +1305,200 @@ ngx_stream_geoip_city(ngx_conf_t *cf, ng default: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid GeoIP City database \"%V\" type:%d", - &value[1], gcf->city->databaseType); + &value[1], ((GeoIP *) gcf->city)->databaseType); + return NGX_CONF_ERROR; + } + +#endif +} + + +static char * +ngx_stream_geoip_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#if (NGX_HAVE_GEOIP_MMDB) + + ngx_stream_geoip_conf_t *gcf = conf; + + u_char *key; + ngx_str_t *value; + ngx_uint_t i, n; + ngx_stream_variable_t *v; + ngx_stream_geoip_variable_t *gv; + + value = cf->args->elts; + + if (value[1].data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + value[1].len--; + value[1].data++; + + v = ngx_stream_add_variable(cf, &value[1], NGX_STREAM_VAR_CHANGEABLE); + if (v == NULL) { + return NGX_CONF_ERROR; + } + + gv = ngx_palloc(cf->pool, sizeof(ngx_stream_geoip_variable_t)); + if (gv == NULL) { + return NGX_CONF_ERROR; + } + + v->get_handler = ngx_stream_geoip_mmdb_variable; + v->data = (uintptr_t) gv; + + /* database file name */ + + if (ngx_conf_full_name(cf->cycle, &value[2], 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + gv->mmdb = ngx_stream_geoip_mmdb_open(cf, gcf, &value[2]); + + if (gv->mmdb == NULL) { + return NGX_CONF_ERROR; + } + + /* data path specification */ + + n = 0; + for (i = 0; i < value[3].len; i++) { + if (value[3].data[i] == '.') { + n++; + } + } + + gv->path = ngx_pcalloc(cf->pool, (n + 2) * sizeof(char *)); + if (gv->path == NULL) { return NGX_CONF_ERROR; } + + n = 0; + key = &value[3].data[0]; + + for (i = 0; i < value[3].len; i++) { + if (value[3].data[i] != '.') { + continue; + } + + value[3].data[i] = '\0'; + gv->path[n++] = (char *) key; + key = &value[3].data[i + 1]; + } + + gv->path[n++] = (char *) key; + gv->path[n] = NULL; + + return NGX_CONF_OK; + +#else + return "is not supported on this platform"; +#endif } +#if (NGX_HAVE_GEOIP_MMDB) + +static MMDB_s * +ngx_stream_geoip_mmdb_open(ngx_conf_t *cf, ngx_stream_geoip_conf_t *gcf, + ngx_str_t *file) +{ + int status; + MMDB_s *mmdb; + ngx_uint_t i; + + if (gcf->mmdb == NULL) { + gcf->mmdb = ngx_array_create(cf->pool, 1, sizeof(MMDB_s)); + if (gcf->mmdb == NULL) { + return NULL; + } + } + + mmdb = gcf->mmdb->elts; + for (i = 0; i < gcf->mmdb->nelts; i++) { + if (ngx_strcmp(mmdb[i].filename, file->data) == 0) { + return &mmdb[i]; + } + } + + mmdb = ngx_array_push(gcf->mmdb); + if (mmdb == NULL) { + return NULL; + } + + status = MMDB_open((char *) file->data, 0, mmdb); + + if (status != MMDB_SUCCESS) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, + (status == MMDB_IO_ERROR) ? ngx_errno : 0, + "MMDB_open(\"%V\") failed: %s", + file, MMDB_strerror(status)); + + gcf->mmdb->nelts--; + + return NULL; + } + + return mmdb; +} + +#endif + + +#if (NGX_HAVE_GEOIP_LEGACY) + +static ngx_int_t +ngx_stream_geoip_mmdb_file(ngx_str_t *file) +{ + if (file->len < sizeof(".mmdb") - 1 + || ngx_strncasecmp(file->data + file->len - (sizeof(".mmdb") - 1), + (u_char *) ".mmdb", sizeof(".mmdb") - 1) + != 0) + { + return NGX_DECLINED; + } + + return NGX_OK; +} + +#endif + + static void ngx_stream_geoip_cleanup(void *data) { ngx_stream_geoip_conf_t *gcf = data; - if (gcf->country) { +#if (NGX_HAVE_GEOIP_LEGACY) + + if (gcf->country && !gcf->country_mmdb) { GeoIP_delete(gcf->country); } - if (gcf->org) { + if (gcf->org && !gcf->org_mmdb) { GeoIP_delete(gcf->org); } - if (gcf->city) { + if (gcf->city && !gcf->city_mmdb) { GeoIP_delete(gcf->city); } + +#endif + +#if (NGX_HAVE_GEOIP_MMDB) + + if (gcf->mmdb) { + MMDB_s *mmdb; + ngx_uint_t i; + + mmdb = gcf->mmdb->elts; + for (i = 0; i < gcf->mmdb->nelts; i++) { + MMDB_close(&mmdb[i]); + } + } + +#endif } From mdounin at mdounin.ru Wed Dec 3 16:32:44 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 03 Dec 2025 19:32:44 +0300 Subject: [PATCH] Tests: tests for GeoIP databases in the MMDB format In-Reply-To: <234a79a752d46a21ecac.1764779470@vm-bsd.mdounin.ru> References: <234a79a752d46a21ecac.1764779470@vm-bsd.mdounin.ru> Message-ID: # HG changeset patch # User Maxim Dounin # Date 1764694278 -10800 # Tue Dec 02 19:51:18 2025 +0300 # Node ID f94d1e13363739c0f2129f404e5672a4ee98146c # Parent 55ff381e5d3a0f0fb2e64b3ec63ec01bf1b6f132 Tests: tests for GeoIP databases in the MMDB format. diff --git a/geoip.t b/geoip.t --- a/geoip.t +++ b/geoip.t @@ -144,7 +144,7 @@ for my $i (0 .. 31) { $t->write_file('org.dat', $data); $t->write_file('index.html', ''); -$t->try_run('no inet6 support')->plan(20); +$t->try_run('no geoip legacy')->plan(20); ############################################################################### diff --git a/geoip.t b/geoip_mmdb.t copy from geoip.t copy to geoip_mmdb.t --- a/geoip.t +++ b/geoip_mmdb.t @@ -1,9 +1,10 @@ #!/usr/bin/perl +# (C) Maxim Dounin # (C) Andrey Zelenkov # (C) Nginx, Inc. -# Tests for geoip module. +# Tests for geoip module with MMDB databases. ############################################################################### @@ -37,9 +38,11 @@ http { geoip_proxy 127.0.0.1/32; - geoip_country %%TESTDIR%%/country.dat; - geoip_city %%TESTDIR%%/city.dat; - geoip_org %%TESTDIR%%/org.dat; + geoip_country test.mmdb; + geoip_city test.mmdb; + geoip_org test.mmdb; + + geoip_set $asn test.mmdb autonomous_system_number; server { listen 127.0.0.1:8080; @@ -47,13 +50,13 @@ http { location / { add_header X-Country-Code $geoip_country_code; - add_header X-Country-Code3 $geoip_country_code3; + add_header X-Country-Code3 $geoip_country_code3:none; add_header X-Country-Name $geoip_country_name; - add_header X-Area-Code $geoip_area_code; + add_header X-Area-Code $geoip_area_code:none; add_header X-C-Continent-Code $geoip_city_continent_code; add_header X-C-Country-Code $geoip_city_country_code; - add_header X-C-Country-Code3 $geoip_city_country_code3; + add_header X-C-Country-Code3 $geoip_city_country_code3:none; add_header X-C-Country-Name $geoip_city_country_name; add_header X-Dma-Code $geoip_dma_code; add_header X-Latitude $geoip_latitude; @@ -64,6 +67,8 @@ http { add_header X-Postal-Code $geoip_postal_code; add_header X-Org $geoip_org; + + add_header X-ASN $asn; } } } @@ -72,127 +77,126 @@ EOF my $d = $t->testdir(); -# country database: -# -# "10.0.0.1","10.0.0.1","RU","Russian Federation" -# "2001:db8::","2001:db8::","US","United States" +# MMDB format specification: +# https://github.com/maxmind/MaxMind-DB/blob/main/MaxMind-DB-spec.md my $data = ''; -for my $i (0 .. 156) { - # skip to offset 32 if 1st bit set in ipv6 address wins - $data .= pack_node($i + 1) . pack_node(32), next if $i == 2; - # otherwise default to RU - $data .= pack_node(0xffffb9) . pack_node(0xffff00), next if $i == 31; - # continue checking bits set in ipv6 address - $data .= pack_node(0xffff00) . pack_node($i + 1), next - if grep $_ == $i, (44, 49, 50, 52, 53, 55, 56, 57); - # last bit set in ipv6 address - $data .= pack_node(0xffffe1) . pack_node(0xffff00), next if $i == 156; - $data .= pack_node($i + 1) . pack_node(0xffff00); -} +# binary search tree +# just one node (two records), 32-bit records + +$data .= pack('NN', 17, 17); + +# data section + +$data .= pack('x16'); + +$data .= pack("B8", "11101000"); + +$data .= pack("B8A*", "01011000", "autonomous_system_number"); +$data .= pack("B8N", "11000100", 64511); + +$data .= pack("B8CA*", "01011101", 30 - 29, "autonomous_system_organization"); +$data .= pack("B8A*", "01001101", "freenginx.org"); -$data .= chr(0x00) x 3; -$data .= chr(0xFF) x 3; -$data .= chr(12); - -$t->write_file('country.dat', $data); +$data .= pack("B8A*", "01000111", "country"); +$data .= pack("B8", "11100010"); +$data .= pack("B8A*", "01001000", "iso_code"); +$data .= pack("B8A*", "01000010", "RU"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01010010", "Russian Federation"); -# city database: -# -# "167772161","167772161","RU","48","Moscow","119034","55.7543",37.6202",, - -$data = ''; +$data .= pack("B8A*", "01001001", "continent"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000100", "code"); +$data .= pack("B8A*", "01000010", "EU"); -for my $i (0 .. 31) { - $data .= pack_node(32) . pack_node($i + 1), next if $i == 4 or $i == 6; - $data .= pack_node(32) . pack_node($i + 2), next if $i == 31; - $data .= pack_node($i + 1) . pack_node(32); -} +$data .= pack("B8A*", "01001100", "subdivisions"); +$data .= pack("B8C", "00000001", 11 - 7); +$data .= pack("B8", "11100010"); +$data .= pack("B8A*", "01001000", "iso_code"); +$data .= pack("B8A*", "01000011", "MOW"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01000110", "Moscow"); -$data .= chr(42); -$data .= chr(185); -$data .= pack('Z*', 48); -$data .= pack('Z*', 'Moscow'); -$data .= pack('Z*', 119034); -$data .= pack_node(int((55.7543 + 180) * 10000)); -$data .= pack_node(int((37.6202 + 180) * 10000)); -$data .= chr(0) x 3; -$data .= chr(0xFF) x 3; -$data .= chr(2); -$data .= pack_node(32); +$data .= pack("B8A*", "01000100", "city"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01000110", "Moscow"); + +$data .= pack("B8A*", "01000110", "postal"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000100", "code"); +$data .= pack("B8A*", "01000110", "119034"); -$t->write_file('city.dat', $data); +$data .= pack("B8A*", "01001000", "location"); +$data .= pack("B8", "11100011"); +$data .= pack("B8A*", "01001000", "latitude"); +$data .= pack("B8d>", "01101000", 55.7543); +$data .= pack("B8A*", "01001001", "longitude"); +$data .= pack("B8d>", "01101000", 37.6202); +$data .= pack("B8A*", "01001010", "metro_code"); +$data .= pack("B8C", "10100001", 0); -# organization database: -# -# "167772161","167772161","Nginx" +# metadata + +$data .= "\xab\xcd\xefMaxMind.com"; -$data = ''; - -for my $i (0 .. 31) { - $data .= pack_org(32) . pack_org($i + 1), next if $i == 4 or $i == 6; - $data .= pack_org(32) . pack_org($i + 2), next if $i == 31; - $data .= pack_org($i + 1) . pack_org(32); -} +$data .= pack("B8", "11101001"); +$data .= pack("B8A*", "01001010", "node_count"); +$data .= pack("B8C", "11000001", 1); +$data .= pack("B8A*", "01001011", "record_size"); +$data .= pack("B8C", "10100001", 32); +$data .= pack("B8A*", "01001010", "ip_version"); +$data .= pack("B8C", "10100001", 6); +$data .= pack("B8A*", "01001101", "database_type"); +$data .= pack("B8A*", "01000100", "test"); +$data .= pack("B8A*", "01001001", "languages"); +$data .= pack("B8B8", "00000001", "00000100"); +$data .= pack("B8A*", "01000100", "test"); +$data .= pack("B8A*", "01011011", "binary_format_major_version"); +$data .= pack("B8C", "10100001", 2); +$data .= pack("B8A*", "01011011", "binary_format_minor_version"); +$data .= pack("B8C", "10100001", 0); +$data .= pack("B8A*", "01001011", "build_epoch"); +$data .= pack("B8B8C", "00000001", "00000010", 1); +$data .= pack("B8A*", "01001011", "description"); +$data .= pack("B8", "11100000"); -$data .= chr(42); -$data .= pack('Z*', 'Nginx'); -$data .= chr(0xFF) x 3; -$data .= chr(5); -$data .= pack_node(32); +$t->write_file('test.mmdb', $data); -$t->write_file('org.dat', $data); $t->write_file('index.html', ''); -$t->try_run('no inet6 support')->plan(20); +$t->try_run('no geoip mmdb')->plan(17); ############################################################################### -my $r = http_xff('10.0.0.1'); +my $r = http_get('/'); + like($r, qr/X-Country-Code: RU/, 'geoip country code'); -like($r, qr/X-Country-Code3: RUS/, 'geoip country code 3'); +like($r, qr/X-Country-Code3: :none/, 'geoip country code 3'); like($r, qr/X-Country-Name: Russian Federation/, 'geoip country name'); -like($r, qr/X-Area-Code: 0/, 'geoip area code'); +like($r, qr/X-Area-Code: :none/, 'geoip area code'); like($r, qr/X-C-Continent-Code: EU/, 'geoip city continent code'); like($r, qr/X-C-Country-Code: RU/, 'geoip city country code'); -like($r, qr/X-C-Country-Code3: RUS/, 'geoip city country code 3'); +like($r, qr/X-C-Country-Code3: :none/, 'geoip city country code 3'); like($r, qr/X-C-Country-Name: Russian Federation/, 'geoip city country name'); like($r, qr/X-Dma-Code: 0/, 'geoip dma code'); like($r, qr/X-Latitude: 55.7543/, 'geoip latitude'); like($r, qr/X-Longitude: 37.6202/, 'geoip longitude'); -like($r, qr/X-Region: 48/, 'geoip region'); -like($r, qr/X-Region-Name: Moscow City/, 'geoip region name'); +like($r, qr/X-Region: MOW/, 'geoip region'); +like($r, qr/X-Region-Name: Moscow/, 'geoip region name'); like($r, qr/X-City: Moscow/, 'geoip city'); like($r, qr/X-Postal-Code: 119034/, 'geoip postal code'); -like($r, qr/X-Org: Nginx/, 'geoip org'); - -like(http_xff('::ffff:10.0.0.1'), qr/X-Org: Nginx/, 'geoip ipv6 ipv4-mapped'); +like($r, qr/X-Org: freenginx.org/, 'geoip org'); -$r = http_xff('2001:db8::'); -like($r, qr/X-Country-Code: US/, 'geoip ipv6 country code'); -like($r, qr/X-Country-Code3: USA/, 'geoip ipv6 country code 3'); -like($r, qr/X-Country-Name: United States/, 'geoip ipv6 country name'); +like($r, qr/X-ASN: 64511/, 'geoip set asn'); ############################################################################### - -sub http_xff { - my ($xff) = @_; - return http(<write_file('org.dat', $data); -$t->try_run('no inet6 support')->plan(20); +$t->try_run('no geoip legacy')->plan(20); ############################################################################### diff --git a/stream_geoip.t b/stream_geoip_mmdb.t copy from stream_geoip.t copy to stream_geoip_mmdb.t --- a/stream_geoip.t +++ b/stream_geoip_mmdb.t @@ -1,9 +1,10 @@ #!/usr/bin/perl +# (C) Maxim Dounin # (C) Andrey Zelenkov # (C) Nginx, Inc. -# Tests for stream geoip module. +# Tests for stream geoip module with MMDB databases. ############################################################################### @@ -25,8 +26,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_geoip stream_return/) - ->has('stream_realip'); +my $t = Test::Nginx->new()->has(qw/stream stream_geoip stream_return/); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -40,22 +40,22 @@ events { stream { %%TEST_GLOBALS_STREAM%% - set_real_ip_from 127.0.0.1/32; + geoip_country test.mmdb; + geoip_city test.mmdb; + geoip_org test.mmdb; - geoip_country %%TESTDIR%%/country.dat; - geoip_city %%TESTDIR%%/city.dat; - geoip_org %%TESTDIR%%/org.dat; + geoip_set $asn test.mmdb autonomous_system_number; server { - listen 127.0.0.1:8080 proxy_protocol; + listen 127.0.0.1:8080; return "country_code:$geoip_country_code - country_code3:$geoip_country_code3 + country_code3:$geoip_country_code3:none country_name:$geoip_country_name - area_code:$geoip_area_code + area_code:$geoip_area_code:none city_continent_code:$geoip_city_continent_code city_country_code:$geoip_city_country_code - city_country_code3:$geoip_city_country_code3 + city_country_code3:$geoip_city_country_code3:none city_country_name:$geoip_city_country_name dma_code:$geoip_dma_code latitude:$geoip_latitude @@ -65,7 +65,9 @@ stream { city:$geoip_city postal_code:$geoip_postal_code - org:$geoip_org"; + org:$geoip_org + + asn:$asn"; } } @@ -73,123 +75,125 @@ EOF my $d = $t->testdir(); -# country database: -# -# "10.0.0.1","10.0.0.1","RU","Russian Federation" -# "2001:db8::","2001:db8::","US","United States" +# MMDB format specification: +# https://github.com/maxmind/MaxMind-DB/blob/main/MaxMind-DB-spec.md my $data = ''; -for my $i (0 .. 156) { - # skip to offset 32 if 1st bit set in ipv6 address wins - $data .= pack_node($i + 1) . pack_node(32), next if $i == 2; - # otherwise default to RU - $data .= pack_node(0xffffb9) . pack_node(0xffff00), next if $i == 31; - # continue checking bits set in ipv6 address - $data .= pack_node(0xffff00) . pack_node($i + 1), next - if grep $_ == $i, (44, 49, 50, 52, 53, 55, 56, 57); - # last bit set in ipv6 address - $data .= pack_node(0xffffe1) . pack_node(0xffff00), next if $i == 156; - $data .= pack_node($i + 1) . pack_node(0xffff00); -} +# binary search tree +# just one node (two records), 32-bit records + +$data .= pack('NN', 17, 17); + +# data section + +$data .= pack('x16'); + +$data .= pack("B8", "11101000"); + +$data .= pack("B8A*", "01011000", "autonomous_system_number"); +$data .= pack("B8N", "11000100", 64511); + +$data .= pack("B8CA*", "01011101", 30 - 29, "autonomous_system_organization"); +$data .= pack("B8A*", "01001101", "freenginx.org"); -$data .= chr(0x00) x 3; -$data .= chr(0xFF) x 3; -$data .= chr(12); - -$t->write_file('country.dat', $data); +$data .= pack("B8A*", "01000111", "country"); +$data .= pack("B8", "11100010"); +$data .= pack("B8A*", "01001000", "iso_code"); +$data .= pack("B8A*", "01000010", "RU"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01010010", "Russian Federation"); -# city database: -# -# "167772161","167772161","RU","48","Moscow","119034","55.7543",37.6202",, - -$data = ''; +$data .= pack("B8A*", "01001001", "continent"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000100", "code"); +$data .= pack("B8A*", "01000010", "EU"); -for my $i (0 .. 31) { - $data .= pack_node(32) . pack_node($i + 1), next if $i == 4 or $i == 6; - $data .= pack_node(32) . pack_node($i + 2), next if $i == 31; - $data .= pack_node($i + 1) . pack_node(32); -} +$data .= pack("B8A*", "01001100", "subdivisions"); +$data .= pack("B8C", "00000001", 11 - 7); +$data .= pack("B8", "11100010"); +$data .= pack("B8A*", "01001000", "iso_code"); +$data .= pack("B8A*", "01000011", "MOW"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01000110", "Moscow"); -$data .= chr(42); -$data .= chr(185); -$data .= pack('Z*', 48); -$data .= pack('Z*', 'Moscow'); -$data .= pack('Z*', 119034); -$data .= pack_node(int((55.7543 + 180) * 10000)); -$data .= pack_node(int((37.6202 + 180) * 10000)); -$data .= chr(0) x 3; -$data .= chr(0xFF) x 3; -$data .= chr(2); -$data .= pack_node(32); +$data .= pack("B8A*", "01000100", "city"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000101", "names"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000010", "en"); +$data .= pack("B8A*", "01000110", "Moscow"); + +$data .= pack("B8A*", "01000110", "postal"); +$data .= pack("B8", "11100001"); +$data .= pack("B8A*", "01000100", "code"); +$data .= pack("B8A*", "01000110", "119034"); -$t->write_file('city.dat', $data); +$data .= pack("B8A*", "01001000", "location"); +$data .= pack("B8", "11100011"); +$data .= pack("B8A*", "01001000", "latitude"); +$data .= pack("B8d>", "01101000", 55.7543); +$data .= pack("B8A*", "01001001", "longitude"); +$data .= pack("B8d>", "01101000", 37.6202); +$data .= pack("B8A*", "01001010", "metro_code"); +$data .= pack("B8C", "10100001", 0); + +# metadata + +$data .= "\xab\xcd\xefMaxMind.com"; -# organization database: -# -# "167772161","167772161","Nginx" - -$data = ''; +$data .= pack("B8", "11101001"); +$data .= pack("B8A*", "01001010", "node_count"); +$data .= pack("B8C", "11000001", 1); +$data .= pack("B8A*", "01001011", "record_size"); +$data .= pack("B8C", "10100001", 32); +$data .= pack("B8A*", "01001010", "ip_version"); +$data .= pack("B8C", "10100001", 6); +$data .= pack("B8A*", "01001101", "database_type"); +$data .= pack("B8A*", "01000100", "test"); +$data .= pack("B8A*", "01001001", "languages"); +$data .= pack("B8B8", "00000001", "00000100"); +$data .= pack("B8A*", "01000100", "test"); +$data .= pack("B8A*", "01011011", "binary_format_major_version"); +$data .= pack("B8C", "10100001", 2); +$data .= pack("B8A*", "01011011", "binary_format_minor_version"); +$data .= pack("B8C", "10100001", 0); +$data .= pack("B8A*", "01001011", "build_epoch"); +$data .= pack("B8B8C", "00000001", "00000010", 1); +$data .= pack("B8A*", "01001011", "description"); +$data .= pack("B8", "11100000"); -for my $i (0 .. 31) { - $data .= pack_org(32) . pack_org($i + 1), next if $i == 4 or $i == 6; - $data .= pack_org(32) . pack_org($i + 2), next if $i == 31; - $data .= pack_org($i + 1) . pack_org(32); -} +$t->write_file('test.mmdb', $data); -$data .= chr(42); -$data .= pack('Z*', 'Nginx'); -$data .= chr(0xFF) x 3; -$data .= chr(5); -$data .= pack_node(32); - -$t->write_file('org.dat', $data); -$t->try_run('no inet6 support')->plan(20); +$t->try_run('no geoip mmdb')->plan(17); ############################################################################### -my %data = stream_pp('10.0.0.1') =~ /(\w+):(.*)/g; +my %data = stream('127.0.0.1:' . port(8080))->read() =~ /(\w+):(.*)/g; + is($data{country_code}, 'RU', 'geoip country code'); -is($data{country_code3}, 'RUS', 'geoip country code 3'); +is($data{country_code3}, ':none', 'geoip country code 3'); is($data{country_name}, 'Russian Federation', 'geoip country name'); -is($data{area_code}, 0, 'geoip area code'); +is($data{area_code}, ':none', 'geoip area code'); is($data{city_continent_code}, 'EU', 'geoip city continent code'); is($data{city_country_code}, 'RU', 'geoip city country code'); -is($data{city_country_code3}, 'RUS', 'geoip city country code 3'); +is($data{city_country_code3}, ':none', 'geoip city country code 3'); is($data{city_country_name}, 'Russian Federation', 'geoip city country name'); is($data{dma_code}, 0, 'geoip dma code'); is($data{latitude}, 55.7543, 'geoip latitude'); is($data{longitude}, 37.6202, 'geoip longitude'); -is($data{region}, 48, 'geoip region'); -is($data{region_name}, 'Moscow City', 'geoip region name'); +is($data{region}, 'MOW', 'geoip region'); +is($data{region_name}, 'Moscow', 'geoip region name'); is($data{city}, 'Moscow', 'geoip city'); is($data{postal_code}, 119034, 'geoip postal code'); -is($data{org}, 'Nginx', 'geoip org'); - -like(stream_pp('::ffff:10.0.0.1'), qr/org:Nginx/, 'geoip ipv6 ipv4-mapped'); +is($data{org}, 'freenginx.org', 'geoip org'); -%data = stream_pp('2001:db8::') =~ /(\w+):(.*)/g; -is($data{country_code}, 'US', 'geoip ipv6 country code'); -is($data{country_code3}, 'USA', 'geoip ipv6 country code 3'); -is($data{country_name}, 'United States', 'geoip ipv6 country name'); +is($data{asn}, '64511', 'geoip set asn'); ############################################################################### - -sub stream_pp { - my ($ip) = @_; - my $type = ($ip =~ ':' ? 'TCP6' : 'TCP4'); - return stream('127.0.0.1:' . port(8080)) - ->io("PROXY $type $ip 127.0.0.1 8080 8080${CRLF}"); -} - -sub pack_node { - substr pack('V', shift), 0, 3; -} - -sub pack_org { - pack('V', shift); -} - -############################################################################### From mdounin at mdounin.ru Sat Dec 6 09:10:21 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sat, 06 Dec 2025 12:10:21 +0300 Subject: [PATCH] Adjusted ngx_file_fs_size() workaround for XFS Message-ID: <3a7cbb9c60d5e058b41d.1765012221@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1765007183 -10800 # Sat Dec 06 10:46:23 2025 +0300 # Node ID 3a7cbb9c60d5e058b41d2aae6e7890e89099e556 # Parent 234a79a752d46a21ecaccddfda660ca2cbfe15f4 Adjusted ngx_file_fs_size() workaround for XFS. With XFS, creating a file might result in large preallocation being reported in st_blocks as returned by fstat() till the file is closed, resulting in incorrect cache size calculations and too aggressive cache clearing based on max_size. This was worked around in 7669:52b34c3f89b4 by introducing the arbitrary sanity limit of eight blocks (8 * st_blksize) for the difference between st_size and size in st_blocks. Unfortunately, it turns out that XFS might return very large values in st_blksize as well, notably when using the "largeio" mount option, which makes the limit ineffective. As a workaround, the limit is changed to 32k instead, which is expected to be large enough to cover real differences between st_size and size in st_blocks, yet small enough to effectively suppress bogus st_blocks values with preallocations. It also matches the previous limit in many practical cases (with st_blksize 4096, previous limit was 32k). Reported by Oleg Mamontov. diff --git a/src/os/unix/ngx_files.h b/src/os/unix/ngx_files.h --- a/src/os/unix/ngx_files.h +++ b/src/os/unix/ngx_files.h @@ -187,7 +187,7 @@ ngx_int_t ngx_set_file_time(u_char *name #define ngx_file_size(sb) (sb)->st_size #define ngx_file_fs_size(sb) \ (((sb)->st_blocks * 512 > (sb)->st_size \ - && (sb)->st_blocks * 512 < (sb)->st_size + 8 * (sb)->st_blksize) \ + && (sb)->st_blocks * 512 < (sb)->st_size + 32768) \ ? (sb)->st_blocks * 512 : (sb)->st_size) #define ngx_file_mtime(sb) (sb)->st_mtime #define ngx_file_uniq(sb) (sb)->st_ino