├── .github └── workflows │ └── ci-devel.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── m4 └── pkg.local.m4 └── src ├── Makefile.am ├── tests ├── b00000.vtc ├── b00001.vtc ├── b00002.vtc ├── b00003.vtc ├── b00004.vtc ├── b00005.vtc ├── libvmod-geoip2.mmdb ├── mmdb-writer.go └── test-data.csv ├── vmod_geoip2.c └── vmod_geoip2.vcc /.github/workflows/ci-devel.yml: -------------------------------------------------------------------------------- 1 | name: ci-devel 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "11 3 * * 6" 8 | 9 | jobs: 10 | test: 11 | if: success() || failure() 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | compiler: [clang, gcc] 18 | 19 | steps: 20 | - uses: varnishcache-friends/setup-varnish@v1 21 | - run: | 22 | sudo apt-get update 23 | sudo apt-get install -y python3-docutils libmaxminddb-dev 24 | - uses: actions/checkout@v4 25 | with: 26 | submodules: recursive 27 | - run: ./autogen.sh 28 | - name: enable gcov 29 | if: matrix.compiler == 'gcc' 30 | run: echo "CONFIGURE_ARGS=--enable-gcov" >> $GITHUB_ENV 31 | - name: configure 32 | run: CC=${{ matrix.compiler }} ./configure CFLAGS="-Wall -Wextra -Werror" $CONFIGURE_ARGS 33 | - run: make 34 | - run: make check VERBOSE=1 35 | - name: codecov 36 | if: matrix.compiler == 'gcc' 37 | run: | 38 | curl -Os https://uploader.codecov.io/latest/linux/codecov 39 | chmod +x codecov 40 | ./codecov -v -f src/.libs/libvmod-geoip2.info -t ${{ secrets.CODECOV_TOKEN }} 41 | - run: make distcheck 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.la 2 | *.lo 3 | *.o 4 | .deps/ 5 | .libs/ 6 | /aclocal.m4 7 | /autom4te.cache/ 8 | /build-aux/ 9 | /config.h 10 | /config.h.in 11 | /config.log 12 | /config.status 13 | /configure 14 | /libtool 15 | /m4/libtool.m4 16 | /m4/lt~obsolete.m4 17 | /m4/ltoptions.m4 18 | /m4/ltsugar.m4 19 | /m4/ltversion.m4 20 | /src/*.3 21 | /src/*.rst 22 | /src/lcov/ 23 | /src/tests/*.log 24 | /src/tests/*.trs 25 | /src/test-suite.log 26 | /src/vcc_if.c 27 | /src/vcc_if.h 28 | /stamp-h1 29 | Makefile 30 | Makefile.in 31 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## unreleased 2 | 3 | * Move repository to varnishcache-friends 4 | 5 | ## 1.3.0 - 2022-06-16 6 | 7 | * Fix null ip handling (#42). 8 | * Drop support for Varnish 6.4, 6.5 and 6.6, and add support for 9 | 7.0 and 7.1. 10 | 11 | ## 1.2.2 - 2021-04-23 12 | 13 | * When we cannot open the DB, send the error to the CLI as well. 14 | * Drop support for Varnish 6.2, 6.3 and 6.4, and add support for 15 | 6.5 and 6.6. 16 | 17 | ## 1.2.1 - 2019-09-29 18 | 19 | * Retire oldstable branch. 20 | * Change master to cover 6.0, 6.2 and 6.3. 21 | * Move master to 6.2. 22 | * Sync with recent changes in varnish master. 23 | * Silence warning when processing the .vcc file. 24 | * Switch Travis CI to xenial and hook codecov.io. 25 | * Add support for returning bytes and strings as JSON. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2022, Federico G. Schwindt 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 -I ${VARNISH_DATAROOT}/aclocal 2 | 3 | SUBDIRS = src 4 | 5 | DISTCHECK_CONFIGURE_FLAGS = VMOD_DIR='$${libdir}/varnish/vmods' 6 | 7 | dist_doc_DATA = LICENSE 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libvmod-geoip2 2 | ============== 3 | 4 | ![ci](https://github.com/varnishcache-friends/libvmod-geoip2/workflows/ci-devel/badge.svg) 5 | [![codecov](https://codecov.io/gh/varnishcache-friends/libvmod-geoip2/branch/devel/graph/badge.svg?token=4xGDQ6c35o)](https://codecov.io/gh/varnishcache-friends/libvmod-geoip2) 6 | 7 | ## About 8 | 9 | A Varnish master VMOD to query MaxMind GeoIP2 DB files. 10 | 11 | For Varnish 6.0, 7.4 and 7.5 refer to main branch. Older Varnish 12 | versions are no longer supported. 13 | 14 | ## Requirements 15 | 16 | To build this VMOD you will need: 17 | 18 | * make 19 | * a C compiler, e.g. GCC or clang 20 | * pkg-config 21 | * python3-docutils or docutils in macOS [1] 22 | * Varnish master built from sources 23 | * libmaxminddb-dev in recent Debian/Ubuntu releases, maxminddb in 24 | macOS [1]. See also https://github.com/maxmind/libmaxminddb 25 | 26 | If you are building from Git, you will also need: 27 | 28 | * autoconf 29 | * automake 30 | * libtool 31 | 32 | You will also need to set `PKG_CONFIG_PATH` to the directory where 33 | **varnishapi.pc** is located before running `autogen.sh` and 34 | `configure`. For example: 35 | 36 | ``` 37 | export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 38 | ``` 39 | 40 | Finally, to use it you will need one or more GeoIP2 or GeoLite2 41 | binary databases. See https://dev.maxmind.com/. 42 | 43 | ## Installation 44 | 45 | ### From a tarball 46 | 47 | To install this VMOD, run the following commands: 48 | 49 | ``` 50 | ./configure 51 | make 52 | make check 53 | sudo make install 54 | ``` 55 | 56 | The `make check` step is optional but it's good to know whether the 57 | tests are passing on your platform. 58 | 59 | ### From the Git repository 60 | 61 | To install from Git, clone this repository by running: 62 | 63 | ``` 64 | git clone --recursive https://github.com/varnishcache-friends/libvmod-geoip2 65 | ``` 66 | 67 | And then run `./autogen.sh` followed by the instructions above for 68 | installing from a tarball. 69 | 70 | ### Packages 71 | 72 | See https://github.com/varnishcache-friends/libvmod-geoip2/wiki#packages. 73 | 74 | ## Example 75 | 76 | ``` 77 | import geoip2; 78 | 79 | sub vcl_init { 80 | new country = geoip2.geoip2("/path/to/GeoLite2-Country.mmdb"); 81 | } 82 | 83 | sub vcl_recv { 84 | if (country.lookup("country/names/en", client.ip) != "Japan") { 85 | ... 86 | } 87 | } 88 | ``` 89 | 90 | More examples available at https://github.com/varnishcache-friends/libvmod-geoip2/wiki. 91 | 92 | ## DB updates 93 | 94 | To update the GeoIP2 DB, download the new file on the same filesystem 95 | as the old one and move it over. See also 96 | https://github.com/maxmind/geoipupdate. 97 | 98 | ## License 99 | 100 | This VMOD is licensed under BSD license. See LICENSE for details. 101 | 102 | ### Note 103 | 104 | 1. Using Homebrew, https://github.com/Homebrew/brew/. 105 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dataroot=$(pkg-config --variable=datarootdir varnishapi 2>/dev/null) 4 | if [ -z "$dataroot" ] ; then 5 | cat <<_EOF 6 | 7 | No package 'varnishapi' found 8 | 9 | Consider adjusting the PKG_CONFIG_PATH environment variable if you 10 | installed software in a non-standard prefix. 11 | 12 | _EOF 13 | exit 1 14 | fi 15 | export VARNISH_DATAROOT=${dataroot} 16 | autoreconf -vif -I${dataroot}/aclocal 17 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([libvmod-geoip2], [devel]) 2 | AC_CONFIG_MACRO_DIR([m4]) 3 | AC_CONFIG_AUX_DIR([build-aux]) 4 | AC_CONFIG_SRCDIR(src/vmod_geoip2.vcc) 5 | AM_CONFIG_HEADER(config.h) 6 | m4_ifndef([VARNISH_VMOD_INCLUDES], AC_MSG_ERROR([varnish.m4 is required.])) 7 | 8 | AM_INIT_AUTOMAKE([foreign color-tests parallel-tests]) 9 | 10 | AC_DISABLE_STATIC 11 | 12 | AC_PROG_CC_C99 13 | AC_PROG_LIBTOOL 14 | 15 | AC_ARG_ENABLE([gcov], 16 | [AS_HELP_STRING([--enable-gcov], 17 | [enable code coverage analysis (default=no)])], 18 | [AC_PATH_PROG([GENHTML], [genhtml]) 19 | AC_PATH_PROG([GCOV], [gcov]) 20 | AC_PATH_PROG([LCOV], [lcov]) 21 | CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage"]) 22 | AM_CONDITIONAL(USE_LCOV, 23 | [test x$GENHTML != x && test x$GCOV != x && test x$LCOV != x]) 24 | 25 | PKG_CHECK_EXISTS([libmaxminddb], [ 26 | PKG_CHECK_MODULES([MAXMINDDB], [libmaxminddb])], [ 27 | AC_SEARCH_LIBS([MMDB_open], [maxminddb], [], 28 | AC_MSG_ERROR([maxminddb is required.])) 29 | ]) 30 | 31 | m4_define_default([_AM_PYTHON_INTERPRETER_LIST], 32 | [python3.9 python3.8 python3.7 python3.6 python3.5 dnl 33 | python3.4 python3 python]) 34 | AM_PATH_PYTHON([3.4], [], [ 35 | AC_MSG_ERROR([Python 3.4 or later is required.]) 36 | ]) 37 | 38 | AC_PATH_PROGS([RST2MAN], [rst2man rst2man.py]) 39 | test -z "$RST2MAN" && AC_MSG_ERROR([rst2man is required.]) 40 | 41 | PKG_CHECK_VAR([VARNISH_PREFIX], [varnishapi], [prefix]) 42 | PKG_CHECK_VAR([VARNISH_BINDIR], [varnishapi], [bindir]) 43 | PKG_CHECK_VAR([VARNISH_SBINDIR], [varnishapi], [sbindir]) 44 | PKG_CHECK_VAR([VARNISH_DATAROOT], [varnishapi], [datarootdir]) 45 | 46 | VARNISH_VMOD_INCLUDES 47 | VARNISH_VMOD_DIR 48 | VARNISH_VMODTOOL 49 | 50 | ac_default_prefix=$VARNISH_PREFIX 51 | 52 | AC_PATH_PROG([VARNISHTEST], [varnishtest], [], [$VARNISH_BINDIR:$PATH]) 53 | AC_PATH_PROG([VARNISHD], [varnishd], [], [$VARNISH_SBINDIR:$PATH]) 54 | 55 | VMOD_TESTS="$(cd $srcdir/src && echo tests/*.vtc)" 56 | AC_SUBST(VMOD_TESTS) 57 | 58 | AC_CONFIG_FILES([Makefile src/Makefile]) 59 | AC_OUTPUT 60 | -------------------------------------------------------------------------------- /m4/pkg.local.m4: -------------------------------------------------------------------------------- 1 | m4_ifndef([PKG_CHECK_VAR], [ 2 | # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, 3 | # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) 4 | # ------------------------------------------- 5 | # Retrieves the value of the pkg-config variable for the given module. 6 | AC_DEFUN([PKG_CHECK_VAR], 7 | [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl 8 | AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl 9 | 10 | _PKG_CONFIG([$1], [variable="][$3]["], [$2]) 11 | AS_VAR_COPY([$1], [pkg_cv_][$1]) 12 | 13 | AS_VAR_IF([$1], [""], [$5], [$4])dnl 14 | ])# PKG_CHECK_VAR 15 | ]) 16 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | TESTS_ENVIRONMENT = PATH=$(VARNISH_BINDIR):$(VARNISH_SBINDIR):$$PATH 3 | 4 | vmoddir = $(VMOD_DIR) 5 | vmod_LTLIBRARIES = libvmod_geoip2.la 6 | 7 | libvmod_geoip2_la_CFLAGS = $(VMOD_INCLUDES) $(MAXMINDDB_CFLAGS) 8 | libvmod_geoip2_la_LDFLAGS = -module -export-dynamic -avoid-version -shared \ 9 | $(LIBS) $(MAXMINDDB_LIBS) 10 | 11 | libvmod_geoip2_la_SOURCES = \ 12 | vmod_geoip2.c 13 | nodist_libvmod_geoip2_la_SOURCES = \ 14 | vcc_if.c \ 15 | vcc_if.h 16 | 17 | dist_man_MANS = vmod_geoip2.3 18 | 19 | VTC_LOG_COMPILER = $(VARNISHTEST) -v \ 20 | -Dvmod_topbuild=$(abs_top_builddir) -Dvmod_topsrc=$(abs_top_srcdir) 21 | TEST_EXTENSIONS = .vtc 22 | TESTS = @VMOD_TESTS@ 23 | 24 | $(libvmod_geoip2_la_OBJECTS): vcc_if.h 25 | 26 | vcc_if.h vmod_geoip2.man.rst vmod_geoip2.rst: vcc_if.c 27 | 28 | vcc_if.c: $(VMODTOOL) $(top_srcdir)/src/vmod_geoip2.vcc 29 | $(PYTHON) $(VMODTOOL) $(top_srcdir)/src/vmod_geoip2.vcc 30 | 31 | vmod_geoip2.3: vmod_geoip2.man.rst 32 | $(RST2MAN) $? $@ 33 | 34 | EXTRA_DIST = vmod_geoip2.vcc \ 35 | tests/libvmod-geoip2.mmdb \ 36 | $(TESTS) 37 | 38 | CLEANFILES = $(builddir)/vcc_if.c \ 39 | $(builddir)/vcc_if.h \ 40 | $(builddir)/vmod_geoip2.man.rst \ 41 | $(builddir)/vmod_geoip2.rst \ 42 | $(builddir)/$(dist_man_MANS) 43 | 44 | if USE_LCOV 45 | check-local: 46 | $(LCOV) -c -d .libs -o .libs/$(PACKAGE).info 47 | $(GENHTML) -o lcov .libs/$(PACKAGE).info 48 | $(GCOV) -o .libs/libvmod_geoip2_la-vcc_if.gcno vmod_geoip2.c 49 | 50 | clean-local: 51 | rm -rf lcov 52 | endif 53 | -------------------------------------------------------------------------------- /src/tests/b00000.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test supported types" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -vcl+backend { 11 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 12 | import std; 13 | 14 | sub vcl_init { 15 | new db1 = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 16 | } 17 | 18 | sub vcl_deliver { 19 | set resp.http.boolean = db1.lookup( 20 | "boolean", 21 | std.ip("1.1.1.1", "0.0.0.0")); 22 | set resp.http.bytes = db1.lookup( 23 | "bytes", 24 | std.ip("1.1.1.1", "0.0.0.0")); 25 | set resp.http.bytes_json = db1.lookup( 26 | "bytes", 27 | std.ip("1.1.1.1", "0.0.0.0"), 28 | true); 29 | set resp.http.double = db1.lookup( 30 | "double", 31 | std.ip("1.1.1.1", "0.0.0.0")); 32 | set resp.http.float = db1.lookup( 33 | "float", 34 | std.ip("1.1.1.1", "0.0.0.0")); 35 | set resp.http.int32 = db1.lookup( 36 | "int32", 37 | std.ip("1.1.1.1", "0.0.0.0")); 38 | set resp.http.utf8_stringX = db1.lookup( 39 | "map/mapX/utf8_stringX", 40 | std.ip("1.1.1.1", "0.0.0.0")); 41 | set resp.http.utf8_stringX_json = db1.lookup( 42 | "map/mapX/utf8_stringX", 43 | std.ip("1.1.1.1", "0.0.0.0"), 44 | true); 45 | set resp.http.uint16 = db1.lookup( 46 | "uint16", 47 | std.ip("1.1.1.1", "0.0.0.0")); 48 | set resp.http.uint32 = db1.lookup( 49 | "uint32", 50 | std.ip("1.1.1.1", "0.0.0.0")); 51 | set resp.http.uint64 = db1.lookup( 52 | "uint64", 53 | std.ip("1.1.1.1", "0.0.0.0")); 54 | set resp.http.utf8_string = db1.lookup( 55 | "utf8_string", 56 | std.ip("1.1.1.1", "0.0.0.0")); 57 | } 58 | } -start 59 | 60 | client c1 { 61 | txreq 62 | rxresp 63 | expect resp.http.boolean == "true" 64 | expect resp.http.bytes == "0000002A" 65 | expect resp.http.bytes_json == {"0000002A"} 66 | expect resp.http.double == "42.123456" 67 | expect resp.http.float == "1.100000" 68 | expect resp.http.int32 == "-268435456" 69 | expect resp.http.utf8_stringX == "hello" 70 | expect resp.http.utf8_stringX_json == {"hello"} 71 | expect resp.http.uint16 == "100" 72 | expect resp.http.uint32 == "268435456" 73 | expect resp.http.uint64 == "1152921504606846976" 74 | expect resp.http.utf8_string == "unicode! ☯ - ♫" 75 | } -run 76 | -------------------------------------------------------------------------------- /src/tests/b00001.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test various lookups" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -vcl+backend { 11 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 12 | import std; 13 | 14 | sub vcl_init { 15 | new db1 = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 16 | new db2 = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 17 | new db3 = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 18 | } 19 | 20 | sub vcl_deliver { 21 | set resp.http.city = db1.lookup( 22 | "city/names/en", 23 | std.ip("81.2.69.192", "0.0.0.0")); 24 | set resp.http.country = db1.lookup( 25 | "country/names/en", 26 | std.ip("2001:218::1", "0.0.0.0")); 27 | set resp.http.connection-type = db2.lookup( 28 | "connection_type", 29 | std.ip("1.0.128.1", "0.0.0.0")); 30 | set resp.http.isp = db3.lookup( 31 | "isp", 32 | std.ip("1.0.128.1", "0.0.0.0")); 33 | } 34 | } -start 35 | 36 | client c1 { 37 | txreq 38 | rxresp 39 | expect resp.http.city == "London" 40 | expect resp.http.country == "Japan" 41 | expect resp.http.connection-type == "Cable/DSL" 42 | expect resp.http.isp == "TOT Public Company Limited" 43 | } -run 44 | -------------------------------------------------------------------------------- /src/tests/b00002.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test error cases" 2 | 3 | server s1 -repeat 2 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -arg "-p thread_pools=1" -vcl+backend { } -start 11 | 12 | varnish v1 -errvcl "Error opening the specified MaxMind DB file" { 13 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 14 | backend default { 15 | .host = "127.0.0.1"; 16 | } 17 | 18 | sub vcl_init { 19 | new db = geoip2.geoip2("${tmpdir}/non-existent.mmdb"); 20 | } 21 | } 22 | 23 | varnish v1 -vcl+backend { 24 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 25 | import std; 26 | 27 | sub vcl_init { 28 | new db = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 29 | } 30 | 31 | sub vcl_deliver { 32 | set resp.http.path-not-found = db.lookup( 33 | "non/existent/path", 34 | std.ip("81.2.69.192", "0.0.0.0")); 35 | set resp.http.type-not-supported = db.lookup( 36 | "city/names", 37 | std.ip("81.2.69.192", "0.0.0.0")); 38 | set resp.http.addr-not-found = db.lookup( 39 | "city/names/en", 40 | std.ip("0.0.0.0", "0.0.0.0")); 41 | set resp.http.missing-path1 = db.lookup( 42 | req.http.nonexistent, 43 | std.ip("0.0.0.0", "0.0.0.0")); 44 | set resp.http.missing-path2 = db.lookup( 45 | "", 46 | std.ip("0.0.0.0", "0.0.0.0")); 47 | } 48 | } 49 | 50 | logexpect l1 -v v1 -g raw -d 1 { 51 | expect * 0 Debug \ 52 | "geoip2.geoip2: Using maxminddb [0-9]" 53 | expect * = Error \ 54 | "geoip2.geoip2: Error opening the specified MaxMind DB file" 55 | 56 | expect * 1001 Debug \ 57 | "geoip2.lookup: No data for this path \\(non/existent/path\\)" 58 | expect * = Error \ 59 | "geoip2.lookup: Unsupported data type \\(7\\)" 60 | expect * = Debug \ 61 | "geoip2.lookup: No entry for this IP address \\(0.0.0.0\\)" 62 | expect * = Error \ 63 | "geoip2.lookup: Invalid or missing path \\(NULL\\)" 64 | expect * = Error \ 65 | "geoip2.lookup: Invalid or missing path \\(\\)" 66 | } -start 67 | 68 | client c1 { 69 | txreq 70 | rxresp 71 | expect resp.http.path-not-found == "" 72 | expect resp.http.type-not-supported == "" 73 | expect resp.http.addr-not-found == "" 74 | expect resp.http.missing-path1 == "" 75 | expect resp.http.missing-path2 == "" 76 | } -run 77 | 78 | logexpect l1 -wait 79 | 80 | varnish v1 -vcl+backend { } 81 | 82 | client c1 { 83 | txreq 84 | rxresp 85 | } -run 86 | 87 | varnish v1 -cliok "vcl.discard vcl1" 88 | varnish v1 -cliok "vcl.discard vcl3" 89 | -------------------------------------------------------------------------------- /src/tests/b00003.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test workspace exhaustion" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -vcl+backend { 11 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 12 | import vtc; 13 | 14 | sub vcl_init { 15 | new db1 = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 16 | } 17 | 18 | sub vcl_deliver { 19 | vtc.workspace_alloc(client, -10); 20 | set resp.http.city = db1.lookup( 21 | "long_string", 22 | client.ip); 23 | } 24 | } -start 25 | 26 | logexpect l1 -v v1 -g raw -d 1 { 27 | expect * 1001 Error "geoip2.lookup: Out of workspace" 28 | } -start 29 | 30 | client c1 { 31 | txreq 32 | rxresp 33 | } -run 34 | 35 | logexpect l1 -wait 36 | -------------------------------------------------------------------------------- /src/tests/b00004.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test logging from vcl_init{}" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -vcl+backend { 11 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 12 | import std; 13 | 14 | sub vcl_init { 15 | new db = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 16 | std.log(db.lookup( 17 | "city/names/en", 18 | std.ip("0.0.0.0", "0.0.0.0"))); 19 | } 20 | } -start 21 | 22 | logexpect l1 -v v1 -g raw -d 1 { 23 | expect * 0 Debug \ 24 | "geoip2.lookup: No entry for this IP address \\(0.0.0.0\\)" 25 | } -start 26 | 27 | client c1 { 28 | txreq 29 | rxresp 30 | } -run 31 | 32 | logexpect l1 -wait 33 | -------------------------------------------------------------------------------- /src/tests/b00005.vtc: -------------------------------------------------------------------------------- 1 | varnishtest "Test passing a null ip fails gracefully" 2 | 3 | server s1 { 4 | rxreq 5 | txresp 6 | } -start 7 | 8 | shell "cp ${vmod_topsrc}/src/tests/libvmod-geoip2.mmdb ${tmpdir}" 9 | 10 | varnish v1 -vcl+backend { 11 | import geoip2 from "${vmod_topbuild}/src/.libs/libvmod_geoip2.so"; 12 | import std; 13 | import vtc; 14 | 15 | sub vcl_init { 16 | new db = geoip2.geoip2("${tmpdir}/libvmod-geoip2.mmdb"); 17 | std.log(db.lookup("city/names/en", vtc.no_ip())); 18 | } 19 | } -start 20 | 21 | logexpect l1 -v v1 -g raw -d 1 { 22 | expect * 0 Error \ 23 | "geoip2.lookup: Missing ip address" 24 | } -start 25 | 26 | client c1 { 27 | txreq 28 | rxresp 29 | } -run 30 | 31 | logexpect l1 -wait 32 | -------------------------------------------------------------------------------- /src/tests/libvmod-geoip2.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varnishcache-friends/libvmod-geoip2/f4da203761c2f5a7b2c865b1b90c681a5158fd0a/src/tests/libvmod-geoip2.mmdb -------------------------------------------------------------------------------- /src/tests/mmdb-writer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/csv" 5 | "encoding/hex" 6 | "io" 7 | "log" 8 | "net" 9 | "os" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/maxmind/mmdbwriter" 14 | "github.com/maxmind/mmdbwriter/inserter" 15 | "github.com/maxmind/mmdbwriter/mmdbtype" 16 | ) 17 | 18 | func makeRecord(key, mmdbType, value string) mmdbtype.DataType { 19 | var mmdbValue mmdbtype.DataType 20 | switch mmdbType { 21 | case "bool": 22 | v, _ := strconv.ParseBool(value) 23 | mmdbValue = mmdbtype.Bool(v) 24 | case "bytes": 25 | v, _ := hex.DecodeString(value) 26 | mmdbValue = mmdbtype.Bytes(v) 27 | case "double": 28 | v, _ := strconv.ParseFloat(value, 64) 29 | mmdbValue = mmdbtype.Float64(v) 30 | case "float": 31 | v, _ := strconv.ParseFloat(value, 32) 32 | mmdbValue = mmdbtype.Float32(v) 33 | case "int32": 34 | v, _ := strconv.ParseInt(value, 10, 32) 35 | mmdbValue = mmdbtype.Int32(v) 36 | case "uint16": 37 | v, _ := strconv.ParseInt(value, 10, 16) 38 | mmdbValue = mmdbtype.Uint16(v) 39 | case "uint32": 40 | v, _ := strconv.ParseInt(value, 10, 32) 41 | mmdbValue = mmdbtype.Uint32(v) 42 | case "uint64": 43 | v, _ := strconv.ParseInt(value, 10, 64) 44 | mmdbValue = mmdbtype.Uint64(v) 45 | case "string": 46 | mmdbValue = mmdbtype.String(value) 47 | case "map": 48 | keys := strings.Split(key, "/") 49 | key = keys[0] 50 | mmdbValue = mmdbtype.Map{ 51 | mmdbtype.String(keys[1]): mmdbtype.Map{ 52 | mmdbtype.String(keys[2]): mmdbtype.String(value), 53 | }, 54 | } 55 | } 56 | record := mmdbtype.Map{} 57 | record[mmdbtype.String(key)] = mmdbValue 58 | 59 | return record 60 | } 61 | 62 | func main() { 63 | writer, err := mmdbwriter.New( 64 | mmdbwriter.Options{ 65 | DatabaseType: "libvmod-geoip2", 66 | Description: map[string]string{"en": "libvmod-geoip2 test database"}, 67 | IncludeReservedNetworks: true, 68 | Inserter: inserter.TopLevelMergeWith, 69 | }, 70 | ) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | file, err := os.Open("test-data.csv") 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | 80 | reader := csv.NewReader(file) 81 | 82 | for { 83 | row, err := reader.Read() 84 | if err == io.EOF { 85 | break 86 | } 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | 91 | _, network, err := net.ParseCIDR(row[0]) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | record := makeRecord(row[1], row[2], row[3]) 97 | err = writer.Insert(network, record) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | } 102 | 103 | file, err = os.Create("libvmod-geoip2.mmdb") 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | 108 | _, err = writer.WriteTo(file) 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/tests/test-data.csv: -------------------------------------------------------------------------------- 1 | "1.0.128.1/32","connection_type","string","Cable/DSL" 2 | "1.0.128.1/32","isp","string","TOT Public Company Limited" 3 | "1.1.1.1/32","boolean","bool","true" 4 | "1.1.1.1/32","bytes","bytes","0000002a" 5 | "1.1.1.1/32","double","double","42.123456" 6 | "1.1.1.1/32","float","float","1.100000" 7 | "1.1.1.1/32","int32","int32","-268435456" 8 | "1.1.1.1/32","map/mapX/utf8_stringX","map","hello" 9 | "1.1.1.1/32","uint16","uint16","100" 10 | "1.1.1.1/32","uint32","uint32","268435456" 11 | "1.1.1.1/32","uint64","uint64","1152921504606846976" 12 | "1.1.1.1/32","utf8_string","string","unicode! ☯ - ♫" 13 | "127.0.0.1/32","long_string","string","thequickbrownfoxjumpsoverthelazydog1234567890" 14 | "2001:218::1/32","country/names/en","map","Japan" 15 | "81.2.69.192/32","city/names/en","map","London" 16 | -------------------------------------------------------------------------------- /src/vmod_geoip2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2018, Federico G. Schwindt 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include "cache/cache.h" 35 | 36 | #include "vsa.h" 37 | #include "vsb.h" 38 | 39 | #include "vcc_if.h" 40 | 41 | struct vmod_geoip2_geoip2 { 42 | unsigned magic; 43 | #define VMOD_GEOIP2_MAGIC 0x19800829 44 | MMDB_s mmdb; 45 | }; 46 | 47 | #define COMPONENT_MAX 10 48 | #define LOOKUP_PATH_MAX 100 49 | 50 | 51 | static void 52 | vslv(VRT_CTX, enum VSL_tag_e tag, const char *fmt, ...) 53 | { 54 | va_list ap; 55 | 56 | va_start(ap, fmt); 57 | if (ctx->vsl) 58 | VSLbv(ctx->vsl, tag, fmt, ap); 59 | else 60 | VSLv(tag, NO_VXID, fmt, ap); 61 | va_end(ap); 62 | } 63 | 64 | VCL_VOID 65 | vmod_geoip2__init(VRT_CTX, struct vmod_geoip2_geoip2 **vpp, 66 | const char *vcl_name, VCL_STRING filename) 67 | { 68 | struct vmod_geoip2_geoip2 *vp; 69 | MMDB_s mmdb; 70 | int error; 71 | 72 | (void)vcl_name; 73 | 74 | CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); 75 | AN(vpp); 76 | AZ(*vpp); 77 | 78 | VSL(SLT_Debug, NO_VXID, 79 | "geoip2.geoip2: Using maxminddb %s", 80 | MMDB_lib_version()); 81 | 82 | error = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); 83 | if (error != MMDB_SUCCESS) { 84 | char errstr[512]; 85 | 86 | snprintf(errstr, sizeof(errstr), 87 | "geoip2.geoip2: %s", 88 | MMDB_strerror(error)); 89 | VSL(SLT_Error, NO_VXID, "%s", errstr); 90 | /* Send the error to the CLI too. */ 91 | VSB_printf(ctx->msg, "%s\n", errstr); 92 | return; 93 | } 94 | 95 | ALLOC_OBJ(vp, VMOD_GEOIP2_MAGIC); 96 | AN(vp); 97 | *vpp = vp; 98 | vp->mmdb = mmdb; 99 | } 100 | 101 | VCL_VOID 102 | vmod_geoip2__fini(struct vmod_geoip2_geoip2 **vpp) 103 | { 104 | struct vmod_geoip2_geoip2 *vp; 105 | 106 | AN(*vpp); 107 | vp = *vpp; 108 | *vpp = NULL; 109 | CHECK_OBJ_NOTNULL(vp, VMOD_GEOIP2_MAGIC); 110 | MMDB_close(&vp->mmdb); 111 | FREE_OBJ(vp); 112 | } 113 | 114 | static char * 115 | printf_bytes(struct ws *ws, const uint8_t *bytes, uint32_t size, 116 | unsigned json) 117 | { 118 | char *p; 119 | uint32_t i; 120 | 121 | p = WS_Alloc(ws, size * 2 + json * 2 + 1); 122 | if (p == NULL) 123 | return (p); 124 | for (i = 0; i < size; i++) 125 | sprintf(&p[i * 2 + json], "%02X", bytes[i]); 126 | if (json) { 127 | p[0] = '"'; 128 | p[size * 2 + 1] = '"'; 129 | p[size * 2 + 2] = '\0'; 130 | } 131 | return (p); 132 | } 133 | 134 | VCL_STRING 135 | vmod_geoip2_lookup(VRT_CTX, struct vmod_geoip2_geoip2 *vp, 136 | VCL_STRING path, VCL_IP addr, VCL_BOOL json) 137 | { 138 | MMDB_lookup_result_s res; 139 | MMDB_entry_data_s data; 140 | const struct sockaddr *sa; 141 | socklen_t addrlen; 142 | const char **ap, *arrpath[COMPONENT_MAX]; 143 | const char *fmt, *q; 144 | char buf[LOOKUP_PATH_MAX]; 145 | char *p, *last; 146 | int error; 147 | 148 | CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); 149 | CHECK_OBJ_NOTNULL(vp, VMOD_GEOIP2_MAGIC); 150 | 151 | if (!path || !*path || strlen(path) >= sizeof(buf)) { 152 | vslv(ctx, SLT_Error, 153 | "geoip2.lookup: Invalid or missing path (%s)", 154 | path ? path : "NULL"); 155 | return (NULL); 156 | } 157 | 158 | if (!addr) { 159 | vslv(ctx, SLT_Error, 160 | "geoip2.lookup: Missing ip address"); 161 | return (NULL); 162 | } 163 | 164 | sa = VSA_Get_Sockaddr(addr, &addrlen); 165 | AN(sa); 166 | 167 | res = MMDB_lookup_sockaddr(&vp->mmdb, sa, &error); 168 | if (error != MMDB_SUCCESS) { 169 | vslv(ctx, SLT_Error, 170 | "geoip2.lookup: MMDB_lookup_sockaddr: %s", 171 | MMDB_strerror(error)); 172 | return (NULL); 173 | } 174 | 175 | if (!res.found_entry) { 176 | vslv(ctx, SLT_Debug, 177 | "geoip2.lookup: No entry for this IP address (%s)", 178 | VRT_IP_string(ctx, addr)); 179 | return (NULL); 180 | } 181 | 182 | strncpy(buf, path, sizeof(buf)); 183 | 184 | last = NULL; 185 | for (p = buf, ap = arrpath; ap < &arrpath[COMPONENT_MAX - 1] && 186 | (*ap = strtok_r(p, "/", &last)) != NULL; p = NULL) { 187 | if (**ap != '\0') 188 | ap++; 189 | } 190 | *ap = NULL; 191 | 192 | error = MMDB_aget_value(&res.entry, &data, arrpath); 193 | if (error != MMDB_SUCCESS && 194 | error != MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { 195 | vslv(ctx, SLT_Error, 196 | "geoip2.lookup: MMDB_aget_value: %s", 197 | MMDB_strerror(error)); 198 | return (NULL); 199 | } 200 | 201 | if (!data.has_data) { 202 | vslv(ctx, SLT_Debug, 203 | "geoip2.lookup: No data for this path (%s)", 204 | path); 205 | return (NULL); 206 | } 207 | 208 | switch (data.type) { 209 | case MMDB_DATA_TYPE_BOOLEAN: 210 | q = WS_Printf(ctx->ws, "%s", data.boolean ? 211 | "true" : "false"); 212 | break; 213 | 214 | case MMDB_DATA_TYPE_BYTES: 215 | q = printf_bytes(ctx->ws, data.bytes, 216 | data.data_size, json); 217 | break; 218 | 219 | case MMDB_DATA_TYPE_DOUBLE: 220 | q = WS_Printf(ctx->ws, "%f", data.double_value); 221 | break; 222 | 223 | case MMDB_DATA_TYPE_FLOAT: 224 | q = WS_Printf(ctx->ws, "%f", data.float_value); 225 | break; 226 | 227 | case MMDB_DATA_TYPE_INT32: 228 | q = WS_Printf(ctx->ws, "%i", data.int32); 229 | break; 230 | 231 | case MMDB_DATA_TYPE_UINT16: 232 | q = WS_Printf(ctx->ws, "%u", data.uint16); 233 | break; 234 | 235 | case MMDB_DATA_TYPE_UINT32: 236 | q = WS_Printf(ctx->ws, "%u", data.uint32); 237 | break; 238 | 239 | case MMDB_DATA_TYPE_UINT64: 240 | q = WS_Printf(ctx->ws, "%ju", (uintmax_t)data.uint64); 241 | break; 242 | 243 | case MMDB_DATA_TYPE_UTF8_STRING: 244 | fmt = json ? "\"%.*s\"" : "%.*s"; 245 | q = WS_Printf(ctx->ws, fmt, data.data_size, 246 | data.utf8_string); 247 | break; 248 | 249 | default: 250 | vslv(ctx, SLT_Error, 251 | "geoip2.lookup: Unsupported data type (%d)", 252 | data.type); 253 | return (NULL); 254 | } 255 | 256 | if (!q) 257 | vslv(ctx, SLT_Error, 258 | "geoip2.lookup: Out of workspace"); 259 | 260 | return (q); 261 | } 262 | -------------------------------------------------------------------------------- /src/vmod_geoip2.vcc: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2014-2016, Federico G. Schwindt 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions 7 | # are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 22 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | $Module geoip2 3 "Varnish GeoIP2 Lookup Module" 29 | 30 | DESCRIPTION 31 | =========== 32 | 33 | $Object geoip2(STRING filename) 34 | 35 | Description 36 | Opens the GeoIP2 MaxMind binary DB file. 37 | 38 | Databases in CSV format are not supported. 39 | Example 40 | new country = geoip2.geoip2("/path/to/GeoLite2-Country.mmdb"); 41 | 42 | $Method STRING .lookup(STRING path, IP ip, BOOL json=0) 43 | 44 | Description 45 | Looks up the IP address `ip` using the lookup path `path`. 46 | 47 | The path consists of a set of map keys and array indexes separated 48 | by slash (/), e.g. "subdivisions/0/names/en". 49 | 50 | Returns the value associated with the specified path, or an 51 | empty string if the IP address or the path are not found, the 52 | value type is not supported or an error occurred. 53 | 54 | If the value associated with the specified path is bytes 55 | or string type and `json` is true, the result will be 56 | converted to a JSON string. 57 | 58 | Supported value types include: boolean, bytes, double, float, 59 | int32, uint16, uint32, uint64 and (UTF-8) string. 60 | 61 | Diagnostic and error messages, if any, are logged to the VSL 62 | using the Debug and Error tags, respectively. 63 | Example 64 | | if (country.lookup("country/names/en", client.ip) != "Japan") { 65 | | ... 66 | | } 67 | 68 | ERRORS 69 | ====== 70 | 71 | * "geoip2.geoip2: %s" 72 | * "geoip2.geoip2: Using maxminddb %s" 73 | * "geoip2.lookup: Database not open" 74 | * "geoip2.lookup: Invalid or missing path (%s)" 75 | * "geoip2.lookup: MMDB_aget_value: %s" 76 | * "geoip2.lookup: MMDB_lookup_sockaddr: %s" 77 | * "geoip2.lookup: Missing ip address" 78 | * "geoip2.lookup: No data for this path (%s)" 79 | * "geoip2.lookup: No entry for this IP address (%s)" 80 | * "geoip2.lookup: Out of workspace" 81 | * "geoip2.lookup: Unsupported data type (%d)" 82 | 83 | NOTES 84 | ===== 85 | 86 | This implementation limits the path to a maximum of 9 map keys and/or 87 | array indexes. 88 | 89 | SEE ALSO 90 | ======== 91 | 92 | * `mmdblookup(1)` 93 | * `varnishlog(1)` 94 | * `vsl(7)` 95 | * `GeoIP2 Databases ` 96 | * `GeoIP2 Update program ` 97 | --------------------------------------------------------------------------------