internal/gateway/geo.go (lookup), internal/gateway/clientip.go
(address handling), scripts/fetch-dbip.sh (download), make geo-fetch.
Licence and attribution
Checked on 2026-11-12 at https://db-ip.com/db/download/ip-to-country-lite.- Licence: Creative Commons Attribution 4.0 International (CC BY 4.0), https://creativecommons.org/licenses/by/4.0/.
- Requirement, in DB-IP’s words: use is allowed “provided you give attribution to DB-IP.com for the data”, and web applications must “include a link back to DB-IP.com on pages that display or use results from the database”.
-
The snippet DB-IP asks for:
-
No account or key is needed. The file is updated monthly. Download URL:
https://download.db-ip.com/free/dbip-country-lite-YYYY-MM.mmdb.gz.
scripts/fetch-dbip.shwritesDBIP-ATTRIBUTION.txtnext to the database. The gateway image ships that file beside the database.- Every page that shows country data must carry the link above: the console pages with country breakdowns, the operator dashboards and the public docs. Umami’s own dashboard is run unmodified, so the console page that links into it and the tenant onboarding guide carry the attribution for it.
- The database is used unmodified. CC BY 4.0 does not restrict commercial use or require share-alike.
File and record shape
Read withgithub.com/oschwald/maxminddb-golang/v2 v2.9.0 from
dbip-country-lite-2026-11.mmdb (built 2026-11-01 02:47:39 UTC, 8,515,063
bytes uncompressed, 4,205,904 bytes gzipped).
Metadata:
One record (the answer for
8.8.8.8, names shortened):
country.iso_code. Facts that shaped the code:
- 1,410,667 networks, 251 distinct
iso_codevalues. One of them isZZ, which DB-IP uses for ranges it cannot place.ZZis not a country, so the gateway reports it as unknown (empty string).XK(Kosovo) appears and is passed through, because it is two capital letters and Umami accepts it. - Private, loopback, documentation and link-local ranges (
10/8,127/8,192.0.2/24,::1,fe80::/10,2001:db8::/32) have no record. The lookup reports them as unknown. OpenGeorefuses a file whosedatabase_typeis notDBIP-Country-Liteor whoseip_versionis not 6, so a wrong download fails at start.- Any other
iso_codevalue that is not two capital letters is an error, not an empty country.
Truncation and its cost
The address is cut to its /24 (IPv4) or /48 (IPv6) network before the lookup: the last octet is zeroed, or the lower 80 bits for IPv6. A network narrower than that can then be answered with the country of the network that holds the truncated address. Measured on the 2026-11 file byTestGeoTruncationMismatch
(internal/gateway/geo_test.go) and a standalone run during research:
How to read this:
- The uniform-by-network sample is dominated by the many tiny networks (single addresses, /29 and similar) that DB-IP carves out of larger blocks. Drawing networks uniformly gives each such sliver the same weight as a /8, so it overstates the error for real traffic.
- Weighted by address space, fewer than one IPv4 address in a thousand gets a different country. For IPv6 the difference is negligible, because nearly all allocations are /48 or wider.
- Real traffic is neither uniform over networks nor over addresses. The address-weighted figure is the closer bound for traffic from ordinary access networks. The cost is accepted, because a /24 or /48 cannot single out a household and the full address is never used for anything else.
- The test fails if the address-weighted rate rises above 0.5% in either family, so a future monthly file that changes this picture is noticed.
Monthly update
scripts/fetch-dbip.sh <dir> [YYYY-MM]downloads the month’s file (default: the current UTC month), decompresses it, opens it with the gateway’s own reader (scripts/dbipcheck), checks that a public address resolves, and writesdbip-country-lite.mmdb,dbip-country-lite.mmdb.monthandDBIP-ATTRIBUTION.txt. It never picks another month by itself: early on the 1st, before DB-IP publishes, pass the previous month explicitly.- The image build runs the script. A running gateway picks up a replaced file
(written to a temporary name and renamed over the old one) on
SIGHUP(Geo.ReloadOnSIGHUP). If the new file does not open, the error is logged at error level and the previous file stays in use.
Tests
The geo tests need the real file. They read its path fromDBIP_MMDB (absolute,
or relative to the repository root) and skip with that instruction when it is
not set: