/* https://www.rfc-editor.org/rfc/rfc3225.html The variable part of an OPT RR may contain zero or more options in the RDATA. Each option MUST be treated as a bit field. Each option is encoded as: +0 (MSB) +1 (LSB) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | OPTION-CODE | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | OPTION-LENGTH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4: | | / OPTION-DATA / / / +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ OPTION-CODE Assigned by the Expert Review process as defined by the DNSEXT working group and the IESG. OPTION-LENGTH Size (in octets) of OPTION-DATA. OPTION-DATA Varies per OPTION-CODE. MUST be treated as a bit field. */ using MailSharp.DNS.Records; using System.Buffers.Binary; using System.Net; using System.Text; namespace MailSharp.DNS.Records; public record RecordOPT : DnsRecord { //public ushort UdpPayloadSize { get; init; } //public byte ExtendedRcode { get; init; } //public byte Version { get; init; } // moet 0 zijn //public ushort Flags { get; init; } // bit 0 = DO (DNSSEC OK) public List Options { get; init; } = new(); //public bool DNSSEC_OK => (Flags & 0x8000) != 0; public RecordOPT(RecordReader rr) : base(rr) { // OPT is een pseudo-record: owner = root, type = 41, class = UDP size, TTL = speciaal //UdpPayloadSize = rr.Class; // "class" veld is hier UDP payload size //ExtendedRcode = (byte)(rr.Ttl >> 24); //Version = (byte)(rr.Ttl >> 16); //Flags = (ushort)(rr.Ttl & 0xFFFF); ushort rdLength = rr.ReadUInt16(-2); // RDLEN while (rr.Position < rdLength) { ushort code = rr.ReadUInt16(); ushort len = rr.ReadUInt16(); byte[] data = rr.ReadBytes(len); Options.Add(new EdnsOption(code, data)); } } public override string ToString() { //var parts = new List //{ // $"; EDNS: version: {Version}, flags:{(DNSSEC_OK ? " do" : "")}; udp: {UdpPayloadSize}" //}; //if (Options.Count > 0) // parts.Add("; " + string.Join("; ", Options)); //if (ExtendedRcode != 0) // parts.Insert(0, $"; EDE: {ExtendedRcode}"); return "Incomplete";// string.Join(Environment.NewLine, parts); } public record EdnsOption(ushort Code, byte[] Data) { public override string ToString() { return Code switch { // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 1 => "LLQ", // Legacy 2 => "UL", // Legacy 3 => $"NSID {ToHexOrAscii(Data)}", // NSID 5 => $"DAU {FormatByteList(Data)}", // DNSSEC Algorithm Understood 6 => $"DHU {FormatByteList(Data)}", // DS Hash Understood 7 => $"N3U {FormatByteList(Data)}", // NSEC3 Hash Understood 8 => $"CLIENT-SUBNET {FormatClientSubnet(Data)}", // EDNS Client Subnet (RFC 7871) 9 => $"EXPIRE {BinaryPrimitives.ReadUInt32BigEndian(Data)}", 10 => $"COOKIE {Convert.ToHexString(Data)}", 11 => "TCP-KEEPALIVE", // RFC 7828 12 => $"PADDING {Data.Length} bytes", 13 => "CHAIN", // RFC 7901 14 => $"KEY-TAG {string.Join(" ", Data.Select(b => b.ToString()))}", 15 => "DEVICEID", // Draft 16 => "EXTENDED-ERROR", // RFC 8914 26944 => "PROXY", // Custom, vaak gebruikt _ => $"OPTION-{Code} {Convert.ToHexString(Data)}" }; } private static string ToHexOrAscii(byte[] data) { if (data.All(b => b >= 32 && b <= 126)) return $"\"{Encoding.ASCII.GetString(data)}\""; return Convert.ToHexString(data); } private static string FormatByteList(byte[] data) => string.Join(" ", data); private static string FormatClientSubnet(byte[] data) { if (data.Length < 4) return Convert.ToHexString(data); ushort family = BinaryPrimitives.ReadUInt16BigEndian(data); byte sourcePrefix = data[2]; byte scopePrefix = data[3]; var addrBytes = data.AsSpan(4); string address = family switch { 1 => new IPAddress(addrBytes.Slice(0, 4)).ToString(), 2 => new IPAddress(addrBytes.Slice(0, 16)).ToString(), _ => Convert.ToHexString(addrBytes.ToArray()) }; return $"{family} {address}/{sourcePrefix}/{scopePrefix}"; } } }