MailSharp / MailSharp.DNS / Records / RecordOPT.cs
Code · 142 lines · 4812 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142/*
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<EdnsOption> 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<string>
		//{
		//	$"; 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}";
		}
	}
}