diff --git a/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsCookieOptionData.cs b/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsCookieOptionData.cs
new file mode 100644
index 00000000..fc4cfce5
--- /dev/null
+++ b/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsCookieOptionData.cs
@@ -0,0 +1,174 @@
+/*
+Technitium Library
+Copyright(C) 2026 Shreyas Zare(shreyas @technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System;
+using System.IO;
+using System.Text.Json;
+
+namespace TechnitiumLibrary.Net.Dns.EDnsOptions
+{
+ ///
+ /// RFC 7873 DNS COOKIE EDNS option.
+ /// Option data:
+ /// - Client cookie: 8 bytes (MUST)
+ /// - Server cookie: 0 or 8-32 bytes (MAY)
+ /// Total option data length: 8 OR 16-40 bytes.
+ ///
+ public class EDnsCookieOptionData : EDnsOptionData
+ {
+ #region variables
+
+ public const int CLIENT_COOKIE_LENGTH = 8;
+ public const int SERVER_COOKIE_MAX_LENGTH = 32;
+ public const int SERVER_COOKIE_MIN_LENGTH = 8;
+ byte[] _clientCookie;
+ byte[] _serverCookie; // null means absent (client-cookie-only)
+
+ #endregion
+
+ #region constructor
+
+ public EDnsCookieOptionData(byte[] clientCookie, byte[] serverCookie = null)
+ {
+ ArgumentNullException.ThrowIfNull(clientCookie);
+
+ if (clientCookie.Length != CLIENT_COOKIE_LENGTH)
+ throw new ArgumentException("Client cookie must be 8 bytes.", nameof(clientCookie));
+
+ if (serverCookie is not null &&
+ (serverCookie.Length < SERVER_COOKIE_MIN_LENGTH || serverCookie.Length > SERVER_COOKIE_MAX_LENGTH))
+ throw new ArgumentException("Server cookie must be 8-32 bytes.", nameof(serverCookie));
+
+ _clientCookie = (byte[])clientCookie.Clone();
+ _serverCookie = serverCookie is null ? null : (byte[])serverCookie.Clone();
+ }
+
+ ///
+ /// Parsing ctor. The stream is positioned at OPTION-LENGTH (immediately after OPTION-CODE),
+ /// because EDnsOption(Stream) already read OPTION-CODE.
+ ///
+ public EDnsCookieOptionData(Stream s)
+ : base(s)
+ { }
+
+ #endregion
+
+ #region protected
+
+ protected override void ReadOptionData(Stream s)
+ {
+ // _length is OPTION-LENGTH (bytes of option data).
+ if (_length < CLIENT_COOKIE_LENGTH)
+ throw new InvalidDataException($"Invalid COOKIE option length: {_length} bytes");
+
+ int serverLen = _length - CLIENT_COOKIE_LENGTH;
+
+ // Valid serverLen: 0 OR 8..32.
+ if (serverLen != 0 && (serverLen < SERVER_COOKIE_MIN_LENGTH || serverLen > SERVER_COOKIE_MAX_LENGTH))
+ throw new InvalidDataException($"Invalid server cookie length: {serverLen} bytes. Valid lengths are exactly 0 bytes, or between {SERVER_COOKIE_MIN_LENGTH} and {SERVER_COOKIE_MAX_LENGTH} bytes.");
+
+ _clientCookie = new byte[CLIENT_COOKIE_LENGTH];
+ s.ReadExactly(_clientCookie);
+
+ if (serverLen == 0)
+ {
+ _serverCookie = null;
+ return;
+ }
+
+ _serverCookie = new byte[serverLen];
+ s.ReadExactly(_serverCookie);
+ }
+
+ protected override void WriteOptionData(Stream s)
+ {
+ s.Write(_clientCookie);
+
+ if (_serverCookie is not null)
+ s.Write(_serverCookie);
+ }
+
+ #endregion
+
+ #region public
+
+ public bool Equals(EDnsCookieOptionData other)
+ {
+ if (other is null)
+ return false;
+
+ if (!_clientCookie.AsSpan().SequenceEqual(other._clientCookie))
+ return false;
+
+ if (_serverCookie is null && other._serverCookie is null)
+ return true;
+
+ if (_serverCookie is null || other._serverCookie is null)
+ return false;
+
+ return _serverCookie.AsSpan().SequenceEqual(other._serverCookie);
+ }
+
+ public override bool Equals(object obj) => Equals(obj as EDnsCookieOptionData);
+
+ public override int GetHashCode()
+ {
+ HashCode hash = new();
+
+ foreach (byte b in _clientCookie)
+ hash.Add(b);
+
+ if (_serverCookie is not null)
+ foreach (byte b in _serverCookie)
+ hash.Add(b);
+
+ return hash.ToHashCode();
+ }
+
+ public override void SerializeTo(Utf8JsonWriter writer)
+ {
+ writer.WriteStartObject();
+
+ writer.WriteString("ClientCookie", Convert.ToHexString(_clientCookie));
+
+ if (_serverCookie is not null)
+ writer.WriteString("ServerCookie", Convert.ToHexString(_serverCookie));
+
+ writer.WriteEndObject();
+ }
+
+ public override string ToString()
+ {
+ if (_serverCookie is null)
+ return $"COOKIE client={Convert.ToHexString(_clientCookie)}";
+
+ return $"COOKIE client={Convert.ToHexString(_clientCookie)} server={Convert.ToHexString(_serverCookie)}";
+ }
+
+ #endregion
+
+ #region properties
+ public ReadOnlySpan ClientCookie => _clientCookie;
+ public bool HasServerCookie => _serverCookie is not null;
+ public ReadOnlySpan ServerCookie => _serverCookie is null ? ReadOnlySpan.Empty : _serverCookie;
+ public override int UncompressedLength => CLIENT_COOKIE_LENGTH + (_serverCookie?.Length ?? 0);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsOption.cs b/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsOption.cs
index e22b7d51..1ed9fd02 100644
--- a/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsOption.cs
+++ b/TechnitiumLibrary.Net/Dns/EDnsOptions/EDnsOption.cs
@@ -78,6 +78,10 @@ public EDnsOption(Stream s)
_data = new EDnsExtendedDnsErrorOptionData(s);
break;
+ case EDnsOptionCode.COOKIE:
+ _data = new EDnsCookieOptionData(s);
+ break;
+
default:
_data = new EDnsUnknownOptionData(s);
break;
@@ -92,6 +96,14 @@ public void WriteTo(Stream s)
{
DnsDatagram.WriteUInt16NetworkOrder((ushort)_code, s);
+ // OPTION-LENGTH=0 is valid; represent with null data.
+ if (_data is null)
+ {
+ DnsDatagram.WriteUInt16NetworkOrder(0, s);
+ return;
+ }
+
+ // EDnsOptionData.WriteTo writes OPTION-LENGTH + option bytes.
_data.WriteTo(s);
}
@@ -128,12 +140,19 @@ public void SerializeTo(Utf8JsonWriter jsonWriter)
{
jsonWriter.WriteStartObject();
- jsonWriter.WriteString("Code", _code.ToString());
- jsonWriter.WriteString("Length", _data.Length + " bytes");
-
- jsonWriter.WritePropertyName("Data");
- _data.SerializeTo(jsonWriter);
+ jsonWriter.WriteString(nameof(Code), _code.ToString());
+ if (_data is null)
+ {
+ jsonWriter.WriteString("Length", "0 bytes");
+ jsonWriter.WriteNull(nameof(Data));
+ }
+ else
+ {
+ jsonWriter.WriteString("Length", _data.Length + " bytes");
+ jsonWriter.WritePropertyName(nameof(Data));
+ _data.SerializeTo(jsonWriter);
+ }
jsonWriter.WriteEndObject();
}
@@ -147,8 +166,7 @@ public EDnsOptionCode Code
public EDnsOptionData Data
{ get { return _data; } }
- public int UncompressedLength
- { get { return 2 + 2 + _data.UncompressedLength; } }
+ public int UncompressedLength => 2 + 2 + (_data?.UncompressedLength ?? 0);
#endregion
}