me-web? 0.5: the shared exit measured, the route from the server, the network's upstreams
- Behind your provider's shared address, me-web? now shows how its exit hands out ports (kept, one after another, from one block, or at random), the range it gave this device, and how many customers may share the address at most; the estimate sharpens with each check on the same address. - It tests whether two devices behind the same address can reach each other (hairpin), and how long an unused connection stays open: the card says "measuring" for about two minutes after the check, then the answer. - With the test from outside, the publisher's server also traces the route back to your address: your provider's routers and the networks in between appear on the path, with their names. - The provider card says which networks your provider reaches the internet through. - On mobile data, the path shows the operator and the type of network (5G, 4G) read from the phone; on Android 13 and later the phone asks once for permission to read the network type. - The speed card adds the round trip to the publisher's server. - The network data now comes from the publisher's server, refreshed there from open sources: iptoasn.com, DB-IP Lite (CC BY 4.0), the University of Oregon Route Views Project (CC BY 4.0) and the Android Open Source Project's list of mobile operators (Apache 2.0); the release notes list each source's date and checksum. - Source links in the cards wrap instead of being cut off. - New strings, drafted in every language. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
8caed6b28d
commit
eb8be87284
145 files changed
+3704
-193
No files matched your search
@@ -1,8 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using MeWeb.Shared;
|
||||
|
||||
@@ -18,13 +21,14 @@ public class ProbeOptions
|
||||
public int PerDay { get; set; } = 30;
|
||||
}
|
||||
|
||||
public class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger<ProbeService> log)
|
||||
public partial class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger<ProbeService> log)
|
||||
{
|
||||
static readonly TimeSpan TicketLife = TimeSpan.FromSeconds(60);
|
||||
readonly ConcurrentDictionary<string, Ticket> tickets = new();
|
||||
readonly ConcurrentDictionary<string, List<DateTimeOffset>> issued = new();
|
||||
readonly ConcurrentDictionary<string, byte> running = new();
|
||||
readonly SemaphoreSlim outbound = new(32, 32);
|
||||
readonly SemaphoreSlim tracing = new(4, 4);
|
||||
|
||||
public (ViewProbeTicket Ticket, ViewRefusal Refusal, int Status) Issue(IPAddress caller)
|
||||
{
|
||||
@@ -73,7 +77,9 @@ public class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger<ProbeSe
|
||||
try
|
||||
{
|
||||
var result = new ViewProbe { Address = caller.ToString() };
|
||||
var route = Trace(caller, ct);
|
||||
foreach (var port in ports) result.Ports.Add(new ViewProbePort { Port = port, Outcome = await Knock(ticket, port, ct) });
|
||||
result.Route = await route;
|
||||
log.LogInformation("{Service} ran {Count} port(s)", nameof(ProbeService), ports.Count);
|
||||
return (result, null, StatusCodes.Status200OK);
|
||||
}
|
||||
@@ -83,6 +89,65 @@ public class ProbeService(IOptionsMonitor<ProbeOptions> options, ILogger<ProbeSe
|
||||
}
|
||||
}
|
||||
|
||||
async Task<List<ViewHop>> Trace(IPAddress target, CancellationToken ct)
|
||||
{
|
||||
var hops = new List<ViewHop>();
|
||||
await tracing.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
var info = new ProcessStartInfo("tracepath") { RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
|
||||
foreach (var argument in new[] { "-n", "-m", "16", target.ToString() }) info.ArgumentList.Add(argument);
|
||||
using var process = Process.Start(info);
|
||||
if (process is null) return hops;
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(25));
|
||||
try
|
||||
{
|
||||
while (await process.StandardOutput.ReadLineAsync(timeout.Token) is { } line)
|
||||
{
|
||||
var match = HopLine().Match(line);
|
||||
if (!match.Success || !IPAddress.TryParse(match.Groups[2].Value, out var address)) continue;
|
||||
var distance = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
if (hops.Any(h => h.Distance == distance)) continue;
|
||||
if (!IsPublic(address) || Refuse(address) is not null) continue;
|
||||
hops.Add(new ViewHop { Distance = distance, Address = address.ToString(), Milliseconds = double.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture) });
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!process.HasExited) process.Kill();
|
||||
}
|
||||
await Task.WhenAll(hops.Select(async hop =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var lookup = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
lookup.CancelAfter(TimeSpan.FromSeconds(2));
|
||||
var entry = await Dns.GetHostEntryAsync(hop.Address, lookup.Token);
|
||||
if (!string.IsNullOrWhiteSpace(entry.HostName) && entry.HostName != hop.Address) hop.Name = entry.HostName;
|
||||
}
|
||||
catch (Exception) when (!ct.IsCancellationRequested)
|
||||
{
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (Exception ex) when (ex is System.ComponentModel.Win32Exception or InvalidOperationException)
|
||||
{
|
||||
log.LogWarning("{Service} could not trace: {Error}", nameof(ProbeService), ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracing.Release();
|
||||
}
|
||||
return hops;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^\s*(\d+):\s+([0-9a-fA-F.:]+)\s+([\d.]+)ms")]
|
||||
internal static partial Regex HopLine();
|
||||
|
||||
async Task<ProbeOutcome> Knock(Ticket ticket, int port, CancellationToken ct)
|
||||
{
|
||||
await outbound.WaitAsync(ct);
|
||||
|
||||
Reference in new issue
Block a user