-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
In System.Net.NameResolution
the DNS
class gives us a simple API to get the host name and addresses by using the internals of Windows or Linux.
What is missing is an interface to query actual DNS servers (custom endpoints/ports) and get more information then just the IP or local host name.
Use Case
One use case could be accessing service discovery (e.g. Consul) to ask for available services by name via DNS:
A local Consul DNS endpoint runs on port 8600 and you'd either query with qtype SRV
or A
.
To query for a service by name in consul, you use <servicename>.service.consul
.
Example query and answers with dig asking for the consul service itself:
$ dig @127.0.0.1 -p 8600 consul.service.consul SRV
;; QUESTION SECTION:
;consul.service.consul. IN SRV
;; ANSWER SECTION:
consul.service.consul. 0 IN SRV 1 1 8300 hostname.node.dc1.consul.
;; ADDITIONAL SECTION:
hostname.node.dc1.consul. 0 IN A 127.0.0.1
The SRV answer gives me the port the service instance is running on, the additional answer gives me the IP address. The result can contain multiple instances.
Why another API?
There are a few other implementations for .NET today, but none of which are really maintained as far as I know nor do they have support for netstandard1.x.
Proposed API
Usage
Creating a lookup client with different overloads:
// create a client using the dns server configured by your network interfaces
var lookup = new LookupClient();
// create a client using a DNS server by IP on default port 53
var lookup = new LookupClient(IPAddress.Parse("127.0.0.1"));
// create a client using a DNS server on port 8600
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8600);
var lookup = new LookupClient(endpoint);
Using the client to query for standard qtypes:
// query for google.com with qtype ANY
DnsQueryResponse result = await lookup.QueryAsync("google.com", QueryType.ANY);
// qtype A and explicitly setting the qclass of the query
DnsQueryResponse result = await lookup.QueryAsync("google.com", QueryType.A, QueryClass.IN);
// reverse query the host name of an IP Address using arpa
DnsQueryResponse result = await lookup.QueryReverseAsync(IPAddress.Parse("192.168.1.1"));
The result in all cases would contain the response header information, the list of resource records and the question.
API
LookupClient:
public class LookupClient
{
// dis/enables caching of responses (TTL would be driven by the server's response)
bool UseCache { get; set; }
public LookupClient();
public LookupClient(params IPEndPoint[] nameServers);
public LookupClient(params IPAddress[] nameServers);
public LookupClient(DnsMessageHandler messageHandler, ICollection<IPEndPoint> nameServers)
public Task<DnsQueryResponse> QueryAsync(string query, QueryType queryType);
public Task<DnsQueryResponse> QueryAsync(string query, QueryType queryType, QueryClass queryClass);
public Task<DnsQueryResponse> QueryAsync(string query, QueryType queryType, CancellationToken cancellationToken);
public Task<DnsQueryResponse> QueryAsync(string query, QueryType queryType, QueryClass queryClass, CancellationToken cancellationToken);
public Task<DnsQueryResponse> QueryReverseAsync(IPAddress ipAddress);
public Task<DnsQueryResponse> QueryReverseAsync(IPAddress ipAddress, CancellationToken cancellationToken);
}
// message handler could be implemented with UDP or TCP, or cool channels or what ever ;)
public class DnsMessageHandler
{
public Task<DnsResponseMessage> QueryAsync(IPEndPoint server, DnsRequestMessage request, CancellationToken cancellationToken);
// processes the query and generates the actual data to send to the server
public virtual byte[] GetRequestData(DnsRequestMessage request)
// processes the raw response data
public virtual DnsResponseMessage GetResponseMessage(byte[] responseData)
}
// the request message
public class DnsRequestMessage
{
public DnsRequestHeader Header { get; }
public DnsQuestion[] Questions { get; }
}
// the request header
public class DnsRequestHeader
{
public DnsHeaderFlag HeaderFlags {get;}
public int Id { get; }
public DnsOpCode OpCode {get;}
public int QuestionCount { get; }
public bool UseRecursion {get;}
}
// Question contains the actual query, used by response and request
public class DnsQuestion
{
public DnsName QueryName { get; }
public QueryClass QuestionClass { get; }
public QueryType QuestionType { get;
}
// the response object returned by the lookup client
public class DnsQueryResponse
{
public IReadOnlyCollection<DnsResourceRecord> Additionals { get; }
public IReadOnlyCollection<DnsResourceRecord> AllRecords
public IReadOnlyCollection<DnsResourceRecord> Answers { get; }
public IReadOnlyCollection<DnsResourceRecord> Authorities { get; }
public string ErrorMessage { get; }
public bool HasError { get; }
public DnsResponseHeader Header { get; }
public IReadOnlyCollection<DnsQuestion> Questions { get; }
}
// see https://tools.ietf.org/html/rfc1035#section-3.2.2 and 3.2.3
public enum QueryType : short
{
A = 1,
NS = 2,
...
}
// see https://tools.ietf.org/html/rfc1035#section-3.2.4
public enum QueryClass : short
{
IN = 1,
CS = 2,
CH = 3,
HS = 4
}
There are some more types like return codes / errors etc.
RFC References
Base RFC https://tools.ietf.org/html/rfc1035
Request and response header share the same RFC https://tools.ietf.org/html/rfc6895#section-2. But only some fields / flags are used in the request, others in the response.
The length is always 12 bytes.
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| OpCode |AA|TC|RD|RA| Z|AD|CD| RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT/ZOCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT/PRCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT/UPCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
The request or response data follows the header data...
I will not list all the details, only the in my opinion most important qtypes/RRs
A: https://tools.ietf.org/html/rfc1035#section-3.4.1
NS: https://tools.ietf.org/html/rfc1035
AAAA: https://tools.ietf.org/html/rfc3596#section-2.2
PTR (for reverse queries): https://tools.ietf.org/html/rfc1035#section-3.3.12
SRV: https://tools.ietf.org/html/rfc2782
MX: https://tools.ietf.org/html/rfc1035#section-3.3.9
TXT: https://tools.ietf.org/html/rfc1035#section-3.3.14
SOA: https://tools.ietf.org/html/rfc1035#section-3.3.13
Implementation
I couldn't help and did a reference/example implementation of the whole thing here: https://github.com/MichaCo/DnsClient.NET.
If we come up with a better API which goes into corefx I'm happy to discontinue that project. But I needed the functionality now ;)
@CIPop I'm creating this proposal as mentioned in #19351
Thanks,
M