XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Network;

/// <summary>
/// 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.
/// </summary>
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<byte> destination, ushort protocol)
    {
        destination.CopyTo(transmit); device.MacAddress.CopyTo(transmit.AsSpan(6)); NetworkPackets.Write16(transmit, 12, protocol);
    }
    void SendArp(uint target, ReadOnlySpan<byte> targetMac, bool response, long deadline)
    {
        Span<byte> broadcast = stackalloc byte[6]; broadcast.Fill(255);
        Ethernet(response ? targetMac : broadcast, 0x0806);
        Span<byte> 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<byte> frame, long deadline)
    {
        if (frame.Length < 42) return;
        ReadOnlySpan<byte> 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<byte> nextHopMac, long deadline)
    {
        if (length < 0 || length > 1480) throw new IOException("IPv4 payload exceeds the fixed Ethernet MTU.");
        Ethernet(nextHopMac, 0x0800); Span<byte> 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<byte> data, long deadline)
    {
        if (data.Length > 1472) throw new IOException("UDP datagram exceeds MTU.");
        byte[] mac = EnsureNeighbor(destination, deadline); Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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; }
}