me-web? 0.5.3: the cards no longer contradict each other
Build / Desktop, server and tools (push) Successful in 1m25s
Release / Every platform, the server, the release (push) Successful in 8m20s

- From the publisher's own network, the tests aimed at its server stay inside the house, so
  the type of address translation, the ports, the hairpin, how long a connection stays open,
  the speed and the round trip described the home network: a report said "hard NAT" next to
  "ports kept". These cards now say the test can't run there, and those messages are not sent.
- Who holds your public IPv4 address is now decided by the same evidence as the address
  sharing card, so a home router behind a provider's shared address no longer reads "your
  router" on one card and "shared with other customers" on the next.
- When your connection differs from what the provider notes say is usual (a shared address,
  port 25), the provider card adds "On this line" with what was found, right under the note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-15 12:59:42 +02:00
1 parent 5a7fbc4c52
commit c72a6be433
117 files changed
+192 -21

No files matched your search

+27 -14
View File
@@ -25,6 +25,7 @@ public class RunReport
public DnsReport Dns { get; set; }
public DeviceReport DeviceReport { get; set; }
public CellularReport Cellular { get; set; }
public bool OwnNetwork { get; set; }
public SpeedReport Speed { get; set; }
public List<UseFinding> Uses { get; set; } = [];
public HistorySummary History { get; set; }
@@ -84,12 +85,20 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
Settle(Verdicts.Online(run.Online, run.Local));
});
var router = Step(CheckId.Router, async () =>
{
await connection;
run.Router = run.Local?.Active?.Kind == LinkKind.Mobile ? new RouterReport() : await Gateway.Inspect(platform, run.Local?.GatewayV4, ct);
Settle(Verdicts.Router(run.Router, run.Local));
});
var addresses = Step(CheckId.Addresses, async () =>
{
run.PublicV4 = await v4;
run.PublicV6 = await v6;
await connection;
Settle(Verdicts.Addresses(run.PublicV4, run.PublicV6, run.Local));
await Task.WhenAll(connection, router);
run.OwnNetwork = Addresses.SameNetwork(await ServerProbe.ServerAddresses(ct), run.PublicV4, run.PublicV6);
Settle(Verdicts.Addresses(run.PublicV4, run.PublicV6, run.Local, run.Router));
});
var provider = Step(CheckId.Provider, async () =>
@@ -102,13 +111,6 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.PublicV4 is not null || run.PublicV6 is not null, run.Local?.Active?.Kind == LinkKind.Mobile));
});
var router = Step(CheckId.Router, async () =>
{
await connection;
run.Router = run.Local?.Active?.Kind == LinkKind.Mobile ? new RouterReport() : await Gateway.Inspect(platform, run.Local?.GatewayV4, ct);
Settle(Verdicts.Router(run.Router, run.Local));
});
var nat = Step(CheckId.Nat, async () =>
{
await Task.WhenAll(router, addresses);
@@ -122,15 +124,20 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
{
await connection;
run.Stun = await Stun.Compare((Shared.Endpoints.SecondStunHost, Shared.Endpoints.SecondStunPort), (Shared.Endpoints.StunHost, Shared.Endpoints.StunPort), run.Local, ct);
run.Stun.Ports = await Stun.Ports((Shared.Endpoints.StunHost, Shared.Endpoints.StunPort), 16, ct);
await addresses;
if (!run.OwnNetwork) run.Stun.Ports = await Stun.Ports((Shared.Endpoints.StunHost, Shared.Endpoints.StunPort), 16, ct);
await nat;
var (low, high) = history.PortRange(run.PublicV4);
Stun.Classify(run.Stun.Ports, low, high);
Settle(Verdicts.Behaviour(run.Stun, run.Nat));
if (run.Stun.Ports is not null)
{
var (low, high) = history.PortRange(run.PublicV4);
Stun.Classify(run.Stun.Ports, low, high);
}
Settle(Verdicts.Behaviour(run.Stun, run.Nat, run.OwnNetwork));
if (run.Stun.Ports is null) return;
_ = Task.Run(async () =>
{
await Stun.Lifetime((Shared.Endpoints.StunHost, Shared.Endpoints.StunPort), run.Stun.Ports, [30, 120], ct);
Settle(Verdicts.Behaviour(run.Stun, run.Nat));
Settle(Verdicts.Behaviour(run.Stun, run.Nat, run.OwnNetwork));
}, ct);
});
@@ -163,6 +170,11 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
Settle(new Finding { Id = CheckId.Speed, Outcome = Outcome.Unknown, Summary = "SpeedOffline" });
return;
}
if (run.OwnNetwork)
{
Settle(new Finding { Id = CheckId.Speed, Outcome = Outcome.Unknown, Summary = "ReachOwnNetwork" });
return;
}
run.Speed = await SpeedCheck.Run(https, run.Local?.Active?.Kind ?? LinkKind.Unknown, ct);
Settle(Verdicts.Speed(run.Speed));
});
@@ -183,6 +195,7 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
PortLow = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.Low : 0,
PortHigh = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.High : 0,
});
Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.PublicV4 is not null || run.PublicV6 is not null, run.Local?.Active?.Kind == LinkKind.Mobile, run));
Settle(Verdicts.History(run.History));
run.Uses = Uses.Decide(run);
});
+12
View File
@@ -127,6 +127,18 @@ public class ServerProbe(HttpClient https, HttpClient plain)
}
}
public static async Task<IPAddress[]> ServerAddresses(CancellationToken ct)
{
try
{
return await Dns.GetHostAddressesAsync(Endpoints.Host, ct);
}
catch (Exception) when (!ct.IsCancellationRequested)
{
return [];
}
}
static async Task<bool> Resolves(string host, CancellationToken ct)
{
try
+22 -6
View File
@@ -73,7 +73,7 @@ public static class Verdicts
return finding;
}
public static Finding Addresses(string v4, string v6, LocalSnapshot local)
public static Finding Addresses(string v4, string v6, LocalSnapshot local, RouterReport router)
{
var finding = new Finding { Id = CheckId.Addresses };
(finding.Outcome, finding.Summary) = (v4, v6) switch
@@ -86,9 +86,7 @@ public static class Verdicts
if (v4 is not null)
{
finding.With("FactPublicV4", v4);
var own = local.Active?.Addresses.Any(a => Net.Addresses.SameAddress(a, v4)) == true;
var shared = local.Active?.Kind == LinkKind.Mobile || Net.Addresses.Parse(local.LocalV4) is { } address && Net.Addresses.Classify(address) == AddressKind.Shared;
finding.WithKey("FactWhoHasIt", own ? "ValueOnThisDevice" : shared ? "ValueSharedProvider" : "ValueSharedAtHome");
finding.WithKey("FactWhoHasIt", Holder(NatOf(local, router, v4)));
}
if (v6 is not null)
{
@@ -99,7 +97,7 @@ public static class Verdicts
return finding;
}
public static Finding Provider(KnowledgeBase knowledge, AsnEntry v4, AsnEntry v6, bool anyAddress, bool mobile)
public static Finding Provider(KnowledgeBase knowledge, AsnEntry v4, AsnEntry v6, bool anyAddress, bool mobile, RunReport run = null)
{
var finding = new Finding { Id = CheckId.Provider };
if (!anyAddress)
@@ -131,9 +129,19 @@ public static class Verdicts
if (upstreams.Count > 0) finding.With("FactUpstreams", string.Join("\n", upstreams.Select(u => $"{knowledge.NameOf(u) ?? "AS" + u} (AS{u})")));
if (profile is null) return finding;
AddFact(finding, "FactProviderIpv4", "ProviderIpv4", profile.Ipv4);
var usual = profile.Ipv4?.Value is null ? null : Pascal(profile.Ipv4.Value);
var sharedHere = run?.Nat is NatKind.Shared or NatKind.Translated;
var ownHere = run?.Nat is NatKind.HomeRouter or NatKind.Direct or NatKind.DoubleNat;
if ((usual is "Shared" or "SharedPorts" && ownHere) || (usual is "PublicDynamic" && sharedHere))
finding.WithKey("FactThisLine", Holder(run.Nat));
AddFact(finding, "FactProviderPublic", "ProviderPublic", profile.PublicAddress);
AddFact(finding, "FactProviderIpv6", "ProviderIpv6", profile.Ipv6);
AddFact(finding, "FactProviderPort25", "ProviderPort25", profile.Port25);
var port25 = profile.Port25?.Value is null ? null : Pascal(profile.Port25.Value);
if (run?.Mail?.Port25 == Core.Mail.PortState.Open && port25 is "Blocked" or "BlockedOnRequest" or "Closed")
finding.WithKey("FactThisLine", "ValuePort25Open", run.Mail.Banner ?? "");
else if (run?.Mail?.Port25 == Core.Mail.PortState.Blocked && port25 is "Open")
finding.WithKey("FactThisLine", "ValuePort25Blocked");
if (profile.Sheet is not null) finding.Facts.Add(new Fact { Label = "FactProviderSheet", Key = "ValueOpenSheet", Link = profile.Sheet });
return finding;
}
@@ -192,6 +200,13 @@ public static class Verdicts
return (finding, kind);
}
static string Holder(NatKind nat) => nat switch
{
NatKind.Direct => "ValueOnThisDevice",
NatKind.Shared or NatKind.Translated => "ValueSharedProvider",
_ => "ValueSharedAtHome",
};
public static NatKind NatOf(LocalSnapshot local, RouterReport router, string v4)
{
if (v4 is null) return NatKind.NoIpv4;
@@ -212,11 +227,12 @@ public static class Verdicts
};
}
public static Finding Behaviour(StunReport stun, NatKind nat)
public static Finding Behaviour(StunReport stun, NatKind nat, bool ownNetwork)
{
var finding = new Finding { Id = CheckId.Behaviour };
(finding.Outcome, finding.Summary) = stun.Behaviour switch
{
_ when ownNetwork => (Outcome.Unknown, "ReachOwnNetwork"),
NatBehaviour.None => (Outcome.Good, "BehaviourNone"),
NatBehaviour.Easy => (Outcome.Good, "BehaviourEasy"),
NatBehaviour.Hard => (Outcome.Caution, "BehaviourHard"),
+11
View File
@@ -36,6 +36,17 @@ public static class Addresses
public static IPAddress Parse(string text) => IPAddress.TryParse(text, out var address) ? address : null;
public static bool SameNetwork(IEnumerable<IPAddress> server, string v4, string v6)
{
foreach (var address in server)
{
if (address.AddressFamily == AddressFamily.InterNetwork && SameAddress(address.ToString(), v4)) return true;
if (address.AddressFamily != AddressFamily.InterNetworkV6 || Parse(v6) is not { } mine) continue;
if (address.GetAddressBytes().AsSpan(0, 6).SequenceEqual(mine.GetAddressBytes().AsSpan(0, 6))) return true;
}
return false;
}
public static bool SameAddress(string left, string right)
{
var a = Parse(left);