me-web? 0.2: the test from outside, calls and games, mail, and what you can host
Phases two and three ship together, because their code grew in the same files. With consent the app asks the router to open 80, 443 and a high port for ten minutes (UPnP, NAT-PMP or PCP), and the server, holding a single-use ticket bound to the caller's own address, connects back and must get an HMAC only the app can give; the mappings are removed after the run and remembered until then, so a crash cannot leave one open. Hairpin is tried from inside. Two STUN servers, the outside one first, tell easy from hard NAT. Mail gets port 25 to Gmail and Outlook, the reverse name and five blocklists through the user's own resolver, network-wide UCEPROTECT listings counted as a caution. DNS, speed against the server (four at a time, twelve an hour per address) and the device follow, and six uses (website, home from outside, files, game server, fediverse, mail) close the run as possible, possible with a named workaround, or can't tell. Findings can be shared after reading the exact JSON: a file per submission under the unit's state directory, ten a day per address, deletable by the token the app keeps, swept after twelve months; /privacy says all of it in English and Italian. QR codes lead to the downloads. Every language carries every string. 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
f79682eaef
commit
7cfc8b6de5
161 files changed
+23159
-305
No files matched your search
@@ -53,6 +53,8 @@ public static class Gateway
|
||||
var baseText = description.Root.Elements().FirstOrDefault(e => e.Name.LocalName == "URLBase")?.Value;
|
||||
var baseUri = Uri.TryCreate(baseText, UriKind.Absolute, out var parsed) && IsLocal(parsed) ? parsed : location;
|
||||
if (!Uri.TryCreate(baseUri, service.Control, out var control) || !IsLocal(control)) return;
|
||||
report.UpnpControl = control.ToString();
|
||||
report.UpnpService = service.Type;
|
||||
await ExternalAddress(control, service.Type, report, ct);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or XmlException or TaskCanceledException && !ct.IsCancellationRequested)
|
||||
@@ -60,22 +62,32 @@ public static class Gateway
|
||||
}
|
||||
}
|
||||
|
||||
static async Task ExternalAddress(Uri control, string serviceType, RouterReport report, CancellationToken ct)
|
||||
public static async Task<(XDocument Answer, int Status)> Soap(Uri control, string serviceType, string action, string arguments, CancellationToken ct)
|
||||
{
|
||||
var body = $"""<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetExternalIPAddress xmlns:u="{serviceType}"/></s:Body></s:Envelope>""";
|
||||
var body = $"""<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:{action} xmlns:u="{serviceType}">{arguments}</u:{action}></s:Body></s:Envelope>""";
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, control) { Content = new StringContent(body, Encoding.UTF8) };
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/xml; charset=\"utf-8\"");
|
||||
request.Headers.TryAddWithoutValidation("SOAPAction", $"\"{serviceType}#GetExternalIPAddress\"");
|
||||
request.Headers.TryAddWithoutValidation("SOAPAction", $"\"{serviceType}#{action}\"");
|
||||
using var response = await Lan.SendAsync(request, ct);
|
||||
var answer = XDocument.Parse(await response.Content.ReadAsStringAsync(ct));
|
||||
return (XDocument.Parse(await response.Content.ReadAsStringAsync(ct)), (int)response.StatusCode);
|
||||
}
|
||||
|
||||
public static int ErrorCode(XDocument answer, int status)
|
||||
{
|
||||
var code = answer.Descendants().FirstOrDefault(e => e.Name.LocalName == "errorCode")?.Value;
|
||||
return int.TryParse(code, out var number) ? number : status;
|
||||
}
|
||||
|
||||
static async Task ExternalAddress(Uri control, string serviceType, RouterReport report, CancellationToken ct)
|
||||
{
|
||||
var (answer, status) = await Soap(control, serviceType, "GetExternalIPAddress", "", ct);
|
||||
var external = answer.Descendants().FirstOrDefault(e => e.Name.LocalName == "NewExternalIPAddress")?.Value?.Trim();
|
||||
if (Addresses.Parse(external) is not null)
|
||||
{
|
||||
report.WanAddress = external;
|
||||
return;
|
||||
}
|
||||
var code = answer.Descendants().FirstOrDefault(e => e.Name.LocalName == "errorCode")?.Value;
|
||||
report.UpnpError = int.TryParse(code, out var number) ? number : (int)response.StatusCode;
|
||||
report.UpnpError = ErrorCode(answer, status);
|
||||
report.Upnp = Answer.Refused;
|
||||
}
|
||||
|
||||
@@ -156,7 +168,7 @@ public static class Gateway
|
||||
report.Pcp = answer[3] == 0 ? Answer.Answered : Answer.Refused;
|
||||
}
|
||||
|
||||
static async Task<byte[]> Exchange(IPAddress gateway, byte[] request, CancellationToken ct)
|
||||
public static async Task<byte[]> Exchange(IPAddress gateway, byte[] request, CancellationToken ct)
|
||||
{
|
||||
using var udp = new UdpClient(AddressFamily.InterNetwork);
|
||||
try
|
||||
@@ -170,7 +182,7 @@ public static class Gateway
|
||||
return await Exchange(udp, request, ct);
|
||||
}
|
||||
|
||||
static async Task<byte[]> Exchange(UdpClient udp, byte[] request, CancellationToken ct)
|
||||
public static async Task<byte[]> Exchange(UdpClient udp, byte[] request, CancellationToken ct)
|
||||
{
|
||||
foreach (var wait in new[] { 1000, 2000 })
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user