Skip to content

Commit

Permalink
Improves robustness of zone parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
alanedwardes committed Feb 10, 2024
1 parent 82c9017 commit 856a0d0
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 14 deletions.
40 changes: 34 additions & 6 deletions src/Ae.Dns.Protocol/Records/DnsResourceRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,42 @@ public string ToZone(IDnsZone zone)
/// <inheritdoc/>
public void FromZone(IDnsZone zone, string input)
{
var parts = input.Split(null, 5);
var parts = input.Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);

Host = zone.FromFormattedHost(parts[0]);
TimeToLive = uint.Parse(parts[1]);
Class = (DnsQueryClass)Enum.Parse(typeof(DnsQueryClass), parts[2]);
Type = (DnsQueryType)Enum.Parse(typeof(DnsQueryType), parts[3]);
var index = 0;

if (char.IsWhiteSpace(input.First()))
{
Host = zone.Records.Last().Host;
}
else
{
Host = zone.FromFormattedHost(parts[index++]);
}

if (uint.TryParse(parts[index], out var ttl))
{
TimeToLive = ttl;
index++;
}
else
{
TimeToLive = (uint)zone.DefaultTtl.TotalSeconds;
}

if (Enum.TryParse<DnsQueryClass>(parts[index], out var cl))
{
Class = cl;
index++;
}
else
{
Class = zone.Records.Last().Class;
}

Type = (DnsQueryType)Enum.Parse(typeof(DnsQueryType), parts[index++]);
Resource = CreateResourceRecord(Type);
Resource.FromZone(zone, parts[4]);
Resource.FromZone(zone, string.Join(" ", parts.Skip(index)));
}
}
}
2 changes: 1 addition & 1 deletion src/Ae.Dns.Protocol/Records/DnsSoaResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public void FromZone(IDnsZone zone, string input)
MName = parts[0].Trim('.');
RName = parts[1].Trim('.');

var parts1 = parts[2].Trim(new[] { '(', ')' }).Split(null);
var parts1 = parts[2].Trim(new[] { '(', ')' }).Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries);
Serial = uint.Parse(parts1[0]);
Refresh = TimeSpan.FromSeconds(int.Parse(parts1[1]));
Retry = TimeSpan.FromSeconds(int.Parse(parts1[2]));
Expand Down
1 change: 0 additions & 1 deletion src/Ae.Dns.Protocol/Records/DnsTextResource.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Ae.Dns.Protocol.Zone;
using System;
using System.Linq;

namespace Ae.Dns.Protocol.Records
Expand Down
45 changes: 43 additions & 2 deletions src/Ae.Dns.Protocol/Zone/DnsZone.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Ae.Dns.Protocol.Enums;
using Ae.Dns.Protocol.Records;
using Ae.Dns.Protocol.Records;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -19,6 +18,22 @@ public sealed class DnsZone : IDnsZone
private readonly List<DnsResourceRecord> _records = new List<DnsResourceRecord>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

/// <summary>
/// Construct a new <see cref="DnsZone"/> with no records.
/// </summary>
public DnsZone()
{
}

/// <summary>
/// Construct a new <see cref="DnsZone"/> with the specified records.
/// </summary>
/// <param name="records"></param>
public DnsZone(IEnumerable<DnsResourceRecord> records)
{
_records = records.ToList();
}

/// <inheritdoc/>
public IReadOnlyList<DnsResourceRecord> Records => _records;

Expand Down Expand Up @@ -68,9 +83,35 @@ public void DeserializeZone(string zone)

var reader = new StringReader(zone);

string? spillage = null;
string? line;
bool spillover = false;
while ((line = reader.ReadLine()) != null)
{
line = line.Split(';')[0];

if (line.Contains("(") && !line.Contains(")"))
{
spillover = true;
}

if (line.Contains(")"))
{
line = spillage + line;
spillover = false;
}

if (spillover)
{
spillage += line;
continue;
}

if (string.IsNullOrWhiteSpace(line))
{
continue;
}

if (line.StartsWith("$ORIGIN"))
{
Origin = line.Substring("$ORIGIN".Length).Trim().Trim('.');
Expand Down
6 changes: 6 additions & 0 deletions tests/Ae.Dns.Tests/Ae.Dns.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,10 @@
</None>
</ItemGroup>

<ItemGroup>
<None Update="Zone\*.zone">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
39 changes: 35 additions & 4 deletions tests/Ae.Dns.Tests/Zone/DnsZoneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Ae.Dns.Protocol.Records;
using Ae.Dns.Protocol.Zone;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Xunit;
Expand All @@ -11,10 +13,9 @@ namespace Ae.Dns.Tests.Zone
[Obsolete]
public sealed class DnsZoneTests
{
[Fact]
public async Task TestRoundTripZone()
private IDnsZone _wikipediaExampleZone = new DnsZone(new[]
{
var zone = await RoundTripRecords(new DnsResourceRecord
new DnsResourceRecord
{
Class = DnsQueryClass.IN,
Type = DnsQueryType.SOA,
Expand Down Expand Up @@ -129,7 +130,17 @@ public async Task TestRoundTripZone()
Host = "mail3.example.com",
TimeToLive = 3600,
Resource = new DnsIpAddressResource { IPAddress = IPAddress.Parse("192.0.2.5") }
});
}
})
{
Origin = "example.com",
DefaultTtl = TimeSpan.FromHours(1)
};

[Fact]
public async Task TestRoundTripZone()
{
var zone = await RoundTripRecords(_wikipediaExampleZone.Records.ToArray());

Assert.NotNull(zone);
}
Expand Down Expand Up @@ -163,6 +174,26 @@ public async Task TestTxtResource()
Assert.NotNull(zone);
}

[Fact]
public async Task TestLoadZone1()
{
var zone = new DnsZone();
zone.DeserializeZone(await File.ReadAllTextAsync("Zone/test1.zone"));

Assert.Equal(zone.Records, _wikipediaExampleZone.Records);

var serialized = await RoundTripRecords(zone.Records.ToArray());

Assert.NotNull(serialized);
}

[Fact]
public async Task TestLoadZone2()
{
var zone = new DnsZone();
zone.DeserializeZone(await File.ReadAllTextAsync("Zone/test2.zone"));
}

public async Task<string> RoundTripRecords(params DnsResourceRecord[] records)
{
var originalZone = new DnsZone
Expand Down
17 changes: 17 additions & 0 deletions tests/Ae.Dns.Tests/Zone/test1.zone
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
$ORIGIN example.com. ; designates the start of this zone file in the namespace
$TTL 3600 ; default expiration time (in seconds) of all RRs without their own TTL value
example.com. IN SOA ns.example.com. username.example.com. ( 2020091025 7200 3600 1209600 3600 )
example.com. IN NS ns ; ns.example.com is a nameserver for example.com
example.com. IN NS ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com
example.com. IN MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com
@ IN MX 20 mail2.example.com. ; equivalent to above line, "@" represents zone origin
@ IN MX 50 mail3 ; equivalent to above line, but using a relative host name
example.com. IN A 192.0.2.1 ; IPv4 address for example.com
IN AAAA 2001:db8:10::1 ; IPv6 address for example.com
ns IN A 192.0.2.2 ; IPv4 address for ns.example.com
IN AAAA 2001:db8:10::2 ; IPv6 address for ns.example.com
www IN CNAME example.com. ; www.example.com is an alias for example.com
wwwtest IN CNAME www ; wwwtest.example.com is another alias for www.example.com
mail IN A 192.0.2.3 ; IPv4 address for mail.example.com
mail2 IN A 192.0.2.4 ; IPv4 address for mail2.example.com
mail3 IN A 192.0.2.5 ; IPv4 address for mail3.example.com
11 changes: 11 additions & 0 deletions tests/Ae.Dns.Tests/Zone/test2.zone
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$ORIGIN localhost.
@ 86400 IN SOA @ root (
1999010100 ; serial
10800 ; refresh (3 hours)
900 ; retry (15 minutes)
604800 ; expire (1 week)
86400 ; minimum (1 day)
)
@ 86400 IN NS @
@ 86400 IN A 127.0.0.1
@ 86400 IN AAAA ::1

0 comments on commit 856a0d0

Please sign in to comment.