-
Notifications
You must be signed in to change notification settings - Fork 300
Adds DnsWriter that implements DNS UPDATE protocol #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,3 +7,4 @@ | |
| # The email address is not required for organizations. | ||
|
|
||
| Google Inc. | ||
| Donuts Inc. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,3 +21,4 @@ Jared Brothers <brothers@google.com> | |
| Pablo Mayrgundter <pmy@google.com> | ||
| Daisuke Yabuki <dxy@google.com> | ||
| Tim Boring <tjb@google.com> | ||
| Hans Ridder <hans.ridder@gmail.com> | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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", | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Don't forget about trailing newline here. (or else our presubmits fail)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.