using XFESystem.Network; namespace XFESystem.Network.Tests; public sealed class DnsTests { static byte[] Name(string value) { var bytes = new List(); foreach (string label in value.Split('.')) { bytes.Add((byte)label.Length); bytes.AddRange(System.Text.Encoding.ASCII.GetBytes(label)); } bytes.Add(0); return bytes.ToArray(); } static byte[] Query() { byte[] bytes = new byte[512]; int length = DnsMessage.WriteQuery(bytes, 1234, "www.bing.com"); return bytes[..length]; } [Fact] public void CompressedAnswerIsBoundToTheExactQuestion() { byte[] answer = PacketFixture.Answer(Query(), 0x01020304); Assert.Equal(0x01020304u, DnsMessage.ReadAddress(answer, 1234, "WWW.BING.COM.")); Assert.Throws(() => DnsMessage.ReadAddress(answer, 1235, "www.bing.com")); Assert.Throws(() => DnsMessage.ReadAddress(answer, 1234, "other.bing.com")); } [Fact] public void CnameChainCanResolveAnAddressAppearingBeforeItsAlias() { byte[] query = Query(), alias = Name("edge.example.com"); byte[] answer = new byte[query.Length + alias.Length + 14 + 12 + alias.Length]; query.CopyTo(answer, 0); NetworkPackets.Write16(answer, 2, 0x8180); NetworkPackets.Write16(answer, 6, 2); int at = query.Length; // A record first; parser must bind its owner through the later CNAME. alias.CopyTo(answer, at); at += alias.Length; NetworkPackets.Write16(answer, at, 1); NetworkPackets.Write16(answer, at + 2, 1); NetworkPackets.Write32(answer, at + 4, 60); NetworkPackets.Write16(answer, at + 8, 4); NetworkPackets.Write32(answer, at + 10, 0x01020304); at += 14; NetworkPackets.Write16(answer, at, 0xc00c); NetworkPackets.Write16(answer, at + 2, 5); NetworkPackets.Write16(answer, at + 4, 1); NetworkPackets.Write32(answer, at + 6, 60); NetworkPackets.Write16(answer, at + 10, (ushort)alias.Length); alias.CopyTo(answer, at + 12); Assert.Equal(0x01020304u, DnsMessage.ReadAddress(answer, 1234, "www.bing.com")); } [Fact] public void TruncationAndCompressionCyclesFailWithoutUnboundedParsing() { byte[] answer = PacketFixture.Answer(Query(), 0x01020304); Assert.Throws(() => DnsMessage.ReadAddress(answer[..^1], 1234, "www.bing.com")); answer[12] = 0xc0; answer[13] = 12; Assert.Throws(() => DnsMessage.ReadAddress(answer, 1234, "www.bing.com")); } [Theory] [InlineData(0x8380)] [InlineData(0x8183)] [InlineData(0x0180)] public void TruncatedNxdomainAndNonresponseAreRejected(int flags) { byte[] answer = PacketFixture.Answer(Query(), 0x01020304); NetworkPackets.Write16(answer, 2, (ushort)flags); Assert.Throws(() => DnsMessage.ReadAddress(answer, 1234, "www.bing.com")); } [Fact] public void UnrelatedAnswerCannotSubstituteAnotherHost() { byte[] answer = PacketFixture.Answer(Query(), 0x01020304); int at = Query().Length; // Point the RR owner at 'bing.com', not the question's 'www.bing.com'. NetworkPackets.Write16(answer, at, 0xc010); Assert.Throws(() => DnsMessage.ReadAddress(answer, 1234, "www.bing.com")); } [Theory] [InlineData("bad..name")] [InlineData("https://www.bing.com/")] [InlineData("bad\r\nname")] public void InvalidQueryNamesAreRejected(string host) => Assert.Throws(() => DnsMessage.WriteQuery(new byte[512], 1, host)); }