mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 08:27:14 +02:00
Adds DnsWriter that implements DNS UPDATE protocol
* DnsUpdateWriter publishes changes to NS, DS, A, AAAA records for domains/hosts as appropriate using RFC 2136 DNS UPDATE protocol * Static configuration separate from RegistryConfig * Include dnsjava library as new third party dependency to generate DNS protocol messages * Expose /_dr/task/writeDns in RegistryTestServer * Currently not included in BackendComponent
This commit is contained in:
parent
954d7e1e8f
commit
20f214b9d0
14 changed files with 1006 additions and 2 deletions
1
AUTHORS
1
AUTHORS
|
@ -7,3 +7,4 @@
|
||||||
# The email address is not required for organizations.
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
Google Inc.
|
Google Inc.
|
||||||
|
Donuts Inc.
|
||||||
|
|
|
@ -21,3 +21,4 @@ Jared Brothers <brothers@google.com>
|
||||||
Pablo Mayrgundter <pmy@google.com>
|
Pablo Mayrgundter <pmy@google.com>
|
||||||
Daisuke Yabuki <dxy@google.com>
|
Daisuke Yabuki <dxy@google.com>
|
||||||
Tim Boring <tjb@google.com>
|
Tim Boring <tjb@google.com>
|
||||||
|
Hans Ridder <hans.ridder@gmail.com>
|
||||||
|
|
|
@ -30,15 +30,22 @@ public interface DnsWriter extends AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads {@code domainName} from datastore and publishes its NS/DS records to the DNS server.
|
* Loads {@code domainName} from datastore and publishes its NS/DS records to the DNS server.
|
||||||
|
* Replaces existing records for the exact name supplied with an NS record for each name server
|
||||||
|
* and a DS record for each delegation signer stored in the registry for the supplied domain name.
|
||||||
|
* If the domain is deleted or is in a "non-publish" state then any existing records are deleted.
|
||||||
*
|
*
|
||||||
* @param domainName the fully qualified domain name
|
* @param domainName the fully qualified domain name, with no trailing dot
|
||||||
*/
|
*/
|
||||||
void publishDomain(String domainName);
|
void publishDomain(String domainName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads {@code hostName} from datastore and publishes its A/AAAA glue records to the DNS server.
|
* Loads {@code hostName} from datastore and publishes its A/AAAA glue records to the DNS server.
|
||||||
|
* Replaces existing records for the exact name supplied, with an A or AAAA record (as
|
||||||
|
* appropriate) for each address stored in the registry, for the supplied host name. If the host is
|
||||||
|
* deleted then the existing records are deleted. Assumes that this method will only be called for
|
||||||
|
* in-bailiwick hosts. The registry does not have addresses for other hosts.
|
||||||
*
|
*
|
||||||
* @param hostName the fully qualified host name
|
* @param hostName the fully qualified host name, with no trailing dot
|
||||||
*/
|
*/
|
||||||
void publishHost(String hostName);
|
void publishHost(String hostName);
|
||||||
|
|
||||||
|
|
26
java/com/google/domain/registry/dns/writer/dnsupdate/BUILD
Normal file
26
java/com/google/domain/registry/dns/writer/dnsupdate/BUILD
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package(
|
||||||
|
default_visibility = ["//java/com/google/domain/registry:registry_project"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "dnsupdate",
|
||||||
|
srcs = glob(["*.java"]),
|
||||||
|
deps = [
|
||||||
|
"//java/com/google/common/annotations",
|
||||||
|
"//java/com/google/common/base",
|
||||||
|
"//java/com/google/common/collect",
|
||||||
|
"//java/com/google/common/io",
|
||||||
|
"//java/com/google/common/net",
|
||||||
|
"//java/com/google/common/primitives",
|
||||||
|
"//java/com/google/domain/registry/config",
|
||||||
|
"//java/com/google/domain/registry/dns/writer/api",
|
||||||
|
"//java/com/google/domain/registry/model",
|
||||||
|
"//java/com/google/domain/registry/util",
|
||||||
|
"//third_party/java/joda_time",
|
||||||
|
"//third_party/java/dagger",
|
||||||
|
"//third_party/java/dnsjava",
|
||||||
|
"//third_party/java/jsr305_annotations",
|
||||||
|
"//third_party/java/jsr330_inject",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Verify.verify;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import com.google.domain.registry.config.ConfigModule.Config;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.xbill.DNS.Message;
|
||||||
|
import org.xbill.DNS.Opcode;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transport for DNS messages. Sends/receives DNS messages over TCP using old-style {@link Socket}
|
||||||
|
* s and the message framing defined in <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a>.
|
||||||
|
* We would like use the dnsjava library's {@link SimpleResolver} class for this, but it requires
|
||||||
|
* {@link SocketChannel} which is not supported on AppEngine.
|
||||||
|
*/
|
||||||
|
public class DnsMessageTransport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of message length field for DNS TCP transport.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a>
|
||||||
|
*/
|
||||||
|
static final int MESSAGE_LENGTH_FIELD_BYTES = 2;
|
||||||
|
private static final int MESSAGE_MAXIMUM_LENGTH = (1 << (MESSAGE_LENGTH_FIELD_BYTES * 8)) - 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The standard DNS port number.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a>
|
||||||
|
*/
|
||||||
|
@VisibleForTesting static final int DNS_PORT = 53;
|
||||||
|
|
||||||
|
private final SocketFactory factory;
|
||||||
|
private final String updateHost;
|
||||||
|
private final int updateTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param factory a factory for TCP sockets
|
||||||
|
* @param updateHost host name of the DNS server
|
||||||
|
* @param updateTimeout update I/O timeout
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public DnsMessageTransport(
|
||||||
|
SocketFactory factory,
|
||||||
|
@Config("dnsUpdateHost") String updateHost,
|
||||||
|
@Config("dnsUpdateTimeout") Duration updateTimeout) {
|
||||||
|
this.factory = factory;
|
||||||
|
this.updateHost = updateHost;
|
||||||
|
this.updateTimeout = Ints.checkedCast(updateTimeout.getMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DNS "query" message (most likely an UPDATE) and returns the response. The response is
|
||||||
|
* checked for matching ID and opcode.
|
||||||
|
*
|
||||||
|
* @param query a message to send
|
||||||
|
* @return the response received from the server
|
||||||
|
* @throws IOException if the Socket input/output streams throws one
|
||||||
|
* @throws IllegalArgumentException if the query is too large to be sent (> 65535 bytes)
|
||||||
|
*/
|
||||||
|
public Message send(Message query) throws IOException {
|
||||||
|
try (Socket socket = factory.createSocket(InetAddress.getByName(updateHost), DNS_PORT)) {
|
||||||
|
socket.setSoTimeout(updateTimeout);
|
||||||
|
writeMessage(socket.getOutputStream(), query);
|
||||||
|
Message response = readMessage(socket.getInputStream());
|
||||||
|
checkValidResponse(query, response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkValidResponse(Message query, Message response) {
|
||||||
|
verify(
|
||||||
|
response.getHeader().getID() == query.getHeader().getID(),
|
||||||
|
"response ID %s does not match query ID %s",
|
||||||
|
response.getHeader().getID(),
|
||||||
|
query.getHeader().getID());
|
||||||
|
verify(
|
||||||
|
response.getHeader().getOpcode() == query.getHeader().getOpcode(),
|
||||||
|
"response opcode '%s' does not match query opcode '%s'",
|
||||||
|
Opcode.string(response.getHeader().getOpcode()),
|
||||||
|
Opcode.string(query.getHeader().getOpcode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMessage(OutputStream outputStream, Message message) throws IOException {
|
||||||
|
byte[] messageData = message.toWire();
|
||||||
|
checkArgument(
|
||||||
|
messageData.length <= MESSAGE_MAXIMUM_LENGTH,
|
||||||
|
"DNS request message larger than maximum of %s: %s",
|
||||||
|
MESSAGE_MAXIMUM_LENGTH,
|
||||||
|
messageData.length);
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(messageData.length + MESSAGE_LENGTH_FIELD_BYTES);
|
||||||
|
buffer.putShort((short) messageData.length);
|
||||||
|
buffer.put(messageData);
|
||||||
|
outputStream.write(buffer.array());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message readMessage(InputStream inputStream) throws IOException {
|
||||||
|
DataInputStream stream = new DataInputStream(inputStream);
|
||||||
|
int length = stream.readUnsignedShort();
|
||||||
|
byte[] messageData = new byte[length];
|
||||||
|
stream.readFully(messageData);
|
||||||
|
return new Message(messageData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import com.google.domain.registry.config.ConfigModule.Config;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class DnsUpdateConfigModule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host that receives DNS updates from the registry.
|
||||||
|
* Usually a "hidden master" for the TLDs.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Config("dnsUpdateHost")
|
||||||
|
public static String provideDnsUpdateHost() {
|
||||||
|
return "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout on the socket for DNS update requests.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Config("dnsUpdateTimeout")
|
||||||
|
public static Duration provideDnsUpdateTimeout() {
|
||||||
|
return Duration.standardSeconds(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DNS time-to-live (TTL) for resource records created by the registry.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Config("dnsUpdateTimeToLive")
|
||||||
|
public static Duration provideDnsUpdateTimeToLive() {
|
||||||
|
return Duration.standardHours(2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import static com.google.common.base.Verify.verify;
|
||||||
|
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
|
||||||
|
import com.google.common.net.InternetDomainName;
|
||||||
|
import com.google.domain.registry.config.ConfigModule.Config;
|
||||||
|
import com.google.domain.registry.dns.writer.api.DnsWriter;
|
||||||
|
import com.google.domain.registry.model.domain.DomainResource;
|
||||||
|
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
|
||||||
|
import com.google.domain.registry.model.host.HostResource;
|
||||||
|
import com.google.domain.registry.model.registry.Registries;
|
||||||
|
import com.google.domain.registry.util.Clock;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.xbill.DNS.AAAARecord;
|
||||||
|
import org.xbill.DNS.ARecord;
|
||||||
|
import org.xbill.DNS.DClass;
|
||||||
|
import org.xbill.DNS.DSRecord;
|
||||||
|
import org.xbill.DNS.Message;
|
||||||
|
import org.xbill.DNS.NSRecord;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Rcode;
|
||||||
|
import org.xbill.DNS.TextParseException;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.Update;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DnsWriter that implements the DNS UPDATE protocol as specified in
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc2136">RFC 2136</a>. Publishes changes in the
|
||||||
|
* domain-registry to a (capable) external DNS server, sometimes called a "hidden master". DNS
|
||||||
|
* UPDATE messages are sent via a "resolver" class which implements the network transport. For each
|
||||||
|
* publish call, a single UPDATE message is created containing the records required to "synchronize"
|
||||||
|
* the DNS with the current (at the time of processing) state of the registry, for the supplied
|
||||||
|
* domain/host.
|
||||||
|
*
|
||||||
|
* <p>The general strategy of the publish methods is to delete <em>all</em> resource records of any
|
||||||
|
* <em>type</em> that match the exact domain/host name supplied. And then for create/update cases,
|
||||||
|
* add any required records. Deleting all records of any type assumes that the registry is
|
||||||
|
* authoritative for all records for names in the zone. This seems appropriate for a TLD DNS server,
|
||||||
|
* which should only contain records required for proper DNS delegation.
|
||||||
|
*
|
||||||
|
* <p>Only NS, DS, A, and AAAA records are published, and in particular no DNSSEC signing is done
|
||||||
|
* assuming that this will be done by a third party DNS provider.
|
||||||
|
*
|
||||||
|
* <p>Each publish call is treated as an atomic update to the DNS. If an update fails an exception is
|
||||||
|
* thrown, expecting the caller to retry the update later. The SOA record serial number is
|
||||||
|
* implicitly incremented by the server on each UPDATE message, as required by RFC 2136. Care must
|
||||||
|
* be taken to make sure the SOA serial number does not go backwards if the entire TLD (zone) is
|
||||||
|
* "reset" to empty and republished.
|
||||||
|
*/
|
||||||
|
public class DnsUpdateWriter implements DnsWriter {
|
||||||
|
|
||||||
|
private final Duration dnsTimeToLive;
|
||||||
|
private final DnsMessageTransport resolver;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param dnsTimeToLive TTL used for any created resource records
|
||||||
|
* @param resolver a resolver used to send/receive the UPDATE messages
|
||||||
|
* @param clock a source of time
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public DnsUpdateWriter(
|
||||||
|
@Config("dnsUpdateTimeToLive") Duration dnsTimeToLive,
|
||||||
|
DnsMessageTransport resolver,
|
||||||
|
Clock clock) {
|
||||||
|
this.dnsTimeToLive = dnsTimeToLive;
|
||||||
|
this.resolver = resolver;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publishDomain(String domainName) {
|
||||||
|
DomainResource domain = loadByUniqueId(DomainResource.class, domainName, clock.nowUtc());
|
||||||
|
try {
|
||||||
|
Update update = new Update(toAbsoluteName(findTldFromName(domainName)));
|
||||||
|
update.delete(toAbsoluteName(domainName), Type.ANY);
|
||||||
|
if (domain != null && domain.shouldPublishToDns()) {
|
||||||
|
update.add(makeNameServerSet(domainName, domain.loadNameservers()));
|
||||||
|
update.add(makeDelegationSignerSet(domainName, domain.getDsData()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Message response = resolver.send(update);
|
||||||
|
verify(
|
||||||
|
response.getRcode() == Rcode.NOERROR,
|
||||||
|
"DNS server failed domain update for '%s' rcode: %s",
|
||||||
|
domainName,
|
||||||
|
Rcode.string(response.getRcode()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("publishDomain failed: " + domainName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void publishHost(String hostName) {
|
||||||
|
HostResource host = loadByUniqueId(HostResource.class, hostName, clock.nowUtc());
|
||||||
|
try {
|
||||||
|
Update update = new Update(toAbsoluteName(findTldFromName(hostName)));
|
||||||
|
update.delete(toAbsoluteName(hostName), Type.ANY);
|
||||||
|
if (host != null) {
|
||||||
|
update.add(makeAddressSet(hostName, host.getInetAddresses()));
|
||||||
|
update.add(makeV6AddressSet(hostName, host.getInetAddresses()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Message response = resolver.send(update);
|
||||||
|
verify(
|
||||||
|
response.getRcode() == Rcode.NOERROR,
|
||||||
|
"DNS server failed host update for '%s' rcode: %s",
|
||||||
|
hostName,
|
||||||
|
Rcode.string(response.getRcode()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("publishHost failed: " + hostName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing. Publish calls are synchronous and atomic.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {}
|
||||||
|
|
||||||
|
private RRset makeDelegationSignerSet(String domainName, Iterable<DelegationSignerData> dsData)
|
||||||
|
throws TextParseException {
|
||||||
|
RRset signerSet = new RRset();
|
||||||
|
for (DelegationSignerData signerData : dsData) {
|
||||||
|
DSRecord dsRecord =
|
||||||
|
new DSRecord(
|
||||||
|
toAbsoluteName(domainName),
|
||||||
|
DClass.IN,
|
||||||
|
dnsTimeToLive.getStandardSeconds(),
|
||||||
|
signerData.getKeyTag(),
|
||||||
|
signerData.getAlgorithm(),
|
||||||
|
signerData.getDigestType(),
|
||||||
|
signerData.getDigest());
|
||||||
|
signerSet.addRR(dsRecord);
|
||||||
|
}
|
||||||
|
return signerSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RRset makeNameServerSet(String domainName, Iterable<HostResource> nameservers)
|
||||||
|
throws TextParseException {
|
||||||
|
RRset nameServerSet = new RRset();
|
||||||
|
for (HostResource host : nameservers) {
|
||||||
|
NSRecord record =
|
||||||
|
new NSRecord(
|
||||||
|
toAbsoluteName(domainName),
|
||||||
|
DClass.IN,
|
||||||
|
dnsTimeToLive.getStandardSeconds(),
|
||||||
|
toAbsoluteName(host.getFullyQualifiedHostName()));
|
||||||
|
nameServerSet.addRR(record);
|
||||||
|
}
|
||||||
|
return nameServerSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RRset makeAddressSet(String hostName, Iterable<InetAddress> addresses)
|
||||||
|
throws TextParseException {
|
||||||
|
RRset addressSet = new RRset();
|
||||||
|
for (InetAddress address : addresses) {
|
||||||
|
if (address instanceof Inet4Address) {
|
||||||
|
ARecord record =
|
||||||
|
new ARecord(
|
||||||
|
toAbsoluteName(hostName), DClass.IN, dnsTimeToLive.getStandardSeconds(), address);
|
||||||
|
addressSet.addRR(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addressSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RRset makeV6AddressSet(String hostName, Iterable<InetAddress> addresses)
|
||||||
|
throws TextParseException {
|
||||||
|
RRset addressSet = new RRset();
|
||||||
|
for (InetAddress address : addresses) {
|
||||||
|
if (address instanceof Inet6Address) {
|
||||||
|
AAAARecord record =
|
||||||
|
new AAAARecord(
|
||||||
|
toAbsoluteName(hostName), DClass.IN, dnsTimeToLive.getStandardSeconds(), address);
|
||||||
|
addressSet.addRR(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addressSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findTldFromName(String name) {
|
||||||
|
return Registries.findTldForNameOrThrow(InternetDomainName.from(name)).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Name toAbsoluteName(String name) throws TextParseException {
|
||||||
|
return Name.fromString(name, Name.root);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import com.google.domain.registry.dns.writer.api.DnsWriter;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
/** Dagger module that provides a DnsUpdateWriter. */
|
||||||
|
@Module
|
||||||
|
public final class DnsUpdateWriterModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
static DnsWriter provideDnsWriter(DnsUpdateWriter dnsWriter) {
|
||||||
|
return dnsWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
static SocketFactory provideSocketFactory() {
|
||||||
|
return SocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ java_library(
|
||||||
"//java/com/google/domain/registry/cron",
|
"//java/com/google/domain/registry/cron",
|
||||||
"//java/com/google/domain/registry/dns",
|
"//java/com/google/domain/registry/dns",
|
||||||
"//java/com/google/domain/registry/dns/writer/api",
|
"//java/com/google/domain/registry/dns/writer/api",
|
||||||
|
"//java/com/google/domain/registry/dns/writer/dnsupdate",
|
||||||
"//java/com/google/domain/registry/export",
|
"//java/com/google/domain/registry/export",
|
||||||
"//java/com/google/domain/registry/export/sheet",
|
"//java/com/google/domain/registry/export/sheet",
|
||||||
"//java/com/google/domain/registry/flows",
|
"//java/com/google/domain/registry/flows",
|
||||||
|
|
|
@ -206,6 +206,12 @@ def domain_registry_repositories():
|
||||||
sha1 = "80276338d1c2542ebebac542b535d1ecd48a3fd7",
|
sha1 = "80276338d1c2542ebebac542b535d1ecd48a3fd7",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
native.maven_jar(
|
||||||
|
name = "dnsjava",
|
||||||
|
artifact = "dnsjava:dnsjava:2.1.7",
|
||||||
|
sha1 = "0a1ed0a251d22bf528cebfafb94c55e6f3f339cf",
|
||||||
|
)
|
||||||
|
|
||||||
native.maven_jar(
|
native.maven_jar(
|
||||||
name = "eclipse_jdt_core",
|
name = "eclipse_jdt_core",
|
||||||
artifact = "org.eclipse.jdt:org.eclipse.jdt.core:3.10.0",
|
artifact = "org.eclipse.jdt:org.eclipse.jdt.core:3.10.0",
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.google.common.base.VerifyException;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.xbill.DNS.ARecord;
|
||||||
|
import org.xbill.DNS.DClass;
|
||||||
|
import org.xbill.DNS.Flags;
|
||||||
|
import org.xbill.DNS.Message;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.Opcode;
|
||||||
|
import org.xbill.DNS.Rcode;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.Update;
|
||||||
|
import org.xbill.DNS.utils.base16;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
/** Unit tests for {@link DnsMessageTransport}. */
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class DnsMessageTransportTest {
|
||||||
|
|
||||||
|
private static final String UPDATE_HOST = "127.0.0.1";
|
||||||
|
|
||||||
|
@Mock private SocketFactory mockFactory;
|
||||||
|
@Mock private Socket mockSocket;
|
||||||
|
private Message simpleQuery;
|
||||||
|
private Message expectedResponse;
|
||||||
|
private DnsMessageTransport resolver;
|
||||||
|
|
||||||
|
@Rule public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
simpleQuery =
|
||||||
|
Message.newQuery(Record.newRecord(Name.fromString("example.com."), Type.A, DClass.IN));
|
||||||
|
expectedResponse = responseMessageWithCode(simpleQuery, Rcode.NOERROR);
|
||||||
|
when(mockFactory.createSocket(InetAddress.getByName(UPDATE_HOST), DnsMessageTransport.DNS_PORT))
|
||||||
|
.thenReturn(mockSocket);
|
||||||
|
resolver = new DnsMessageTransport(mockFactory, UPDATE_HOST, Duration.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sentMessageHasCorrectLengthAndContent() throws Exception {
|
||||||
|
ByteArrayInputStream inputStream =
|
||||||
|
new ByteArrayInputStream(messageToBytesWithLength(expectedResponse));
|
||||||
|
when(mockSocket.getInputStream()).thenReturn(inputStream);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(outputStream);
|
||||||
|
|
||||||
|
resolver.send(simpleQuery);
|
||||||
|
|
||||||
|
ByteBuffer sentMessage = ByteBuffer.wrap(outputStream.toByteArray());
|
||||||
|
int messageLength = sentMessage.getShort();
|
||||||
|
byte[] messageData = new byte[messageLength];
|
||||||
|
sentMessage.get(messageData);
|
||||||
|
assertThat(messageLength).isEqualTo(simpleQuery.toWire().length);
|
||||||
|
assertThat(base16.toString(messageData)).isEqualTo(base16.toString(simpleQuery.toWire()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void receivedMessageWithLengthHasCorrectContent() throws Exception {
|
||||||
|
ByteArrayInputStream inputStream =
|
||||||
|
new ByteArrayInputStream(messageToBytesWithLength(expectedResponse));
|
||||||
|
when(mockSocket.getInputStream()).thenReturn(inputStream);
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||||
|
|
||||||
|
Message actualResponse = resolver.send(simpleQuery);
|
||||||
|
|
||||||
|
assertThat(base16.toString(actualResponse.toWire()))
|
||||||
|
.isEqualTo(base16.toString(expectedResponse.toWire()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void eofReceivingResponse() throws Exception {
|
||||||
|
byte[] messageBytes = messageToBytesWithLength(expectedResponse);
|
||||||
|
ByteArrayInputStream inputStream =
|
||||||
|
new ByteArrayInputStream(Arrays.copyOf(messageBytes, messageBytes.length - 1));
|
||||||
|
when(mockSocket.getInputStream()).thenReturn(inputStream);
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||||
|
thrown.expect(EOFException.class);
|
||||||
|
|
||||||
|
Message expectedQuery = new Message();
|
||||||
|
resolver.send(expectedQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void timeoutReceivingResponse() throws Exception {
|
||||||
|
InputStream mockInputStream = mock(InputStream.class);
|
||||||
|
when(mockInputStream.read()).thenThrow(new SocketTimeoutException("testing"));
|
||||||
|
when(mockSocket.getInputStream()).thenReturn(mockInputStream);
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||||
|
|
||||||
|
Duration testTimeout = Duration.standardSeconds(1);
|
||||||
|
DnsMessageTransport resolver = new DnsMessageTransport(mockFactory, UPDATE_HOST, testTimeout);
|
||||||
|
Message expectedQuery = new Message();
|
||||||
|
try {
|
||||||
|
resolver.send(expectedQuery);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
verify(mockSocket).setSoTimeout((int) testTimeout.getMillis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sentMessageTooLongThrowsException() throws Exception {
|
||||||
|
Update oversize = new Update(Name.fromString("tld", Name.root));
|
||||||
|
for (int i = 0; i < 2000; i++) {
|
||||||
|
oversize.add(
|
||||||
|
ARecord.newRecord(
|
||||||
|
Name.fromString("test-extremely-long-name-" + i + ".tld", Name.root),
|
||||||
|
Type.A,
|
||||||
|
DClass.IN));
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(outputStream);
|
||||||
|
thrown.expect(IllegalArgumentException.class);
|
||||||
|
thrown.expectMessage("message larger than maximum");
|
||||||
|
|
||||||
|
resolver.send(oversize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseIdMismatchThrowsExeption() throws Exception {
|
||||||
|
expectedResponse.getHeader().setID(1 + simpleQuery.getHeader().getID());
|
||||||
|
when(mockSocket.getInputStream())
|
||||||
|
.thenReturn(new ByteArrayInputStream(messageToBytesWithLength(expectedResponse)));
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||||
|
thrown.expect(VerifyException.class);
|
||||||
|
thrown.expectMessage(
|
||||||
|
"response ID "
|
||||||
|
+ expectedResponse.getHeader().getID()
|
||||||
|
+ " does not match query ID "
|
||||||
|
+ simpleQuery.getHeader().getID());
|
||||||
|
|
||||||
|
resolver.send(simpleQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void responseOpcodeMismatchThrowsException() throws Exception {
|
||||||
|
simpleQuery.getHeader().setOpcode(Opcode.QUERY);
|
||||||
|
expectedResponse.getHeader().setOpcode(Opcode.STATUS);
|
||||||
|
when(mockSocket.getInputStream())
|
||||||
|
.thenReturn(new ByteArrayInputStream(messageToBytesWithLength(expectedResponse)));
|
||||||
|
when(mockSocket.getOutputStream()).thenReturn(new ByteArrayOutputStream());
|
||||||
|
thrown.expect(VerifyException.class);
|
||||||
|
thrown.expectMessage("response opcode 'STATUS' does not match query opcode 'QUERY'");
|
||||||
|
|
||||||
|
resolver.send(simpleQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message responseMessageWithCode(Message query, int responseCode) {
|
||||||
|
Message message = new Message(query.getHeader().getID());
|
||||||
|
message.getHeader().setOpcode(query.getHeader().getOpcode());
|
||||||
|
message.getHeader().setFlag(Flags.QR);
|
||||||
|
message.getHeader().setRcode(responseCode);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] messageToBytesWithLength(Message message) throws IOException {
|
||||||
|
byte[] bytes = message.toWire();
|
||||||
|
ByteBuffer buffer =
|
||||||
|
ByteBuffer.allocate(bytes.length + DnsMessageTransport.MESSAGE_LENGTH_FIELD_BYTES);
|
||||||
|
buffer.putShort((short) bytes.length);
|
||||||
|
buffer.put(bytes);
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.domain.registry.dns.writer.dnsupdate;
|
||||||
|
|
||||||
|
import static com.google.common.io.BaseEncoding.base16;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.createTld;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistActiveDomain;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistActiveHost;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistActiveSubordinateHost;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistDeletedDomain;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistDeletedHost;
|
||||||
|
import static com.google.domain.registry.testing.DatastoreHelper.persistResource;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.google.common.base.VerifyException;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.net.InetAddresses;
|
||||||
|
import com.google.domain.registry.model.domain.DomainResource;
|
||||||
|
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||||
|
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
|
||||||
|
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||||
|
import com.google.domain.registry.model.host.HostResource;
|
||||||
|
import com.google.domain.registry.model.ofy.Ofy;
|
||||||
|
import com.google.domain.registry.testing.AppEngineRule;
|
||||||
|
import com.google.domain.registry.testing.FakeClock;
|
||||||
|
import com.google.domain.registry.testing.InjectRule;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.xbill.DNS.Flags;
|
||||||
|
import org.xbill.DNS.Message;
|
||||||
|
import org.xbill.DNS.Opcode;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Rcode;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Section;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.Update;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
|
/** Unit tests for {@link DnsUpdateWriter}. */
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class DnsUpdateWriterTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final AppEngineRule appEngine =
|
||||||
|
AppEngineRule.builder().withDatastore().withTaskQueue().build();
|
||||||
|
|
||||||
|
@Rule public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Rule public final InjectRule inject = new InjectRule();
|
||||||
|
|
||||||
|
private final FakeClock clock = new FakeClock(DateTime.parse("1971-01-01TZ"));
|
||||||
|
|
||||||
|
@Mock private DnsMessageTransport mockResolver;
|
||||||
|
@Captor private ArgumentCaptor<Update> updateCaptor;
|
||||||
|
private DelegationSignerData testSignerData =
|
||||||
|
DelegationSignerData.create(1, 3, 1, base16().decode("0123456789ABCDEF"));
|
||||||
|
private DnsUpdateWriter writer;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
inject.setStaticField(Ofy.class, "clock", clock);
|
||||||
|
|
||||||
|
createTld("tld");
|
||||||
|
when(mockResolver.send(any(Update.class))).thenReturn(messageWithResponseCode(Rcode.NOERROR));
|
||||||
|
|
||||||
|
writer = new DnsUpdateWriter(Duration.ZERO, mockResolver, clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishDomainCreatePublishesNameServers() throws Exception {
|
||||||
|
HostResource host1 = persistActiveHost("ns1.example.tld");
|
||||||
|
HostResource host2 = persistActiveHost("ns2.example.tld");
|
||||||
|
DomainResource domain =
|
||||||
|
persistActiveDomain("example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.setNameservers(
|
||||||
|
ImmutableSet.of(ReferenceUnion.create(host1), ReferenceUnion.create(host2)))
|
||||||
|
.build();
|
||||||
|
persistResource(domain);
|
||||||
|
|
||||||
|
writer.publishDomain("example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "example.tld.", Type.ANY);
|
||||||
|
assertThatUpdateAdds(update, "example.tld.", Type.NS, "ns1.example.tld.", "ns2.example.tld.");
|
||||||
|
assertThatTotalUpdateSetsIs(update, 2); // The delete and NS sets
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishDomainCreatePublishesDelegationSigner() throws Exception {
|
||||||
|
DomainResource domain =
|
||||||
|
persistActiveDomain("example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.setNameservers(
|
||||||
|
ImmutableSet.of(ReferenceUnion.create(persistActiveHost("ns1.example.tld"))))
|
||||||
|
.setDsData(ImmutableSet.of(testSignerData))
|
||||||
|
.build();
|
||||||
|
persistResource(domain);
|
||||||
|
|
||||||
|
writer.publishDomain("example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "example.tld.", Type.ANY);
|
||||||
|
assertThatUpdateAdds(update, "example.tld.", Type.NS, "ns1.example.tld.");
|
||||||
|
assertThatUpdateAdds(update, "example.tld.", Type.DS, "1 3 1 0123456789ABCDEF");
|
||||||
|
assertThatTotalUpdateSetsIs(update, 3); // The delete, the NS, and DS sets
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishDomainWhenNotActiveRemovesDnsRecords() throws Exception {
|
||||||
|
DomainResource domain =
|
||||||
|
persistActiveDomain("example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.addStatusValue(StatusValue.SERVER_HOLD)
|
||||||
|
.setNameservers(
|
||||||
|
ImmutableSet.of(ReferenceUnion.create(persistActiveHost("ns1.example.tld"))))
|
||||||
|
.build();
|
||||||
|
persistResource(domain);
|
||||||
|
|
||||||
|
writer.publishDomain("example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "example.tld.", Type.ANY);
|
||||||
|
assertThatTotalUpdateSetsIs(update, 1); // Just the delete set
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishDomainDeleteRemovesDnsRecords() throws Exception {
|
||||||
|
persistDeletedDomain("example.tld", clock.nowUtc());
|
||||||
|
|
||||||
|
writer.publishDomain("example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "example.tld.", Type.ANY);
|
||||||
|
assertThatTotalUpdateSetsIs(update, 1); // Just the delete set
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishHostCreatePublishesAddressRecords() throws Exception {
|
||||||
|
HostResource host =
|
||||||
|
persistActiveSubordinateHost("ns1.example.tld", persistActiveDomain("example.tld"))
|
||||||
|
.asBuilder()
|
||||||
|
.setInetAddresses(
|
||||||
|
ImmutableSet.of(
|
||||||
|
InetAddresses.forString("10.0.0.1"),
|
||||||
|
InetAddresses.forString("10.1.0.1"),
|
||||||
|
InetAddresses.forString("fd0e:a5c8:6dfb:6a5e:0:0:0:1")))
|
||||||
|
.build();
|
||||||
|
persistResource(host);
|
||||||
|
|
||||||
|
writer.publishHost("ns1.example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "ns1.example.tld.", Type.ANY);
|
||||||
|
assertThatUpdateAdds(update, "ns1.example.tld.", Type.A, "10.0.0.1", "10.1.0.1");
|
||||||
|
assertThatUpdateAdds(update, "ns1.example.tld.", Type.AAAA, "fd0e:a5c8:6dfb:6a5e:0:0:0:1");
|
||||||
|
assertThatTotalUpdateSetsIs(update, 3); // The delete, the A, and AAAA sets
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishHostDeleteRemovesDnsRecords() throws Exception {
|
||||||
|
persistDeletedHost("ns1.example.tld", clock.nowUtc());
|
||||||
|
|
||||||
|
writer.publishHost("ns1.example.tld");
|
||||||
|
|
||||||
|
verify(mockResolver).send(updateCaptor.capture());
|
||||||
|
Update update = updateCaptor.getValue();
|
||||||
|
assertThatUpdatedZoneIs(update, "tld.");
|
||||||
|
assertThatUpdateDeletes(update, "ns1.example.tld.", Type.ANY);
|
||||||
|
assertThatTotalUpdateSetsIs(update, 1); // Just the delete set
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishDomainFailsWhenDnsUpdateReturnsError() throws Exception {
|
||||||
|
DomainResource domain =
|
||||||
|
persistActiveDomain("example.tld")
|
||||||
|
.asBuilder()
|
||||||
|
.setNameservers(
|
||||||
|
ImmutableSet.of(ReferenceUnion.create(persistActiveHost("ns1.example.tld"))))
|
||||||
|
.build();
|
||||||
|
persistResource(domain);
|
||||||
|
when(mockResolver.send(any(Message.class))).thenReturn(messageWithResponseCode(Rcode.SERVFAIL));
|
||||||
|
thrown.expect(VerifyException.class);
|
||||||
|
thrown.expectMessage("SERVFAIL");
|
||||||
|
|
||||||
|
writer.publishDomain("example.tld");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishHostFailsWhenDnsUpdateReturnsError() throws Exception {
|
||||||
|
HostResource host =
|
||||||
|
persistActiveSubordinateHost("ns1.example.tld", persistActiveDomain("example.tld"))
|
||||||
|
.asBuilder()
|
||||||
|
.setInetAddresses(ImmutableSet.of(InetAddresses.forString("10.0.0.1")))
|
||||||
|
.build();
|
||||||
|
persistResource(host);
|
||||||
|
when(mockResolver.send(any(Message.class))).thenReturn(messageWithResponseCode(Rcode.SERVFAIL));
|
||||||
|
thrown.expect(VerifyException.class);
|
||||||
|
thrown.expectMessage("SERVFAIL");
|
||||||
|
|
||||||
|
writer.publishHost("ns1.example.tld");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatUpdatedZoneIs(Update update, String zoneName) {
|
||||||
|
Record[] zoneRecords = update.getSectionArray(Section.ZONE);
|
||||||
|
assertThat(zoneRecords[0].getName().toString()).isEqualTo(zoneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatTotalUpdateSetsIs(Update update, int count) {
|
||||||
|
assertThat(update.getSectionRRsets(Section.UPDATE)).hasLength(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatUpdateDeletes(Update update, String resourceName, int recordType) {
|
||||||
|
ImmutableList<Record> deleted = findUpdateRecords(update, resourceName, recordType);
|
||||||
|
// There's only an empty (i.e. "delete") record.
|
||||||
|
assertThat(deleted.get(0).rdataToString()).hasLength(0);
|
||||||
|
assertThat(deleted).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThatUpdateAdds(
|
||||||
|
Update update, String resourceName, int recordType, String... resourceData) {
|
||||||
|
ArrayList<String> expectedData = new ArrayList<>();
|
||||||
|
for (String resourceDatum : resourceData) {
|
||||||
|
expectedData.add(resourceDatum.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> actualData = new ArrayList<>();
|
||||||
|
for (Record record : findUpdateRecords(update, resourceName, recordType)) {
|
||||||
|
actualData.add(record.rdataToString().toLowerCase());
|
||||||
|
}
|
||||||
|
assertThat(actualData).containsExactlyElementsIn(expectedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableList<Record> findUpdateRecords(
|
||||||
|
Update update, String resourceName, int recordType) {
|
||||||
|
for (RRset set : update.getSectionRRsets(Section.UPDATE)) {
|
||||||
|
if (set.getName().toString().equals(resourceName) && set.getType() == recordType) {
|
||||||
|
return fixIterator(Record.class, set.rrs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionFailedError(
|
||||||
|
"no record set found for resource '"
|
||||||
|
+ resourceName
|
||||||
|
+ "', type '"
|
||||||
|
+ Type.string(recordType)
|
||||||
|
+ "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "unused"})
|
||||||
|
private static <T> ImmutableList<T> fixIterator(Class<T> clazz, final Iterator<?> iterator) {
|
||||||
|
return ImmutableList.copyOf((Iterator<T>) iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message messageWithResponseCode(int responseCode) {
|
||||||
|
Message message = new Message();
|
||||||
|
message.getHeader().setOpcode(Opcode.UPDATE);
|
||||||
|
message.getHeader().setFlag(Flags.QR);
|
||||||
|
message.getHeader().setRcode(responseCode);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,6 +68,10 @@ public final class RegistryTestServer {
|
||||||
route("/_dr/task/nordnVerify",
|
route("/_dr/task/nordnVerify",
|
||||||
com.google.domain.registry.module.backend.BackendServlet.class),
|
com.google.domain.registry.module.backend.BackendServlet.class),
|
||||||
|
|
||||||
|
// Process DNS pull queue
|
||||||
|
route("/_dr/task/writeDns",
|
||||||
|
com.google.domain.registry.module.backend.BackendServlet.class),
|
||||||
|
|
||||||
// Registrar Console
|
// Registrar Console
|
||||||
route("/registrar", com.google.domain.registry.module.frontend.FrontendServlet.class),
|
route("/registrar", com.google.domain.registry.module.frontend.FrontendServlet.class),
|
||||||
route("/registrar-settings",
|
route("/registrar-settings",
|
||||||
|
|
8
third_party/java/dnsjava/BUILD
vendored
Normal file
8
third_party/java/dnsjava/BUILD
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"]) # BSD 2-Clause
|
||||||
|
|
||||||
|
java_library(
|
||||||
|
name = "dnsjava",
|
||||||
|
exports = ["@dnsjava//jar"],
|
||||||
|
)
|
Loading…
Add table
Add a link
Reference in a new issue