GithubHelp home page GithubHelp logo

michaco / dnsclient.net Goto Github PK

View Code? Open in Web Editor NEW
750.0 24.0 129.0 10.97 MB

DnsClient.NET is a simple yet very powerful and high performant open source library for the .NET Framework to do DNS lookups

Home Page: https://dnsclient.michaco.net

License: Apache License 2.0

C# 99.99% Batchfile 0.01%
dns-server dns-client dns c-sharp dotnet dotnet-core

dnsclient.net's Introduction

DnsClient.NET

Build Status Code Coverage NuGet NuGet

DnsClient.NET is a simple yet very powerful and high performance open source library for the .NET Framework to do DNS lookups.

Usage

See the DnsClient site for more details and documentation.

The following example instantiates a new LookupClient to query some IP address.

var lookup = new LookupClient();
var result = await lookup.QueryAsync("google.com", QueryType.A);

var record = result.Answers.ARecords().FirstOrDefault();
var ip = record?.Address;

Features

General

  • Sync & Async API
  • UDP and TCP lookup, configurable if TCP should be used as fallback in case the UDP result is truncated (default=true).
  • Configurable EDNS support to change the default UDP buffer size and request security relevant records
  • Caching
    • Query result cache based on provided TTL
    • 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)
    • Maximum TTL to limit cache duration
    • Cache can be disabled
  • Nameserver auto discovery. If no servers are explicitly configured, DnsClient will try its best to resolve them based on your local system configuration. This includes DNS servers configured via network interfaces or even via Windows specific NRPT policies.
  • Multiple DNS endpoints can be configured. DnsClient will use them in random or sequential order (configurable), with re-tries.
  • Configurable retry of queries
  • Optional audit trail of each response and exception
  • Configurable error handling. Throwing DNS errors, like NotExistentDomain is turned off by default
  • Optional Trace/Logging

Supported resource records

  • 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, NAPTR, CERT
  • PTR for reverse lookups
  • SRV for service discovery. LookupClient has some extensions to help with that.
  • 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)

Build from Source

To build and contribute to this project, you must have the latest .NET SDKs and Visual Studio 2022 installed.

Examples

dnsclient.net's People

Contributors

304notmodified avatar antonfirsov avatar dutchdestroyer avatar jameskovacs avatar joeshook avatar mbp avatar michaco avatar rj2skipper avatar rufusjwb avatar sipsorcery avatar stevesyfuhs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dnsclient.net's Issues

New Feature: Basic Tracing/Logging

With the lack of good tracing it is currently relatively hard to figure out rare errors in remove applications.

It seems there are a couple of issues when running in certain container environments.
While trying to improve the resiliency of this library, I'd also like to give you some way to get more information about what's going (wr)on(g). (#51 #52)

Adding logging is always a fun one... I don't want to add any external Nuget dependency, so even the fact that Microsoft.Extensions.Logging.Abstractions doesn't have any dependencies to anything else, and it would probably ok to use it. I still don't want to add it now in a minor version update and then deal with different versions of that package...

I will probably go for the following:
(see #58)

  • An internal logging abstraction which is an even simpler version of the Microsoft framework to have at least a nice API to log errors, warnings of debug messages...
  • Offer different options to hook into those log messages which will be disabled if the consumer doesn't do anything.
    • Simple static TraceSource
    • Simple adapter to Microsoft.Extensions.Logging if you want to forward DnsClient's logs to your already existing logging setup
      This will probably be just a code example consumers can copy&paste, I'll probably not create a NuGet for just that
  • Add more logging to
    • parsing errors
    • nameserver resolution
    • Udp and Tcp handlers

I opted for this way over adding it to the configuration of the LookupClient because we need a way to hook into logs even if you don't have access to the LookupClient instance.
For example, if DnsClient is used in the MongoDriver, you don't control the instance or configuration that library uses, but you still need log messages.

Those APIs might be subject to change if it turns out that there are better alternatives or no-one has anything against adding a dependency to Microsoft.Extensions.Logging.Abstractions I'll be happy to just use that instead.

Option 1 - TraceSource

To use the first option, you'll be able to hook into TraceSource like this:

DnsClient.Tracing.Source.Switch.Level = SourceLevels.Information;
DnsClient.Tracing.Source.Listeners.Add(new ConsoleTraceListener());

That't all you need to get the logs send to a TraceListener.
(I never used that Tracing framework to expose trace messages, so if there is a better way then a static instance, I'm happy to hear feedback ^^)

Option 2 - Forward to Microsoft.Extensions.Logging

Forwarding the log messages from my internal logging to the Microsoft framework just requires some stitching.
There is a static instance of my own LoggerFactory which can be set to a new implementation.
All you have to do is setup the Microsoft logging configuration and such and then use the wrapper below and make DnsClient use that LoggerFactory instead of the default one.

// Replace with your logging setup..
var services = new ServiceCollection();
services.AddLogging(o =>
{
    o.AddConsole();
    o.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
});
var provider = services.BuildServiceProvider();

// get an instance of ILoggerFactory and pass it to the wrapper
var factory = provider.GetRequiredService<Microsoft.Extensions.Logging.ILoggerFactory>();

// Re-define the LoggerFactory used by the library to the new wrapper
DnsClient.Logging.LoggerFactory = new LoggerFactoryWrapper(factory);

The wrapper code (just an example, feel free to improve it ^^)

internal class LoggerFactoryWrapper : DnsClient.Internal.ILoggerFactory
{
    private readonly Microsoft.Extensions.Logging.ILoggerFactory _microsoftLoggerFactory;

    public LoggerFactoryWrapper(Microsoft.Extensions.Logging.ILoggerFactory microsoftLoggerFactory)
    {
        _microsoftLoggerFactory = microsoftLoggerFactory ?? throw new ArgumentNullException(nameof(microsoftLoggerFactory));
    }

    public DnsClient.Internal.ILogger CreateLogger(string categoryName)
    {
        return new DnsLogger(_microsoftLoggerFactory.CreateLogger(categoryName));
    }

    private class DnsLogger : DnsClient.Internal.ILogger
    {
        private readonly Microsoft.Extensions.Logging.ILogger _logger;

        public DnsLogger(Microsoft.Extensions.Logging.ILogger logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public bool IsEnabled(DnsClient.Internal.LogLevel logLevel)
        {
            return _logger.IsEnabled((Microsoft.Extensions.Logging.LogLevel)logLevel);
        }

        public void Log(DnsClient.Internal.LogLevel logLevel, int eventId, Exception exception, string message, params object[] args)
        {
            _logger.Log((Microsoft.Extensions.Logging.LogLevel)logLevel, eventId, exception, message, args);
        }
    }
}

New Feature: EDNS settings

Up to version 1.2.0, EDNS was always enabled without an option to opt out.
EDNS is used to inform the DnsServer that the client can handle bigger UDP buffers then the default maximum of 512 bytes.
This information is sent via an additional OPT record, and the response must include an "answer" OPT record which contains the buffer size the server is willing to handle.

The OPT record can also request DNSSEC records by setting the Do bit. DnsClient didn't do that so far.

Both things can now be changed and disabled or enabled via configuration!

New properties on the configuration object which gets passed into LookupClient or Query overloads:

ExtendedDnsBufferSize

The default value is 4096 and you usually don't have to change that.
But you can. Setting this property to <= 512 (and leaving RequestDnsSecRecords=false) will disable EDNS and will not send an OPT record.

RequestDnsSecRecords

Is disabled per default. If set to true, the Do bit will be set and the response might contain DNSSEC records.
DnsClient does not validate those records, but if someone wants to, the records and the raw bytes will be available in the response.

Enabled EDNS Example

(These are the default settings which do not have to be set/changed)

var dns = new LookupClient(NameServer.GooglePublicDns);

var result = dns.Query("google.com", QueryType.A, queryOptions: new DnsQueryAndServerOptions()
{
    RequestDnsSecRecords = false,
    ExtendedDnsBufferSize = 4096
});

Disabled EDNS Example

Both settings can enable EDNS, so both have to be set to disable it completely.

var dns = new LookupClient(NameServer.GooglePublicDns);

var result = dns.Query("google.com", QueryType.A, queryOptions: new DnsQueryAndServerOptions()
{
    RequestDnsSecRecords = false,
    ExtendedDnsBufferSize = 512
});

Additionals count in Header

hi, it seems that Additionals count in header is wrong. its always 1+ then actual count. please have a look, and any idea how to correct it. thanks

Unhandled Exception when used in AWS Lambda Environment

Just to let you know: When your library is embedded into a program that is executed in an AWS Lambda environment, it throws an Unhandled Exception: System.IO.FileNotFoundException: Could not find file '/sys/class/net/lo/mtu'.

After some research it turned out, the problem is in the method NetworkInterface.GetAllNetworkInterfaces(); which seems to be not always working under Linux. I opened a stackoverflow thread: https://stackoverflow.com/questions/46567897/how-to-prevent-filenotfoundexception-in-getallnetworkinterfaces-when-called-in

IPv6 issue

Hi,

I seem to be getting an error when resolving an SRV record while being connected to IPv6 network. This is the error message that I'm receiving:

No connection could be established to any of the following name servers: [2001:4860:4860::8844]:53 (Udp: 512), [2001:4860:4860::8888]:53 (Udp: 512), 8.8.4.4:53 (Udp: 512), 8.8.8.8:53 (Udp: 512).

This happens when testing Xamarin.iOS app on iPhone 6s iOS 11.3.1 real device (in simulator it works fine). I'm using 1.1.0 version of nuget package. I didn't test this particular issue on more devices but Apple is rejecting my app due to this error so I'm pretty sure it happens on other devices as well.

I should also add that when being connected to IPv4 network everything works fine. And thanks for a cool package btw :)

Library not resolving DNS records for custom domains

For some reason, the library does not resolve DNS records for some custom domains, here is an example (I use miniDig, but the library itself does not work as well):

dotnet run -f netcoreapp2.0 hacken.io  any

and the response I get:

Chris E, [Jun 19, 2018 at 11:40:13 AM]:
...; <<>> MiniDiG 1.0.0.0 Darwin 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar  5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 <<>> hacken.io any
; (1 server found)
;; Got answer:
;; ->>HEADER<<- opcode: Query, status: No Error, id: 1732
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 8192
;; QUESTION SECTION:
hacken.io.                       	IN 	ANY

;; ANSWER SECTION:
hacken.io.                      3312 	IN 	HINFO 	"ANY obsoleted" "See draft-ietf-dnsop-refuse-any"


;; Query time: 55 msec
;; SERVER: 192.168.1.1#53
;; WHEN: Tue Jun 19 10:39:01 Z 2018
;; MSG SIZE  rcvd: 96

and this one works(gmail.com):


; <<>> MiniDiG 1.0.0.0 Darwin 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar  5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 <<>> gmail.com any
; (1 server found)
;; Got answer:
;; ->>HEADER<<- opcode: Query, status: No Error, id: 24762
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 8192
;; QUESTION SECTION:
gmail.com.                       	IN 	ANY

;; ANSWER SECTION:
gmail.com.                      47 	IN 	A 	216.58.198.101
gmail.com.                      129 	IN 	AAAA 	2a00:1450:4009:802::2005
gmail.com.                      345600 	IN 	NS 	ns4.google.com.
gmail.com.                      345600 	IN 	NS 	ns1.google.com.
gmail.com.                      345600 	IN 	NS 	ns2.google.com.
gmail.com.                      345600 	IN 	NS 	ns3.google.com.
gmail.com.                      60 	IN 	SOA 	ns1.google.com. dns-admin.google.com. 201144473 900 900 1800 60
gmail.com.                      3600 	IN 	MX 	10 alt1.gmail-smtp-in.l.google.com.
gmail.com.                      3600 	IN 	MX 	30 alt3.gmail-smtp-in.l.google.com.
gmail.com.                      3600 	IN 	MX 	20 alt2.gmail-smtp-in.l.google.com.
gmail.com.                      3600 	IN 	MX 	5 gmail-smtp-in.l.google.com.
gmail.com.                      3600 	IN 	MX 	40 alt4.gmail-smtp-in.l.google.com.
gmail.com.                      300 	IN 	TXT 	"v=spf1 redirect=_spf.google.com"

Currently my email validation fails for quite some domains.

The actual email verification I use:

public async Task<bool> IsValid(string email)
{
    try
    {
        var mailAddress = new MailAddress(email);
        var domain = mailAddress.Host;

        var result = await this.lookupClient.QueryAsync(domain, QueryType.ANY).ConfigureAwait(false);
        var records = result.Answers.Where(record => record.RecordType == DnsClient.Protocol.ResourceRecordType.A ||
                                                        record.RecordType == DnsClient.Protocol.ResourceRecordType.AAAA ||
                                                        record.RecordType == DnsClient.Protocol.ResourceRecordType.MX);

        Console.WriteLine("test", records.Count());
        return records.Any();
    }
    catch (Exception e)
    {
        Console.WriteLine("failure", e);
        return false;
    }
}

I have tried to use "-s 8.8.8.8" as well (use google dns), but same output. Am I missing something obvious?

// Edit,
I've got it to work, I need to explictly ask for MX records in order to receive MX records, otherwise I will only get HInfo records for some servers. I also use google nameservers now.

Cheers

Intermittent "Record reader index out of sync." error on SRV record Resolve.

We're using MongoD.Driver that use DNS Client to resolve SRV record of our mongo db Altas cluster.

The code run from a kubernetes cluster in AKS (Azure kubernetes services). On a newly created cluster, we have lots of connection error with this stacktrace :

A timeout occured after 30000ms selecting a server using CompositeServerSelector 
{ Selectors = MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector, LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 } }. 
Client view of cluster state is { ClusterId : \"1\", ConnectionMode : \"ReplicaSet\", Type : \"ReplicaSet\", State : \"Disconnected\", Servers : [], DnsMonitorException : \"DnsClient.DnsResponseException: Unhandled exception ---> 
**System.InvalidOperationException: Record reader index out of sync.
**  at DnsClient.DnsRecordFactory.GetRecord(ResourceRecordInfo info)
   at DnsClient.DnsMessageHandler.GetResponseMessage(ArraySegment`1 responseData)
   at DnsClient.DnsUdpMessageHandler.Query(IPEndPoint server, DnsRequestMessage request, TimeSpan timeout)
   at DnsClient.LookupClient.ResolveQuery(IReadOnlyCollection`1 servers, DnsMessageHandler handler, DnsRequestMessage request, Boolean useCache, LookupClientAudit continueAudit)
   --- End of inner exception stack trace ---
   at DnsClient.LookupClient.ResolveQuery(IReadOnlyCollection`1 servers, DnsMessageHandler handler, DnsRequestMessage request, Boolean useCache, LookupClientAudit continueAudit)
   at DnsClient.LookupClient.QueryInternal(IReadOnlyCollection`1 servers, DnsQuestion question, Boolean useCache)
   at DnsClient.LookupClient.Query(String query, QueryType queryType, QueryClass queryClass)
   at MongoDB.Driver.Core.Misc.DnsClientWrapper.ResolveSrvRecords(String service, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Clusters.DnsMonitor.Monitor()\" 

I've made a small test program that use direclty DNSClient and it seems the SRV lookup fails from time to time.

Any clues on whats going on ?

Using ResolveServiceProcessResult looses important information

When using the ResolveService method, or it's 'Async counterpart', a result with hosts and ports is returned which is fine, but the priority and weights are lost in the results. Big part of the point of SRV records is being able to know in which order and with what preference to approach which host.

If I may make a suggestion then I'd prefer DnsClient to simply return priority, weight and ttl for these methods. Alternatively, a structure that contains the results in sorted groups and sorted priorities within the groups is also an option.

Resolv dns from root servers

It would be nice if we can resolve a hostname using root servers and the recursion could be done by your library has resolver as show on the picture below. Actually, the only way, as I understand, to achieve that with your library is to wrap your library and instantiate a new LookupClient with the new dns servers each time. I have you a better solution to propose ?

image

DNS-SD support

Issue

It doesn't appear that DNS-SD is entirely supported due to the fact that DNS label names in DnsClient.NET strictly follow the recommendation in RFC 1035.

Background

DNS Service Discovery (DNS-SD) is defined in RFC 6763. It uses normal DNS queries to find and resolve services. Bonjour and Avahi are Apple and Linux's implementations, respectively.

According to Stuart Cheshire (the main guy on the RFC):

The configuration the clients need is not who to ask, but what to ask

This is because a DNS-SD query is just a regular DNS query: service descriptions are stored in SRV and TXT records.

mDNS is just DNS implemented in a distributed manner on the 224.0.0.251 multicast address over UDP port 5353.

So, DNS-SD queries can be asked of either a normal DNS server or of an mDNS network. My examples focus on mDNS+DNS-SD. So just keep in mind that most things I say would still apply if I were communicating with a "real" DNS server.

Details

I am able to fabricate a query to discover all printers via mDNS, DNS-SD style. It's really neat that this works with DnsClient.NET, by the way. Kudos!

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "_printer._tcp.local.",
    QueryType.ANY);

Because I happen to have an HP printer on my network which implements mDNS+DNS-SD, then in response I get all the PTR, SRV, and TXT records I need to find its friendly name, regular cryptic DNS name, IP addresses, port numbers, and printer config stuff.

But I think I would quickly face truncation as the number of printers on the network grows.

Instead of querying for everything, section 4 of RFC 6763 says to issue a PTR query. The responses will include friendly names that can be individually resolved.

And the PTR query works. For example, I get back a PtrRecord containing PtrDomainName=HP LaserJet MXYZ (ABCDEF)._printer._tcp.local. when I execute this code:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "_printer._tcp.local.",
    QueryType.ANY);

The PtrDomainName is of the format <Instance>.<Service>.<Domain>. and in this case the <Instance> is HP LaserJet MXYZ (ABCDEF). According to section 4.1.1:

[The instance] is a user-friendly name consisting of arbitrary Net-Unicode text [RFC5198]. It MUST NOT contain ASCII control characters (byte values 0x00-0x1F and 0x7F) [RFC20] but otherwise is allowed to contain any characters, without restriction, including spaces, uppercase, lowercase, punctuation -- including dots -- accented characters, non-Roman text, and anything else that may be represented using Net-Unicode

This is where the trouble starts: instance names include spaces (and other bad stuff) which isn't a strictly traditional DNS label. Therefore I'm unable to issue further SRV and TXT queries for this specific instance.

For example, this code:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(
    "HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local.",
    QueryType.ANY);

...results in this error:

HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local.' is not a valid hostname.

Proposal

I propose that you relax the label name requirements to be "anything at most 63 octets in length, except ASCII control characters".

After all, as you know names end up encoded as just a bunch of length-prefixed byte arrays. So strictly speaking, any 63-octet label is able to be transmitted.

Unfortunately, this would be a breaking change. So if you're concerned about yanking out the rug from under the feet of existing users relying on DnsString to sanitize DNS names, then perhaps the DnsString class could gain a static IsTraditional method for folks to use instead?

Alternative

I know what you're going to say:

RFC 1035 section 2.3.1 says that labels cannot include spaces, or parentheses, or anything non-A-Z or non-0-9!

And although I disagree (the RFC merely recommends that format), I can understand where you'd be coming from.

So if you feel that way, then I propose that you create a query method having this signature:

public Task<IDnsQueryResponse> QueryAsync(DnsQuestion query, CancellationToken cancellationToken = default (CancellationToken))

(In fact, perhaps this could replace the existing QueryAsync method, since the existing method signature could be an extension method deriving from the above signature. And there would be no breaking changes.)

Then I would be free to shoot myself in the foot with strange queries if I want to issue DNS-SD queries like:

var lookup = new LookupClient(
    new IPEndPoint(
        address: IPAddress.Parse("224.0.0.251"),
        port: 5353
    )) {UseTcpFallback = false};
var result = await lookup.QueryAsync(new DnsQuestion(
    query: DnsString.FromResponseQueryString("HP LaserJet MXYZ (ABCDEFG)._printer._tcp.local."),
    questionType: QueryType.ANY,
    questionClass: QueryClass.IN
));

(And if DnsString.FromResponseQueryString is too strange in this context, then perhaps you could give us a DnsString.FromUnchecked method or open up the DnsString constructor?)

Conclusion

I believe that either of these options would enable full DNS-SD support.

Bonus ๐Ÿ‘

Full DNS-SD support would bring you up to par with onovotny/Zeroconf. I would be able to use your package to do everything that Zeroconf does, and more.

Behavoir on iOS

Seems that getting name servers is strange on iOS (iPhone 6 on wifi). Making this call to retrieve name servers:
var nameServers = NameServer.ResolveNameServers(false, true);

returns:
2001:4860:4860::8844
2001:4860:4860::8888
8.8.4.4
8.8.8.8

First of all the parameter for ipv6 is set to false but it appears it was returned anyways. Secondly, it only pulled back good dns servers rather than the real DNS server. I think this is a limitation on iOS but I just want to confirm. On Android the real DNS server is returns and google DNS servers are not included.

Thanks
Ray

Why does the SOA record not contain a collection of name servers?

SoaRecord.MName contains one of the (presumably the primary) name servers. I would expect there to be a collection of these rather than a single property.

When performing an nslookup command, I get the following results:

C:\>nslookup -q=soa hourstrackercloud.com
Server:  rdns.dynect.net
Address:  216.146.35.35

Non-authoritative answer:
hourstrackercloud.com
        primary name server = ns71.domaincontrol.com
        responsible mail addr = dns.jomax.net
        serial  = 2019030312
        refresh = 28800 (8 hours)
        retry   = 7200 (2 hours)
        expire  = 604800 (7 days)
        default TTL = 3600 (1 hour)

hourstrackercloud.com   nameserver = ns72.domaincontrol.com
hourstrackercloud.com   nameserver = ns71.domaincontrol.com

On the other hand, the following code:

var soaResponse = lookupClient.Query("hourstrackercloud.com", QueryType.SOA);
var nameServer = soaResponse.MName; #ns71.domaincontrol.com

only exposes one of the two name servers.

Unhandled exception "Record reader index out of sync." when record type 'OPT' received.

When you request a Type A record from the Cloudflare DOH server, a Type OPT record is returned along with the Type A record.
(I'm using Cloudflare DOH through Local DNS Proxy.)

Currently DnsRecordFactory.GetRecord method is checking _reader.Index value. Therefore, the value of _reader.Index should be updated when processing records, but "Record reader index out of sync." exception occurs because there is no code associated with processing Type OPT records.

related pull req' : #54

DNS Queries for SRV are timing out when upgraded from 1.1.1 to 1.2.0

We recently upgraded our version of MongoDB.Driver which also upgrade DnsClient from 1.1.1 to 1.2.0. We are noticing the following errors being returned in our application logs and causing issues for users of our application.

A timeout occured after 30000ms selecting a server using CompositeServerSelector{ Selectors = MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector, LatencyLimitingServerSelector{ AllowedLatencyRange = 00:00:00.0150000 } }. Client view of cluster state is { ClusterId : "1", ConnectionMode : "ReplicaSet", Type : "ReplicaSet", State : "Disconnected", Servers : [], DnsMonitorException : "DnsClient.DnsResponseException: No connection could be established to any of the following name servers: 10.0.3.22:53 (Udp: 512), 10.0.5.177:53 (Udp: 512), 192.168.1.45:53 (Udp: 512).
   at DnsClient.LookupClient.ResolveQuery(IReadOnlyCollection`1 servers, DnsMessageHandler handler, DnsRequestMessage request, Boolean useCache, LookupClientAudit continueAudit)
   at DnsClient.LookupClient.QueryInternal(IReadOnlyCollection`1 servers, DnsQuestion question, Boolean useCache)
   at MongoDB.Driver.Core.Misc.DnsClientWrapper.ResolveSrvRecords(String service, CancellationToken cancellationToken)
   at MongoDB.Driver.Core.Clusters.DnsMonitor.Monitor()" }.

Were there any huge changes around the above area in the new version? Are we the only ones experiencing this issue?

Cant read that many bytes error

I am trying to query the hotmail.com servers like so:

var result = await this.lookupClient.QueryAsync("hotmail.com", QueryType.ANY).ConfigureAwait(false);
                var records = result.Answers.Where(record => record.RecordType == DnsClient.Protocol.ResourceRecordType.A ||
                                                             record.RecordType == DnsClient.Protocol.ResourceRecordType.AAAA ||
                                                             record.RecordType == DnsClient.Protocol.ResourceRecordType.MX);

however it blows up with the following response:

DnsClient.DnsResponseException: Unhandled exception ---> System.IndexOutOfRangeException: Cannot read that many bytes: '68'.
   at DnsClient.DnsDatagramReader.ReadBytes(Int32 length)
   at DnsClient.DnsRecordFactory.ResolveTXTRecord(ResourceRecordInfo info)
   at DnsClient.DnsRecordFactory.GetRecord(ResourceRecordInfo info)
   at DnsClient.DnsMessageHandler.GetResponseMessage(ArraySegment`1 responseData)
   at DnsClient.DnsUdpMessageHandler.<QueryAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Threading.Tasks.TaskExtensions.<WithCancellation>d__0`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at DnsClient.LookupClient.<ResolveQueryAsync>d__63.MoveNext()
   --- End of inner exception stack trace ---
   at DnsClient.LookupClient.<ResolveQueryAsync>d__63.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at DnsClient.LookupClient.<QueryAsync>d__62.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()

I tried gmail.com instead, and it worked without problems. Any ideas?

TaskStatus Failed

Hi, Im getting a Task status failed. If i don't check it with the if statement it will crash the program. I have System.Buffers 4.01 which is all that is called for with framework 4.5 or greater. So what gets me is this use to work and all a sudden it didn't anymore. My guess is there is more requirements than Buffers. If so can we update the notes to show what is needed. Im thinking of putting the source code in the project to debug it. But im not sure that is the issue since it has to do with the Threading.

using System;
using System.Linq;
using DnsClient;

namespace WEA_Resources
{
class EmailChecker
{
public bool EmailOK(string email)
{
// Does a DNS lookup to check if anything exsist out there.

        string output = email.Substring(email.IndexOf('@') + 1);
        var lookup = new LookupClient();
        var result = lookup.QueryAsync(output, QueryType.ANY, QueryClass.IN);
        if (result.Status != System.Threading.Tasks.TaskStatus.Faulted)
        {
            var mx_count = result.Result.AllRecords.MxRecords().Count();    // Mail Exchanges
            var A_count = result.Result.AllRecords.ARecords().Count();      //IPv4 Addresses
            var Aaaa_count = result.Result.AllRecords.AaaaRecords().Count();//Ipv6 Addresses

            if (mx_count > 0)
            {
                return true;
            }
            else
            {
                System.Windows.MessageBox.Show("No MX Records found for that email address!" + Environment.NewLine + "Please Double check that email!", "Invalid Email!", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
                return false;
            }
        }
        else
        {
            System.Windows.MessageBox.Show("MX Records Checker Faulted!" + Environment.NewLine + "Fait of Emil Questionable!", "Error!", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
            return true;
        }
    }        
}

}

MiniDig Sample "Your project targets multiple frameworks."

If I try to run MiniDig example, I get error:

Chriss-MBP:MiniDig chriseelmaa$ dotnet run -s localhost micha.mcnet.com any
Unable to run your project
Your project targets multiple frameworks. Please specify which framework to run using '--framework'.

If I run this, it works:

dotnet run -f netcoreapp2.0 -s localhost hacken.io  any

Why enforce UseStd3AsciiRules

Why do you force the
new IdnMapping() { UseStd3AsciiRules = true }; in DnsString.cs ?

I would like to use your library to do dns queries with international domain names containing other than ascii characters, but that hardcoded value effectively stops that. At least could it be an option that we can set?

I'm not a dns expert but as far as i can read from the standard https://tools.ietf.org/html/rfc3490#page-10 it should be okay.

I tested relaxing the rule, and it works fine in my case, but I would really prefer not to have to create my own version of the library.

DNS suffix isn't being honored on Linux

It appears that the DNS search suffix in the resolv.conf file isn't being appended to the hostname during query. I can see that the source code has a parser for it but didn't see any obvious places where it was being used.

Error in v1.1.0 on .NET 4.6.1: System.Runtime not found in the GAC

When I try to update my nuget package from v1.0.7 to v1.1.0 on .NET 4.6.1, I get the following error:

Failed to add reference. The package 'DnsClient' tried to add a framework reference to 'System.Runtime' which was not found in the GAC. This is possibly a bug in the package. Please contact the package owners for assistance.

It appears that another package, NLog, experienced the same problem, so the solution might be found here: NLog/NLog.Extensions.Logging#91

DnsClient.DnsResponseException: Unhandled exception (FileNotFoundException: System.Buffers, Version=4.0.2.0) in .NET Framework 4.7.2

I'm using the most recent NuGet package available for DnsClient (v1.2.0), which states in its dependencies that it uses System.Buffers >= 4.4.0 under the .NET Framework 4.7.1. I also have the current NuGet package for System.Buffers (4.5.0) installed in the project, however my application has been raising the above exception when it attempts to execute DnsClient.LookupClient.Query("domainname", DnsClient.QueryType.MX)

I realize that I'm using a slightly newer version of the .NET Framework (4.7.2 instead of 4.7.1) and that this could be the source of the issue. Also, I don't have the debugger set to break on these exceptions, so it is able to continue execution without too much headache, but I'm just wondering why it appears to be looking for the much older version 4.0.2.0 of the System.Buffers library instead of at least 4.4.0.

I'm using this in an e-mail address validation method, and the problem is that the exception causes pretty much every e-mail address I test to come back as invalid since it won't retrieve the MX record for the domain.

Exception while retrieving DNS server on Windows in GetFixedInformation

randomly I get exceptions while the code tries to retrieve the dns servers on Windows.
The IP address in the debugger shows random characters then (see screenshots).

Could not really figure out yet what the problems is. Something going on with the native calls and pointers I geuss.

screen1
screen2

Maximum cache duration?

I see that there is a MinimumCacheTimeout option. Is there some way to have a corresponding maximum cache timeout? I want to just cache responses for a short period of time, rather than for their entire TTL.

Would you consider breaking out core functionality into a separate package?

Or alternatively, would you consider making classes public instead of internal like DnsDatagramReader?

Riding on the coattails of #26, it would be really handy to have DNS packet serializing/deserializing broken out as its own thing. Then I would be free to use DNS packets over a totally different medium than unicast DNS (specifically I need mDNS).

I would rather use DnsClient.NET as the base for what I need than other packages because of how much more popular DnsClient.NET is than the other guys. More popularity = more bugs have been found and fixed = more stable.

I'd be happy to mock up what I mean via a PR. Just let me know.

DnsResponseException when using Library in PowerShell 5

There is a bug when using this with PowerShell v5

PS C:\temp\dnsvalidator> import-module .\DnsClient\lib\net45\DnsClient.dll
PS C:\temp\dnsvalidator> $dnsClient = new-object DnsClient.LookupClient
PS C:\temp\dnsvalidator> $dnsClient.Query("google.com", "A")
Exception calling "Query" with "2" argument(s): "Unhandled exception"
At line:1 char:1

  • $dnsClient.Query("google.com", "A")
  •   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
      + FullyQualifiedErrorId : DnsResponseException
    
    

Works great in Powershell 6/Core.

Out of size exception

If I try to query the CAA records for the site big.basic.caatestsuite.com, I receive an ArgumentOutOfRangeException, regardless if I run it locally or if I run it on http://dnsclient.michaco.net/ . This site has 1001 CAA records. Do you have an idea, where I can increase the maximum size?

System.Buffers 4.0.2.0 not found issue

Hello,

We are referencing System.Buffers 4.0.3.0 ( from System.Buffers 4.5.0 nuget package ) and this is the corresponding assembly in our applications bin directly. When we our application we get shown the following error message

!!! System.TypeInitializationException: The type initializer for 'xx.DataMigrations.Helpers.MongoDB.MongoDbDatabaseSingleton' threw an exception. ---> DnsClient.DnsResponseException: Unhandled exception ---> System.IO.FileNotFoundException: Could not load file or assembly 'System.Buffers, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified. 
February 10th 2020 16:26:52Error
   at DnsClient.Internal.PooledBytes..ctor(Int32 length) 
February 10th 2020 16:26:52Error
   at DnsClient.DnsDatagramWriter..ctor() 
February 10th 2020 16:26:52Error
   at DnsClient.DnsUdpMessageHandler.Query(IPEndPoint server, DnsRequestMessage request, TimeSpan timeout) 

I noticed that this package references System.Buffers 4.0.2.0 ( from System.Buffers 4.4.0 nuget package ). We attempted to use a binding redirect in our application to try and force it to use 4.0.3.0 but this seems to not having any affect. Any idea why we are running into this blocker? Is this because System.Buffers is strongly signed/named assembly?

REFUSED doesn't cause retry

If a query gets the response REFUSED from a name server, LookupClient doesn't retry the query against any other servers.

Is this intentional behaviour?

The trouble I'm running into with this when a client has, for example, hardcoded DNS servers pointing at their (not current) ISP (think laptop/wifi) and the ISP DNS refuses queries from anything but the ISP network. I've also seen this happen with VPN, where a client connects over a VPN but continues to use the ISP DNS server (which no longer works since traffic comes from the VPN's network).

Since the ISP DNS servers return REFUSED for everything, queries fail even when the client has other DNS servers that are working.

Assign source IP Address

I have a multihomed (multi-interface) server, and I need to ensure DNS requests are sourced from a specific IP Address on the server. The default for Udp/TcpClient is to source from the address "closest" from a routing point of view to the destination address. I need it to be sourced from a fixed IP Address regardless of which interface the request "leaves" the server on.

If this helps, for other code, I've used the UdpClient/TcpClient constructors that take an IPEndpoint allowing you to specify the local address the client binds to.

eg/
var udpClient = new UdpClient(new IPEndPoint(srcIPAddress,0));

DnsResponseException: Trying to read truncated (invalid) response

I'm trying to query a nameserver (40.90.4.1:53 (Udp: 512)) for a TXT record and getting the following:

DnsClient.DnsResponseException: Unhandled exception ---> System.IndexOutOfRangeException: Cannot read that many bytes: '43'.
   at DnsClient.DnsDatagramReader.ReadBytes(Int32 length)
   at DnsClient.DnsRecordFactory.ResolveTXTRecord(ResourceRecordInfo info)
   at DnsClient.DnsRecordFactory.GetRecord(ResourceRecordInfo info)
   at DnsClient.DnsMessageHandler.GetResponseMessage(ArraySegment`1 responseData)
   at DnsClient.DnsUdpMessageHandler.Query(IPEndPoint server, DnsRequestMessage request, TimeSpan timeout)
   at DnsClient.LookupClient.ResolveQuery(IReadOnlyCollection`1 servers, DnsMessageHandler handler, DnsRequestMessage request, Boolean useCache, LookupClientAudit continueAudit)
   --- End of inner exception stack trace ---
   at DnsClient.LookupClient.ResolveQuery(IReadOnlyCollection`1 servers, DnsMessageHandler handler, DnsRequestMessage request, Boolean useCache, LookupClientAudit continueAudit)
   at DnsClient.LookupClient.QueryInternal(IReadOnlyCollection`1 servers, DnsQuestion question, Boolean useCache)
   at DnsClient.LookupClient.QueryServer(IReadOnlyCollection`1 servers, String query, QueryType queryType, QueryClass queryClass)
   at DnsClient.LookupClient.QueryServer(IReadOnlyCollection`1 servers, String query, QueryType queryType, QueryClass queryClass)

It happens after a few retries, so I think it might be rate limiting / throttling. In this issue #22 you mentioned that it's a sign of an invalid/incomplete message, so it would be great if you could print the raw response in such cases. This way we have more debugging information to work with other than "bad message".

Any request problem

Hi,
First at all thanks for this library.
I think i find a bug. When i query this domain as "any" query type, it has error but it has no error while i select query type like MX. I don't understand why i getting error.

Thanks,
Recep.

Release NuGet

do you have plans to publish a release NuGet soon?
I have done some testing with SRV and TXT lookups and it works great

Not working for Android Oreo

I am testing on Android API level 27 and getting an error:

01-07 13:31:44.463 W/ctions.app( 4673): type=1400 audit(0.0:27): avc: denied { read } for name="u:object_r:net_dns_prop:s0" dev="tmpfs" ino=6200 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:net_dns_prop:s0 tclass=file permissive=0

01-07 13:31:44.467 E/libc ( 4673): Access denied finding property "net.dns1"

A similar report is made here for another app.
c-ares/c-ares#111

You can reproduce this using the Android Emulator and API 27.

Why would DnsClient be slower than raw nslookup?

Deployed on Win7. Targeting .NET Core 2. NuGet package v1.0.7.

lookup.QueryAsync(domain, QueryType.MX) is taking ~20s to resolve while a similar nslookup is nigh instantaneous (closer to what I expected). This runs blindingly fast on all the examples online I see, so I feel like it must be a configuration issue on my end? I just have no idea where to look.

Update:
It's taking almost exactly the amount of time I set the Timeout property on LookupClient to:

var lookup = new LookupClient(IPAddress.Parse("8.8.8.8"), IPAddress.Parse("8.8.8.4"))
{
	Timeout = TimeSpan.FromMilliseconds(500)
};
...
var result = await lookup.QueryAsync(domain, QueryType.MX);
// ~500 ms to run

Mock friendly API

Adding/verifying usage and test-ability of the API

/cc @brantburnett

I just added interfaces over the most important things to make it easy to mock the LookupClient and query results in 3787def

There is also an example unit test which I used to test testing ~~

// arrange
var lookupMock = new Mock<IDnsQuery>();

var aRecord = new ARecord(new ResourceRecordInfo("any", ResourceRecordType.A, QueryClass.IN, 0, 0), IPAddress.Any);

var responseMsg = new DnsResponseMessage(new DnsResponseHeader(123, 256, 1, 1, 0, 1), 123);
responseMsg.Answers.Add(aRecord);
IDnsQueryResponse dnsResponse = responseMsg.AsQueryResponse(new NameServer(NameServer.GooglePublicDns));

//// or mock response
//var dnsResponseMock = new Mock<IDnsQueryResponse>();
//dnsResponseMock
//    .Setup(p => p.Answers)
//        .Returns(new DnsResourceRecord[] { aRecord });
            
Task<IDnsQueryResponse> response = Task.FromResult(dnsResponse);
lookupMock.Setup(f => f.QueryAsync(It.IsAny<string>(), QueryType.A)).Returns(response);
var lookup = lookupMock.Object;

// act
var result = await lookup.QueryAsync("any", QueryType.A);

// assert
Assert.Equal(1, result.Header.AnswerCount);
Assert.Equal("any.", result.Answers.First().DomainName);
Assert.Equal(IPAddress.Any, result.Answers.ARecords().First().Address);            

I will leave this issue open for more feedback/comments.
Thanks ;)

New Feature: Configuration Settings

All this is subject to change/work in progress

  • Options/Settings API rework
    • First version of the changes (6f2d948)
    • Finalize
  • Rework "by server" query to configured/custom query
    • Replace "by server" queries with optional DnsQueryOptions
      • Revert that change, maybe simplify some overloads, but that API might still be useful => Configure the client once, use it with different servers. (The initial configuration doesn't require servers)
    • Decide on fallback of name servers (e.g. if nothing is specified in query options, use the client's name servers)
  • Breaking changes marked as obsolete instead
  • More unit tests to validate the different ways of configuring a query
  • Remove obsolete parts in the future (v1.x/2.0)
  • Documentation changes in all examples and on the website

The Problem

The LookupClient class has many properties which define the behavior of that class, like number of retries for a query, timeouts, caching etc...
All properties can be set at any point in time because LookupClient is not immutable.
Also, LookupClient is not a cheap object and it is advisable to reuse it as much as possible.

The problem with this is, that the state of LookupClient can be altered by different parts of an application and can cause unpredictable behavior.

For example, 2 different threads run a query with LookupClient at the same time. One sets UseCaching to true and one to false, before running the query.
There is no way to ensure thread safety at this point for the UseCaching property to be set correctly.

Solution

I will remove all properties from the LookupClient itself and move them to a options class which can be used to initialize LookupClient.

previous version

var client = new LookupClient(NameServer.GooglePublicDns)
{
    UseCache = true,
    EnableAuditTrail = false,
    Retries = 3,
    Timeout = TimeSpan.FromSeconds(10),
    ThrowDnsErrors = true
};

new version

var client = new LookupClient(
    new LookupClientOptions(NameServer.GooglePublicDns)
    {
        UseCache = true,
        EnableAuditTrail = false,
        Retries = 3,
        Timeout = TimeSpan.FromSeconds(10),
        ThrowDnsErrors = true                   
    });

LookupClientOptions can be changed as much as needed before it gets passed to the client's ctor (which also give a little bit more flexibility to consumers).

After the LookupClient gets initialized, the options are stored as read-only copy and cannot be changed anymore.

The settings can be accessed via client.Settings.

This is a Breaking Change

Yes, this will be a bigger breaking change, because all the properties on the class are getting removed and the way to configure LookupClient changes.

Instead of removing the existing functionality right away, I will mark everything obsolete which will be removed some versions later (probably in 2.0).

Additional changes:

The list of name servers was a collection of IPEndPoints. IPEndPoint itself is mutable though, so it would be possible to change e.g. the IP address of an endpoint.
That will be changed, too. Instead, the NameServer class becomes an immutable type and will be used across the board.

Custom Queries

In version 1.1.0, I added "by server" query overloads which allows to query a different endpoint than configured initially.
Those endpoints will be removed alltogether in the next version (yes its breaking but I hope that feature wasn't really used much yet).
Instead, there will be new Query... methods taking a subset of LookupClientOptions as an additional/optional parameter.

There will be new overloads to Query, QueryAsync and QueryReverse which take DnsQueryOptions, which is a subset of LookupClientOptions.

With that, one can fully configure individual queries while still using the same client instance.
This feature can be used to query a dedicated endpoint or just use a different configuration for a particular query.

Example A, disabling cache for one query.

client.Query("google.com", QueryType.A, queryOptions: new DnsQueryOptions(NameServer.GooglePublicDns)
{
    UseCache = false
});

The query options will replace any settings previously configured on the client. There is no fallback to what was configured previously (including name servers)!, except for name servers.
In case the passed in query options do not provide one or more name servers, the query will try to use the name servers of the LookupClient instance.

If anyone did read all of this ;) and has comments/ideas, let me know; happy to get feedback on this.

NetworkInterface.GetAllNetworkInterfaces() throw exceptions in some docker environments

There's a know defect where GetAllNetworkInterfaces() throws an error when it doesn't have privileged access to the filesystem in docker. I'd recommend wrapping that call with its own exception handler and just parsing /etc/resolv.conf for nameservers as a final approach.

As a work around, I've done just that and then passed the resulting nameservers into your LookupClient for service discovery.

Problem with AXFR Zone Transfers

I have a problem with the AXFR (zone transfer) query results. Not all records are returned.

First, I want to say that I am querying an internal DNS server and the machine running the query has its IP approved for zone transfers on the DNS server. In one zone, I have over 2,000 DNS entries and only about 1/4 (466) of them are returned. The smaller zones seem to transfer fine. The results literally arrive in order and stop at a particular entry and the rest of the records are not retrieved.

There are no errors... the results come back as though they were returned complete. I don't know what the issue is, but it seems like the query stream may be quitting mid-retrieval, like there is a buffer problem, or perhaps large zone transfers are split into multiple transfers but its not handled, or perhaps an error is triggering a cancellation of a complete transfer but is swallowed somewhere. I don't know.

NOTE: The computer that is running the DNSClient code can retrieve all the records using nslookup. I was originally using the version of DNSClient from nuget, but downloaded the source code to try and resolve this. Within the method, within the class DnsMessageHandler:

public virtual DnsResponseMessage GetResponseMessage(ArraySegment responseData)

The variable, 'response', contains the 466 answers. If I look at the 'reader' variable, it seems to always have an index size of 16371. Don't know if this a variable size issue, or a size issue on the response from the DNS server. I'm sure you know how to debug this better than I do. Willing to help to resolve this.

QueryAsync failing?

DnsClient: 1.2.0
.NET Framework 4.7
Visual Studio 2017 Professional

Using the simplest example, the QueryAsync method is failing for me? If I step through the code the program exits unexpectedly (no exception) on the line with the await.

using DnsClient;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Main();
        }

        static async Task Main()
        {
            var lookup = new LookupClient();
            var result = await lookup.QueryAsync("google.com", QueryType.ANY);

            var record = result.Answers.ARecords().FirstOrDefault();
            var address = record?.Address;
        }
    }
}

If I change from QueryAsync to Query and remove the await the program runs as expected.

Expose Value of DnsResourceRecord

I can't work out how to get the string "Value" of the DnsResourceRecord without having to either split the output of "ToString()" on the tab characters, or to test the DnsResourceRecords type and access a type specific "value" property.

There is a private protected abstract RecordToString() method that each deriving DnsResourceRecord type implements appropriately, Would it be possible to expose the result of this method as either a property or method?

Understanding Server Order

Opened new issue, but more of a question. I was hoping the server resolution would go in order of the name server list. But it appears to be a little random (or I just dont understand the ordering). Here is the Audit Trail for doing a bunch of lookups at the same time. You can see it alternates between the network DNS server and Google DNS. So just wanted to understand how your library chooses the lookup order. Thanks.

;; Got answer:
;; ->>HEADER<<- opcode: Query, status: No Error, id: 20956

;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
01-08 12:51:40.554 I/mono-stdout(21142): ;; Got answer:;; QUESTION SECTION:
_minecraft._tcp.lobby.muttsworldmine.com. IN SRV

;; ANSWER SECTION:
_minecraft._tcp.lobby.muttsworldmine.com.299 IN SRV 0 5 25565 lobby.muttsworldmine.com.

01-08 12:51:40.554 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: No Error, id: 20956
;; Query time: 413 msec
;; SERVER: 8.8.4.4#53
;; WHEN: Mon Jan 08 17:51:38 Z 2018
;; MSG SIZE rcvd: 113
01-08 12:51:40.554 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTORITY: 0, ADDITIONAL: 1
01-08 12:51:40.555 I/mono-stdout(21142):
01-08 12:51:40.555 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:40.555 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:40.555 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:40.555 I/mono-stdout(21142): _minecraft._tcp.lobby.muttsworldmine.com. IN SRV
01-08 12:51:40.555 I/mono-stdout(21142):
01-08 12:51:40.555 I/mono-stdout(21142): ;; ANSWER SECTION:
01-08 12:51:40.556 I/mono-stdout(21142): _minecraft._tcp.lobby.muttsworldmine.com.299 IN SRV 0 5 25565 lobby.muttsworldmine.com.
01-08 12:51:40.556 I/mono-stdout(21142):
01-08 12:51:40.556 I/mono-stdout(21142): ;; Query time: 413 msec
01-08 12:51:40.556 I/mono-stdout(21142): ;; SERVER: 8.8.4.4#53
01-08 12:51:40.556 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:38 Z 2018
01-08 12:51:40.556 I/mono-stdout(21142): ;; MSG SIZE rcvd: 113
01-08 12:51:40.556 I/mono-stdout(21142):
[0:] DNS Complete: lobby.muttsworldmine.com:158.69.97.57
01-08 12:51:40.699 I/Choreographer(21142): Skipped 40 frames! The application may be doing too much work on its main thread.
01-08 12:51:40.730 D/ViewRootImpl@1201a22SplashActivity: mHardwareRenderer.destroy()#4
01-08 12:51:40.730 D/ViewRootImpl@1201a22SplashActivity: dispatchDetachedFromWindow
01-08 12:51:40.738 D/InputTransport(21142): Input channel destroyed: fd=69
[0:] Refresh: MCBF - Vanilla Server
192.168.1.1:5301-08 12:51:40.838 I/mono-stdout(21142): 192.168.1.1:53

[0:] Refresh: Official MineChat Server
192.168.1.1:5301-08 12:51:40.913 I/mono-stdout(21142): 192.168.1.1:53

01-08 12:51:40.957 I/art (21142): Starting a blocking GC Explicit
01-08 12:51:41.013 I/art (21142): Explicit concurrent mark sweep GC freed 5509(563KB) AllocSpace objects, 2(40KB) LOS objects, 54% free, 3MB/7MB, paused 791us total 55.452ms
01-08 12:51:41.014 D/Mono (21142): GC_TAR_BRIDGE bridges 95 objects 1525 opaque 856 colors 95 colors-bridged 95 colors-visible 95 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.09ms tarjan 1.53ms scc-setup 0.09ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.10ms
01-08 12:51:41.014 D/Mono (21142): GC_BRIDGE: Complete, was running for 58.07ms
01-08 12:51:41.014 D/Mono (21142): GC_MINOR: (Nursery full) time 22.39ms, stw 23.54ms promoted 907K major size: 1872K in use: 1190K los size: 1024K in use: 331K
[0:] Refresh: MianiteLegacyMC
192.168.1.1:5301-08 12:51:41.090 I/mono-stdout(21142): 192.168.1.1:53

01-08 12:51:41.132 D/Mono (21142): Assembly Ref addref Newtonsoft.Json[0xa09ef080] -> System.Diagnostics.Debug[0x86cee620]: 4
[0:] Refresh: Kongo PvP
192.168.1.1:5301-08 12:51:41.177 I/mono-stdout(21142): 192.168.1.1:53

[0:] Refresh: Novaria
192.168.1.1:5301-08 12:51:41.266 I/mono-stdout(21142): 192.168.1.1:53

01-08 12:51:41.268 D/ViewRootImpl@81a2e8eMainActivity: MSG_WINDOW_FOCUS_CHANGED 1
01-08 12:51:41.269 D/ViewRootImpl@81a2e8eMainActivity: mHardwareRenderer.initializeIfNeeded()#2 mSurface={isValid=true -2043650048}
01-08 12:51:41.274 V/InputMethodManager(21142): Starting input: tba=android.view.inputmethod.EditorInfo@d982c5f nm : com.kellyproductions.minechat ic=null
01-08 12:51:41.274 I/InputMethodManager(21142): startInputInner - mService.startInputOrWindowGainedFocus
01-08 12:51:41.276 D/InputTransport(21142): Input channel constructed: fd=84
01-08 12:51:41.276 D/InputTransport(21142): Input channel destroyed: fd=78
01-08 12:51:41.277 E/ViewRootImpl(21142): sendUserActionEvent() returned.
01-08 12:51:41.281 I/Choreographer(21142): Skipped 34 frames! The application may be doing too much work on its main thread.
; (3 server found)
01-08 12:51:41.590 I/mono-stdout(21142): ; (3 server found);; Got answer:
;; ->>HEADER<<- opcode: Query, status: No Error, id: 20958
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
_minecraft._tcp.play.mcbf.pw. IN SRV

;; ANSWER SECTION:
_minecraft._tcp.play.mcbf.pw. 1800 IN SRV 0 1 25580 mc15.fadehost.com.

;; Query time: 183 msec
;; SERVER: 192.168.1.1#53
;; WHEN: Mon Jan 08 17:51:41 Z 2018
;; MSG SIZE rcvd: 9401-08 12:51:41.591 I/mono-stdout(21142): ;; Got answer:

01-08 12:51:41.592 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: No Error, id: 20958
01-08 12:51:41.592 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTORITY: 0, ADDITIONAL: 1
01-08 12:51:41.592 I/mono-stdout(21142):
01-08 12:51:41.592 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:41.592 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:41.592 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:41.593 I/mono-stdout(21142): _minecraft._tcp.play.mcbf.pw. IN SRV
01-08 12:51:41.593 I/mono-stdout(21142):
01-08 12:51:41.593 I/mono-stdout(21142): ;; ANSWER SECTION:
01-08 12:51:41.593 I/mono-stdout(21142): _minecraft._tcp.play.mcbf.pw. 1800 IN SRV 0 1 25580 mc15.fadehost.com.
01-08 12:51:41.593 I/mono-stdout(21142):
01-08 12:51:41.593 I/mono-stdout(21142): ;; Query time: 183 msec
01-08 12:51:41.593 I/mono-stdout(21142): ;; SERVER: 192.168.1.1#53
01-08 12:51:41.594 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.594 I/mono-stdout(21142): ;; MSG SIZE rcvd: 94
01-08 12:51:41.594 I/mono-stdout(21142):
[0:] DNS Complete: play.mcbf.pw:167.114.4.25
; (3 server found)01-08 12:51:41.636 I/mono-stdout(21142): ; (3 server found)

;; Got answer:
;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20959
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1

;; ERROR: Non-Existent Domain
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
_minecraft._tcp.minechat.town. IN SRV

;; AUTHORITIES SECTION:
minechat.town. 1799 IN SOA a.ns.joker.com. hostmaster.joker.com. 2015082172 10240 7200 1209600 3600

;; Query time: 213 msec
;; SERVER: 8.8.4.4#53
;; WHEN: Mon Jan 08 17:51:41 Z 2018
;; MSG SIZE rcvd: 119
01-08 12:51:41.636 I/mono-stdout(21142): ;; Got answer:
01-08 12:51:41.637 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20959
01-08 12:51:41.637 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1
01-08 12:51:41.637 I/mono-stdout(21142):
[0:] DNS Complete: minechat.town:23.91.4.4601-08 12:51:41.637 I/mono-stdout(21142): ;; ERROR: Non-Existent Domain

01-08 12:51:41.637 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:41.637 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:41.637 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:41.637 I/mono-stdout(21142): _minecraft._tcp.minechat.town. IN SRV; (3 server found)
01-08 12:51:41.637 I/mono-stdout(21142):

01-08 12:51:41.637 I/mono-stdout(21142): ;; AUTHORITIES SECTION:
;; Got answer:
;; ->>HEADER<<- opcode: Query, status: No Error, id: 20960
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
_minecraft._tcp.s48.hosthorde.com. IN SRV

;; AUTHORITIES SECTION:
hosthorde.com. 1799 IN SOA sid.ns.cloudflare.com. dns.cloudflare.com. 2026301719 10000 2400 604800 3600

;; Query time: 40 msec
01-08 12:51:41.637 I/mono-stdout(21142): minechat.town. 1799 IN SOA a.ns.joker.com. hostmaster.joker.com. 2015082172 10240 7200 1209600 3600
01-08 12:51:41.637 I/mono-stdout(21142): ;; SERVER: 8.8.4.4#53
;; WHEN: Mon Jan 08 17:51:41 Z 2018
;; MSG SIZE rcvd: 120

01-08 12:51:41.637 I/mono-stdout(21142): ;; Query time: 213 msec
01-08 12:51:41.637 I/mono-stdout(21142): ;; SERVER: 8.8.4.4#53
01-08 12:51:41.637 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.637 I/mono-stdout(21142): ;; MSG SIZE rcvd: 119
01-08 12:51:41.638 I/mono-stdout(21142):
01-08 12:51:41.648 I/mono-stdout(21142): ; (3 server found)
01-08 12:51:41.648 I/mono-stdout(21142): ;; Got answer:
01-08 12:51:41.648 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: No Error, id: 20960
01-08 12:51:41.648 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1
01-08 12:51:41.648 I/mono-stdout(21142):
01-08 12:51:41.648 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:41.649 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:41.649 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:41.649 I/mono-stdout(21142): _minecraft._tcp.s48.hosthorde.com. IN SRV
01-08 12:51:41.649 I/mono-stdout(21142):
01-08 12:51:41.649 I/mono-stdout(21142): ;; AUTHORITIES SECTION:
01-08 12:51:41.649 I/mono-stdout(21142): hosthorde.com. 1799 IN SOA sid.ns.cloudflare.com. dns.cloudflare.com. 2026301719 10000 2400 604800 3600
01-08 12:51:41.649 I/mono-stdout(21142):
01-08 12:51:41.649 I/mono-stdout(21142): ;; Query time: 40 msec
01-08 12:51:41.649 I/mono-stdout(21142): ;; SERVER: 8.8.4.4#53
01-08 12:51:41.649 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.649 I/mono-stdout(21142): ;; MSG SIZE rcvd: 120
01-08 12:51:41.649 I/mono-stdout(21142):
[0:] DNS Complete: s48.hosthorde.com:198.178.127.44
; (3 server found)
;; Got answer:
;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20961
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 101-08 12:51:41.701 I/mono-stdout(21142): ; (3 server found)

01-08 12:51:41.701 I/mono-stdout(21142): ;; Got answer:;; ERROR: Non-Existent Domain
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
_minecraft._tcp.kongopvp.com. IN SRV

;; AUTHORITIES SECTION:
kongopvp.com. 600 IN SOA ns35.domaincontrol.com. dns.jomax.net. 2017122204 28800 7200 604800 600

;; Query time: 57 msec
;; SERVER: 192.168.1.1#53
;; WHEN: Mon Jan 08 17:51:41 Z 2018
;; MSG SIZE rcvd: 125

01-08 12:51:41.701 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20961
01-08 12:51:41.701 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1
01-08 12:51:41.702 I/mono-stdout(21142):
01-08 12:51:41.702 I/mono-stdout(21142): ;; ERROR: Non-Existent Domain
01-08 12:51:41.702 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:41.702 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:41.702 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:41.702 I/mono-stdout(21142): _minecraft._tcp.kongopvp.com. IN SRV
01-08 12:51:41.702 I/mono-stdout(21142):
01-08 12:51:41.702 I/mono-stdout(21142): ;; AUTHORITIES SECTION:
01-08 12:51:41.702 I/mono-stdout(21142): kongopvp.com. 600 IN SOA ns35.domaincontrol.com. dns.jomax.net. 2017122204 28800 7200 604800 600
01-08 12:51:41.702 I/mono-stdout(21142):
01-08 12:51:41.702 I/mono-stdout(21142): ;; Query time: 57 msec
01-08 12:51:41.702 I/mono-stdout(21142): ;; SERVER: 192.168.1.1#53
01-08 12:51:41.702 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.702 I/mono-stdout(21142): ;; MSG SIZE rcvd: 125
01-08 12:51:41.702 I/mono-stdout(21142):
[0:] DNS Complete: kongopvp.com:198.50.183.187
; (3 server found)01-08 12:51:41.765 I/mono-stdout(21142): ; (3 server found)

;; Got answer:
;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20962
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1

;; ERROR: Non-Existent Domain
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
_minecraft._tcp.play.novaria.world. IN SRV

;; AUTHORITIES SECTION:
novaria.world. 1799 IN SOA pdns1.registrar-servers.com. hostmaster.registrar-servers.com. 2017120602 43200 3600 604800 3601

;; Query time: 33 msec
;; SERVER: 8.8.4.4#53
;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.766 I/mono-stdout(21142): ;; Got answer:;; MSG SIZE rcvd: 137

01-08 12:51:41.766 I/mono-stdout(21142): ;; ->>HEADER<<- opcode: Query, status: Non-Existent Domain, id: 20962
01-08 12:51:41.766 I/mono-stdout(21142): ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTORITY: 1, ADDITIONAL: 1
01-08 12:51:41.766 I/mono-stdout(21142):
01-08 12:51:41.766 I/mono-stdout(21142): ;; ERROR: Non-Existent Domain
01-08 12:51:41.766 I/mono-stdout(21142): ;; OPT PSEUDOSECTION:
01-08 12:51:41.766 I/mono-stdout(21142): ; EDNS: version: 0, flags:; udp: 512
01-08 12:51:41.766 I/mono-stdout(21142): ;; QUESTION SECTION:
01-08 12:51:41.766 I/mono-stdout(21142): _minecraft._tcp.play.novaria.world. IN SRV
01-08 12:51:41.767 I/mono-stdout(21142):
01-08 12:51:41.767 I/mono-stdout(21142): ;; AUTHORITIES SECTION:
01-08 12:51:41.767 I/mono-stdout(21142): novaria.world. 1799 IN SOA pdns1.registrar-servers.com. hostmaster.registrar-servers.com. 2017120602 43200 3600 604800 3601
01-08 12:51:41.767 I/mono-stdout(21142):
01-08 12:51:41.767 I/mono-stdout(21142): ;; Query time: 33 msec
01-08 12:51:41.767 I/mono-stdout(21142): ;; SERVER: 8.8.4.4#53
01-08 12:51:41.767 I/mono-stdout(21142): ;; WHEN: Mon Jan 08 17:51:41 Z 2018
01-08 12:51:41.767 I/mono-stdout(21142): ;; MSG SIZE rcvd: 137
01-08 12:51:41.767 I/mono-stdout(21142):
[0:] DNS Complete: play.novaria.world:172.93.238.121

Getting "Server Failure" and "Error: No Connection could be established to any.."

I have tried to use the library on my machine, no custom dns servers. literally a copy paste of the code on the readme/wiki/documentation page.

I get errors like: ServerFailure and Error: No Connection could be established to any..<dns servers>. However DNS.GetHostEntry for the same IP will resolve correctly? What causes this exception? Why is this lib getting errors but not the main .net/windows version? Theyre using the same DNS servers!

Additionals count varies for the same MX lookup

Hi Michael
Thanks for creating this client. It has saved me a lot of time.
I have an issue where I'm doing an MX lookup for a domain and the Client returns the three expected records in the Answers but the Additionals count varies. Most of the time it returns the Additionals for Answer(0) only (whichever record happens to be there) but every once in a while it will return the Additionals for two of the answers (not necessarily Answer(0) and Answer(1)) and very rarely the Additionals for all three Answers. If I Dig the domain, I get all 38 records. Have I missed some setting?

DNS query is only checking one DNS server per request.

I'm not sure how the RFC reads, so I may be wrong about how the client is supposed to behave. It seems like a single query should automatically move through all name servers in the list even if one server returns Non-Existent Domain.

Let's say that I have 2 name servers configured at the OS, where 1 is valid for the domain and the other has no knowledge of the domain.

ns1 - has entry for host1.mydomain.
ns2 - does not have any knowledge of .mydomain.

The way that the client works now is that if I create an instance of the client and execute a query for host1.mydomain., it will work. Then, if using the same instance of the client, I make another query for host1.mydomain., it uses ns2 and fails returning no records.

Add "motivation" or "comparison to System.Net.Dns" to readme?

Ordinarily I use System.Net.Dns which is included in .NET Core 1.0+, .NET Framework, and .NET Standard 2.0+ - I was referred to using this library from a StackOverflow post specifically for performing MX lookups (to validate e-mail addresses in an account registration page on a website I'm working on) and I was looking for a quick explanation of why I should use DnsClient.NET instead of System.Net.Dns and after reading the docs for about 10 minutes now I still feel like I'm missing out on something.

I know System.Net.Dns is lacking some functionality, like the ability to perform true async queries for DNS records other than A/AAAA records - but 95% of my use-cases are met by the built-in class.

So a quick feature-comparison or statement-of-motivation (i.e. why you built this) would really help the introductory documentation for this project, imo.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.