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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text;

namespace XFESystem.Network;

/// <summary>Bounded RFC 1035 A/CNAME parser. Compression cycles, truncation and unrelated answers are rejected.</summary>
public static class DnsMessage
{
    public static string NormalizeName(string value)
    {
        string name = value.TrimEnd('.').ToLowerInvariant();
        if (name.Length is 0 or > 253 || name.Split('.').Any(label => label.Length is 0 or > 63 ||
            label.Any(c => c is not (>= 'a' and <= 'z') and not (>= '0' and <= '9') and not '-')))
            throw new ArgumentException("DNS requires a bounded ASCII host name.", nameof(value));
        return name;
    }
    public static int WriteQuery(Span<byte> output, ushort id, string host)
    {
        string name = NormalizeName(host);
        if (output.Length < name.Length + 18) throw new ArgumentException("DNS output is too small.");
        output.Clear(); NetworkPackets.Write16(output, 0, id); NetworkPackets.Write16(output, 2, 0x0100);
        NetworkPackets.Write16(output, 4, 1); int at = 12;
        foreach (string label in name.Split('.'))
        { output[at++] = (byte)label.Length; at += Encoding.ASCII.GetBytes(label, output[at..]); }
        output[at++] = 0; NetworkPackets.Write16(output, at, 1); NetworkPackets.Write16(output, at + 2, 1);
        return at + 4;
    }
    static string Name(ReadOnlySpan<byte> packet, ref int offset)
    {
        int at = offset, resumed = -1, steps = 0; var name = new StringBuilder(64);
        while (true)
        {
            if (++steps > 128 || at >= packet.Length) throw new InvalidDataException("Invalid DNS name or compression cycle.");
            byte length = packet[at++];
            if ((length & 0xc0) == 0xc0)
            {
                if (at >= packet.Length) throw new InvalidDataException("Truncated DNS pointer.");
                int pointer = ((length & 63) << 8) | packet[at++];
                if (pointer >= packet.Length) throw new InvalidDataException("DNS pointer is outside the packet.");
                if (resumed < 0) resumed = at;
                at = pointer; continue;
            }
            if ((length & 0xc0) != 0 || length > 63 || at + length > packet.Length)
                throw new InvalidDataException("Invalid DNS label.");
            if (length == 0) { offset = resumed < 0 ? at : resumed; return name.ToString().ToLowerInvariant(); }
            if (name.Length != 0) name.Append('.');
            for (int i = 0; i < length; i++)
            {
                byte c = packet[at++];
                if (c is not (>= (byte)'a' and <= (byte)'z') and not (>= (byte)'A' and <= (byte)'Z') and
                    not (>= (byte)'0' and <= (byte)'9') and not (byte)'-')
                    throw new InvalidDataException("Invalid DNS host character.");
                name.Append((char)c);
            }
            if (name.Length > 253) throw new InvalidDataException("DNS host name is too long.");
        }
    }
    public static uint ReadAddress(ReadOnlySpan<byte> packet, ushort id, string host)
    {
        if (packet.Length < 12 || NetworkPackets.Read16(packet, 0) != id) throw new InvalidDataException("DNS transaction mismatch.");
        ushort flags = NetworkPackets.Read16(packet, 2);
        if ((flags & 0xf80f) != 0x8000 || (flags & 0x0200) != 0 || NetworkPackets.Read16(packet, 4) != 1)
            throw new InvalidDataException("DNS response is an error, truncated, or has an unsupported opcode.");
        string expected = NormalizeName(host); int at = 12;
        if (Name(packet, ref at) != expected || at + 4 > packet.Length || NetworkPackets.Read16(packet, at) != 1 || NetworkPackets.Read16(packet, at + 2) != 1)
            throw new InvalidDataException("DNS question mismatch.");
        at += 4; int count = NetworkPackets.Read16(packet, 6);
        if (count > 64) throw new InvalidDataException("Too many DNS answers.");
        var aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        var addresses = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase);
        for (int i = 0; i < count; i++)
        {
            string owner = Name(packet, ref at);
            if (at + 10 > packet.Length) throw new InvalidDataException("Truncated DNS answer.");
            ushort type = NetworkPackets.Read16(packet, at), family = NetworkPackets.Read16(packet, at + 2), length = NetworkPackets.Read16(packet, at + 8);
            at += 10; int end = at + length;
            if (end > packet.Length) throw new InvalidDataException("DNS answer exceeds packet.");
            if (family == 1 && type == 1 && length == 4) addresses[owner] = NetworkPackets.Read32(packet, at);
            if (family == 1 && type == 5)
            { int position = at; string alias = Name(packet, ref position); if (position != end) throw new InvalidDataException("Invalid CNAME length."); aliases[owner] = alias; }
            at = end;
        }
        for (int step = 0; step < 8; step++)
        {
            if (addresses.TryGetValue(expected, out uint address) && address != 0 && (address >> 28) < 14) return address;
            if (!aliases.TryGetValue(expected, out string? next)) break;
            expected = next;
        }
        throw new InvalidDataException("DNS has no A address for the requested host/CNAME chain.");
    }
}