public void TestMinimumConfigFile() { // The smallest file that you can get away with is an empty zone, which has just an SOA var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> </zone> " ); var zone = DNSZone.Unserialize(config); Assert.That(zone.Relays, Is.Empty); Assert.That(zone.StartOfAuthority, Is.EqualTo( new DNSRecord( new Domain("example.com"), AddressClass.INTERNET, 7200, new SOAResource( new Domain("ns.example.com"), new Domain("admin.example.com"), 0, 3600, 60, 3600, 60)))); }
public static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine("Usage: dns-server <config-file> <ip-address> <port>"); Environment.Exit(1); } var config = new XmlDocument(); config.Load(args[0]); var zone = DNSZone.Unserialize(config); var clock = new StopwatchClock(); var cache = new ResolverCache(clock, 2048); var resolver = new StubResolver(cache, ResolverUtils.SendQuery); var query_exec = new QueryExecutor(zone, resolver, cache); var bind_addr = new IPEndPoint(IPAddress.Parse(args[1]), int.Parse(args[2])); var server = new UDPServer(bind_addr, query_exec); server.Start(); }
public void TestNotAuthorityWhenPTRIsUnknown() { // We can't be authorities for PTR records we don't recognize var zone = new DNSZone(start_of_authority, relays); Assert.That(zone.IsAuthorityFor(new Domain("1.0.168.192.in-addr.arpa")), Is.False); }
public void TestEmptyConfigFileFails() { // The empty configuration file is not valid, since it lacks a SOA var config = ParseXML("<zone></zone>"); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestNotAuthorityWithoutDelegation() { // Ensure that we are not considered authoritative for things clearly // outside of our zone var zone = new DNSZone(start_of_authority, relays); Assert.That(zone.IsAuthorityFor(new Domain("bogus.com")), Is.False); }
public void TestNoSubzoneAuthoritative() { // Ensures that we don't return a subzone for things we're directly an // authority for var zone = new DNSZone(start_of_authority, relays); Assert.That(zone.FindSubZone(new Domain("www.example.com")), Is.Null); }
public void TestAuthorityWithoutDelegation() { // Ensure that we are considered authoritative for things that // are in our zone (without involvement by subzones) var zone = new DNSZone(start_of_authority, relays); Assert.That(zone.IsAuthorityFor(new Domain("www.example.com")), Is.True); }
public void TestNoSubzoneNotAuthoritatie() { // Ensures that we don't return a subzone for things that are outside of // our authority var zone = new DNSZone(start_of_authority, relays); Assert.That(zone.FindSubZone(new Domain("bogus.com")), Is.Null); }
public void TestSOABadPrimaryNS() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""3600"" primary-ns=""....example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestSOAMissingMinTTL() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""3600"" primary-ns=""ns.example.com"" hostmaster=""hostmaster.example.com"" refresh=""3600"" retry=""3600"" expire=""3600"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestHighTTLFails() { // TTLs are 32-bit numbers, so any TTL over 136 years doesn't work var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""9999999999"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestAuthorityWhenPTRIsKnownV6() { // We should be an authority for a PTR record that we know about var zone = new DNSZone(start_of_authority, relays); var ptr = new DNSRecord( new Domain("b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa"), AddressClass.INTERNET, 42, new PTRResource(new Domain("www.example.com"))); zone.Add(ptr); Assert.That(zone.IsAuthorityFor(new Domain("b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa")), Is.True); }
public void TestRelayTooHighPort() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <relay address=""192.168.0.1"" port=""1000000"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestPTRMissingPointer() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""3600"" primary-ns=""ns.example.com"" hostmaster=""hostmaster.example.com"" refresh=""3600"" retry=""3600"" expire=""3600"" /> <PTR name=""1.0.168.192.in-addr.arpa"" class=""IN"" ttl=""3600"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestMXGarbageMailserver() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""3600"" primary-ns=""ns.example.com"" hostmaster=""hostmaster.example.com"" refresh=""3600"" retry=""3600"" expire=""3600"" /> <MX name=""example.com"" class=""IN"" ttl=""3600"" priority=""1"" mailserver="".....example.com"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestAuthorityWithDelegation() { // Ensures that we're not an authority for things that are subzones var zone = new DNSZone(start_of_authority, relays); var subzone = new DNSRecord( new Domain("foo.example.com"), AddressClass.INTERNET, 42, new NSResource(new Domain("ns.foo.example.com"))); zone.Add(subzone); Assert.That(zone.IsAuthorityFor(new Domain("www.foo.example.com")), Is.False); }
public void TestAIPv6Address() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""3600"" primary-ns=""ns.example.com"" hostmaster=""hostmaster.example.com"" refresh=""3600"" retry=""3600"" expire=""3600"" /> <A name=""example.com"" class=""IN"" ttl=""3600"" address=""2001:beef:cafe::1337"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestAuthorityWhenEqualToDelegation() { // Ensure that we are an authority for the subzone's address itself (which can // occur if, say, there's a CNAME pointing to it) var zone = new DNSZone(start_of_authority, relays); var subzone = new DNSRecord( new Domain("foo.example.com"), AddressClass.INTERNET, 42, new NSResource(new Domain("ns.foo.example.com"))); zone.Add(subzone); Assert.That(zone.IsAuthorityFor(new Domain("foo.example.com")), Is.True); }
public void TestSubzoneWithDelegation() { // Ensure that we can find the subzone for a domain in that subzone var zone = new DNSZone(start_of_authority, relays); var subzone = new DNSRecord( new Domain("foo.example.com"), AddressClass.INTERNET, 42, new NSResource(new Domain("ns.foo.example.com"))); zone.Add(subzone); Assert.That(zone.FindSubZone(new Domain("www.foo.example.com")), Is.EqualTo(new Domain("foo.example.com"))); }
public void TestRelayGarbagePort() { // The smallest file that you can get away with is an empty zone, which has just an SOA var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <relay address=""192.168.0.1"" port=""blargh"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestMultipleSOAFails() { // Multiple SOA records are not allowed, since we currently don't support multiple zones var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <SOA name=""fabrikam.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> </zone> " ); Assert.Throws <InvalidDataException>(() => DNSZone.Unserialize(config)); }
public void TestQuery() { var zone = new DNSZone(start_of_authority, relays); var www_a_record = new DNSRecord( new Domain("www.example.com"), AddressClass.INTERNET, 42, new AResource(IPv4Address.Parse("192.168.0.1"))); var www_a_record_2 = new DNSRecord( new Domain("www.example.com"), AddressClass.INTERNET, 42, new AResource(IPv4Address.Parse("192.168.0.2"))); // Something that matches the class and record type, but not the domain var www2_a_record = new DNSRecord( new Domain("www2.example.com"), AddressClass.INTERNET, 42, new AResource(IPv4Address.Parse("192.168.0.3"))); // Something that matches the domain and class, but not the record type var www_cname_record = new DNSRecord( new Domain("www.example.com"), AddressClass.INTERNET, 42, new CNAMEResource(new Domain("www2.example.com"))); zone.Add(www_a_record); zone.Add(www_a_record_2); zone.Add(www2_a_record); zone.Add(www_cname_record); var query_result = zone.Query( new Domain("www.example.com"), ResourceRecordType.HOST_ADDRESS, AddressClass.INTERNET); var expected = new List <DNSRecord>(); expected.Add(www_a_record); expected.Add(www_a_record_2); Assert.That(query_result, Is.EquivalentTo(expected)); }
public void TestConfigRelay() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <relay address=""192.168.0.1"" port=""53"" /> </zone> " ); var zone = DNSZone.Unserialize(config); var relays = new EndPoint[] { new IPEndPoint(IPAddress.Parse("192.168.0.1"), 53) }; Assert.That(zone.Relays, Is.EquivalentTo(relays)); }
public void TestConfigMXRecord() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <MX name=""example.com"" class=""IN"" ttl=""3600"" priority=""42"" mailserver=""smtp.example.com"" /> </zone> " ); var zone = DNSZone.Unserialize(config); var records = new List <DNSRecord>(); records.Add(new DNSRecord( new Domain("example.com"), AddressClass.INTERNET, 3600, new MXResource(42, new Domain("smtp.example.com")))); Assert.That(zone.Records, Is.EquivalentTo(records)); }
public void TestConfigAAAARecord() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <AAAA name=""www.example.com"" class=""IN"" ttl=""3600"" address=""2001:0DB8:AC10:FE01:0000:0000:0000:0000"" /> </zone> " ); var zone = DNSZone.Unserialize(config); var records = new List <DNSRecord>(); records.Add(new DNSRecord( new Domain("www.example.com"), AddressClass.INTERNET, 3600, new AAAAResource(IPv6Address.Parse("2001:0DB8:AC10:FE01:0000:0000:0000:0000")))); Assert.That(zone.Records, Is.EquivalentTo(records)); }
public void TestSOAMinTTLIsEnforced() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <A name=""www.example.com"" class=""IN"" ttl=""5"" address=""192.168.0.1"" /> </zone> " ); var zone = DNSZone.Unserialize(config); var records = new List <DNSRecord>(); records.Add(new DNSRecord( new Domain("www.example.com"), AddressClass.INTERNET, 60, new AResource(IPv4Address.Parse("192.168.0.1")))); Assert.That(zone.Records, Is.EquivalentTo(records)); }
public void TestConfigPTRRecordV6() { var config = ParseXML(@" <zone> <SOA name=""example.com"" class=""IN"" ttl=""7200"" primary-ns=""ns.example.com"" hostmaster=""admin.example.com"" serial=""0"" refresh=""3600"" retry=""60"" expire=""3600"" min-ttl=""60"" /> <PTR name=""b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa"" class=""IN"" ttl=""3600"" pointer=""www.example.com"" /> </zone> " ); var zone = DNSZone.Unserialize(config); var records = new List <DNSRecord>(); records.Add(new DNSRecord( new Domain("b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa"), AddressClass.INTERNET, 3600, new PTRResource(new Domain("www.example.com")))); Assert.That(zone.Records, Is.EquivalentTo(records)); }
/** * Produces a new zone from an input XML file. */ public static DNSZone Unserialize(XmlDocument config) { DNSRecord start_of_authority = null; var records = new List <DNSRecord>(); var relays = new List <EndPoint>(); if (config.DocumentElement.Name != "zone") { throw new InvalidDataException("Root element must be called zone"); } foreach (var entry in config.DocumentElement.ChildNodes.OfType <XmlNode>()) { if (entry.NodeType != XmlNodeType.Element) { logger.Trace("Ignoring node of type {0}", entry.NodeType); continue; } bool is_record = entry.Name == "A" || entry.Name == "NS" || entry.Name == "CNAME" || entry.Name == "MX" || entry.Name == "SOA" || entry.Name == "PTR" || entry.Name == "AAAA"; if (is_record) { if (entry.Attributes["name"] == null || entry.Attributes["class"] == null || entry.Attributes["ttl"] == null) { throw new InvalidDataException("Resource records must have 'name', 'class' and 'ttl' attributes"); } var record_name = new Domain(entry.Attributes["name"].Value); AddressClass record_class; switch (entry.Attributes["class"].Value) { case "IN": record_class = AddressClass.INTERNET; break; default: throw new InvalidDataException("Only address class 'IN' is supported"); } UInt32 record_ttl = 0; try { record_ttl = UInt32.Parse(entry.Attributes["ttl"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["ttl"].Value + " is not a valid TTL"); } else { throw; } } IDNSResource resource = null; switch (entry.Name) { case "A": if (entry.Attributes["address"] == null) { throw new InvalidDataException("A record must have address"); } IPv4Address address; try { address = IPv4Address.Parse(entry.Attributes["address"].Value); } catch (FormatException) { throw new InvalidDataException(entry.Attributes["address"].Value + " is not a valid IPv4 address"); } resource = new AResource(address); logger.Trace("A record: address={0}", ((AResource)resource).Address); break; case "NS": if (entry.Attributes["nameserver"] == null) { throw new InvalidDataException("NS record must have a nameserver"); } resource = new NSResource(new Domain(entry.Attributes["nameserver"].Value)); logger.Trace("NS record: nameserver={0}", ((NSResource)resource).Nameserver); break; case "CNAME": if (entry.Attributes["alias"] == null) { throw new InvalidDataException("CNAME record must have an alias"); } resource = new CNAMEResource(new Domain(entry.Attributes["alias"].Value)); logger.Trace("CNAME record: alias={0}", ((CNAMEResource)resource).Alias); break; case "MX": if (entry.Attributes["priority"] == null || entry.Attributes["mailserver"] == null) { throw new InvalidDataException("MX record must have priority and mailserver"); } var mailserver = new Domain(entry.Attributes["mailserver"].Value); UInt16 preference = 0; try { preference = UInt16.Parse(entry.Attributes["priority"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["priority"].Value + " is not a valid priority value"); } else { throw; } } resource = new MXResource(preference, mailserver); logger.Trace("MX record: priority={0} mailserver={1}", ((MXResource)resource).Preference, ((MXResource)resource).Mailserver); break; case "PTR": if (entry.Attributes["pointer"] == null) { throw new InvalidDataException("PTR record must have pointer"); } if (!reverse_zone_v4.IsSubdomain(record_name) && !reverse_zone_v6.IsSubdomain(record_name)) { throw new InvalidDataException("PTR record be in the in-addr.arpa or ip6.arpa zone"); } resource = new PTRResource(new Domain(entry.Attributes["pointer"].Value)); logger.Trace("PTR record: pointer={0}", ((PTRResource)resource).Pointer); break; case "AAAA": if (entry.Attributes["address"] == null) { throw new InvalidDataException("AAAA record must have address"); } IPv6Address v6address; try { v6address = IPv6Address.Parse(entry.Attributes["address"].Value); } catch (FormatException) { throw new InvalidDataException(entry.Attributes["address"].Value + " is not a valid IPv4 address"); } resource = new AAAAResource(v6address); logger.Trace("AAAA record: address={0}", ((AAAAResource)resource).Address); break; case "SOA": if (entry.Attributes["primary-ns"] == null || entry.Attributes["hostmaster"] == null || entry.Attributes["serial"] == null || entry.Attributes["refresh"] == null || entry.Attributes["retry"] == null || entry.Attributes["expire"] == null || entry.Attributes["min-ttl"] == null) { throw new InvalidDataException("SOA record missing one of: primary-ns, hostmaster, serial, refresh, retry, expire and min-ttl"); } var primary_ns = new Domain(entry.Attributes["primary-ns"].Value); var hostmaster = new Domain(entry.Attributes["hostmaster"].Value); UInt32 serial = 0; try { serial = UInt32.Parse(entry.Attributes["serial"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["serial"].Value + " is not a valid serial number"); } else { throw; } } UInt32 refresh = 0; try { refresh = UInt32.Parse(entry.Attributes["refresh"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["refresh"].Value + " is not a valid refresh value"); } else { throw; } } UInt32 retry = 0; try { retry = UInt32.Parse(entry.Attributes["retry"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["retry"].Value + " is not a valid retry value"); } else { throw; } } UInt32 expire = 0; try { expire = UInt32.Parse(entry.Attributes["expire"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["expire"].Value + " is not a valid expire value"); } else { throw; } } UInt32 minttl = 0; try { minttl = UInt32.Parse(entry.Attributes["min-ttl"].Value); } catch (Exception err) { if (err is OverflowException || err is FormatException) { throw new InvalidDataException(entry.Attributes["min-ttl"].Value + " is not a valid expire value"); } else { throw; } } resource = new SOAResource(primary_ns, hostmaster, serial, refresh, retry, expire, minttl); logger.Trace("SOA record: primary-ns={0} hostmaster={1} serial={2} refresh={3} retry={4} expire={5} min-ttl={6}", ((SOAResource)resource).PrimaryNameServer, ((SOAResource)resource).Hostmaster, ((SOAResource)resource).Serial, ((SOAResource)resource).RefreshSeconds, ((SOAResource)resource).RetrySeconds, ((SOAResource)resource).ExpireSeconds, ((SOAResource)resource).MinimumTTL); break; } var record = new DNSRecord(record_name, record_class, record_ttl, resource); if (record.Resource.Type == ResourceRecordType.START_OF_AUTHORITY) { if (start_of_authority == null) { logger.Trace("Found SOA: {0}", record); start_of_authority = record; } else { throw new InvalidDataException("Cannot have more than one SOA record in zone"); } } else { logger.Trace("Found other record: {0}", record); records.Add(record); } } else if (entry.Name == "relay") { if (entry.Attributes["address"] == null || entry.Attributes["port"] == null) { throw new InvalidDataException("relay record must have address and port"); } IPAddress address; int port; try { address = IPAddress.Parse(entry.Attributes["address"].Value); } catch (FormatException) { throw new InvalidDataException(entry.Attributes["address"].Value + " is not a valid IPv4 address"); } try { port = int.Parse(entry.Attributes["port"].Value); } catch (FormatException) { throw new InvalidDataException(entry.Attributes["port"].Value + " is not a valid port"); } try { relays.Add(new IPEndPoint(address, port)); } catch (ArgumentOutOfRangeException) { throw new InvalidDataException(entry.Attributes["port"].Value + " is not a valid port"); } logger.Trace("Found relay: {0}:{1}", address, port); } else { throw new InvalidDataException(entry.Name + " is not a valid zone entry"); } } if (start_of_authority == null) { throw new InvalidDataException("Zone does not have SOA record"); } var zone = new DNSZone(start_of_authority, relays.ToArray()); foreach (var record in records) { if (record.TimeToLive < ((SOAResource)start_of_authority.Resource).MinimumTTL) { logger.Trace("Correcting TTL: Record {0} has smaller TTL than SOA MinTTL {1}", record, start_of_authority); record.TimeToLive = ((SOAResource)start_of_authority.Resource).MinimumTTL; } zone.Add(record); } return(zone); }
public QueryExecutor(DNSZone zone, IResolver resolver, IDNSCache cache) { this.zone = zone; this.resolver = resolver; this.cache = cache; }