namespace XFESystem.Network; /// /// One-owner, bounded IPv4 client stack over real Ethernet. Static configuration, /// ARP, UDP DNS and one active TCP stream; no host sockets or implicit fallback. /// public sealed class Ipv4Network { readonly IEthernetDevice device; readonly INetworkPlatform platform; readonly Ipv4Configuration configuration; readonly byte[] receive = new byte[2048], transmit = new byte[1514], payload = new byte[1500]; readonly uint[] neighborAddresses = new uint[8]; readonly byte[][] neighborMacs = Enumerable.Range(0, 8).Select(_ => new byte[6]).ToArray(); int neighborCursor; uint awaitingNeighbor; ushort ipId, dnsId, dnsPort; string? dnsHost, dnsError; uint dnsAddress; TcpConnection? active; public Ipv4Network(IEthernetDevice device, INetworkPlatform platform, Ipv4Configuration configuration) { this.device = device; this.platform = platform; this.configuration = configuration; if (!NetworkPackets.IsUnicastMac(device.MacAddress) || configuration.Address == 0 || configuration.Gateway == 0 || configuration.DnsServer == 0) throw new ArgumentException("The network requires a real unicast MAC and explicit IPv4/NAT configuration."); } internal long Now => platform.Milliseconds; internal void Wait() => platform.Wait(1); internal void Trace(string value) => platform.Trace(value); internal static void CheckTimeout(long now, long deadline, string operation) { if (now >= deadline) throw new IOException(operation + " timed out."); } internal long Deadline(int milliseconds) { if (milliseconds is < 1 or > 120_000) throw new ArgumentOutOfRangeException(nameof(milliseconds)); return checked(Now + milliseconds); } public Stream OpenTcp(string host, int port, int timeoutMilliseconds = 15_000) => OpenTcpUntil(host, port, Deadline(timeoutMilliseconds), timeoutMilliseconds); public Stream OpenTcpUntil(string host, int port, long deadlineMilliseconds, int streamTimeoutMilliseconds = 15_000) { if (active != null) throw new InvalidOperationException("The one-owner network already has an active TCP stream."); if (port is < 1 or > 65535) throw new ArgumentOutOfRangeException(nameof(port)); _ = Deadline(streamTimeoutMilliseconds); long deadline = deadlineMilliseconds; CheckTimeout(Now, deadline, "TCP open"); if (deadline - Now > 120000) throw new ArgumentOutOfRangeException(nameof(deadlineMilliseconds)); uint address = NetworkPackets.TryParseAddress(host, out uint literal) ? literal : Resolve(host, deadline); if (address == 0 || address == configuration.Address || (address >> 28) >= 14) throw new IOException("TCP destination is not a usable unicast IPv4 address."); EnsureNeighbor(address, deadline); ushort localPort = (ushort)(49152 + (platform.Random32() & 16383)); CheckTimeout(Now, deadline, "TCP open"); var connection = new TcpConnection(this, address, localPort, (ushort)port, platform.Random32(), streamTimeoutMilliseconds); active = connection; try { connection.Connect(deadline); return connection; } catch { active = null; connection.Abort(deadline); throw; } } public uint Resolve(string host, int timeoutMilliseconds = 5000) => Resolve(host, Deadline(timeoutMilliseconds)); uint Resolve(string host, long deadline) { if (active != null) throw new InvalidOperationException("DNS cannot interrupt another active TCP owner."); dnsHost = DnsMessage.NormalizeName(host); dnsId = (ushort)platform.Random32(); dnsPort = (ushort)(49152 + (platform.Random32() & 16383)); dnsAddress = 0; dnsError = null; byte[] query = new byte[512]; int length = DnsMessage.WriteQuery(query, dnsId, dnsHost); try { long nextSend = 0; int attempts = 0; while (dnsAddress == 0) { CheckTimeout(Now, deadline, "DNS"); if (Now >= nextSend) { if (attempts++ >= 4) throw new IOException("DNS did not answer after bounded retransmission."); SendUdp(configuration.DnsServer, dnsPort, 53, query.AsSpan(0, length), deadline); nextSend = Now + Math.Min(1000, 250 << (attempts - 1)); } Poll(deadline); if (dnsError != null) throw new IOException(dnsError); if (dnsAddress == 0) Wait(); } Trace("XFE_NETWORK_DNS|Host|" + dnsHost + "|Address|" + NetworkPackets.Format(dnsAddress) + "\n"); return dnsAddress; } finally { dnsHost = null; } } uint NextHop(uint remote) => (remote & configuration.Mask) == (configuration.Address & configuration.Mask) ? remote : configuration.Gateway; byte[]? FindNeighbor(uint address) { for (int i = 0; i < neighborAddresses.Length; i++) if (neighborAddresses[i] == address) return neighborMacs[i]; return null; } byte[] EnsureNeighbor(uint remote, long deadline) { uint address = NextHop(remote); byte[]? existing = FindNeighbor(address); if (existing != null) return existing; awaitingNeighbor = address; try { long nextSend = 0; int attempts = 0; while ((existing = FindNeighbor(address)) == null) { CheckTimeout(Now, deadline, "ARP"); if (Now >= nextSend) { if (attempts++ >= 4) throw new IOException("ARP next hop did not respond."); SendArp(address, default, false, deadline); nextSend = Now + 250; } Poll(deadline); if (FindNeighbor(address) == null) Wait(); } return existing; } finally { awaitingNeighbor = 0; } } void SendFrame(int length, long deadline) { CheckTimeout(Now, deadline, "Ethernet transmit"); int padded = Math.Max(length, 60); transmit.AsSpan(length, padded - length).Clear(); if (!device.Send(transmit.AsSpan(0, padded), deadline)) throw new IOException("Ethernet transmit failed or timed out."); CheckTimeout(Now, deadline, "Ethernet transmit"); } void Ethernet(ReadOnlySpan destination, ushort protocol) { destination.CopyTo(transmit); device.MacAddress.CopyTo(transmit.AsSpan(6)); NetworkPackets.Write16(transmit, 12, protocol); } void SendArp(uint target, ReadOnlySpan targetMac, bool response, long deadline) { Span broadcast = stackalloc byte[6]; broadcast.Fill(255); Ethernet(response ? targetMac : broadcast, 0x0806); Span arp = transmit.AsSpan(14, 28); arp.Clear(); NetworkPackets.Write16(arp, 0, 1); NetworkPackets.Write16(arp, 2, 0x0800); arp[4] = 6; arp[5] = 4; NetworkPackets.Write16(arp, 6, response ? (ushort)2 : (ushort)1); device.MacAddress.CopyTo(arp[8..]); NetworkPackets.Write32(arp, 14, configuration.Address); if (response) targetMac.CopyTo(arp[18..]); NetworkPackets.Write32(arp, 24, target); SendFrame(42, deadline); } void ReceiveArp(ReadOnlySpan frame, long deadline) { if (frame.Length < 42) return; ReadOnlySpan arp = frame[14..]; if (NetworkPackets.Read16(arp, 0) != 1 || NetworkPackets.Read16(arp, 2) != 0x0800 || arp[4] != 6 || arp[5] != 4 || !arp.Slice(8, 6).SequenceEqual(frame.Slice(6, 6)) || !NetworkPackets.IsUnicastMac(arp.Slice(8, 6)) || NetworkPackets.Read32(arp, 24) != configuration.Address) return; uint sender = NetworkPackets.Read32(arp, 14); ushort operation = NetworkPackets.Read16(arp, 6); if (operation == 2 && sender == awaitingNeighbor && arp.Slice(18, 6).SequenceEqual(device.MacAddress)) { int slot = neighborCursor; neighborCursor = (neighborCursor + 1) % neighborAddresses.Length; neighborAddresses[slot] = sender; arp.Slice(8, 6).CopyTo(neighborMacs[slot]); Trace("XFE_NETWORK_ARP|NextHop|" + NetworkPackets.Format(sender) + "|Resolved|1\n"); } if (operation == 1 && sender != 0) SendArp(sender, arp.Slice(8, 6), true, deadline); } void SendIpv4(uint destination, byte protocol, int length, ReadOnlySpan nextHopMac, long deadline) { if (length < 0 || length > 1480) throw new IOException("IPv4 payload exceeds the fixed Ethernet MTU."); Ethernet(nextHopMac, 0x0800); Span ip = transmit.AsSpan(14, 20); ip.Clear(); ip[0] = 0x45; NetworkPackets.Write16(ip, 2, (ushort)(20 + length)); NetworkPackets.Write16(ip, 4, ++ipId); NetworkPackets.Write16(ip, 6, 0x4000); ip[8] = 64; ip[9] = protocol; NetworkPackets.Write32(ip, 12, configuration.Address); NetworkPackets.Write32(ip, 16, destination); NetworkPackets.Write16(ip, 10, NetworkPackets.Checksum(ip)); payload.AsSpan(0, length).CopyTo(transmit.AsSpan(34)); SendFrame(34 + length, deadline); } void SendUdp(uint destination, ushort sourcePort, ushort destinationPort, ReadOnlySpan data, long deadline) { if (data.Length > 1472) throw new IOException("UDP datagram exceeds MTU."); byte[] mac = EnsureNeighbor(destination, deadline); Span segment = payload.AsSpan(0, data.Length + 8); segment.Clear(); NetworkPackets.Write16(segment, 0, sourcePort); NetworkPackets.Write16(segment, 2, destinationPort); NetworkPackets.Write16(segment, 4, (ushort)segment.Length); data.CopyTo(segment[8..]); ushort checksum = NetworkPackets.TransportChecksum(configuration.Address, destination, 17, segment); NetworkPackets.Write16(segment, 6, checksum == 0 ? (ushort)65535 : checksum); SendIpv4(destination, 17, segment.Length, mac, deadline); } internal void SendTcp(uint destination, ushort sourcePort, ushort destinationPort, uint sequence, uint acknowledgement, byte flags, ushort window, ReadOnlySpan data, long deadline, bool syn = false) { byte[] mac = FindNeighbor(NextHop(destination)) ?? throw new IOException("TCP next-hop ARP entry is unavailable."); int header = syn ? 24 : 20; if (data.Length > 1460 || header + data.Length > 1480) throw new IOException("TCP segment exceeds MTU."); Span segment = payload.AsSpan(0, header + data.Length); segment.Clear(); NetworkPackets.Write16(segment, 0, sourcePort); NetworkPackets.Write16(segment, 2, destinationPort); NetworkPackets.Write32(segment, 4, sequence); NetworkPackets.Write32(segment, 8, acknowledgement); segment[12] = (byte)((header / 4) << 4); segment[13] = flags; NetworkPackets.Write16(segment, 14, window); if (syn) { segment[20] = 2; segment[21] = 4; NetworkPackets.Write16(segment, 22, 1460); } data.CopyTo(segment[header..]); NetworkPackets.Write16(segment, 16, NetworkPackets.TransportChecksum(configuration.Address, destination, 6, segment)); SendIpv4(destination, 6, segment.Length, mac, deadline); } internal void Poll(long deadline) { // A finite drain gives the scheduler/UI another chance even under flood. for (int budget = 0; budget < 64; budget++) { CheckTimeout(Now, deadline, "Ethernet poll"); int length = device.Receive(receive); CheckTimeout(Now, deadline, "Ethernet poll"); if (length == 0) return; if (length < 14 || length > receive.Length) continue; ReadOnlySpan frame = receive.AsSpan(0, length); ushort type = NetworkPackets.Read16(frame, 12); if (type == 0x0806) { ReceiveArp(frame, deadline); continue; } if (type != 0x0800 || length < 34) continue; ReadOnlySpan ip = frame[14..]; int header = (ip[0] & 15) * 4, total = NetworkPackets.Read16(ip, 2); if ((ip[0] >> 4) != 4 || header < 20 || header > ip.Length || total < header || total > ip.Length || NetworkPackets.Checksum(ip[..header]) != 0 || (NetworkPackets.Read16(ip, 6) & 0x3fff) != 0 || ip[8] == 0 || NetworkPackets.Read32(ip, 16) != configuration.Address) continue; uint source = NetworkPackets.Read32(ip, 12); byte protocol = ip[9]; ReadOnlySpan body = ip.Slice(header, total - header); if (protocol == 6 && body.Length >= 20 && NetworkPackets.TransportChecksum(source, configuration.Address, 6, body) == 0) active?.Receive(source, body, deadline); if (protocol != 17 || dnsHost == null || source != configuration.DnsServer || body.Length < 8 || NetworkPackets.Read16(body, 0) != 53 || NetworkPackets.Read16(body, 2) != dnsPort) continue; int udpLength = NetworkPackets.Read16(body, 4); if (udpLength < 8 || udpLength > body.Length) continue; body = body[..udpLength]; if (NetworkPackets.Read16(body, 6) != 0 && NetworkPackets.TransportChecksum(source, configuration.Address, 17, body) != 0) continue; ReadOnlySpan answer = body[8..]; if (answer.Length < 2 || NetworkPackets.Read16(answer, 0) != dnsId) continue; try { dnsAddress = DnsMessage.ReadAddress(answer, dnsId, dnsHost); } catch (InvalidDataException error) { dnsError = "DNS validation failed: " + error.Message; } } } internal void Released(TcpConnection connection) { if (ReferenceEquals(active, connection)) active = null; } }