me-web? 0.2: the test from outside, calls and games, mail, and what you can host
Build / Desktop, server and tools (push) Successful in 59s
Release / Every platform, the server, the release (push) Successful in 7m27s

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:
thepraandClaude Opus 5.5 committed 2026-10-15 05:41:44 +02:00
1 parent f79682eaef
commit 7cfc8b6de5
161 files changed
+23159 -305

No files matched your search

+189
View File
@@ -0,0 +1,189 @@
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Xml;
using MeWeb.Core.Net;
namespace MeWeb.Core.Router;
public enum MapVia
{
None,
Upnp,
NatPmp,
Pcp,
}
public class Mapping
{
public MapVia Via { get; set; }
public string Gateway { get; set; }
public int ExternalPort { get; set; }
public int InternalPort { get; set; }
public string InternalClient { get; set; }
public string Control { get; set; }
public string Service { get; set; }
public string Nonce { get; set; }
public DateTimeOffset Until { get; set; }
}
public class MappingAttempt
{
public int ExternalPort { get; set; }
public Mapping Mapping { get; set; }
public MapVia Via { get; set; }
public int Error { get; set; }
public bool Done => Mapping is not null;
}
public static class PortMapper
{
public const int LeaseSeconds = 600;
const int PortMappingPort = 5351;
public static async Task<MappingAttempt> Map(RouterReport router, string localAddress, int external, int @internal, CancellationToken ct)
{
var attempt = new MappingAttempt { ExternalPort = external };
if (router.UpnpControl is not null)
{
attempt.Via = MapVia.Upnp;
(attempt.Mapping, attempt.Error) = await Upnp(router, localAddress, external, @internal, ct);
if (attempt.Done) return attempt;
}
if (router.NatPmp == Answer.Answered)
{
attempt.Via = MapVia.NatPmp;
(attempt.Mapping, attempt.Error) = await NatPmp(router.Gateway, external, @internal, ct);
if (attempt.Done) return attempt;
}
if (router.Pcp != Answer.Silent)
{
attempt.Via = MapVia.Pcp;
(attempt.Mapping, attempt.Error) = await Pcp(router.Gateway, localAddress, external, @internal, ct);
}
return attempt;
}
public static async Task Remove(Mapping mapping, CancellationToken ct)
{
try
{
switch (mapping.Via)
{
case MapVia.Upnp:
await Gateway.Soap(new Uri(mapping.Control), mapping.Service, "DeletePortMapping",
$"<NewRemoteHost></NewRemoteHost><NewExternalPort>{mapping.ExternalPort}</NewExternalPort><NewProtocol>TCP</NewProtocol>", ct);
break;
case MapVia.NatPmp:
await Gateway.Exchange(IPAddress.Parse(mapping.Gateway), NatPmpRequest(mapping.InternalPort, 0, 0), ct);
break;
case MapVia.Pcp:
await PcpExchange(mapping.Gateway, mapping.InternalClient, Convert.FromBase64String(mapping.Nonce), mapping.ExternalPort, mapping.InternalPort, 0, ct);
break;
}
}
catch (Exception ex) when (ex is HttpRequestException or XmlException or SocketException or FormatException or TaskCanceledException && !ct.IsCancellationRequested)
{
}
}
static async Task<(Mapping, int)> Upnp(RouterReport router, string localAddress, int external, int @internal, CancellationToken ct)
{
var control = new Uri(router.UpnpControl);
foreach (var lease in new[] { LeaseSeconds, 0 })
{
try
{
var (answer, status) = await Gateway.Soap(control, router.UpnpService, "AddPortMapping",
$"<NewRemoteHost></NewRemoteHost><NewExternalPort>{external}</NewExternalPort><NewProtocol>TCP</NewProtocol>" +
$"<NewInternalPort>{@internal}</NewInternalPort><NewInternalClient>{localAddress}</NewInternalClient><NewEnabled>1</NewEnabled>" +
$"<NewPortMappingDescription>me-web? test</NewPortMappingDescription><NewLeaseDuration>{lease}</NewLeaseDuration>", ct);
var error = status == 200 ? 0 : Gateway.ErrorCode(answer, status);
if (error == 0)
return (new Mapping
{
Via = MapVia.Upnp,
Gateway = router.Gateway,
ExternalPort = external,
InternalPort = @internal,
InternalClient = localAddress,
Control = router.UpnpControl,
Service = router.UpnpService,
Until = lease == 0 ? DateTimeOffset.MaxValue : DateTimeOffset.UtcNow.AddSeconds(lease),
}, 0);
if (error != 725) return (null, error);
}
catch (Exception ex) when (ex is HttpRequestException or XmlException or TaskCanceledException && !ct.IsCancellationRequested)
{
return (null, -1);
}
}
return (null, 725);
}
static async Task<(Mapping, int)> NatPmp(string gateway, int external, int @internal, CancellationToken ct)
{
var answer = await Gateway.Exchange(IPAddress.Parse(gateway), NatPmpRequest(@internal, external, LeaseSeconds), ct);
if (answer is null || answer.Length < 16 || answer[0] != 0 || answer[1] != 130) return (null, -1);
var result = BinaryPrimitives.ReadUInt16BigEndian(answer.AsSpan(2));
if (result != 0) return (null, result);
var mapped = BinaryPrimitives.ReadUInt16BigEndian(answer.AsSpan(10));
var lifetime = BinaryPrimitives.ReadUInt32BigEndian(answer.AsSpan(12));
return (new Mapping
{
Via = MapVia.NatPmp,
Gateway = gateway,
ExternalPort = mapped,
InternalPort = @internal,
Until = DateTimeOffset.UtcNow.AddSeconds(lifetime),
}, 0);
}
static async Task<(Mapping, int)> Pcp(string gateway, string localAddress, int external, int @internal, CancellationToken ct)
{
var nonce = RandomNumberGenerator.GetBytes(12);
var answer = await PcpExchange(gateway, localAddress, nonce, external, @internal, LeaseSeconds, ct);
if (answer is null || answer.Length < 60 || answer[0] != 2 || answer[1] != 0x81) return (null, -1);
if (answer[3] != 0) return (null, answer[3]);
var lifetime = BinaryPrimitives.ReadUInt32BigEndian(answer.AsSpan(4));
var mapped = BinaryPrimitives.ReadUInt16BigEndian(answer.AsSpan(42));
return (new Mapping
{
Via = MapVia.Pcp,
Gateway = gateway,
ExternalPort = mapped,
InternalPort = @internal,
InternalClient = localAddress,
Nonce = Convert.ToBase64String(nonce),
Until = DateTimeOffset.UtcNow.AddSeconds(lifetime),
}, 0);
}
static byte[] NatPmpRequest(int @internal, int external, uint lifetime)
{
var request = new byte[12];
request[1] = 2;
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(4), (ushort)@internal);
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(6), (ushort)external);
BinaryPrimitives.WriteUInt32BigEndian(request.AsSpan(8), lifetime);
return request;
}
static async Task<byte[]> PcpExchange(string gateway, string localAddress, byte[] nonce, int external, int @internal, uint lifetime, CancellationToken ct)
{
var request = new byte[60];
request[0] = 2;
request[1] = 1;
BinaryPrimitives.WriteUInt32BigEndian(request.AsSpan(4), lifetime);
(Addresses.Parse(localAddress) ?? IPAddress.Any).MapToIPv6().GetAddressBytes().CopyTo(request, 8);
nonce.CopyTo(request, 24);
request[36] = 6;
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(40), (ushort)@internal);
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(42), (ushort)external);
IPAddress.Any.MapToIPv6().GetAddressBytes().CopyTo(request, 44);
return await Gateway.Exchange(IPAddress.Parse(gateway), request, ct);
}
}