M9 (first part): the means to locate a server

- IConnectedAddresses remembers which address each host's last connection reached, set in
  SafeHttpHandlerFactory's connect callback. That is once per pooled connection, with no
  second DNS lookup, and the address is never stored.
- IGeoLocator / DbIpLocator reads the offline DB-IP Lite city and ASN databases (MaxMind
  .mmdb, via MaxMind.Db). It maps memory, swaps to new files within ten minutes, rounds
  coordinates to one decimal, never looks up a private address, and answers nothing
  when the files are missing. CdnNetworks names the CDNs whose edge addresses say nothing
  about where a server is.
- deploy/max/geo-update.sh fetches this or last month's databases, checks them and swaps
  them in atomically. The privapub-geo timer runs it monthly as www-data, and setup.sh
  installs the directory, the script, the units and a first download.

Describing servers will use these in M8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-03 11:33:30 +02:00
1 parent fc15f6356d
commit cee85309b8
12 files changed
+357 -7

No files matched your search

@@ -0,0 +1,69 @@
using Microsoft.Extensions.Logging.Abstractions;
using PrivaPub.Infrastructure.Geo;
using PrivaPub.Infrastructure.Http;
using PrivaPub.Infrastructure.Statistics;
using PrivaPub.Tests.Support;
using System.Net;
namespace PrivaPub.Tests.Infrastructure
{
public class GeoLocatorTests
{
static DbIpLocator Locator(string directory) =>
new(new StaticOptions<StatisticsOptions>(new StatisticsOptions { GeoDirectory = directory }), NullLogger<DbIpLocator>.Instance);
[Fact]
public void Without_databases_nothing_is_located()
{
using var locator = Locator(Path.Combine(Path.GetTempPath(), $"no-geo-{Guid.NewGuid():N}"));
Assert.Null(locator.Locate(IPAddress.Parse("1.1.1.1")));
Assert.Null(locator.Source);
}
[Fact]
public void A_private_address_is_never_looked_up()
{
using var locator = Locator(Path.GetTempPath());
Assert.Null(locator.Locate(IPAddress.Parse("10.1.2.3")));
Assert.Null(locator.Locate(IPAddress.Loopback));
Assert.Null(locator.Locate(default));
}
[Fact]
public void The_real_databases_give_a_country_a_city_and_a_network()
{
var directory = Environment.GetEnvironmentVariable("PRIVAPUB_TEST_GEO");
Assert.SkipWhen(string.IsNullOrEmpty(directory), "set PRIVAPUB_TEST_GEO to a directory holding the DB-IP Lite city and ASN files");
using var locator = Locator(directory);
var fix = locator.Locate(IPAddress.Parse("1.1.1.1"));
Assert.NotNull(fix);
Assert.Equal(2, fix.Country.Length);
Assert.Equal(13335, fix.Asn);
Assert.Equal("Cloudflare", CdnNetworks.Of(fix.Asn));
Assert.NotNull(fix.AsnOrg);
Assert.NotNull(fix.Latitude);
Assert.Equal(Math.Round(fix.Latitude.Value, 1), fix.Latitude);
Assert.StartsWith("DB-IP Lite ", locator.Source);
}
[Fact]
public void A_connection_remembers_its_address_as_ipv4()
{
var connected = new ConnectedAddresses();
connected.Remember("Social.Example", IPAddress.Parse("::ffff:203.0.113.7"));
Assert.Equal(IPAddress.Parse("203.0.113.7"), connected.Of("social.example"));
Assert.Null(connected.Of("other.example"));
Assert.Equal("Fastly", CdnNetworks.Of(54113));
Assert.Null(CdnNetworks.Of(64496));
Assert.Null(CdnNetworks.Of(default));
}
}
}