google.registry.dns.writer.dnsupdate.DnsUpdateWriter.java Source code

Java tutorial

Introduction

Here is the source code for google.registry.dns.writer.dnsupdate.DnsUpdateWriter.java

Source

// Copyright 2016 The Nomulus 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 google.registry.dns.writer.dnsupdate;

import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import static google.registry.model.EppResourceUtils.loadByForeignKey;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InternetDomainName;
import google.registry.config.RegistryConfig.Config;
import google.registry.dns.writer.DnsWriter;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.host.HostResource;
import google.registry.model.registry.Registries;
import google.registry.util.Clock;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import javax.inject.Inject;
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;

/**
 * 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 supplied "transport" class. 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 {

    /**
     * The name of the pricing engine, as used in {@code Registry.dnsWriter}. Remember to change
     * the value on affected Registry objects to prevent runtime failures.
     */
    public static final String NAME = "DnsUpdateWriter";

    private final Duration dnsTimeToLive;
    private final DnsMessageTransport transport;
    private final Clock clock;

    /**
     * Class constructor.
     *
     * @param dnsTimeToLive TTL used for any created resource records
     * @param transport the transport used to send/receive the UPDATE messages
     * @param clock a source of time
     */
    @Inject
    public DnsUpdateWriter(@Config("dnsUpdateTimeToLive") Duration dnsTimeToLive, DnsMessageTransport transport,
            Clock clock) {
        this.dnsTimeToLive = dnsTimeToLive;
        this.transport = transport;
        this.clock = clock;
    }

    /**
     * Publish the domain, while keeping tracking of which host refresh quest triggered this domain
     * refresh. Delete the requesting host in addition to all subordinate hosts.
     *
     * @param domainName the fully qualified domain name, with no trailing dot
     * @param requestingHostName the fully qualified host name, with no trailing dot, that triggers
     *     this domain refresh request
     */
    private void publishDomain(String domainName, String requestingHostName) {
        DomainResource domain = loadByForeignKey(DomainResource.class, domainName, clock.nowUtc());
        try {
            Update update = new Update(toAbsoluteName(findTldFromName(domainName)));
            update.delete(toAbsoluteName(domainName), Type.ANY);
            if (domain != null) {
                // As long as the domain exists, orphan glues should be cleaned.
                deleteSubordinateHostAddressSet(domain, requestingHostName, update);
                if (domain.shouldPublishToDns()) {
                    addInBailiwickNameServerSet(domain, update);
                    update.add(makeNameServerSet(domain));
                    update.add(makeDelegationSignerSet(domain));
                }
            }
            Message response = transport.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 publishDomain(String domainName) {
        publishDomain(domainName, null);
    }

    @Override
    public void publishHost(String hostName) {
        // Get the superordinate domain name of the host.
        InternetDomainName host = InternetDomainName.from(hostName);
        ImmutableList<String> hostParts = host.parts();
        Optional<InternetDomainName> tld = Registries.findTldForName(host);

        // host not managed by our registry, no need to update DNS.
        if (!tld.isPresent()) {
            return;
        }

        ImmutableList<String> tldParts = tld.get().parts();
        ImmutableList<String> domainParts = hostParts.subList(hostParts.size() - tldParts.size() - 1,
                hostParts.size());
        String domain = Joiner.on(".").join(domainParts);

        // Refresh the superordinate domain, always delete the host first to ensure idempotency,
        // and only publish the host if it is a glue record.
        publishDomain(domain, hostName);
    }

    /**
     * Does nothing. Publish calls are synchronous and atomic.
     */
    @Override
    public void close() {
    }

    private RRset makeDelegationSignerSet(DomainResource domain) throws TextParseException {
        RRset signerSet = new RRset();
        for (DelegationSignerData signerData : domain.getDsData()) {
            DSRecord dsRecord = new DSRecord(toAbsoluteName(domain.getFullyQualifiedDomainName()), DClass.IN,
                    dnsTimeToLive.getStandardSeconds(), signerData.getKeyTag(), signerData.getAlgorithm(),
                    signerData.getDigestType(), signerData.getDigest());
            signerSet.addRR(dsRecord);
        }
        return signerSet;
    }

    private void deleteSubordinateHostAddressSet(DomainResource domain, String additionalHost, Update update)
            throws TextParseException {
        for (String hostName : union(domain.getSubordinateHosts(),
                (additionalHost == null ? ImmutableSet.<String>of() : ImmutableSet.of(additionalHost)))) {
            update.delete(toAbsoluteName(hostName), Type.ANY);
        }
    }

    private void addInBailiwickNameServerSet(DomainResource domain, Update update) throws TextParseException {
        for (String hostName : intersection(domain.loadNameserverFullyQualifiedHostNames(),
                domain.getSubordinateHosts())) {
            HostResource host = loadByForeignKey(HostResource.class, hostName, clock.nowUtc());
            update.add(makeAddressSet(host));
            update.add(makeV6AddressSet(host));
        }
    }

    private RRset makeNameServerSet(DomainResource domain) throws TextParseException {
        RRset nameServerSet = new RRset();
        for (String hostName : domain.loadNameserverFullyQualifiedHostNames()) {
            NSRecord record = new NSRecord(toAbsoluteName(domain.getFullyQualifiedDomainName()), DClass.IN,
                    dnsTimeToLive.getStandardSeconds(), toAbsoluteName(hostName));
            nameServerSet.addRR(record);
        }
        return nameServerSet;
    }

    private RRset makeAddressSet(HostResource host) throws TextParseException {
        RRset addressSet = new RRset();
        for (InetAddress address : host.getInetAddresses()) {
            if (address instanceof Inet4Address) {
                ARecord record = new ARecord(toAbsoluteName(host.getFullyQualifiedHostName()), DClass.IN,
                        dnsTimeToLive.getStandardSeconds(), address);
                addressSet.addRR(record);
            }
        }
        return addressSet;
    }

    private RRset makeV6AddressSet(HostResource host) throws TextParseException {
        RRset addressSet = new RRset();
        for (InetAddress address : host.getInetAddresses()) {
            if (address instanceof Inet6Address) {
                AAAARecord record = new AAAARecord(toAbsoluteName(host.getFullyQualifiedHostName()), 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);
    }
}