Files
MeWeb/tools/knowledge.cs
T
thepraandClaude Opus 5.5 aa031c6012
Build / Desktop, server and tools (push) Successful in 1m4s
Release / Every platform, the server, the release (push) Successful in 21m49s
"me-web?" 0.11.0: the name of the block your address belongs to
- The provider card now shows the address block your connection belongs to, as the RIPE Database names it, for
  example "MOBILE-USERS-BLOCK (IT)" on Iliad mobile or "FASTWEB-L3-PAT_NAT (IT)" on a Fastweb shared address. The
  path shows it under your provider too.
- No block is left out: the server keeps every block in the RIPE Database, small and unique ones included, and tells
  the app the block of the address it sees, never anyone else's. The app also carries about 430,000 of the larger
  blocks, for when the server can't be reached.
- Only each block's range, name and country are used, nothing about people. The data comes from the RIPE NCC's RIPE
  Database, used with the RIPE NCC's permission; it may lag behind the live database, and the "What this app
  contacts" sheet gives its date.
- The privacy page says that the server's answer now includes that block name; nothing new is stored or sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-16 18:10:19 +02:00

89 lines
4.2 KiB
C#

#:project ../src/MeWeb.Core/MeWeb.Core.csproj
#:project ../src/MeWeb.Shared/MeWeb.Shared.csproj
using System.Net.Http.Json;
using System.Security.Cryptography;
using MeWeb.Core.Knowledge;
using MeWeb.Shared;
const string Fallback = "https://iptoasn.com/data/ip2asn-combined.tsv.gz";
var root = FindRoot();
var output = Path.Combine(root, "artifacts", "knowledge");
var cache = Path.Combine(root, "tools", "cache");
Directory.CreateDirectory(output);
Directory.CreateDirectory(cache);
var server = Environment.GetEnvironmentVariable("MEWEB_KNOWLEDGE_URL") ?? Endpoints.Base;
using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };
http.DefaultRequestHeaders.UserAgent.ParseAdd("me-web-knowledge/2 (+https://git.thepra.dev/Arasaka/MeWeb)");
if (!args.Contains("--local"))
{
try
{
var manifest = await http.GetFromJsonAsync(server + Endpoints.Knowledge.GET_Manifest, MeWebJsonContext.Default.ViewKnowledge);
if (manifest is { Files.Count: > 0 })
{
foreach (var file in manifest.Files)
{
var bytes = await http.GetByteArrayAsync(server + Endpoints.Knowledge.GET_M_File(file.Name));
var hash = Convert.ToHexStringLower(SHA256.HashData(bytes));
if (hash != file.Sha256) throw new InvalidDataException($"{file.Name}: sha256 {hash}, the manifest says {file.Sha256}");
File.WriteAllBytes(Path.Combine(output, file.Name), bytes);
Console.WriteLine($"knowledge: {file.Name} {bytes.Length / 1024} KiB from {server}");
}
File.Move(Path.Combine(output, "SOURCES.txt"), Path.Combine(output, "SOURCE.txt"), overwrite: true);
Report();
return 0;
}
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or InvalidDataException or System.Text.Json.JsonException)
{
Console.Error.WriteLine($"knowledge: the server's files could not be used ({ex.Message}); building the provider map alone");
}
}
var download = Path.Combine(cache, "ip2asn-combined.tsv.gz");
var sourceDate = File.Exists(download) ? new DateTimeOffset(File.GetLastWriteTimeUtc(download), TimeSpan.Zero) : DateTimeOffset.UtcNow;
if (!File.Exists(download) || File.GetLastWriteTimeUtc(download) < DateTime.UtcNow.AddHours(-12) || args.Contains("--refresh"))
{
using var response = await http.GetAsync(Fallback, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
sourceDate = response.Content.Headers.LastModified ?? DateTimeOffset.UtcNow;
await using (var file = File.Create(download + ".tmp"))
await response.Content.CopyToAsync(file);
File.Move(download + ".tmp", download, overwrite: true);
File.SetLastWriteTimeUtc(download, sourceDate.UtcDateTime);
}
using (var input = File.OpenRead(download))
using (var file = File.Create(Path.Combine(output, "ip2asn.bin")))
AsnTable.Write(file, sourceDate, Sources.IpToAsn(input));
foreach (var stale in new[] { "networks.bin", "carriers.json", "registry.bin" }) File.Delete(Path.Combine(output, stale));
string sha;
using (var input = File.OpenRead(download)) sha = Convert.ToHexStringLower(SHA256.HashData(input));
File.WriteAllText(Path.Combine(output, "SOURCE.txt"), $"iptoasn.com, Public Domain Dedication and License (PDDL) 1.0\n{Fallback}\nlast-modified {sourceDate:O}\nsha256 {sha}\n");
Report();
return 0;
void Report()
{
using var stream = File.OpenRead(Path.Combine(output, "ip2asn.bin"));
var table = AsnTable.Read(stream);
var path = Path.Combine(output, "networks.bin");
NetworkTable networks = null;
if (File.Exists(path))
using (var input = File.OpenRead(path)) networks = NetworkTable.Read(input);
RegistryTable registry = null;
path = Path.Combine(output, "registry.bin");
if (File.Exists(path))
using (var input = File.OpenRead(path)) registry = RegistryTable.Read(input);
Console.WriteLine($"knowledge: {table.Count} networks in the provider map (source {table.SourceDate:yyyy-MM-dd}), {networks?.Count ?? 0} with names or upstreams, {registry?.Count ?? 0} address block names");
}
static string FindRoot()
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "MeWeb.slnx"))) dir = dir.Parent;
return dir?.FullName ?? throw new InvalidOperationException("run from inside the repository (MeWeb.slnx not found)");
}