Skip to content

Commit 7239fe4

Browse files
authored
Adding NSEC3 records (MichaCo#119)
Added NSEC3 resource record Added NSEC3PARAM resource record Added a Base32 en-/de-coder with the hex version which is used by NSEC3 Also fixed a few things in the bitmap reader implementation of the NSEC record and wrote a writer part, too, to test multi windowed bitmaps properly. The algorithm is inspired by the c++ implementation of the unbound resolver.
1 parent 4ce4a4e commit 7239fe4

File tree

15 files changed

+748
-64
lines changed

15 files changed

+748
-64
lines changed

README.md

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ var ip = record?.Address;
3434
* Minimum TTL setting to overrule the result's TTL and always cache the responses for at least that time. (Even very low value, like a few milliseconds, do make a huge difference if used in high traffic low latency scenarios)
3535
* Maximum TTL to limit cache duration
3636
* Cache can be disabled
37+
* Nameserver auto discovery. If no servers are explicitly configured, DnsClient will try its best to resolve them based on your local system configuration.
38+
This includes DNS servers configured via network interfaces or even via Windows specific [NRPT policies](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745).
3739
* Multiple DNS endpoints can be configured. DnsClient will use them in random or sequential order (configurable), with re-tries.
3840
* Configurable retry of queries
3941
* Optional audit trail of each response and exception
@@ -42,32 +44,19 @@ var ip = record?.Address;
4244

4345
### Supported resource records
4446

45-
* A, AAAA, NS, CNAME, SOA, MB, MG, MR, WKS, HINFO, MINFO, MX, RP, TXT, AFSDB, URI, CAA, NULL, SSHFP, TLSA, RRSIG, NSEC, DNSKEY, DS
47+
* A, AAAA, NS, CNAME, SOA, MB, MG, MR, WKS, HINFO, MINFO, MX, RP, TXT, AFSDB, URI, CAA, NULL, SSHFP, TLSA, RRSIG, NSEC, NSEC3, NSEC3PARAM, DNSKEY, DS
4648
* PTR for reverse lookups
4749
* SRV for service discovery. `LookupClient` has some extensions to help with that.
4850
* AXFR zone transfer (as per spec, LookupClient has to be set to TCP mode only for this type. Also, the result depends on if the DNS server trusts your current connection)
4951

5052
## Build from Source
5153

52-
The solution requires a .NET Core 3.x SDK and the [.NET 4.7.1 Dev Pack](https://www.microsoft.com/net/download/dotnet-framework/net471) being installed.
53-
54-
Just clone the repository and open the solution in Visual Studio 2017/2019.
55-
56-
The unit tests don't require any additional setup right now.
57-
58-
If you want to test the different record types, there are config files for Bind under tools.
59-
Just [download Bind](https://www.isc.org/downloads/) for Windows and copy the binaries to tools/BIND, then run bind.cmd.
60-
If you are running this on Linux, you can use my config files and replace the default ones if you want.
61-
62-
Now, you can use **samples/MiniDig** to query the local DNS server.
63-
The following should return many different resource records:
64-
65-
``` cmd
66-
dotnet run -s localhost mcnet.com any
67-
```
54+
To build and contribute to this project, you must have the latest [.NET 5 SDK](https://dotnet.microsoft.com/download) installed.
55+
Just clone the repository and open the solution in Visual Studio 2019.
6856

6957
## Examples
7058

59+
* See [MiniDig](https://github.com/MichaCo/DnsClient.NET/tree/dev/samples/MiniDig)'s readme for what this example command line tool can do.
7160
* More documentation and a simple query window on http://dnsclient.michaco.net
7261
* The [Samples](https://github.com/MichaCo/DnsClient.NET.Samples) repository (there might be more in the future).
73-
* [MiniDig](https://github.com/MichaCo/DnsClient.NET/tree/dev/samples/MiniDig)
62+

samples/MiniDig/readme.md

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,66 @@ It is supposed to work similar to the well-known `dig` command line tool on Linu
55
## How to Build/Run it
66
To run it, open a command line windows, or bash, navigate to `/Samples/MiniDig` and run `dotnet restore` and `dotnet run`.
77

8-
MiniDig is multi targeted for now, which means, when you use `dotnet run` you have to specify a framework
9-
`-f NetCoreApp2.0` or `-f net472` for example.
8+
MiniDig is targeting .NET5 and will run on any supported platform as long as the .NET5 SDK or runtime is installed.
109

1110
## Examples
12-
`dotnet run -f netcoreapp2.0 google.com ANY` to query for google.com
11+
`dotnet run google.com ANY` to query for google.com
12+
13+
Example output:
14+
```csharp
15+
; <<>> MiniDiG 1.0.0.0 Microsoft Windows 10.0.19042 <<>> google.com any
16+
; Servers: 192.168.0.2:53
17+
; (1 server found)
18+
;; Got answer:
19+
;; ->>HEADER<<- opcode: Query, status: No Error, id: 39207
20+
;; flags: qr rd ra; QUERY: 1, ANSWER: 22, AUTHORITY: 0, ADDITIONAL: 1
21+
22+
;; OPT PSEUDOSECTION:
23+
; EDNS: version: 0, flags:; UDP: 1472; code: NoError
24+
;; QUESTION SECTION:
25+
google.com. IN ANY
26+
27+
;; ANSWER SECTION:
28+
google.com. 1195 IN A 216.58.207.142
29+
google.com. 1195 IN AAAA 2a00:1450:4016:806::200e
30+
google.com. 1195 IN SOA ns1.google.com. dns-admin.google.com. 375442030 900 900 1800 60
31+
google.com. 345595 IN NS ns2.google.com.
32+
google.com. 345595 IN NS ns4.google.com.
33+
google.com. 345595 IN NS ns3.google.com.
34+
google.com. 345595 IN NS ns1.google.com.
35+
google.com. 86395 IN CAA 0 issue "pki.goog"
36+
google.com. 3595 IN TXT "docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e"
37+
google.com. 3595 IN TXT "docusign=1b0a6754-49b1-4db5-8540-d2c12664b289"
38+
google.com. 3595 IN TXT "facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95"
39+
google.com. 3595 IN TXT "google-site-verification=TV9-DBe4R80X4v0M4U_bd_J9cpOJM0nikft0jAgjmsQ"
40+
google.com. 3595 IN TXT "MS=E4A68B9AB2BB9670BCE15412F62916164C0B20BB"
41+
google.com. 3595 IN TXT "globalsign-smime-dv=CDYX+XFHUw2wml6/Gb8+59BsH31KzUr6c1l2BPvqKX8="
42+
google.com. 3595 IN TXT "google-site-verification=wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o"
43+
google.com. 3595 IN TXT "apple-domain-verification=30afIBcvSuDV2PLX"
44+
google.com. 3595 IN TXT "v=spf1 include:_spf.google.com ~all"
45+
google.com. 1195 IN MX 10 aspmx.l.google.com.
46+
google.com. 1195 IN MX 20 alt1.aspmx.l.google.com.
47+
google.com. 1195 IN MX 30 alt2.aspmx.l.google.com.
48+
google.com. 1195 IN MX 40 alt3.aspmx.l.google.com.
49+
google.com. 1195 IN MX 50 alt4.aspmx.l.google.com.
50+
51+
;; Query time: 58 msec
52+
;; SERVER: 192.168.0.2#53
53+
;; WHEN: Mon May 24 21:52:45 Z 2021
54+
;; MSG SIZE rcvd: 922
55+
```
1356

1457
If nothing else is specified, it uses the DNS server configured for your local network adapter.
1558
To specify a different server, use the `-s` switch, for example:
1659

17-
`dotnet run -f netcoreapp2.0 -s 8.8.8.8 google.com` to use the public Google name server.
60+
`dotnet run -s 8.8.8.8 google.com` to use the public Google name server.
1861

1962

20-
`dotnet run -f netcoreapp2.0 -s 127.0.0.1#8600` to also specify a custom port.
63+
`dotnet run -s 127.0.0.1#8600` to also specify a custom port.
2164

2265

2366
## Performance Testing
2467
One subcommand `perf` can be used to run performance tests
2568

2669
## Random Testing
27-
The `random` sub command does a lookup on a list of domain names found in names.txt.
70+
The `random` sub command does a lookup on a list of domain names found in names.txt.

src/DnsClient/DnsRecordFactory.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ public DnsResourceRecord GetRecord(ResourceRecordInfo info)
159159
result = ResolveDnsKeyRecord(info);
160160
break;
161161

162+
case ResourceRecordType.NSEC3: // 50
163+
result = ResolveNSec3Record(info);
164+
break;
165+
166+
case ResourceRecordType.NSEC3PARAM: // 51
167+
result = ResolveNSec3ParamRecord(info);
168+
break;
169+
162170
case ResourceRecordType.TLSA: // 52
163171
result = ResolveTlsaRecord(info);
164172
break;
@@ -306,6 +314,30 @@ private DnsResourceRecord ResolveNSecRecord(ResourceRecordInfo info)
306314
return new NSecRecord(info, nextName, bitMaps);
307315
}
308316

317+
private DnsResourceRecord ResolveNSec3Record(ResourceRecordInfo info)
318+
{
319+
var startIndex = _reader.Index;
320+
var hashAlgorithm = _reader.ReadByte();
321+
var flags = _reader.ReadByte();
322+
var iterations = _reader.ReadUInt16NetworkOrder();
323+
var saltLength = _reader.ReadByte();
324+
var salt = _reader.ReadBytes(saltLength).ToArray();
325+
var nextOwnerLength = _reader.ReadByte();
326+
var nextOwnersName = _reader.ReadBytes(nextOwnerLength).ToArray();
327+
var bitMaps = _reader.ReadBytesToEnd(startIndex, info.RawDataLength).ToArray();
328+
return new NSec3Record(info, hashAlgorithm, flags, iterations, salt, nextOwnersName, bitMaps);
329+
}
330+
331+
private DnsResourceRecord ResolveNSec3ParamRecord(ResourceRecordInfo info)
332+
{
333+
var hashAlgorithm = _reader.ReadByte();
334+
var flags = _reader.ReadByte();
335+
var iterations = _reader.ReadUInt16NetworkOrder();
336+
var saltLength = _reader.ReadByte();
337+
var salt = _reader.ReadBytes(saltLength).ToArray();
338+
return new NSec3ParamRecord(info, hashAlgorithm, flags, iterations, salt);
339+
}
340+
309341
private DnsResourceRecord ResolveDnsKeyRecord(ResourceRecordInfo info)
310342
{
311343
var startIndex = _reader.Index;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace DnsClient.Internal
5+
{
6+
/// <summary>
7+
/// Base32 encoder with the extended hey alphabet
8+
/// </summary>
9+
/// <remarks>
10+
/// See https://datatracker.ietf.org/doc/html/rfc4648#section-7
11+
/// <![CDATA[
12+
/// Table 4: The "Extended Hex" Base 32 Alphabet
13+
///
14+
/// Value Encoding Value Encoding Value Encoding Value Encoding
15+
/// 0 0 9 9 18 I 27 R
16+
/// 1 1 10 A 19 J 28 S
17+
/// 2 2 11 B 20 K 29 T
18+
/// 3 3 12 C 21 L 30 U
19+
/// 4 4 13 D 22 M 31 V
20+
/// 5 5 14 E 23 N
21+
/// 6 6 15 F 24 O (pad) =
22+
/// 7 7 16 G 25 P
23+
/// 8 8 17 H 26 Q
24+
///
25+
/// ]]>
26+
/// </remarks>
27+
/// <seealso href="https://datatracker.ietf.org/doc/html/rfc4648#section-7">RFC4648</seealso>
28+
public static class Base32Hex
29+
{
30+
/// <summary>
31+
/// Converts the specified string, which encodes binary data as base-32 digits
32+
/// using the extended hex alphabet, to an equivalent 8-bit unsigned integer array.
33+
/// </summary>
34+
/// <param name="input">The string to convert.</param>
35+
/// <returns>An array of 8-bit unsigned integers that is equivalent to <paramref name="input"/>.</returns>
36+
public static byte[] FromBase32HexString(string input)
37+
{
38+
if (input == null)
39+
{
40+
throw new ArgumentNullException(nameof(input));
41+
}
42+
43+
if (input.Length == 0)
44+
{
45+
return new byte[0];
46+
}
47+
48+
input = input.TrimEnd('=');
49+
var byteCount = input.Length * 5 / 8;
50+
var result = new byte[byteCount];
51+
byte currentByte = 0, bitsRemaining = 8;
52+
var arrayIndex = 0;
53+
foreach (var value in input.Select(CharToValue))
54+
{
55+
int mask;
56+
if (bitsRemaining > 5)
57+
{
58+
mask = value << (bitsRemaining - 5);
59+
currentByte = (byte)(currentByte | mask);
60+
bitsRemaining -= 5;
61+
}
62+
else
63+
{
64+
mask = value >> (5 - bitsRemaining);
65+
currentByte = (byte)(currentByte | mask);
66+
result[arrayIndex++] = currentByte;
67+
unchecked
68+
{
69+
currentByte = (byte)(value << (3 + bitsRemaining));
70+
}
71+
bitsRemaining += 3;
72+
}
73+
}
74+
if (arrayIndex != byteCount)
75+
{
76+
result[arrayIndex] = currentByte;
77+
}
78+
79+
return result;
80+
}
81+
82+
/// <summary>
83+
/// Converts an array of 8-bit unsigned integers to its equivalent string
84+
/// representation that is encoded with base-32 digits using the extended hex alphabet.
85+
/// </summary>
86+
/// <param name="input">An array of 8-bit unsigned integers.</param>
87+
/// <returns>The string representation in base 32 hex of <paramref name="input"/>.</returns>
88+
public static string ToBase32HexString(byte[] input)
89+
{
90+
if (input == null)
91+
{
92+
throw new ArgumentNullException(nameof(input));
93+
}
94+
95+
if (input.Length == 0)
96+
{
97+
return string.Empty;
98+
}
99+
100+
var charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
101+
var result = new char[charCount];
102+
byte nextChar = 0, bitsRemaining = 5;
103+
var arrayIndex = 0;
104+
foreach (var b in input)
105+
{
106+
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
107+
result[arrayIndex++] = ValueToChar(nextChar);
108+
if (bitsRemaining < 4)
109+
{
110+
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
111+
result[arrayIndex++] = ValueToChar(nextChar);
112+
bitsRemaining += 5;
113+
}
114+
bitsRemaining -= 3;
115+
nextChar = (byte)((b << bitsRemaining) & 31);
116+
}
117+
if (arrayIndex == charCount)
118+
{
119+
return new string(result);
120+
}
121+
122+
result[arrayIndex++] = ValueToChar(nextChar);
123+
while (arrayIndex != charCount)
124+
{
125+
result[arrayIndex++] = '=';
126+
}
127+
128+
return new string(result);
129+
}
130+
131+
private static int CharToValue(char c)
132+
{
133+
var value = c;
134+
if (value <= 58 && value >= 48)
135+
{
136+
return value - 48;
137+
}
138+
if (value <= 86 && value >= 65)
139+
{
140+
return value - 55;
141+
}
142+
143+
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
144+
}
145+
146+
private static char ValueToChar(byte b)
147+
{
148+
if (b < 10)
149+
{
150+
return (char)(b + 48);
151+
}
152+
if (b <= 32)
153+
{
154+
return (char)(b + 55);
155+
}
156+
157+
throw new ArgumentException("Byte is not a value Base32 value.", nameof(b));
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)