CDNs found by themselves, and servers followed through time
PrivaPub now finds CDNs three ways, best first: the address ranges the CDNs publish (Cloudflare, Fastly, Amazon CloudFront, Bunny, Gcore, Imperva), downloaded daily by CdnUpdater and kept in CdnRangeSet; the CDN's fingerprint in the responses it already gets from a server (EdgeHintsHandler on the federation client); and the networks that carry only a CDN. The fixed ASN list is gone; ASNs shared with plain hosting (AWS, DataPacket) no longer hide a server. A server's Geo records the CDN, its domain and how it was found, and weekly snapshots now keep the city and coordinates too. Servers through time (ServerPlaces): /instances/:host/history lists a server's weekly snapshots, a CDN-fronted server's geo names the CDN's domain and where the server was before it (before_cdn), and /api/privapub/v1/cdns and /cdns/:domain group servers by CDN with week by week who joined and who left. Owner decisions recorded in ROADMAP. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
5d7edc4a4c
commit
436f7da464
26 files changed
+1159
-62
No files matched your search
@@ -0,0 +1,65 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models.Jobs;
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace PrivaPub.Infrastructure.Geo
|
||||
{
|
||||
public interface ICdnRanges
|
||||
{
|
||||
// the CDN whose published ranges hold the address, or default
|
||||
CdnProvider Of(IPAddress address);
|
||||
void Replace(CdnRangeSet set);
|
||||
Task Load(CancellationToken token);
|
||||
// per CDN key: how many ranges are known and when they were downloaded
|
||||
IReadOnlyDictionary<string, (int Ranges, DateTime FetchedAt)> Status { get; }
|
||||
}
|
||||
|
||||
// The published CDN ranges in memory, rebuilt from the stored CdnRangeSets at start and replaced set by set as
|
||||
// CdnUpdater downloads them. A few thousand networks: a linear scan per describe is cheap, and describes are weekly.
|
||||
public class CdnRanges : ICdnRanges
|
||||
{
|
||||
readonly object _gate = new();
|
||||
Dictionary<string, (CdnProvider Provider, IPNetwork[] Networks, DateTime FetchedAt)> _sets = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public IReadOnlyDictionary<string, (int Ranges, DateTime FetchedAt)> Status
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_gate)
|
||||
return _sets.ToDictionary(s => s.Key, s => (s.Value.Networks.Length, s.Value.FetchedAt), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public CdnProvider Of(IPAddress address)
|
||||
{
|
||||
if (address == default)
|
||||
return default;
|
||||
if (address.IsIPv4MappedToIPv6)
|
||||
address = address.MapToIPv4();
|
||||
Dictionary<string, (CdnProvider Provider, IPNetwork[] Networks, DateTime FetchedAt)> sets;
|
||||
lock (_gate)
|
||||
sets = _sets;
|
||||
foreach (var (provider, networks, _) in sets.Values)
|
||||
foreach (var network in networks)
|
||||
if (network.BaseAddress.AddressFamily == address.AddressFamily && network.Contains(address))
|
||||
return provider;
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Replace(CdnRangeSet set)
|
||||
{
|
||||
var provider = CdnCatalog.ByKey(set.Key) ?? new CdnProvider(set.Name, set.Key, Array.Empty<int>(), Array.Empty<CdnList>(), _ => false);
|
||||
var networks = set.Ranges.Select(r => IPNetwork.TryParse(r, out var n) ? n : (IPNetwork?)null).Where(n => n != default).Select(n => n!.Value).ToArray();
|
||||
lock (_gate)
|
||||
_sets = new(_sets, StringComparer.OrdinalIgnoreCase) { [set.Key] = (provider, networks, set.FetchedAt) };
|
||||
}
|
||||
|
||||
public async Task Load(CancellationToken token)
|
||||
{
|
||||
foreach (var set in await DB.Default.Find<CdnRangeSet>().ExecuteAsync(token))
|
||||
Replace(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user