Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] message validation #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Makaretu.Dns
/// All communications inside of the domain protocol are carried in a single
/// format called a message.
/// </summary>
public class Message : DnsObject
public partial class Message : DnsObject
{
/// <summary>
/// The least significant 4 bits of the opcode.
Expand Down Expand Up @@ -134,7 +134,7 @@ public MessageOperation Opcode
/// authority for the domain name in question section.
///
/// Note that the contents of the answer section may have
/// multiple owner names because of aliases.The AA bit
/// multiple owner names because of aliases. The AA bit
/// corresponds to the name which matches the query name, or
/// the first owner name in the answer section.
/// </summary>
Expand All @@ -144,7 +144,7 @@ public MessageOperation Opcode
public bool AA { get; set; }

/// <summary>
/// TrunCation - specifies that this message was truncated
/// Truncation - specifies that this message was truncated
/// due to length greater than that permitted on the
/// transmission channel.
/// </summary>
Expand All @@ -167,7 +167,7 @@ public MessageOperation Opcode
public bool RD { get; set; }

/// <summary>
/// Recursion Available - this be is set or cleared in a
/// Recursion Available - this bit is set or cleared in a
/// response, and denotes whether recursive query support is
/// available in the name server.
/// </summary>
Expand Down
81 changes: 81 additions & 0 deletions src/MessageValidation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Makaretu.Dns
{
public partial class Message
{
/// <summary>
/// Throws if the response is invalid.
/// </summary>
/// <param name="response">
/// The response to this query.
/// </param>
/// <param name="resolver">
/// Used to obtain the <see cref="DNSKEYRecord"/> and <see cref="DSRecord"/>
/// resource records when doing DNSEC validations.
/// </param>
/// <param name="cancel">
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
/// </param>
/// <exception cref="Exception">
/// When the <paramref name="response"/> is not valid.
/// </exception>
/// <seealso cref="ValidateResponseAsync"/>
public async Task EnsureValidResponseAsync(
Message response,
IResolver resolver,
CancellationToken cancel = default(CancellationToken)
)
{
var reason = await ValidateResponseAsync(response, resolver, cancel);
if (reason != null)
throw new Exception(reason);
}

/// <summary>
/// Validate the response to a query.
/// </summary>
/// <param name="response">
/// The response to this query.
/// </param>
/// <param name="resolver">
/// Used to obtain the <see cref="DNSKEYRecord"/> and <see cref="DSRecord"/>
/// resource records when doing DNSEC validations.
/// </param>
/// <param name="cancel">
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
/// </param>
/// <returns>
/// A task that represents the asynchronous operation. The task's value is
/// <b>null</b> if the <paramref name="response"/> is valid; otherwise, a
/// <see cref="string"/> containing the reason why the response is invalid.
/// </returns>
/// <seealso cref="EnsureValidResponseAsync"/>
public async Task<string> ValidateResponseAsync(
Message response,
IResolver resolver,
CancellationToken cancel = default(CancellationToken)
)
{
if (!response.QR)
return "Not a response, QR is not set.";
if (Id != response.Id)
return "Response and query IDs are not equal.";
if (response.Status == MessageStatus.NoError
&& (response.Answers.Count + response.AuthorityRecords.Count == 0))
return "No answers.";
// TODO: if MXDOMAIN needs an SOA authority record

// TODO: DNSSEC

// Everything is good.
return null;
}

}
}
103 changes: 103 additions & 0 deletions test/MessageValidationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Makaretu.Dns
{

[TestClass]
public class MessageValidationTest
{
[TestMethod]
public async Task EnsureValidResponse()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};
var response = query.CreateResponse();
response.Answers.Add(AddressRecord.Create("foo.bar", IPAddress.Loopback));

await query.EnsureValidResponseAsync(response, null);
}

[TestMethod]
[ExpectedException(typeof(Exception))]
public async Task EnsureValidResponse_Throws()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};
var response = query.CreateResponse();
response.Id = 4321;
response.Answers.Add(AddressRecord.Create("foo.bar", IPAddress.Loopback));

await query.EnsureValidResponseAsync(response, null);
}


[TestMethod]
public async Task ValidateResponse()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};
var response = query.CreateResponse();
response.Answers.Add(AddressRecord.Create("foo.bar", IPAddress.Loopback));

var failure = await query.ValidateResponseAsync(response, null);
Assert.AreEqual(null, failure);
}

[TestMethod]
public async Task ValidateResponse_WrongID()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};
var response = query.CreateResponse();
response.Id = 4321;
response.Answers.Add(AddressRecord.Create("foo.bar", IPAddress.Loopback));

var failure = await query.ValidateResponseAsync(response, null);
Assert.IsNotNull(failure);
}

[TestMethod]
public async Task ValidateResponse_QR()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};

var failure = await query.ValidateResponseAsync(query, null);
Assert.IsNotNull(failure);
}

[TestMethod]
public async Task ValidateResponse_MissingAnswer()
{
var query = new Message
{
Id = 1234,
Questions = { new Question { Name = "foo.bar", Type = DnsType.A } }
};
var response = query.CreateResponse();

var failure = await query.ValidateResponseAsync(response, null);
Assert.IsNotNull(failure);
}
}
}