Add DNS library to support RFC 8484 DoH (ticket #2201)

WIP - not yet hooked in

This is a portion of release 1.0.0 of MiniDNS from https://github.com/MiniDNS/minidns/ 2020-07-18
Only contains the minidns-core portion of the library.
Removed tests, most util classes, and DnsRootServer class.
Unmodified, as a base for future checkins.
Total size of zipped classes is about 75 KB.

This software may be used under the terms of (at your choice)
- LGPL version 2 (or later)
- Apache Software licence
- WTFPL
This commit is contained in:
zzz
2020-12-05 14:21:08 +00:00
parent 58020b4b58
commit 69deddcbc7
56 changed files with 6514 additions and 1 deletions

View File

@ -94,6 +94,12 @@ Public domain except as listed below:
Copyright (C) 2016 Southern Storm Software, Pty Ltd.
See licenses/LICENSE-Noise.txt
MiniDNS library 1.0.0
This software may be used under the terms of (at your choice)
- LGPL version 2 (or later) (see licenses/LICENSE-LGPL2.1.txt)
- Apache Software licence (see licenses/LICENSE-Apache2.0.txt)
- DWTFYWTPL
Router (router.jar):
Public domain except as listed below:

View File

@ -956,7 +956,7 @@
additionalparam="-notimestamp"
doctitle="I2P Javadocs for Release ${release.number} Build ${i2p.build.number}${build.extra}"
windowtitle="I2P Anonymous Network - Java Documentation - Version ${release.number}">
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:org.apache.http.conn.ssl:org.apache.http.conn.util:org.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20" />
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:org.apache.http.conn.ssl:org.apache.http.conn.util:org.apache.http.util:org.json.simple:com.southernstorm.noise.crypto.x25519:com.southernstorm.noise.crypto.chacha20:org.minidns:org.minidns.*" />
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters:com.maxmind.*:com.southernstorm.noise.*" />
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.web.*:net.i2p.router.update:net.i2p.router.sybil:edu.internet2.ndt:net.i2p.router.news:com.vuze.*" />

View File

@ -0,0 +1,99 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.constants;
import java.util.HashMap;
import java.util.Map;
public final class DnssecConstants {
/**
* Do not allow to instantiate DNSSECConstants
*/
private DnssecConstants() {
}
private static final Map<Byte, SignatureAlgorithm> SIGNATURE_ALGORITHM_LUT = new HashMap<>();
/**
* DNSSEC Signature Algorithms.
*
* @see <a href=
* "http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml">
* IANA DNSSEC Algorithm Numbers</a>
*/
public enum SignatureAlgorithm {
@Deprecated
RSAMD5(1, "RSA/MD5"),
DH(2, "Diffie-Hellman"),
DSA(3, "DSA/SHA1"),
RSASHA1(5, "RSA/SHA-1"),
DSA_NSEC3_SHA1(6, "DSA_NSEC3-SHA1"),
RSASHA1_NSEC3_SHA1(7, "RSASHA1-NSEC3-SHA1"),
RSASHA256(8, "RSA/SHA-256"),
RSASHA512(10, "RSA/SHA-512"),
ECC_GOST(12, "GOST R 34.10-2001"),
ECDSAP256SHA256(13, "ECDSA Curve P-256 with SHA-256"),
ECDSAP384SHA384(14, "ECDSA Curve P-384 with SHA-384"),
INDIRECT(252, "Reserved for Indirect Keys"),
PRIVATEDNS(253, "private algorithm"),
PRIVATEOID(254, "private algorithm oid"),
;
SignatureAlgorithm(int number, String description) {
if (number < 0 || number > 255) {
throw new IllegalArgumentException();
}
this.number = (byte) number;
this.description = description;
SIGNATURE_ALGORITHM_LUT.put(this.number, this);
}
public final byte number;
public final String description;
public static SignatureAlgorithm forByte(byte b) {
return SIGNATURE_ALGORITHM_LUT.get(b);
}
}
private static final Map<Byte, DigestAlgorithm> DELEGATION_DIGEST_LUT = new HashMap<>();
/**
* DNSSEC Digest Algorithms.
*
* @see <a href=
* "https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml">
* IANA Delegation Signer (DS) Resource Record (RR)</a>
*/
public enum DigestAlgorithm {
SHA1(1, "SHA-1"),
SHA256(2, "SHA-256"),
GOST(3, "GOST R 34.11-94"),
SHA384(4, "SHA-384"),
;
DigestAlgorithm(int value, String description) {
if (value < 0 || value > 255) {
throw new IllegalArgumentException();
}
this.value = (byte) value;
this.description = description;
DELEGATION_DIGEST_LUT.put(this.value, this);
}
public final byte value;
public final String description;
public static DigestAlgorithm forByte(byte b) {
return DELEGATION_DIGEST_LUT.get(b);
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
import org.minidns.idna.MiniDnsIdna;
public final class ALabel extends XnLabel {
protected ALabel(String label) {
super(label);
}
@Override
protected String getInternationalizedRepresentationInternal() {
return MiniDnsIdna.toUnicode(label);
}
}

View File

@ -0,0 +1,186 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
* A DNS label is an individual component of a DNS name. Labels are usually shown separated by dots.
* <p>
* This class implements {@link Comparable} which compares DNS labels according to the Canonical DNS Name Order as
* specified in <a href="https://tools.ietf.org/html/rfc4034#section-6.1">RFC 4034 § 6.1</a>.
* </p>
* <p>
* Note that as per <a href="https://tools.ietf.org/html/rfc2181#section-11">RFC 2181 § 11</a> DNS labels may contain
* any byte.
* </p>
*
* @see <a href="https://tools.ietf.org/html/rfc5890#section-2.2">RFC 5890 § 2.2. DNS-Related Terminology</a>
* @author Florian Schmaus
*
*/
public abstract class DnsLabel implements CharSequence, Comparable<DnsLabel> {
/**
* The maximum length of a DNS label in octets.
*
* @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035 § 2.3.4.</a>
*/
public static final int MAX_LABEL_LENGTH_IN_OCTETS = 63;
public static final DnsLabel WILDCARD_LABEL = DnsLabel.from("*");
/**
* Whether or not the DNS label is validated on construction.
*/
public static boolean VALIDATE = true;
public final String label;
protected DnsLabel(String label) {
this.label = label;
if (!VALIDATE) {
return;
}
setBytesIfRequired();
if (byteCache.length > MAX_LABEL_LENGTH_IN_OCTETS) {
throw new LabelToLongException(label);
}
}
private transient String internationalizedRepresentation;
public final String getInternationalizedRepresentation() {
if (internationalizedRepresentation == null) {
internationalizedRepresentation = getInternationalizedRepresentationInternal();
}
return internationalizedRepresentation;
}
protected String getInternationalizedRepresentationInternal() {
return label;
}
public final String getLabelType() {
return getClass().getSimpleName();
}
@Override
public final int length() {
return label.length();
}
@Override
public final char charAt(int index) {
return label.charAt(index);
}
@Override
public final CharSequence subSequence(int start, int end) {
return label.subSequence(start, end);
}
@Override
public final String toString() {
return label;
}
@Override
public final boolean equals(Object other) {
if (!(other instanceof DnsLabel)) {
return false;
}
DnsLabel otherDnsLabel = (DnsLabel) other;
return label.equals(otherDnsLabel.label);
}
@Override
public final int hashCode() {
return label.hashCode();
}
private transient DnsLabel lowercasedVariant;
public final DnsLabel asLowercaseVariant() {
if (lowercasedVariant == null) {
String lowercaseLabel = label.toLowerCase(Locale.US);
lowercasedVariant = DnsLabel.from(lowercaseLabel);
}
return lowercasedVariant;
}
private transient byte[] byteCache;
private void setBytesIfRequired() {
if (byteCache == null) {
byteCache = label.getBytes(StandardCharsets.US_ASCII);
}
}
public final void writeToBoas(ByteArrayOutputStream byteArrayOutputStream) {
setBytesIfRequired();
byteArrayOutputStream.write(byteCache.length);
byteArrayOutputStream.write(byteCache, 0, byteCache.length);
}
@Override
public final int compareTo(DnsLabel other) {
String myCanonical = asLowercaseVariant().label;
String otherCanonical = other.asLowercaseVariant().label;
return myCanonical.compareTo(otherCanonical);
}
public static DnsLabel from(String label) {
if (label == null || label.isEmpty()) {
throw new IllegalArgumentException("Label is null or empty");
}
if (LdhLabel.isLdhLabel(label)) {
return LdhLabel.fromInternal(label);
}
return NonLdhLabel.fromInternal(label);
}
public static DnsLabel[] from(String[] labels) {
DnsLabel[] res = new DnsLabel[labels.length];
for (int i = 0; i < labels.length; i++) {
res[i] = DnsLabel.from(labels[i]);
}
return res;
}
public static boolean isIdnAcePrefixed(String string) {
return string.toLowerCase(Locale.US).startsWith("xn--");
}
public static class LabelToLongException extends IllegalArgumentException {
/**
*
*/
private static final long serialVersionUID = 1L;
public final String label;
LabelToLongException(String label) {
this.label = label;
}
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
public final class FakeALabel extends XnLabel {
protected FakeALabel(String label) {
super(label);
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A LDH (<b>L</b>etters, <b>D</b>igits, <b>H</b>yphen) label, which is the classical label form.
*
* @see <a href="https://tools.ietf.org/html/rfc5890#section-2.3.1">RFC 5890 § 2.3.1. LDH Label</a>
*
*/
public abstract class LdhLabel extends DnsLabel {
protected LdhLabel(String label) {
super(label);
}
public static boolean isLdhLabel(String label) {
if (label.isEmpty()) {
return false;
}
if (LeadingOrTrailingHyphenLabel.isLeadingOrTrailingHypenLabelInternal(label)) {
return false;
}
for (int i = 0; i < label.length(); i++) {
char c = label.charAt(i);
if ((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '-') {
continue;
}
return false;
}
return true;
}
protected static LdhLabel fromInternal(String label) {
assert isLdhLabel(label);
if (ReservedLdhLabel.isReservedLdhLabel(label)) {
// Label starts with '??--'. Now let us see if it is a XN-Label, starting with 'xn--', but be aware that the
// 'xn' part is case insensitive. The XnLabel.isXnLabelInternal(String) method takes care of this.
if (XnLabel.isXnLabelInternal(label)) {
return XnLabel.fromInternal(label);
} else {
return new ReservedLdhLabel(label);
}
}
return new NonReservedLdhLabel(label);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A DNS label with a leading or trailing hyphen ('-').
*/
public final class LeadingOrTrailingHyphenLabel extends NonLdhLabel {
protected LeadingOrTrailingHyphenLabel(String label) {
super(label);
}
protected static boolean isLeadingOrTrailingHypenLabelInternal(String label) {
if (label.isEmpty()) {
return false;
}
if (label.charAt(0) == '-') {
return true;
}
if (label.charAt(label.length() - 1) == '-') {
return true;
}
return false;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A DNS label which contains more than just letters, digits and a hyphen.
*
*/
public abstract class NonLdhLabel extends DnsLabel {
protected NonLdhLabel(String label) {
super(label);
}
protected static DnsLabel fromInternal(String label) {
if (UnderscoreLabel.isUnderscoreLabelInternal(label)) {
return new UnderscoreLabel(label);
}
if (LeadingOrTrailingHyphenLabel.isLeadingOrTrailingHypenLabelInternal(label)) {
return new LeadingOrTrailingHyphenLabel(label);
}
return new OtherNonLdhLabel(label);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A Non-Reserved LDH label (NR-LDH label), which do <em>not</em> have "--" in the third and fourth position.
*
*/
public final class NonReservedLdhLabel extends LdhLabel {
protected NonReservedLdhLabel(String label) {
super(label);
assert isNonReservedLdhLabelInternal(label);
}
public static boolean isNonReservedLdhLabel(String label) {
if (!isLdhLabel(label)) {
return false;
}
return isNonReservedLdhLabelInternal(label);
}
static boolean isNonReservedLdhLabelInternal(String label) {
return !ReservedLdhLabel.isReservedLdhLabelInternal(label);
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A Non-LDH label which does <em>not</em> begin with an underscore ('_'), hyphen ('-') or ends with an hyphen.
*
*/
public final class OtherNonLdhLabel extends NonLdhLabel {
protected OtherNonLdhLabel(String label) {
super(label);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A reserved LDH label (R-LDH label), which have the property that they contain "--" in the third and fourth characters.
*
*/
public class ReservedLdhLabel extends LdhLabel {
protected ReservedLdhLabel(String label) {
super(label);
assert isReservedLdhLabelInternal(label);
}
public static boolean isReservedLdhLabel(String label) {
if (!isLdhLabel(label)) {
return false;
}
return isReservedLdhLabelInternal(label);
}
static boolean isReservedLdhLabelInternal(String label) {
return label.length() >= 4
&& label.charAt(2) == '-'
&& label.charAt(3) == '-';
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
/**
* A DNS label which begins with an underscore ('_').
*
*/
public final class UnderscoreLabel extends NonLdhLabel {
protected UnderscoreLabel(String label) {
super(label);
}
protected static boolean isUnderscoreLabelInternal(String label) {
return label.charAt(0) == '_';
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnslabel;
import java.util.Locale;
import org.minidns.idna.MiniDnsIdna;
/**
* A label that begins with "xn--" and follows the LDH rule.
*/
public abstract class XnLabel extends ReservedLdhLabel {
protected XnLabel(String label) {
super(label);
}
protected static LdhLabel fromInternal(String label) {
assert isIdnAcePrefixed(label);
String uLabel = MiniDnsIdna.toUnicode(label);
if (label.equals(uLabel)) {
// No Punycode conversation to Unicode was performed, this is a fake A-label!
return new FakeALabel(label);
} else {
return new ALabel(label);
}
}
public static boolean isXnLabel(String label) {
if (!isLdhLabel(label)) {
return false;
}
return isXnLabelInternal(label);
}
static boolean isXnLabelInternal(String label) {
return label.substring(0, 2).toLowerCase(Locale.US).equals("xn");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,180 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnsmessage;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.CLASS;
import org.minidns.record.Record.TYPE;
/**
* A DNS question (request).
*/
public class Question {
/**
* The question string (e.g. "measite.de").
*/
public final DnsName name;
/**
* The question type (e.g. A).
*/
public final TYPE type;
/**
* The question class (usually IN for Internet).
*/
public final CLASS clazz;
/**
* UnicastQueries have the highest bit of the CLASS field set to 1.
*/
private final boolean unicastQuery;
/**
* Cache for the serialized object.
*/
private byte[] byteArray;
/**
* Create a dns question for the given name/type/class.
* @param name The name e.g. "measite.de".
* @param type The type, e.g. A.
* @param clazz The class, usually IN (internet).
* @param unicastQuery True if this is a unicast query.
*/
public Question(CharSequence name, TYPE type, CLASS clazz, boolean unicastQuery) {
this(DnsName.from(name), type, clazz, unicastQuery);
}
public Question(DnsName name, TYPE type, CLASS clazz, boolean unicastQuery) {
assert name != null;
assert type != null;
assert clazz != null;
this.name = name;
this.type = type;
this.clazz = clazz;
this.unicastQuery = unicastQuery;
}
/**
* Create a dns question for the given name/type/class.
* @param name The name e.g. "measite.de".
* @param type The type, e.g. A.
* @param clazz The class, usually IN (internet).
*/
public Question(DnsName name, TYPE type, CLASS clazz) {
this(name, type, clazz, false);
}
/**
* Create a dns question for the given name/type/IN (internet class).
* @param name The name e.g. "measite.de".
* @param type The type, e.g. A.
*/
public Question(DnsName name, TYPE type) {
this(name, type, CLASS.IN);
}
/**
* Create a dns question for the given name/type/class.
* @param name The name e.g. "measite.de".
* @param type The type, e.g. A.
* @param clazz The class, usually IN (internet).
*/
public Question(CharSequence name, TYPE type, CLASS clazz) {
this(DnsName.from(name), type, clazz);
}
/**
* Create a dns question for the given name/type/IN (internet class).
* @param name The name e.g. "measite.de".
* @param type The type, e.g. A.
*/
public Question(CharSequence name, TYPE type) {
this(DnsName.from(name), type);
}
/**
* Parse a byte array and rebuild the dns question from it.
* @param dis The input stream.
* @param data The plain data (for dns name references).
* @throws IOException On errors (read outside of packet).
*/
public Question(DataInputStream dis, byte[] data) throws IOException {
name = DnsName.parse(dis, data);
type = TYPE.getType(dis.readUnsignedShort());
clazz = CLASS.getClass(dis.readUnsignedShort());
unicastQuery = false;
}
/**
* Generate a binary paket for this dns question.
* @return The dns question.
*/
public byte[] toByteArray() {
if (byteArray == null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
DataOutputStream dos = new DataOutputStream(baos);
try {
name.writeToStream(dos);
dos.writeShort(type.getValue());
dos.writeShort(clazz.getValue() | (unicastQuery ? (1 << 15) : 0));
dos.flush();
} catch (IOException e) {
// Should never happen
throw new RuntimeException(e);
}
byteArray = baos.toByteArray();
}
return byteArray;
}
@Override
public int hashCode() {
return Arrays.hashCode(toByteArray());
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Question)) {
return false;
}
byte[] t = toByteArray();
byte[] o = ((Question) other).toByteArray();
return Arrays.equals(t, o);
}
@Override
public String toString() {
return name.getRawAce() + ".\t" + clazz + '\t' + type;
}
public DnsMessage.Builder asMessageBuilder() {
DnsMessage.Builder builder = DnsMessage.builder();
builder.setQuestion(this);
return builder;
}
public DnsMessage asQueryMessage() {
return asMessageBuilder().build();
}
}

View File

@ -0,0 +1,606 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnsname;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import org.minidns.dnslabel.DnsLabel;
import org.minidns.idna.MiniDnsIdna;
/**
* A DNS name, also called "domain name". A DNS name consists of multiple 'labels' and is subject to certain restrictions (see
* for example <a href="https://tools.ietf.org/html/rfc3696#section-2">RFC 3696 § 2.</a>).
* <p>
* Instances of this class can be created by using {@link #from(String)}.
* </p>
* <p>
* This class holds three representations of a DNS name: ACE, raw ACE and IDN. ACE (ASCII Compatible Encoding), which
* can be accessed via {@link #ace}, represents mostly the data that got send over the wire. But since DNS names are
* case insensitive, the ACE value is normalized to lower case. You can use {@link #getRawAce()} to get the raw ACE data
* that was received, which possibly includes upper case characters. The IDN (Internationalized Domain Name), that is
* the DNS name as it should be shown to the user, can be retrieved using {@link #asIdn()}.
* </p>
* More information about Internationalized Domain Names can be found at:
* <ul>
* <li><a href="https://unicode.org/reports/tr46/">UTS #46 - Unicode IDNA Compatibility Processing</a>
* <li><a href="https://tools.ietf.org/html/rfc8753">RFC 8753 - Internationalized Domain Names for Applications (IDNA) Review for New Unicode Versions</a>
* </ul>
*
* @see <a href="https://tools.ietf.org/html/rfc3696">RFC 3696</a>
* @author Florian Schmaus
*
*/
public final class DnsName implements CharSequence, Serializable, Comparable<DnsName> {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* @see <a href="https://www.ietf.org/rfc/rfc3490.txt">RFC 3490 § 3.1 1.</a>
*/
private static final String LABEL_SEP_REGEX = "[.\u3002\uFF0E\uFF61]";
/**
* @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035 § 2.3.4.</a<
*/
static final int MAX_DNSNAME_LENGTH_IN_OCTETS = 255;
public static final int MAX_LABELS = 128;
public static final DnsName ROOT = new DnsName(".");
public static final DnsName IN_ADDR_ARPA = new DnsName("in-addr.arpa");
public static final DnsName IP6_ARPA = new DnsName("ip6.arpa");
/**
* Whether or not the DNS name is validated on construction.
*/
public static boolean VALIDATE = true;
/**
* The DNS name in ASCII Compatible Encoding (ACE).
*/
public final String ace;
/**
* The DNS name in raw format, i.e. as it was received from the remote server. This means that compared to
* {@link #ace}, this String may not be lower-cased.
*/
private final String rawAce;
private transient byte[] bytes;
private transient byte[] rawBytes;
private transient String idn;
private transient String domainpart;
private transient String hostpart;
/**
* The labels in <b>reverse</b> order.
*/
private transient DnsLabel[] labels;
private transient DnsLabel[] rawLabels;
private transient int hashCode;
private int size = -1;
private DnsName(String name) {
this(name, true);
}
private DnsName(String name, boolean inAce) {
if (name.isEmpty()) {
rawAce = ROOT.rawAce;
} else {
final int nameLength = name.length();
final int nameLastPos = nameLength - 1;
// Strip potential trailing dot. N.B. that we require nameLength > 2, because we don't want to strip the one
// character string containing only a single dot to the empty string.
if (nameLength >= 2 && name.charAt(nameLastPos) == '.') {
name = name.subSequence(0, nameLastPos).toString();
}
if (inAce) {
// Name is already in ACE format.
rawAce = name;
} else {
rawAce = MiniDnsIdna.toASCII(name);
}
}
ace = rawAce.toLowerCase(Locale.US);
if (!VALIDATE) {
return;
}
// Validate the DNS name.
validateMaxDnsnameLengthInOctets();
}
private DnsName(DnsLabel[] rawLabels, boolean validateMaxDnsnameLength) {
this.rawLabels = rawLabels;
this.labels = new DnsLabel[rawLabels.length];
int size = 0;
for (int i = 0; i < rawLabels.length; i++) {
size += rawLabels[i].length() + 1;
labels[i] = rawLabels[i].asLowercaseVariant();
}
rawAce = labelsToString(rawLabels, size);
ace = labelsToString(labels, size);
// The following condition is deliberately designed that VALIDATE=false causes the validation to be skipped even
// if validateMaxDnsnameLength is set to true. There is no need to validate even if this constructor is called
// with validateMaxDnsnameLength set to true if VALIDATE is globally set to false.
if (!validateMaxDnsnameLength || !VALIDATE) {
return;
}
validateMaxDnsnameLengthInOctets();
}
private static String labelsToString(DnsLabel[] labels, int stringLength) {
StringBuilder sb = new StringBuilder(stringLength);
for (int i = labels.length - 1; i >= 0; i--) {
sb.append(labels[i]).append('.');
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
private void validateMaxDnsnameLengthInOctets() {
setBytesIfRequired();
if (bytes.length > MAX_DNSNAME_LENGTH_IN_OCTETS) {
throw new InvalidDnsNameException.DNSNameTooLongException(ace, bytes);
}
}
public void writeToStream(OutputStream os) throws IOException {
setBytesIfRequired();
os.write(bytes);
}
/**
* Serialize a domain name under IDN rules.
*
* @return The binary domain name representation.
*/
public byte[] getBytes() {
setBytesIfRequired();
return bytes.clone();
}
public byte[] getRawBytes() {
if (rawBytes == null) {
setLabelsIfRequired();
rawBytes = toBytes(rawLabels);
}
return rawBytes.clone();
}
private void setBytesIfRequired() {
if (bytes != null)
return;
setLabelsIfRequired();
bytes = toBytes(labels);
}
private static byte[] toBytes(DnsLabel[] labels) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
for (int i = labels.length - 1; i >= 0; i--) {
labels[i].writeToBoas(baos);
}
baos.write(0);
assert baos.size() <= MAX_DNSNAME_LENGTH_IN_OCTETS;
return baos.toByteArray();
}
private void setLabelsIfRequired() {
if (labels != null && rawLabels != null) return;
if (isRootLabel()) {
rawLabels = labels = new DnsLabel[0];
return;
}
labels = getLabels(ace);
rawLabels = getLabels(rawAce);
}
private static DnsLabel[] getLabels(String ace) {
String[] labels = ace.split(LABEL_SEP_REGEX, MAX_LABELS);
// Reverse the labels, so that 'foo, example, org' becomes 'org, example, foo'.
for (int i = 0; i < labels.length / 2; i++) {
String t = labels[i];
int j = labels.length - i - 1;
labels[i] = labels[j];
labels[j] = t;
}
try {
return DnsLabel.from(labels);
} catch (DnsLabel.LabelToLongException e) {
throw new InvalidDnsNameException.LabelTooLongException(ace, e.label);
}
}
public String getRawAce() {
return rawAce;
}
public String asIdn() {
if (idn != null)
return idn;
idn = MiniDnsIdna.toUnicode(ace);
return idn;
}
/**
* Domainpart in ACE representation.
*
* @return the domainpart in ACE representation.
*/
public String getDomainpart() {
setHostnameAndDomainpartIfRequired();
return domainpart;
}
/**
* Hostpart in ACE representation.
*
* @return the hostpart in ACE representation.
*/
public String getHostpart() {
setHostnameAndDomainpartIfRequired();
return hostpart;
}
public DnsLabel getHostpartLabel() {
setLabelsIfRequired();
return labels[labels.length];
}
private void setHostnameAndDomainpartIfRequired() {
if (hostpart != null) return;
String[] parts = ace.split(LABEL_SEP_REGEX, 2);
hostpart = parts[0];
if (parts.length > 1) {
domainpart = parts[1];
} else {
domainpart = "";
}
}
public int size() {
if (size < 0) {
if (isRootLabel()) {
size = 1;
} else {
size = ace.length() + 2;
}
}
return size;
}
@Override
public int length() {
return ace.length();
}
@Override
public char charAt(int index) {
return ace.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return ace.subSequence(start, end);
}
@Override
public String toString() {
return ace;
}
public static DnsName from(CharSequence name) {
return from(name.toString());
}
public static DnsName from(String name) {
return new DnsName(name, false);
}
/**
* Create a DNS name by "concatenating" the child under the parent name. The child can also be seen as the "left"
* part of the resulting DNS name and the parent is the "right" part.
* <p>
* For example using "i.am.the.child" as child and "of.this.parent.example" as parent, will result in a DNS name:
* "i.am.the.child.of.this.parent.example".
* </p>
*
* @param child the child DNS name.
* @param parent the parent DNS name.
* @return the resulting of DNS name.
*/
public static DnsName from(DnsName child, DnsName parent) {
child.setLabelsIfRequired();
parent.setLabelsIfRequired();
DnsLabel[] rawLabels = new DnsLabel[child.rawLabels.length + parent.rawLabels.length];
System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);
System.arraycopy(child.rawLabels, 0, rawLabels, parent.rawLabels.length, child.rawLabels.length);
return new DnsName(rawLabels, true);
}
public static DnsName from(DnsLabel child, DnsName parent) {
parent.setLabelsIfRequired();
DnsLabel[] rawLabels = new DnsLabel[parent.rawLabels.length + 1];
System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);
rawLabels[rawLabels.length] = child;
return new DnsName(rawLabels, true);
}
public static DnsName from(DnsLabel grandchild, DnsLabel child, DnsName parent) {
parent.setBytesIfRequired();
DnsLabel[] rawLabels = new DnsLabel[parent.rawLabels.length + 2];
System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);
rawLabels[parent.rawLabels.length] = child;
rawLabels[parent.rawLabels.length + 1] = grandchild;
return new DnsName(rawLabels, true);
}
public static DnsName from(DnsName... nameComponents) {
int labelCount = 0;
for (DnsName component : nameComponents) {
component.setLabelsIfRequired();
labelCount += component.rawLabels.length;
}
DnsLabel[] rawLabels = new DnsLabel[labelCount];
int destLabelPos = 0;
for (int i = nameComponents.length - 1; i >= 0; i--) {
DnsName component = nameComponents[i];
System.arraycopy(component.rawLabels, 0, rawLabels, destLabelPos, component.rawLabels.length);
destLabelPos += component.rawLabels.length;
}
return new DnsName(rawLabels, true);
}
public static DnsName from(String[] parts) {
DnsLabel[] rawLabels = DnsLabel.from(parts);
return new DnsName(rawLabels, true);
}
/**
* Parse a domain name starting at the current offset and moving the input
* stream pointer past this domain name (even if cross references occure).
*
* @param dis The input stream.
* @param data The raw data (for cross references).
* @return The domain name string.
* @throws IOException Should never happen.
*/
public static DnsName parse(DataInputStream dis, byte[] data)
throws IOException {
int c = dis.readUnsignedByte();
if ((c & 0xc0) == 0xc0) {
c = ((c & 0x3f) << 8) + dis.readUnsignedByte();
HashSet<Integer> jumps = new HashSet<Integer>();
jumps.add(c);
return parse(data, c, jumps);
}
if (c == 0) {
return DnsName.ROOT;
}
byte[] b = new byte[c];
dis.readFully(b);
String childLabelString = new String(b, StandardCharsets.US_ASCII);
DnsName child = new DnsName(childLabelString);
DnsName parent = parse(dis, data);
return DnsName.from(child, parent);
}
/**
* Parse a domain name starting at the given offset.
*
* @param data The raw data.
* @param offset The offset.
* @param jumps The list of jumps (by now).
* @return The parsed domain name.
* @throws IllegalStateException on cycles.
*/
private static DnsName parse(byte[] data, int offset, HashSet<Integer> jumps)
throws IllegalStateException {
int c = data[offset] & 0xff;
if ((c & 0xc0) == 0xc0) {
c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff);
if (jumps.contains(c)) {
throw new IllegalStateException("Cyclic offsets detected.");
}
jumps.add(c);
return parse(data, c, jumps);
}
if (c == 0) {
return DnsName.ROOT;
}
String childLabelString = new String(data, offset + 1, c, StandardCharsets.US_ASCII);
DnsName child = new DnsName(childLabelString);
DnsName parent = parse(data, offset + 1 + c, jumps);
return DnsName.from(child, parent);
}
@Override
public int compareTo(DnsName other) {
return ace.compareTo(other.ace);
}
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (other instanceof DnsName) {
DnsName otherDnsName = (DnsName) other;
setBytesIfRequired();
otherDnsName.setBytesIfRequired();
return Arrays.equals(bytes, otherDnsName.bytes);
}
return false;
}
@Override
public int hashCode() {
if (hashCode == 0 && !isRootLabel()) {
setBytesIfRequired();
hashCode = Arrays.hashCode(bytes);
}
return hashCode;
}
public boolean isDirectChildOf(DnsName parent) {
setLabelsIfRequired();
parent.setLabelsIfRequired();
int parentLabelsCount = parent.labels.length;
if (labels.length - 1 != parentLabelsCount)
return false;
for (int i = 0; i < parent.labels.length; i++) {
if (!labels[i].equals(parent.labels[i]))
return false;
}
return true;
}
public boolean isChildOf(DnsName parent) {
setLabelsIfRequired();
parent.setLabelsIfRequired();
if (labels.length < parent.labels.length)
return false;
for (int i = 0; i < parent.labels.length; i++) {
if (!labels[i].equals(parent.labels[i]))
return false;
}
return true;
}
public int getLabelCount() {
setLabelsIfRequired();
return labels.length;
}
/**
* Get a copy of the labels of this DNS name. The resulting array will contain the labels in reverse order, that is,
* the top-level domain will be at res[0].
*
* @return an array of the labels in reverse order.
*/
public DnsLabel[] getLabels() {
setLabelsIfRequired();
return labels.clone();
}
public DnsLabel getLabel(int labelNum) {
setLabelsIfRequired();
return labels[labelNum];
}
/**
* Get a copy of the raw labels of this DNS name. The resulting array will contain the labels in reverse order, that is,
* the top-level domain will be at res[0].
*
* @return an array of the raw labels in reverse order.
*/
public DnsLabel[] getRawLabels() {
setLabelsIfRequired();
return rawLabels.clone();
}
public DnsName stripToLabels(int labelCount) {
setLabelsIfRequired();
if (labelCount > labels.length) {
throw new IllegalArgumentException();
}
if (labelCount == labels.length) {
return this;
}
if (labelCount == 0) {
return ROOT;
}
DnsLabel[] stripedLabels = Arrays.copyOfRange(rawLabels, 0, labelCount);
return new DnsName(stripedLabels, false);
}
/**
* Return the parent of this DNS label. Will return the root label if this label itself is the root label (because there is no parent of root).
* <p>
* For example:
* </p>
* <ul>
* <li><code>"foo.bar.org".getParent() == "bar.org"</code></li>
* <li><code> ".".getParent() == "."</code></li>
* </ul>
* @return the parent of this DNS label.
*/
public DnsName getParent() {
if (isRootLabel()) return ROOT;
return stripToLabels(getLabelCount() - 1);
}
public boolean isRootLabel() {
return ace.isEmpty() || ace.equals(".");
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.dnsname;
import org.minidns.dnslabel.DnsLabel;
public abstract class InvalidDnsNameException extends IllegalStateException {
private static final long serialVersionUID = 1L;
protected final String ace;
protected InvalidDnsNameException(String ace) {
this.ace = ace;
}
public static class LabelTooLongException extends InvalidDnsNameException {
/**
*
*/
private static final long serialVersionUID = 1L;
private final String label;
public LabelTooLongException(String ace, String label) {
super(ace);
this.label = label;
}
@Override
public String getMessage() {
return "The DNS name '" + ace + "' contains the label '" + label
+ "' which exceeds the maximum label length of " + DnsLabel.MAX_LABEL_LENGTH_IN_OCTETS + " octets by "
+ (label.length() - DnsLabel.MAX_LABEL_LENGTH_IN_OCTETS) + " octets.";
}
}
public static class DNSNameTooLongException extends InvalidDnsNameException {
/**
*
*/
private static final long serialVersionUID = 1L;
private final byte[] bytes;
public DNSNameTooLongException(String ace, byte[] bytes) {
super(ace);
this.bytes = bytes;
}
@Override
public String getMessage() {
return "The DNS name '" + ace + "' exceeds the maximum name length of "
+ DnsName.MAX_DNSNAME_LENGTH_IN_OCTETS + " octets by "
+ (bytes.length - DnsName.MAX_DNSNAME_LENGTH_IN_OCTETS) + " octets.";
}
}
}

View File

@ -0,0 +1,235 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.edns;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Data;
import org.minidns.record.OPT;
import org.minidns.record.Record;
import org.minidns.record.Record.TYPE;
/**
* EDNS - Extension Mechanism for DNS.
*
* @see <a href="https://tools.ietf.org/html/rfc6891">RFC 6891 - Extension Mechanisms for DNS (EDNS(0))</a>
*
*/
public class Edns {
/**
* Inform the dns server that the client supports DNSSEC.
*/
public static final int FLAG_DNSSEC_OK = 0x8000;
/**
* The EDNS option code.
*
* @see <a href="http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11">IANA - DNS EDNS0 Option Codes (OPT)</a>
*/
public enum OptionCode {
UNKNOWN(-1, UnknownEdnsOption.class),
NSID(3, Nsid.class),
;
private static Map<Integer, OptionCode> INVERSE_LUT = new HashMap<>(OptionCode.values().length);
static {
for (OptionCode optionCode : OptionCode.values()) {
INVERSE_LUT.put(optionCode.asInt, optionCode);
}
}
public final int asInt;
public final Class<? extends EdnsOption> clazz;
OptionCode(int optionCode, Class<? extends EdnsOption> clazz) {
this.asInt = optionCode;
this.clazz = clazz;
}
public static OptionCode from(int optionCode) {
OptionCode res = INVERSE_LUT.get(optionCode);
if (res == null) res = OptionCode.UNKNOWN;
return res;
}
}
public final int udpPayloadSize;
/**
* 8-bit extended return code.
*
* RFC 6891 § 6.1.3 EXTENDED-RCODE
*/
public final int extendedRcode;
/**
* 8-bit version field.
*
* RFC 6891 § 6.1.3 VERSION
*/
public final int version;
/**
* 16-bit flags.
*
* RFC 6891 § 6.1.4
*/
public final int flags;
public final List<EdnsOption> variablePart;
public final boolean dnssecOk;
private Record<OPT> optRecord;
public Edns(Record<OPT> optRecord) {
assert optRecord.type == TYPE.OPT;
udpPayloadSize = optRecord.clazzValue;
extendedRcode = (int) ((optRecord.ttl >> 8) & 0xff);
version = (int) ((optRecord.ttl >> 16) & 0xff);
flags = (int) optRecord.ttl & 0xffff;
dnssecOk = (optRecord.ttl & FLAG_DNSSEC_OK) > 0;
OPT opt = optRecord.payloadData;
variablePart = opt.variablePart;
this.optRecord = optRecord;
}
public Edns(Builder builder) {
udpPayloadSize = builder.udpPayloadSize;
extendedRcode = builder.extendedRcode;
version = builder.version;
int flags = 0;
if (builder.dnssecOk) {
flags |= FLAG_DNSSEC_OK;
}
dnssecOk = builder.dnssecOk;
this.flags = flags;
if (builder.variablePart != null) {
variablePart = builder.variablePart;
} else {
variablePart = Collections.emptyList();
}
}
@SuppressWarnings("unchecked")
public <O extends EdnsOption> O getEdnsOption(OptionCode optionCode) {
for (EdnsOption o : variablePart) {
if (o.getOptionCode().equals(optionCode)) {
return (O) o;
}
}
return null;
}
public Record<OPT> asRecord() {
if (optRecord == null) {
long optFlags = flags;
optFlags |= extendedRcode << 8;
optFlags |= version << 16;
optRecord = new Record<OPT>(DnsName.ROOT, Record.TYPE.OPT, udpPayloadSize, optFlags, new OPT(variablePart));
}
return optRecord;
}
private String terminalOutputCache;
public String asTerminalOutput() {
if (terminalOutputCache == null) {
StringBuilder sb = new StringBuilder();
sb.append("EDNS: version: ").append(version).append(", flags:");
if (dnssecOk)
sb.append(" do");
sb.append("; udp: ").append(udpPayloadSize);
if (!variablePart.isEmpty()) {
sb.append('\n');
Iterator<EdnsOption> it = variablePart.iterator();
while (it.hasNext()) {
EdnsOption edns = it.next();
sb.append(edns.getOptionCode()).append(": ");
sb.append(edns.asTerminalOutput());
if (it.hasNext()) {
sb.append('\n');
}
}
}
terminalOutputCache = sb.toString();
}
return terminalOutputCache;
}
@Override
public String toString() {
return asTerminalOutput();
}
public static Edns fromRecord(Record<? extends Data> record) {
if (record.type != TYPE.OPT) return null;
@SuppressWarnings("unchecked")
Record<OPT> optRecord = (Record<OPT>) record;
return new Edns(optRecord);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private int udpPayloadSize;
private int extendedRcode;
private int version;
private boolean dnssecOk;
private List<EdnsOption> variablePart;
private Builder() {
}
public Builder setUdpPayloadSize(int udpPayloadSize) {
if (udpPayloadSize > 0xffff) {
throw new IllegalArgumentException("UDP payload size must not be greater than 65536, was " + udpPayloadSize);
}
this.udpPayloadSize = udpPayloadSize;
return this;
}
public Builder setDnssecOk(boolean dnssecOk) {
this.dnssecOk = dnssecOk;
return this;
}
public Builder setDnssecOk() {
dnssecOk = true;
return this;
}
public Builder addEdnsOption(EdnsOption ednsOption) {
if (variablePart == null) {
variablePart = new ArrayList<>(4);
}
variablePart.add(ednsOption);
return this;
}
public Edns build() {
return new Edns(this);
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.edns;
import java.io.DataOutputStream;
import java.io.IOException;
import org.minidns.edns.Edns.OptionCode;
public abstract class EdnsOption {
public final int optionCode;
public final int optionLength;
protected final byte[] optionData;
protected EdnsOption(int optionCode, byte[] optionData) {
this.optionCode = optionCode;
this.optionLength = optionData.length;
this.optionData = optionData;
}
protected EdnsOption(byte[] optionData) {
this.optionCode = getOptionCode().asInt;
this.optionLength = optionData.length;
this.optionData = optionData;
}
public final void writeToDos(DataOutputStream dos) throws IOException {
dos.writeShort(optionCode);
dos.writeShort(optionLength);
dos.write(optionData);
}
public abstract OptionCode getOptionCode();
private String toStringCache;
@Override
public final String toString() {
if (toStringCache == null) {
toStringCache = toStringInternal().toString();
}
return toStringCache;
}
protected abstract CharSequence toStringInternal();
private String terminalOutputCache;
public final String asTerminalOutput() {
if (terminalOutputCache == null) {
terminalOutputCache = asTerminalOutputInternal().toString();
}
return terminalOutputCache;
}
protected abstract CharSequence asTerminalOutputInternal();
public static EdnsOption parse(int intOptionCode, byte[] optionData) {
OptionCode optionCode = OptionCode.from(intOptionCode);
EdnsOption res;
switch (optionCode) {
case NSID:
res = new Nsid(optionData);
break;
default:
res = new UnknownEdnsOption(intOptionCode, optionData);
break;
}
return res;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.edns;
import java.nio.charset.StandardCharsets;
import org.minidns.edns.Edns.OptionCode;
import org.minidns.util.Hex;
public class Nsid extends EdnsOption {
public static final Nsid REQUEST = new Nsid();
private Nsid() {
this(new byte[0]);
}
public Nsid(byte[] payload) {
super(payload);
}
@Override
public OptionCode getOptionCode() {
return OptionCode.NSID;
}
@Override
protected CharSequence toStringInternal() {
String res = OptionCode.NSID + ": ";
res += new String(optionData, StandardCharsets.US_ASCII);
return res;
}
@Override
protected CharSequence asTerminalOutputInternal() {
return Hex.from(optionData);
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.edns;
import org.minidns.edns.Edns.OptionCode;
import org.minidns.util.Hex;
public class UnknownEdnsOption extends EdnsOption {
protected UnknownEdnsOption(int optionCode, byte[] optionData) {
super(optionCode, optionData);
}
@Override
public OptionCode getOptionCode() {
return OptionCode.UNKNOWN;
}
@Override
protected CharSequence asTerminalOutputInternal() {
return Hex.from(optionData);
}
@Override
protected CharSequence toStringInternal() {
return asTerminalOutputInternal();
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.idna;
import java.net.IDN;
import org.minidns.dnsname.DnsName;
public class DefaultIdnaTransformator implements IdnaTransformator {
@Override
public String toASCII(String input) {
// Special case if input is ".", i.e. a string containing only a single dot character. This is a workaround for
// IDN.toASCII() implementations throwing an IllegalArgumentException on this input string (for example Android
// APIs level 26, see https://issuetracker.google.com/issues/113070416).
if (DnsName.ROOT.ace.equals(input)) {
return DnsName.ROOT.ace;
}
return IDN.toASCII(input);
}
@Override
public String toUnicode(String input) {
return IDN.toUnicode(input);
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.idna;
public interface IdnaTransformator {
String toASCII(String input);
String toUnicode(String input);
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.idna;
public class MiniDnsIdna {
private static IdnaTransformator idnaTransformator = new DefaultIdnaTransformator();
public static String toASCII(String string) {
return idnaTransformator.toASCII(string);
}
public static String toUnicode(String string) {
return idnaTransformator.toUnicode(string);
}
public static void setActiveTransformator(IdnaTransformator idnaTransformator) {
if (idnaTransformator == null) {
throw new IllegalArgumentException();
}
MiniDnsIdna.idnaTransformator = idnaTransformator;
}
}

View File

@ -0,0 +1,16 @@
<html><body>
<p>
This is a portion of release 1.0.0 of MiniDNS from https://github.com/MiniDNS/minidns/ 2020-07-18
Only contains the minidns-core portion of the library.
Removed tests, most util classes, and DnsRootServer class.
</p><pre>
This software may be used under the terms of (at your choice)
- LGPL version 2 (or later) (see LICENCE_LGPL2.1 for details)
- Apache Software licence (see LICENCE_APACHE for details)
- WTFPL (see LICENCE_WTFPL for details)
</pre>
</body></html>

View File

@ -0,0 +1,68 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Inet4Address;
import org.minidns.record.Record.TYPE;
import org.minidns.util.InetAddressUtil;
/**
* A record payload (ip pointer).
*/
public class A extends InternetAddressRR<Inet4Address> {
@Override
public TYPE getType() {
return TYPE.A;
}
public A(Inet4Address inet4Address) {
super(inet4Address);
assert ip.length == 4;
}
public A(int q1, int q2, int q3, int q4) {
super(new byte[] { (byte) q1, (byte) q2, (byte) q3, (byte) q4 });
if (q1 < 0 || q1 > 255 || q2 < 0 || q2 > 255 || q3 < 0 || q3 > 255 || q4 < 0 || q4 > 255) {
throw new IllegalArgumentException();
}
}
public A(byte[] ip) {
super(ip);
if (ip.length != 4) {
throw new IllegalArgumentException("IPv4 address in A record is always 4 byte");
}
}
public A(CharSequence ipv4CharSequence) {
this(InetAddressUtil.ipv4From(ipv4CharSequence));
}
public static A parse(DataInputStream dis)
throws IOException {
byte[] ip = new byte[4];
dis.readFully(ip);
return new A(ip);
}
@Override
public String toString() {
return Integer.toString(ip[0] & 0xff) + "." +
Integer.toString(ip[1] & 0xff) + "." +
Integer.toString(ip[2] & 0xff) + "." +
Integer.toString(ip[3] & 0xff);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Inet6Address;
import org.minidns.record.Record.TYPE;
import org.minidns.util.InetAddressUtil;
/**
* AAAA payload (an ipv6 pointer).
*/
public class AAAA extends InternetAddressRR<Inet6Address> {
@Override
public TYPE getType() {
return TYPE.AAAA;
}
public AAAA(Inet6Address inet6address) {
super(inet6address);
assert ip.length == 16;
}
public AAAA(byte[] ip) {
super(ip);
if (ip.length != 16) {
throw new IllegalArgumentException("IPv6 address in AAAA record is always 16 byte");
}
}
public AAAA(CharSequence ipv6CharSequence) {
this(InetAddressUtil.ipv6From(ipv6CharSequence));
}
public static AAAA parse(DataInputStream dis)
throws IOException {
byte[] ip = new byte[16];
dis.readFully(ip);
return new AAAA(ip);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ip.length; i += 2) {
if (i != 0) {
sb.append(':');
}
sb.append(Integer.toHexString(
((ip[i] & 0xff) << 8) + (ip[i + 1] & 0xff)
));
}
return sb.toString();
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.IOException;
/**
* CNAME payload (pointer to another domain / address).
*/
public class CNAME extends RRWithTarget {
public static CNAME parse(DataInputStream dis, byte[] data) throws IOException {
DnsName target = DnsName.parse(dis, data);
return new CNAME(target);
}
public CNAME(String target) {
this(DnsName.from(target));
}
public CNAME(DnsName target) {
super(target);
}
@Override
public TYPE getType() {
return TYPE.CNAME;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.IOException;
import org.minidns.constants.DnssecConstants.DigestAlgorithm;
import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
/**
* DLV record payload.
*
* According to RFC4431, DLV has exactly the same format as DS records.
*/
public class DLV extends DelegatingDnssecRR {
public static DLV parse (DataInputStream dis, int length) throws IOException {
SharedData parsedData = DelegatingDnssecRR.parseSharedData(dis, length);
return new DLV(parsedData.keyTag, parsedData.algorithm, parsedData.digestType, parsedData.digest);
}
public DLV(int keyTag, byte algorithm, byte digestType, byte[] digest) {
super(keyTag, algorithm, digestType, digest);
}
public DLV(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {
super(keyTag, algorithm, digestType, digest);
}
@Override
public Record.TYPE getType() {
return Record.TYPE.DLV;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.IOException;
/**
* A DNAME resource record.
*
* @see <a href="https://tools.ietf.org/html/rfc6672">RFC 6672 - DNAME Redirection in the DNS</a>
*/
public class DNAME extends RRWithTarget {
public static DNAME parse(DataInputStream dis, byte[] data) throws IOException {
DnsName target = DnsName.parse(dis, data);
return new DNAME(target);
}
public DNAME(String target) {
this(DnsName.from(target));
}
public DNAME(DnsName target) {
super(target);
}
@Override
public TYPE getType() {
return TYPE.DNAME;
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
import org.minidns.record.Record.TYPE;
import org.minidns.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
/**
* DNSKEY record payload.
*/
public class DNSKEY extends Data {
/**
* Whether the key should be used as a secure entry point key.
*
* see RFC 3757
*/
public static final short FLAG_SECURE_ENTRY_POINT = 0x1;
/**
* Whether the record holds a revoked key.
*/
public static final short FLAG_REVOKE = 0x80;
/**
* Whether the record holds a DNS zone key.
*/
public static final short FLAG_ZONE = 0x100;
/**
* Use the protocol defined in RFC 4034.
*/
public static final byte PROTOCOL_RFC4034 = 3;
/**
* Bitmap of flags: {@link #FLAG_SECURE_ENTRY_POINT}, {@link #FLAG_REVOKE}, {@link #FLAG_ZONE}.
*
* @see <a href="https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml">IANA - DNSKEY RR Flags</a>
*/
public final short flags;
/**
* Must be {@link #PROTOCOL_RFC4034}.
*/
public final byte protocol;
/**
* The public key's cryptographic algorithm used.
*
*/
public final SignatureAlgorithm algorithm;
/**
* The byte value of the public key's cryptographic algorithm used.
*
*/
public final byte algorithmByte;
/**
* The public key material. The format depends on the algorithm of the key being stored.
*/
private final byte[] key;
/**
* This DNSKEY's key tag. Calculated just-in-time when using {@link #getKeyTag()}
*/
private transient Integer keyTag;
public static DNSKEY parse(DataInputStream dis, int length) throws IOException {
short flags = dis.readShort();
byte protocol = dis.readByte();
byte algorithm = dis.readByte();
byte[] key = new byte[length - 4];
dis.readFully(key);
return new DNSKEY(flags, protocol, algorithm, key);
}
private DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte algorithmByte, byte[] key) {
this.flags = flags;
this.protocol = protocol;
assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
this.algorithmByte = algorithmByte;
this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
this.key = key;
}
public DNSKEY(short flags, byte protocol, byte algorithm, byte[] key) {
this(flags, protocol, SignatureAlgorithm.forByte(algorithm), key);
}
public DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte[] key) {
this(flags, protocol, algorithm, algorithm.number, key);
}
@Override
public TYPE getType() {
return TYPE.DNSKEY;
}
/**
* Retrieve the key tag identifying this DNSKEY.
* The key tag is used within the DS and RRSIG record to distinguish multiple keys for the same name.
*
* This implementation is based on the reference implementation shown in RFC 4034 Appendix B.
*
* @return this DNSKEY's key tag
*/
public /* unsigned short */ int getKeyTag() {
if (keyTag == null) {
byte[] recordBytes = toByteArray();
long ac = 0;
for (int i = 0; i < recordBytes.length; ++i) {
ac += ((i & 1) > 0) ? recordBytes[i] & 0xFFL : ((recordBytes[i] & 0xFFL) << 8);
}
ac += (ac >> 16) & 0xFFFF;
keyTag = (int) (ac & 0xFFFF);
}
return keyTag;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeShort(flags);
dos.writeByte(protocol);
dos.writeByte(algorithm.number);
dos.write(key);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(flags).append(' ')
.append(protocol).append(' ')
.append(algorithm).append(' ')
.append(Base64.encodeToString(key));
return sb.toString();
}
public int getKeyLength() {
return key.length;
}
public byte[] getKey() {
return key.clone();
}
public DataInputStream getKeyAsDataInputStream() {
return new DataInputStream(new ByteArrayInputStream(key));
}
private transient String keyBase64Cache;
public String getKeyBase64() {
if (keyBase64Cache == null) {
keyBase64Cache = Base64.encodeToString(key);
}
return keyBase64Cache;
}
private transient BigInteger keyBigIntegerCache;
public BigInteger getKeyBigInteger() {
if (keyBigIntegerCache == null) {
keyBigIntegerCache = new BigInteger(key);
}
return keyBigIntegerCache;
}
public boolean keyEquals(byte[] otherKey) {
return Arrays.equals(key, otherKey);
}
public boolean isSecureEntryPoint() {
return (flags & FLAG_SECURE_ENTRY_POINT) == 1;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.constants.DnssecConstants.DigestAlgorithm;
import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.IOException;
/**
* DS (Delegation Signer) record payload.
*
* @see <a href="https://tools.ietf.org/html/rfc4034#section-5">RFC 4034 § 5</a>
*/
public class DS extends DelegatingDnssecRR {
public static DS parse(DataInputStream dis, int length) throws IOException {
SharedData parsedData = DelegatingDnssecRR.parseSharedData(dis, length);
return new DS(parsedData.keyTag, parsedData.algorithm, parsedData.digestType, parsedData.digest);
}
public DS(int keyTag, byte algorithm, byte digestType, byte[] digest) {
super(keyTag, algorithm, digestType, digest);
}
public DS(int keyTag, SignatureAlgorithm algorithm, byte digestType, byte[] digest) {
super(keyTag, algorithm, digestType, digest);
}
public DS(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {
super(keyTag, algorithm, digestType, digest);
}
@Override
public TYPE getType() {
return TYPE.DS;
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import org.minidns.record.Record.TYPE;
/**
* Generic payload class.
*/
public abstract class Data {
/**
* The payload type.
* @return The payload type.
*/
public abstract TYPE getType();
/**
* The internal method used to serialize Data subclasses.
*
* @param dos the output stream to serialize to.
* @throws IOException if an I/O error occurs.
*/
protected abstract void serialize(DataOutputStream dos) throws IOException;
private byte[] bytes;
private void setBytes() {
if (bytes != null) return;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
serialize(dos);
} catch (IOException e) {
// Should never happen.
throw new AssertionError(e);
}
bytes = baos.toByteArray();
}
public final int length() {
setBytes();
return bytes.length;
}
public final void toOutputStream(OutputStream outputStream) throws IOException {
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
toOutputStream(dataOutputStream);
}
/**
* Write the binary representation of this payload to the given {@link DataOutputStream}.
*
* @param dos the DataOutputStream to write to.
* @throws IOException if an I/O error occurs.
*/
public final void toOutputStream(DataOutputStream dos) throws IOException {
setBytes();
dos.write(bytes);
}
public final byte[] toByteArray() {
setBytes();
return bytes.clone();
}
private transient Integer hashCodeCache;
@Override
public final int hashCode() {
if (hashCodeCache == null) {
setBytes();
hashCodeCache = Arrays.hashCode(bytes);
}
return hashCodeCache;
}
@Override
public final boolean equals(Object other) {
if (!(other instanceof Data)) {
return false;
}
if (other == this) {
return true;
}
Data otherData = (Data) other;
otherData.setBytes();
setBytes();
return Arrays.equals(bytes, otherData.bytes);
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import org.minidns.constants.DnssecConstants.DigestAlgorithm;
import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
/**
* DS (Delegation Signer) record payload.
*
* @see <a href="https://tools.ietf.org/html/rfc4034#section-5">RFC 4034 § 5</a>
*/
public abstract class DelegatingDnssecRR extends Data {
/**
* The key tag value of the DNSKEY RR that validates this signature.
*/
public final int /* unsigned short */ keyTag;
/**
* The cryptographic algorithm used to create the signature. If MiniDNS
* isn't aware of the signature algorithm, then this field will be
* <code>null</code>.
*
* @see #algorithmByte
*/
public final SignatureAlgorithm algorithm;
/**
* The byte value of the cryptographic algorithm used to create the signature.
*/
public final byte algorithmByte;
/**
* The algorithm used to construct the digest. If MiniDNS
* isn't aware of the digest algorithm, then this field will be
* <code>null</code>.
*
* @see #digestTypeByte
*/
public final DigestAlgorithm digestType;
/**
* The byte value of algorithm used to construct the digest.
*/
public final byte digestTypeByte;
/**
* The digest build from a DNSKEY.
*/
protected final byte[] digest;
protected static SharedData parseSharedData(DataInputStream dis, int length) throws IOException {
int keyTag = dis.readUnsignedShort();
byte algorithm = dis.readByte();
byte digestType = dis.readByte();
byte[] digest = new byte[length - 4];
if (dis.read(digest) != digest.length) throw new IOException();
return new SharedData(keyTag, algorithm, digestType, digest);
}
protected static final class SharedData {
protected final int keyTag;
protected final byte algorithm;
protected final byte digestType;
protected final byte[] digest;
private SharedData(int keyTag, byte algorithm, byte digestType, byte[] digest) {
this.keyTag = keyTag;
this.algorithm = algorithm;
this.digestType = digestType;
this.digest = digest;
}
}
protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, byte algorithmByte, DigestAlgorithm digestType, byte digestTypeByte, byte[] digest) {
this.keyTag = keyTag;
assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
this.algorithmByte = algorithmByte;
this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
assert digestTypeByte == (digestType != null ? digestType.value : digestTypeByte);
this.digestTypeByte = digestTypeByte;
this.digestType = digestType != null ? digestType : DigestAlgorithm.forByte(digestTypeByte);
assert digest != null;
this.digest = digest;
}
protected DelegatingDnssecRR(int keyTag, byte algorithm, byte digestType, byte[] digest) {
this(keyTag, null, algorithm, null, digestType, digest);
}
protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {
this(keyTag, algorithm, algorithm.number, digestType, digestType.value, digest);
}
protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, byte digestType, byte[] digest) {
this(keyTag, algorithm, algorithm.number, null, digestType, digest);
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeShort(keyTag);
dos.writeByte(algorithmByte);
dos.writeByte(digestTypeByte);
dos.write(digest);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(keyTag).append(' ')
.append(algorithm).append(' ')
.append(digestType).append(' ')
.append(new BigInteger(1, digest).toString(16).toUpperCase());
return sb.toString();
}
private transient BigInteger digestBigIntCache;
public BigInteger getDigestBigInteger() {
if (digestBigIntCache == null) {
digestBigIntCache = new BigInteger(1, digest);
}
return digestBigIntCache;
}
private transient String digestHexCache;
public String getDigestHex() {
if (digestHexCache == null) {
digestHexCache = getDigestBigInteger().toString(16).toUpperCase();
}
return digestHexCache;
}
public boolean digestEquals(byte[] otherDigest) {
return Arrays.equals(digest, otherDigest);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* A resource record representing a internet address. Provides {@link #getInetAddress()}.
*/
public abstract class InternetAddressRR<IA extends InetAddress> extends Data {
/**
* Target IP.
*/
protected final byte[] ip;
/**
* Cache for the {@link InetAddress} this record presents.
*/
private transient IA inetAddress;
protected InternetAddressRR(byte[] ip) {
this.ip = ip;
}
protected InternetAddressRR(IA inetAddress) {
this(inetAddress.getAddress());
this.inetAddress = inetAddress;
}
@Override
public final void serialize(DataOutputStream dos) throws IOException {
dos.write(ip);
}
/**
* Allocates a new byte buffer and fills the buffer with the bytes representing the IP address of this resource record.
*
* @return a new byte buffer containing the bytes of the IP.
*/
public final byte[] getIp() {
return ip.clone();
}
@SuppressWarnings("unchecked")
public final IA getInetAddress() {
if (inetAddress == null) {
try {
inetAddress = (IA) InetAddress.getByAddress(ip);
} catch (UnknownHostException e) {
throw new IllegalStateException(e);
}
}
return inetAddress;
}
public static InternetAddressRR<? extends InetAddress> from(InetAddress inetAddress) {
if (inetAddress instanceof Inet4Address) {
return new A((Inet4Address) inetAddress);
}
return new AAAA((Inet6Address) inetAddress);
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
/**
* MX record payload (mail service pointer).
*/
public class MX extends Data {
/**
* The priority of this service. Lower values mean higher priority.
*/
public final int priority;
/**
* The name of the target server.
*/
public final DnsName target;
/**
* The name of the target server.
*
* @deprecated use {@link #target} instead.
*/
@Deprecated
public final DnsName name;
public static MX parse(DataInputStream dis, byte[] data)
throws IOException {
int priority = dis.readUnsignedShort();
DnsName name = DnsName.parse(dis, data);
return new MX(priority, name);
}
public MX(int priority, String name) {
this(priority, DnsName.from(name));
}
public MX(int priority, DnsName name) {
this.priority = priority;
this.target = name;
this.name = target;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeShort(priority);
target.writeToStream(dos);
}
@Override
public String toString() {
return priority + " " + target + '.';
}
@Override
public TYPE getType() {
return TYPE.MX;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.IOException;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
/**
* Nameserver record.
*/
public class NS extends RRWithTarget {
public static NS parse(DataInputStream dis, byte[] data) throws IOException {
DnsName target = DnsName.parse(dis, data);
return new NS(target);
}
public NS(DnsName name) {
super(name);
}
@Override
public TYPE getType() {
return TYPE.NS;
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
/**
* NSEC record payload.
*/
public class NSEC extends Data {
private static final Logger LOGGER = Logger.getLogger(NSEC.class.getName());
/**
* The next owner name that contains a authoritative data or a delegation point.
*/
public final DnsName next;
private final byte[] typeBitmap;
/**
* The RR types existing at the owner name.
*/
public final List<TYPE> types;
public static NSEC parse(DataInputStream dis, byte[] data, int length) throws IOException {
DnsName next = DnsName.parse(dis, data);
byte[] typeBitmap = new byte[length - next.size()];
if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();
List<TYPE> types = readTypeBitMap(typeBitmap);
return new NSEC(next, types);
}
public NSEC(String next, List<TYPE> types) {
this(DnsName.from(next), types);
}
public NSEC(String next, TYPE... types) {
this(DnsName.from(next), Arrays.asList(types));
}
public NSEC(DnsName next, List<TYPE> types) {
this.next = next;
this.types = Collections.unmodifiableList(types);
this.typeBitmap = createTypeBitMap(types);
}
@Override
public TYPE getType() {
return TYPE.NSEC;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
next.writeToStream(dos);
dos.write(typeBitmap);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(next).append('.');
for (TYPE type : types) {
sb.append(' ').append(type);
}
return sb.toString();
}
@SuppressWarnings("NarrowingCompoundAssignment")
static byte[] createTypeBitMap(List<TYPE> types) {
List<Integer> typeList = new ArrayList<Integer>(types.size());
for (TYPE type : types) {
typeList.add(type.getValue());
}
Collections.sort(typeList);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
int windowBlock = -1;
byte[] bitmap = null;
for (Integer type : typeList) {
if (windowBlock == -1 || (type >> 8) != windowBlock) {
if (windowBlock != -1) writeOutBlock(bitmap, dos);
windowBlock = type >> 8;
dos.writeByte(windowBlock);
bitmap = new byte[32];
}
int a = (type >> 3) % 32;
int b = type % 8;
bitmap[a] |= 128 >> b;
}
if (windowBlock != -1) writeOutBlock(bitmap, dos);
} catch (IOException e) {
// Should never happen.
throw new RuntimeException(e);
}
return baos.toByteArray();
}
private static void writeOutBlock(byte[] values, DataOutputStream dos) throws IOException {
int n = 0;
for (int i = 0; i < values.length; i++) {
if (values[i] != 0) n = i + 1;
}
dos.writeByte(n);
for (int i = 0; i < n; i++) {
dos.writeByte(values[i]);
}
}
// TODO: This method should probably just return List<Integer> so that unknown types can be act on later.
static List<TYPE> readTypeBitMap(byte[] typeBitmap) throws IOException {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(typeBitmap));
int read = 0;
ArrayList<TYPE> typeList = new ArrayList<TYPE>();
while (typeBitmap.length > read) {
int windowBlock = dis.readUnsignedByte();
int bitmapLength = dis.readUnsignedByte();
for (int i = 0; i < bitmapLength; i++) {
int b = dis.readUnsignedByte();
for (int j = 0; j < 8; j++) {
if (((b >> j) & 0x1) > 0) {
int typeInt = (windowBlock << 8) + (i * 8) + (7 - j);
TYPE type = TYPE.getType(typeInt);
if (type == TYPE.UNKNOWN) {
LOGGER.warning("Skipping unknown type in type bitmap: " + typeInt);
continue;
}
typeList.add(type);
}
}
}
read += bitmapLength + 2;
}
return typeList;
}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.dnslabel.DnsLabel;
import org.minidns.record.Record.TYPE;
import org.minidns.util.Base32;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* NSEC3 record payload.
*/
public class NSEC3 extends Data {
/**
* This Flag indicates whether this NSEC3 RR may cover unsigned
* delegations.
*/
public static final byte FLAG_OPT_OUT = 0x1;
private static final Map<Byte, HashAlgorithm> HASH_ALGORITHM_LUT = new HashMap<>();
/**
* DNSSEC NSEC3 Hash Algorithms.
*
* @see <a href=
* "https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3">
* IANA DNSSEC NSEC3 Hash Algorithms</a>
*/
public enum HashAlgorithm {
RESERVED(0, "Reserved"),
SHA1(1, "SHA-1"),
;
HashAlgorithm(int value, String description) {
if (value < 0 || value > 255) {
throw new IllegalArgumentException();
}
this.value = (byte) value;
this.description = description;
HASH_ALGORITHM_LUT.put(this.value, this);
}
public final byte value;
public final String description;
public static HashAlgorithm forByte(byte b) {
return HASH_ALGORITHM_LUT.get(b);
}
}
/**
* The cryptographic hash algorithm used. If MiniDNS
* isn't aware of the hash algorithm, then this field will be
* <code>null</code>.
*
* @see #hashAlgorithmByte
*/
public final HashAlgorithm hashAlgorithm;
/**
* The byte value of the cryptographic hash algorithm used.
*/
public final byte hashAlgorithmByte;
/**
* Bitmap of flags: {@link #FLAG_OPT_OUT}.
*/
public final byte flags;
/**
* The number of iterations the hash algorithm is applied.
*/
public final int /* unsigned short */ iterations;
/**
* The salt appended to the next owner name before hashing.
*/
private final byte[] salt;
/**
* The next hashed owner name in hash order.
*/
private final byte[] nextHashed;
private final byte[] typeBitmap;
/**
* The RR types existing at the original owner name.
*/
public final List<TYPE> types;
public static NSEC3 parse(DataInputStream dis, int length) throws IOException {
byte hashAlgorithm = dis.readByte();
byte flags = dis.readByte();
int iterations = dis.readUnsignedShort();
int saltLength = dis.readUnsignedByte();
byte[] salt = new byte[saltLength];
if (dis.read(salt) != salt.length) throw new IOException();
int hashLength = dis.readUnsignedByte();
byte[] nextHashed = new byte[hashLength];
if (dis.read(nextHashed) != nextHashed.length) throw new IOException();
byte[] typeBitmap = new byte[length - (6 + saltLength + hashLength)];
if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();
List<TYPE> types = NSEC.readTypeBitMap(typeBitmap);
return new NSEC3(hashAlgorithm, flags, iterations, salt, nextHashed, types);
}
private NSEC3(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {
assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);
this.hashAlgorithmByte = hashAlgorithmByte;
this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);
this.flags = flags;
this.iterations = iterations;
this.salt = salt;
this.nextHashed = nextHashed;
this.types = types;
this.typeBitmap = NSEC.createTypeBitMap(types);
}
public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {
this(null, hashAlgorithm, flags, iterations, salt, nextHashed, types);
}
public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE... types) {
this(null, hashAlgorithm, flags, iterations, salt, nextHashed, Arrays.asList(types));
}
@Override
public TYPE getType() {
return TYPE.NSEC3;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeByte(hashAlgorithmByte);
dos.writeByte(flags);
dos.writeShort(iterations);
dos.writeByte(salt.length);
dos.write(salt);
dos.writeByte(nextHashed.length);
dos.write(nextHashed);
dos.write(typeBitmap);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(hashAlgorithm).append(' ')
.append(flags).append(' ')
.append(iterations).append(' ')
.append(salt.length == 0 ? "-" : new BigInteger(1, salt).toString(16).toUpperCase()).append(' ')
.append(Base32.encodeToString(nextHashed));
for (TYPE type : types) {
sb.append(' ').append(type);
}
return sb.toString();
}
public byte[] getSalt() {
return salt.clone();
}
public int getSaltLength() {
return salt.length;
}
public byte[] getNextHashed() {
return nextHashed.clone();
}
private String nextHashedBase32Cache;
public String getNextHashedBase32() {
if (nextHashedBase32Cache == null) {
nextHashedBase32Cache = Base32.encodeToString(nextHashed);
}
return nextHashedBase32Cache;
}
private DnsLabel nextHashedDnsLabelCache;
public DnsLabel getNextHashedDnsLabel() {
if (nextHashedDnsLabelCache == null) {
String nextHashedBase32 = getNextHashedBase32();
nextHashedDnsLabelCache = DnsLabel.from(nextHashedBase32);
}
return nextHashedDnsLabelCache;
}
public void copySaltInto(byte[] dest, int destPos) {
System.arraycopy(salt, 0, dest, destPos, salt.length);
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.record.NSEC3.HashAlgorithm;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
/**
* NSEC3PARAM record payload.
*/
public class NSEC3PARAM extends Data {
/**
* The cryptographic hash algorithm used.
*
*/
public final HashAlgorithm hashAlgorithm;
/**
* The cryptographic hash algorithm used.
*
*/
public final byte hashAlgorithmByte;
public final byte flags;
/**
* The number of iterations the hash algorithm is applied.
*/
public final int /* unsigned short */ iterations;
/**
* The salt appended to the next owner name before hashing.
*/
private final byte[] salt;
public static NSEC3PARAM parse(DataInputStream dis) throws IOException {
byte hashAlgorithm = dis.readByte();
byte flags = dis.readByte();
int iterations = dis.readUnsignedShort();
int saltLength = dis.readUnsignedByte();
byte[] salt = new byte[saltLength];
if (dis.read(salt) != salt.length && salt.length != 0) throw new IOException();
return new NSEC3PARAM(hashAlgorithm, flags, iterations, salt);
}
private NSEC3PARAM(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt) {
assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);
this.hashAlgorithmByte = hashAlgorithmByte;
this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);
this.flags = flags;
this.iterations = iterations;
this.salt = salt;
}
NSEC3PARAM(byte hashAlgorithm, byte flags, int iterations, byte[] salt) {
this(null, hashAlgorithm, flags, iterations, salt);
}
@Override
public TYPE getType() {
return TYPE.NSEC3PARAM;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeByte(hashAlgorithmByte);
dos.writeByte(flags);
dos.writeShort(iterations);
dos.writeByte(salt.length);
dos.write(salt);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(hashAlgorithm).append(' ')
.append(flags).append(' ')
.append(iterations).append(' ')
.append(salt.length == 0 ? "-" : new BigInteger(1, salt).toString(16).toUpperCase());
return sb.toString();
}
public int getSaltLength() {
return salt.length;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.util.Base64;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class OPENPGPKEY extends Data {
private final byte[] publicKeyPacket;
public static OPENPGPKEY parse(DataInputStream dis, int length) throws IOException {
byte[] publicKeyPacket = new byte[length];
dis.readFully(publicKeyPacket);
return new OPENPGPKEY(publicKeyPacket);
}
OPENPGPKEY(byte[] publicKeyPacket) {
this.publicKeyPacket = publicKeyPacket;
}
@Override
public Record.TYPE getType() {
return Record.TYPE.OPENPGPKEY;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.write(publicKeyPacket);
}
@Override
public String toString() {
return getPublicKeyPacketBase64();
}
private transient String publicKeyPacketBase64Cache;
public String getPublicKeyPacketBase64() {
if (publicKeyPacketBase64Cache == null) {
publicKeyPacketBase64Cache = Base64.encodeToString(publicKeyPacket);
}
return publicKeyPacketBase64Cache;
}
public byte[] getPublicKeyPacket() {
return publicKeyPacket.clone();
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.edns.EdnsOption;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* OPT payload (see RFC 2671 for details).
*/
public class OPT extends Data {
public final List<EdnsOption> variablePart;
public OPT() {
this(Collections.<EdnsOption>emptyList());
}
public OPT(List<EdnsOption> variablePart) {
this.variablePart = Collections.unmodifiableList(variablePart);
}
public static OPT parse(DataInputStream dis, int payloadLength) throws IOException {
List<EdnsOption> variablePart;
if (payloadLength == 0) {
variablePart = Collections.emptyList();
} else {
int payloadLeft = payloadLength;
variablePart = new ArrayList<>(4);
while (payloadLeft > 0) {
int optionCode = dis.readUnsignedShort();
int optionLength = dis.readUnsignedShort();
byte[] optionData = new byte[optionLength];
dis.read(optionData);
EdnsOption ednsOption = EdnsOption.parse(optionCode, optionData);
variablePart.add(ednsOption);
payloadLeft -= 2 + 2 + optionLength;
// Assert that payloadLeft never becomes negative
assert payloadLeft >= 0;
}
}
return new OPT(variablePart);
}
@Override
public TYPE getType() {
return TYPE.OPT;
}
@Override
protected void serialize(DataOutputStream dos) throws IOException {
for (EdnsOption endsOption : variablePart) {
endsOption.writeToDos(dos);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.IOException;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
/**
* A PTR record is handled like a CNAME.
*/
public class PTR extends RRWithTarget {
public static PTR parse(DataInputStream dis, byte[] data) throws IOException {
DnsName target = DnsName.parse(dis, data);
return new PTR(target);
}
PTR(String name) {
this(DnsName.from(name));
}
PTR(DnsName name) {
super(name);
}
@Override
public TYPE getType() {
return TYPE.PTR;
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
import org.minidns.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* RRSIG record payload.
*/
public class RRSIG extends Data {
/**
* The type of RRset covered by this signature.
*/
public final TYPE typeCovered;
/**
* The cryptographic algorithm used to create the signature.
*/
public final SignatureAlgorithm algorithm;
/**
* The cryptographic algorithm used to create the signature.
*/
public final byte algorithmByte;
/**
* The number of labels in the original RRSIG RR owner name.
*/
public final byte labels;
/**
* The TTL of the covered RRset.
*/
public final long /* unsigned int */ originalTtl;
/**
* The date and time this RRSIG records expires.
*/
public final Date signatureExpiration;
/**
* The date and time this RRSIG records starts to be valid.
*/
public final Date signatureInception;
/**
* The key tag value of the DNSKEY RR that validates this signature.
*/
public final int /* unsigned short */ keyTag;
/**
* The owner name of the DNSKEY RR that a validator is supposed to use.
*/
public final DnsName signerName;
/**
* Signature that covers RRSIG RDATA (excluding the signature field) and RRset data.
*/
private final byte[] signature;
public static RRSIG parse(DataInputStream dis, byte[] data, int length) throws IOException {
TYPE typeCovered = TYPE.getType(dis.readUnsignedShort());
byte algorithm = dis.readByte();
byte labels = dis.readByte();
long originalTtl = dis.readInt() & 0xFFFFFFFFL;
Date signatureExpiration = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
Date signatureInception = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);
int keyTag = dis.readUnsignedShort();
DnsName signerName = DnsName.parse(dis, data);
int sigSize = length - signerName.size() - 18;
byte[] signature = new byte[sigSize];
if (dis.read(signature) != signature.length) throw new IOException();
return new RRSIG(typeCovered, null, algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName,
signature);
}
private RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte algorithmByte, byte labels, long originalTtl, Date signatureExpiration,
Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
this.typeCovered = typeCovered;
assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
this.algorithmByte = algorithmByte;
this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
this.labels = labels;
this.originalTtl = originalTtl;
this.signatureExpiration = signatureExpiration;
this.signatureInception = signatureInception;
this.keyTag = keyTag;
this.signerName = signerName;
this.signature = signature;
}
public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration,
Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {
this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName, signature);
}
public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration,
Date signatureInception, int keyTag, String signerName, byte[] signature) {
this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, DnsName.from(signerName), signature);
}
public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
long originalTtl, Date signatureExpiration, Date signatureInception,
int keyTag, DnsName signerName, byte[] signature) {
this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
keyTag, signerName, signature);
}
public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,
long originalTtl, Date signatureExpiration, Date signatureInception,
int keyTag, String signerName, byte[] signature) {
this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,
keyTag, DnsName.from(signerName), signature);
}
public byte[] getSignature() {
return signature.clone();
}
public DataInputStream getSignatureAsDataInputStream() {
return new DataInputStream(new ByteArrayInputStream(signature));
}
public int getSignatureLength() {
return signature.length;
}
private transient String base64SignatureCache;
public String getSignatureBase64() {
if (base64SignatureCache == null) {
base64SignatureCache = Base64.encodeToString(signature);
}
return base64SignatureCache;
}
@Override
public TYPE getType() {
return TYPE.RRSIG;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
writePartialSignature(dos);
dos.write(signature);
}
public void writePartialSignature(DataOutputStream dos) throws IOException {
dos.writeShort(typeCovered.getValue());
dos.writeByte(algorithmByte);
dos.writeByte(labels);
dos.writeInt((int) originalTtl);
dos.writeInt((int) (signatureExpiration.getTime() / 1000));
dos.writeInt((int) (signatureInception.getTime() / 1000));
dos.writeShort(keyTag);
signerName.writeToStream(dos);
}
@Override
public String toString() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
StringBuilder sb = new StringBuilder()
.append(typeCovered).append(' ')
.append(algorithm).append(' ')
.append(labels).append(' ')
.append(originalTtl).append(' ')
.append(dateFormat.format(signatureExpiration)).append(' ')
.append(dateFormat.format(signatureInception)).append(' ')
.append(keyTag).append(' ')
.append(signerName).append(". ")
.append(getSignatureBase64());
return sb.toString();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataOutputStream;
import java.io.IOException;
import org.minidns.dnsname.DnsName;
/**
* A resource record pointing to a target.
*/
public abstract class RRWithTarget extends Data {
public final DnsName target;
/**
* The target of this resource record.
* @deprecated {@link #target} instead.
*/
@Deprecated
public final DnsName name;
@Override
public void serialize(DataOutputStream dos) throws IOException {
target.writeToStream(dos);
}
protected RRWithTarget(DnsName target) {
this.target = target;
this.name = target;
}
@Override
public String toString() {
return target + ".";
}
public final DnsName getTarget() {
return target;
}
}

View File

@ -0,0 +1,628 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName;
/**
* A generic DNS record.
*/
public final class Record<D extends Data> {
/**
* The resource record type.
*
* @see <a href=
* "http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4">
* IANA DNS Parameters - Resource Record (RR) TYPEs</a>
*/
public enum TYPE {
UNKNOWN(-1),
A(1, A.class),
NS(2, NS.class),
MD(3),
MF(4),
CNAME(5, CNAME.class),
SOA(6, SOA.class),
MB(7),
MG(8),
MR(9),
NULL(10),
WKS(11),
PTR(12, PTR.class),
HINFO(13),
MINFO(14),
MX(15, MX.class),
TXT(16, TXT.class),
RP(17),
AFSDB(18),
X25(19),
ISDN(20),
RT(21),
NSAP(22),
NSAP_PTR(23),
SIG(24),
KEY(25),
PX(26),
GPOS(27),
AAAA(28, AAAA.class),
LOC(29),
NXT(30),
EID(31),
NIMLOC(32),
SRV(33, SRV.class),
ATMA(34),
NAPTR(35),
KX(36),
CERT(37),
A6(38),
DNAME(39, DNAME.class),
SINK(40),
OPT(41, OPT.class),
APL(42),
DS(43, DS.class),
SSHFP(44),
IPSECKEY(45),
RRSIG(46, RRSIG.class),
NSEC(47, NSEC.class),
DNSKEY(48, DNSKEY.class),
DHCID(49),
NSEC3(50, NSEC3.class),
NSEC3PARAM(51, NSEC3PARAM.class),
TLSA(52, TLSA.class),
HIP(55),
NINFO(56),
RKEY(57),
TALINK(58),
CDS(59),
CDNSKEY(60),
OPENPGPKEY(61, OPENPGPKEY.class),
CSYNC(62),
SPF(99),
UINFO(100),
UID(101),
GID(102),
UNSPEC(103),
NID(104),
L32(105),
L64(106),
LP(107),
EUI48(108),
EUI64(109),
TKEY(249),
TSIG(250),
IXFR(251),
AXFR(252),
MAILB(253),
MAILA(254),
ANY(255),
URI(256),
CAA(257),
TA(32768),
DLV(32769, DLV.class),
;
/**
* The value of this DNS record type.
*/
private final int value;
private final Class<?> dataClass;
/**
* Internal lookup table to map values to types.
*/
private static final Map<Integer, TYPE> INVERSE_LUT = new HashMap<>();
private static final Map<Class<?>, TYPE> DATA_LUT = new HashMap<>();
static {
// Initialize the reverse lookup table.
for (TYPE t : TYPE.values()) {
INVERSE_LUT.put(t.getValue(), t);
if (t.dataClass != null) {
DATA_LUT.put(t.dataClass, t);
}
}
}
/**
* Create a new record type.
*
* @param value The binary value of this type.
*/
TYPE(int value) {
this(value, null);
}
/**
* Create a new record type.
*
* @param <D> The class for this type.
* @param dataClass The class for this type.
* @param value The binary value of this type.
*/
<D extends Data> TYPE(int value, Class<D> dataClass) {
this.value = value;
this.dataClass = dataClass;
}
/**
* Retrieve the binary value of this type.
* @return The binary value.
*/
public int getValue() {
return value;
}
/**
* Get the {@link Data} class for this type.
*
* @param <D> The class for this type.
* @return the {@link Data} class for this type.
*/
@SuppressWarnings("unchecked")
public <D extends Data> Class<D> getDataClass() {
return (Class<D>) dataClass;
}
/**
* Retrieve the symbolic type of the binary value.
* @param value The binary type value.
* @return The symbolic tpye.
*/
public static TYPE getType(int value) {
TYPE type = INVERSE_LUT.get(value);
if (type == null) return UNKNOWN;
return type;
}
/**
* Retrieve the type for a given {@link Data} class.
*
* @param <D> The class for this type.
* @param dataClass the class to lookup the type for.
* @return the type for the given data class.
*/
public static <D extends Data> TYPE getType(Class<D> dataClass) {
return DATA_LUT.get(dataClass);
}
}
/**
* The symbolic class of a DNS record (usually {@link CLASS#IN} for Internet).
*
* @see <a href="http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2">IANA Domain Name System (DNS) Parameters - DNS CLASSes</a>
*/
public enum CLASS {
/**
* The Internet class. This is the most common class used by todays DNS systems.
*/
IN(1),
/**
* The Chaos class.
*/
CH(3),
/**
* The Hesiod class.
*/
HS(4),
NONE(254),
ANY(255);
/**
* Internal reverse lookup table to map binary class values to symbolic
* names.
*/
private static final HashMap<Integer, CLASS> INVERSE_LUT =
new HashMap<Integer, CLASS>();
static {
// Initialize the interal reverse lookup table.
for (CLASS c : CLASS.values()) {
INVERSE_LUT.put(c.getValue(), c);
}
}
/**
* The binary value of this dns class.
*/
private final int value;
/**
* Create a new DNS class based on a binary value.
* @param value The binary value of this DNS class.
*/
CLASS(int value) {
this.value = value;
}
/**
* Retrieve the binary value of this DNS class.
* @return The binary value of this DNS class.
*/
public int getValue() {
return value;
}
/**
* Retrieve the symbolic DNS class for a binary class value.
* @param value The binary DNS class value.
* @return The symbolic class instance.
*/
public static CLASS getClass(int value) {
return INVERSE_LUT.get(value);
}
}
/**
* The generic name of this record.
*/
public final DnsName name;
/**
* The type (and payload type) of this record.
*/
public final TYPE type;
/**
* The record class (usually CLASS.IN).
*/
public final CLASS clazz;
/**
* The value of the class field of a RR.
*
* According to RFC 2671 (OPT RR) this is not necessarily representable
* using clazz field and unicastQuery bit
*/
public final int clazzValue;
/**
* The ttl of this record.
*/
public final long ttl;
/**
* The payload object of this record.
*/
public final D payloadData;
/**
* MDNS defines the highest bit of the class as the unicast query bit.
*/
public final boolean unicastQuery;
/**
* Parse a given record based on the full message data and the current
* stream position.
*
* @param dis The DataInputStream positioned at the first record byte.
* @param data The full message data.
* @return the record which was parsed.
* @throws IOException In case of malformed replies.
*/
public static Record<Data> parse(DataInputStream dis, byte[] data) throws IOException {
DnsName name = DnsName.parse(dis, data);
int typeValue = dis.readUnsignedShort();
TYPE type = TYPE.getType(typeValue);
int clazzValue = dis.readUnsignedShort();
CLASS clazz = CLASS.getClass(clazzValue & 0x7fff);
boolean unicastQuery = (clazzValue & 0x8000) > 0;
long ttl = (((long) dis.readUnsignedShort()) << 16) +
dis.readUnsignedShort();
int payloadLength = dis.readUnsignedShort();
Data payloadData;
switch (type) {
case SOA:
payloadData = SOA.parse(dis, data);
break;
case SRV:
payloadData = SRV.parse(dis, data);
break;
case MX:
payloadData = MX.parse(dis, data);
break;
case AAAA:
payloadData = AAAA.parse(dis);
break;
case A:
payloadData = A.parse(dis);
break;
case NS:
payloadData = NS.parse(dis, data);
break;
case CNAME:
payloadData = CNAME.parse(dis, data);
break;
case DNAME:
payloadData = DNAME.parse(dis, data);
break;
case PTR:
payloadData = PTR.parse(dis, data);
break;
case TXT:
payloadData = TXT.parse(dis, payloadLength);
break;
case OPT:
payloadData = OPT.parse(dis, payloadLength);
break;
case DNSKEY:
payloadData = DNSKEY.parse(dis, payloadLength);
break;
case RRSIG:
payloadData = RRSIG.parse(dis, data, payloadLength);
break;
case DS:
payloadData = DS.parse(dis, payloadLength);
break;
case NSEC:
payloadData = NSEC.parse(dis, data, payloadLength);
break;
case NSEC3:
payloadData = NSEC3.parse(dis, payloadLength);
break;
case NSEC3PARAM:
payloadData = NSEC3PARAM.parse(dis);
break;
case TLSA:
payloadData = TLSA.parse(dis, payloadLength);
break;
case OPENPGPKEY:
payloadData = OPENPGPKEY.parse(dis, payloadLength);
break;
case DLV:
payloadData = DLV.parse(dis, payloadLength);
break;
case UNKNOWN:
default:
payloadData = UNKNOWN.parse(dis, payloadLength, type);
break;
}
return new Record<>(name, type, clazz, clazzValue, ttl, payloadData, unicastQuery);
}
public Record(DnsName name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {
this(name, type, clazz, clazz.getValue() + (unicastQuery ? 0x8000 : 0), ttl, payloadData, unicastQuery);
}
public Record(String name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {
this(DnsName.from(name), type, clazz, ttl, payloadData, unicastQuery);
}
public Record(String name, TYPE type, int clazzValue, long ttl, D payloadData) {
this(DnsName.from(name), type, CLASS.NONE, clazzValue, ttl, payloadData, false);
}
public Record(DnsName name, TYPE type, int clazzValue, long ttl, D payloadData) {
this(name, type, CLASS.NONE, clazzValue, ttl, payloadData, false);
}
private Record(DnsName name, TYPE type, CLASS clazz, int clazzValue, long ttl, D payloadData, boolean unicastQuery) {
this.name = name;
this.type = type;
this.clazz = clazz;
this.clazzValue = clazzValue;
this.ttl = ttl;
this.payloadData = payloadData;
this.unicastQuery = unicastQuery;
}
public void toOutputStream(OutputStream outputStream) throws IOException {
if (payloadData == null) {
throw new IllegalStateException("Empty Record has no byte representation");
}
DataOutputStream dos = new DataOutputStream(outputStream);
name.writeToStream(dos);
dos.writeShort(type.getValue());
dos.writeShort(clazzValue);
dos.writeInt((int) ttl);
dos.writeShort(payloadData.length());
payloadData.toOutputStream(dos);
}
private transient byte[] bytes;
public byte[] toByteArray() {
if (bytes == null) {
int totalSize = name.size()
+ 10 // 2 byte short type + 2 byte short classValue + 4 byte int ttl + 2 byte short payload length.
+ payloadData.length();
ByteArrayOutputStream baos = new ByteArrayOutputStream(totalSize);
DataOutputStream dos = new DataOutputStream(baos);
try {
toOutputStream(dos);
} catch (IOException e) {
// Should never happen.
throw new AssertionError(e);
}
bytes = baos.toByteArray();
}
return bytes.clone();
}
/**
* Retrieve a textual representation of this resource record.
* @return String
*/
@Override
public String toString() {
return name.getRawAce() + ".\t" + ttl + '\t' + clazz + '\t' + type + '\t' + payloadData;
}
/**
* Check if this record answers a given query.
* @param q The query.
* @return True if this record is a valid answer.
*/
public boolean isAnswer(Question q) {
return ((q.type == type) || (q.type == TYPE.ANY)) &&
((q.clazz == clazz) || (q.clazz == CLASS.ANY)) &&
q.name.equals(name);
}
/**
* See if this query/response was a unicast query (highest class bit set).
* @return True if it is a unicast query/response record.
*/
public boolean isUnicastQuery() {
return unicastQuery;
}
/**
* The payload data, usually a subclass of data (A, AAAA, CNAME, ...).
* @return The payload data.
*/
public D getPayload() {
return payloadData;
}
/**
* Retrieve the record ttl.
* @return The record ttl.
*/
public long getTtl() {
return ttl;
}
/**
* Get the question asking for this resource record. This will return <code>null</code> if the record is not retrievable, i.e.
* {@link TYPE#OPT}.
*
* @return the question for this resource record or <code>null</code>.
*/
public Question getQuestion() {
switch (type) {
case OPT:
// OPT records are not retrievable.
return null;
case RRSIG:
RRSIG rrsig = (RRSIG) payloadData;
return new Question(name, rrsig.typeCovered, clazz);
default:
return new Question(name, type, clazz);
}
}
public DnsMessage.Builder getQuestionMessage() {
Question question = getQuestion();
if (question == null) {
return null;
}
return question.asMessageBuilder();
}
private transient Integer hashCodeCache;
@Override
public int hashCode() {
if (hashCodeCache == null) {
int hashCode = 1;
hashCode = 37 * hashCode + name.hashCode();
hashCode = 37 * hashCode + type.hashCode();
hashCode = 37 * hashCode + clazz.hashCode();
hashCode = 37 * hashCode + payloadData.hashCode();
hashCodeCache = hashCode;
}
return hashCodeCache;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Record)) {
return false;
}
if (other == this) {
return true;
}
Record<?> otherRecord = (Record<?>) other;
if (!name.equals(otherRecord.name)) return false;
if (type != otherRecord.type) return false;
if (clazz != otherRecord.clazz) return false;
// Note that we do not compare the TTL here, since we consider two Records with everything but the TTL equal to
// be equal too.
if (!payloadData.equals(otherRecord.payloadData)) return false;
return true;
}
/**
* Return the record if possible as record with the given {@link Data} class. If the record does not hold payload of
* the given data class type, then {@code null} will be returned.
*
* @param dataClass a class of the {@link Data} type.
* @param <E> a subtype of {@link Data}.
* @return the record with a specialized payload type or {@code null}.
* @see #as(Class)
*/
@SuppressWarnings("unchecked")
public <E extends Data> Record<E> ifPossibleAs(Class<E> dataClass) {
if (type.dataClass == dataClass) {
return (Record<E>) this;
}
return null;
}
/**
* Return the record as record with the given {@link Data} class. If the record does not hold payload of
* the given data class type, then a {@link IllegalArgumentException} will be thrown.
*
* @param dataClass a class of the {@link Data} type.
* @param <E> a subtype of {@link Data}.
* @return the record with a specialized payload type.
* @see #ifPossibleAs(Class)
*/
public <E extends Data> Record<E> as(Class<E> dataClass) {
Record<E> eRecord = ifPossibleAs(dataClass);
if (eRecord == null) {
throw new IllegalArgumentException("The instance " + this + " can not be cast to a Record with" + dataClass);
}
return eRecord;
}
public static <E extends Data> void filter(Collection<Record<E>> result, Class<E> dataClass,
Collection<Record<? extends Data>> input) {
for (Record<? extends Data> record : input) {
Record<E> filteredRecord = record.ifPossibleAs(dataClass);
if (filteredRecord == null)
continue;
result.add(filteredRecord);
}
}
public static <E extends Data> List<Record<E>> filter(Class<E> dataClass,
Collection<Record<? extends Data>> input) {
List<Record<E>> result = new ArrayList<>(input.size());
filter(result, dataClass, input);
return result;
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* SOA (start of authority) record payload.
*/
public class SOA extends Data {
/**
* The domain name of the name server that was the original or primary source of data for this zone.
*/
public final DnsName mname;
/**
* A domain name which specifies the mailbox of the person responsible for this zone.
*/
public final DnsName rname;
/**
* The unsigned 32 bit version number of the original copy of the zone. Zone transfers preserve this value. This
* value wraps and should be compared using sequence space arithmetic.
*/
public final long /* unsigned int */ serial;
/**
* A 32 bit time interval before the zone should be refreshed.
*/
public final int refresh;
/**
* A 32 bit time interval that should elapse before a failed refresh should be retried.
*/
public final int retry;
/**
* A 32 bit time value that specifies the upper limit on the time interval that can elapse before the zone is no
* longer authoritative.
*/
public final int expire;
/**
* The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone.
*/
public final long /* unsigned int */ minimum;
public static SOA parse(DataInputStream dis, byte[] data)
throws IOException {
DnsName mname = DnsName.parse(dis, data);
DnsName rname = DnsName.parse(dis, data);
long serial = dis.readInt() & 0xFFFFFFFFL;
int refresh = dis.readInt();
int retry = dis.readInt();
int expire = dis.readInt();
long minimum = dis.readInt() & 0xFFFFFFFFL;
return new SOA(mname, rname, serial, refresh, retry, expire, minimum);
}
public SOA(String mname, String rname, long serial, int refresh, int retry, int expire, long minimum) {
this(DnsName.from(mname), DnsName.from(rname), serial, refresh, retry, expire, minimum);
}
public SOA(DnsName mname, DnsName rname, long serial, int refresh, int retry, int expire, long minimum) {
this.mname = mname;
this.rname = rname;
this.serial = serial;
this.refresh = refresh;
this.retry = retry;
this.expire = expire;
this.minimum = minimum;
}
@Override
public TYPE getType() {
return TYPE.SOA;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
mname.writeToStream(dos);
rname.writeToStream(dos);
dos.writeInt((int) serial);
dos.writeInt(refresh);
dos.writeInt(retry);
dos.writeInt(expire);
dos.writeInt((int) minimum);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(mname).append(". ")
.append(rname).append(". ")
.append(serial).append(' ')
.append(refresh).append(' ')
.append(retry).append(' ')
.append(expire).append(' ')
.append(minimum);
return sb.toString();
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.minidns.dnsname.DnsName;
import org.minidns.record.Record.TYPE;
/**
* SRV record payload (service pointer).
*/
public class SRV extends Data implements Comparable<SRV> {
/**
* The priority of this service. Lower values mean higher priority.
*/
public final int priority;
/**
* The weight of this service. Services with the same priority should be
* balanced based on weight.
*/
public final int weight;
/**
* The target port.
*/
public final int port;
/**
* The target server.
*/
public final DnsName target;
/**
* The target server.
*
* @deprecated use {@link #target} instead.
*/
@Deprecated
public final DnsName name;
public static SRV parse(DataInputStream dis, byte[] data)
throws IOException {
int priority = dis.readUnsignedShort();
int weight = dis.readUnsignedShort();
int port = dis.readUnsignedShort();
DnsName name = DnsName.parse(dis, data);
return new SRV(priority, weight, port, name);
}
public SRV(int priority, int weight, int port, String name) {
this(priority, weight, port, DnsName.from(name));
}
public SRV(int priority, int weight, int port, DnsName name) {
this.priority = priority;
this.weight = weight;
this.port = port;
this.target = name;
this.name = target;
}
/**
* Check if the service is available at this domain. This checks f the target points to the root label. As per RFC
* 2782 the service is decidedly not available if there is only a single SRV answer pointing to the root label. From
* RFC 2782:
*
* <blockquote>A Target of "." means that the service is decidedly not available at this domain.</blockquote>
*
* @return true if the service is available at this domain.
*/
public boolean isServiceAvailable() {
return !target.isRootLabel();
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeShort(priority);
dos.writeShort(weight);
dos.writeShort(port);
target.writeToStream(dos);
}
@Override
public String toString() {
return priority + " " + weight + " " + port + " " + target + ".";
}
@Override
public TYPE getType() {
return TYPE.SRV;
}
@Override
public int compareTo(SRV other) {
int res = other.priority - this.priority;
if (res == 0) {
res = this.weight - other.weight;
}
return res;
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class TLSA extends Data {
private static final Map<Byte, CertUsage> CERT_USAGE_LUT = new HashMap<>();
public enum CertUsage {
caConstraint((byte) 0),
serviceCertificateConstraint((byte) 1),
trustAnchorAssertion((byte) 2),
domainIssuedCertificate((byte) 3),
;
public final byte byteValue;
CertUsage(byte byteValue) {
this.byteValue = byteValue;
CERT_USAGE_LUT.put(byteValue, this);
}
}
private static final Map<Byte, Selector> SELECTOR_LUT = new HashMap<>();
public enum Selector {
fullCertificate((byte) 0),
subjectPublicKeyInfo((byte) 1),
;
public final byte byteValue;
Selector(byte byteValue) {
this.byteValue = byteValue;
SELECTOR_LUT.put(byteValue, this);
}
}
private static final Map<Byte, MatchingType> MATCHING_TYPE_LUT = new HashMap<>();
public enum MatchingType {
noHash((byte) 0),
sha256((byte) 1),
sha512((byte) 2),
;
public final byte byteValue;
MatchingType(byte byteValue) {
this.byteValue = byteValue;
MATCHING_TYPE_LUT.put(byteValue, this);
}
}
static {
// Ensure that the LUTs are initialized.
CertUsage.values();
Selector.values();
MatchingType.values();
}
/**
* The provided association that will be used to match the certificate presented in
* the TLS handshake.
*/
public final byte certUsageByte;
public final CertUsage certUsage;
/**
* Which part of the TLS certificate presented by the server will be matched against the
* association data.
*/
public final byte selectorByte;
public final Selector selector;
/**
* How the certificate association is presented.
*/
public final byte matchingTypeByte;
public final MatchingType matchingType;
/**
* The "certificate association data" to be matched.
*/
private final byte[] certificateAssociation;
public static TLSA parse(DataInputStream dis, int length) throws IOException {
byte certUsage = dis.readByte();
byte selector = dis.readByte();
byte matchingType = dis.readByte();
byte[] certificateAssociation = new byte[length - 3];
if (dis.read(certificateAssociation) != certificateAssociation.length) throw new IOException();
return new TLSA(certUsage, selector, matchingType, certificateAssociation);
}
TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte[] certificateAssociation) {
this.certUsageByte = certUsageByte;
this.certUsage = CERT_USAGE_LUT.get(certUsageByte);
this.selectorByte = selectorByte;
this.selector = SELECTOR_LUT.get(selectorByte);
this.matchingTypeByte = matchingTypeByte;
this.matchingType = MATCHING_TYPE_LUT.get(matchingTypeByte);
this.certificateAssociation = certificateAssociation;
}
@Override
public Record.TYPE getType() {
return Record.TYPE.TLSA;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.writeByte(certUsageByte);
dos.writeByte(selectorByte);
dos.writeByte(matchingTypeByte);
dos.write(certificateAssociation);
}
@Override
public String toString() {
return new StringBuilder()
.append(certUsageByte).append(' ')
.append(selectorByte).append(' ')
.append(matchingTypeByte).append(' ')
.append(new BigInteger(1, certificateAssociation).toString(16)).toString();
}
public byte[] getCertificateAssociation() {
return certificateAssociation.clone();
}
public boolean certificateAssociationEquals(byte[] otherCertificateAssociation) {
return Arrays.equals(certificateAssociation, otherCertificateAssociation);
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.minidns.record.Record.TYPE;
/**
* A TXT record. Actually a binary blob containing extents, each of which is a one-byte count
* followed by that many bytes of data, which can usually be interpreted as ASCII strings
* but not always.
*/
public class TXT extends Data {
private final byte[] blob;
public static TXT parse(DataInputStream dis, int length) throws IOException {
byte[] blob = new byte[length];
dis.readFully(blob);
return new TXT(blob);
}
public TXT(byte[] blob) {
this.blob = blob;
}
public byte[] getBlob() {
return blob.clone();
}
private transient String textCache;
public String getText() {
if (textCache == null) {
StringBuilder sb = new StringBuilder();
Iterator<String> it = getCharacterStrings().iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(" / ");
}
}
textCache = sb.toString();
}
return textCache;
}
private transient List<String> characterStringsCache;
public List<String> getCharacterStrings() {
if (characterStringsCache == null) {
List<byte[]> extents = getExtents();
List<String> characterStrings = new ArrayList<>(extents.size());
for (byte[] extent : extents) {
try {
characterStrings.add(new String(extent, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
characterStringsCache = Collections.unmodifiableList(characterStrings);
}
return characterStringsCache;
}
public List<byte[]> getExtents() {
ArrayList<byte[]> extents = new ArrayList<byte[]>();
int segLength = 0;
for (int used = 0; used < blob.length; used += segLength) {
segLength = 0x00ff & blob[used];
int end = ++used + segLength;
byte[] extent = Arrays.copyOfRange(blob, used, end);
extents.add(extent);
}
return extents;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.write(blob);
}
@Override
public TYPE getType() {
return TYPE.TXT;
}
@Override
public String toString() {
return "\"" + getText() + "\"";
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.record;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.minidns.record.Record.TYPE;
public final class UNKNOWN extends Data {
private final TYPE type;
private final byte[] data;
private UNKNOWN(DataInputStream dis, int payloadLength, TYPE type) throws IOException {
this.type = type;
this.data = new byte[payloadLength];
dis.readFully(data);
}
@Override
public TYPE getType() {
return type;
}
@Override
public void serialize(DataOutputStream dos) throws IOException {
dos.write(data);
}
public static UNKNOWN parse(DataInputStream dis, int payloadLength, TYPE type)
throws IOException {
return new UNKNOWN(dis, payloadLength, type);
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.util;
/**
* Very minimal Base32 encoder.
*/
public final class Base32 {
private static final String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
private static final String PADDING = "======";
/**
* Do not allow to instantiate Base32
*/
private Base32() {
}
public static String encodeToString(byte[] bytes) {
int paddingCount = (int) (8 - (bytes.length % 5) * 1.6) % 8;
byte[] padded = new byte[bytes.length + paddingCount];
System.arraycopy(bytes, 0, padded, 0, bytes.length);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i += 5) {
long j = ((long) (padded[i] & 0xff) << 32) + ((long) (padded[i + 1] & 0xff) << 24)
+ ((padded[i + 2] & 0xff) << 16) + ((padded[i + 3] & 0xff) << 8) + (padded[i + 4] & 0xff);
sb.append(ALPHABET.charAt((int) ((j >> 35) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 30) & 0x1f)))
.append(ALPHABET.charAt((int) ((j >> 25) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 20) & 0x1f)))
.append(ALPHABET.charAt((int) ((j >> 15) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 10) & 0x1f)))
.append(ALPHABET.charAt((int) ((j >> 5) & 0x1f))).append(ALPHABET.charAt((int) (j & 0x1f)));
}
return sb.substring(0, sb.length() - paddingCount) + PADDING.substring(0, paddingCount);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.util;
/**
* Very minimal Base64 encoder.
*/
public final class Base64 {
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static final String PADDING = "==";
/**
* Do not allow to instantiate Base64
*/
private Base64() {
}
public static String encodeToString(byte[] bytes) {
int paddingCount = (3 - (bytes.length % 3)) % 3;
byte[] padded = new byte[bytes.length + paddingCount];
System.arraycopy(bytes, 0, padded, 0, bytes.length);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i += 3) {
int j = ((padded[i] & 0xff) << 16) + ((padded[i + 1] & 0xff) << 8) + (padded[i + 2] & 0xff);
sb.append(ALPHABET.charAt((j >> 18) & 0x3f)).append(ALPHABET.charAt((j >> 12) & 0x3f))
.append(ALPHABET.charAt((j >> 6) & 0x3f)).append(ALPHABET.charAt(j & 0x3f));
}
return sb.substring(0, sb.length() - paddingCount) + PADDING.substring(0, paddingCount);
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.util;
public class Hex {
public static StringBuilder from(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb;
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2015-2020 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns.util;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
import org.minidns.dnsname.DnsName;
public class InetAddressUtil {
public static Inet4Address ipv4From(CharSequence cs) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(cs.toString());
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
if (inetAddress instanceof Inet4Address) {
return (Inet4Address) inetAddress;
}
throw new IllegalArgumentException();
}
public static Inet6Address ipv6From(CharSequence cs) {
InetAddress inetAddress;
try {
inetAddress = InetAddress.getByName(cs.toString());
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
if (inetAddress instanceof Inet6Address) {
return (Inet6Address) inetAddress;
}
throw new IllegalArgumentException();
}
// IPV4_REGEX from http://stackoverflow.com/a/46168/194894 by Kevin Wong (http://stackoverflow.com/users/4792/kevin-wong) licensed under
// CC BY-SA 3.0.
private static final Pattern IPV4_PATTERN = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
public static boolean isIpV4Address(CharSequence address) {
if (address == null) {
return false;
}
return IPV4_PATTERN.matcher(address).matches();
}
// IPv6 Regular Expression from http://stackoverflow.com/a/17871737/194894 by David M. Syzdek
// (http://stackoverflow.com/users/903194/david-m-syzdek) licensed under CC BY-SA 3.0.
private static final Pattern IPV6_PATTERN = Pattern.compile(
"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");
public static boolean isIpV6Address(CharSequence address) {
if (address == null) {
return false;
}
return IPV6_PATTERN.matcher(address).matches();
}
public static boolean isIpAddress(CharSequence address) {
return isIpV6Address(address) || isIpV4Address(address);
}
public static InetAddress convertToInetAddressIfPossible(CharSequence address) {
if (!isIpAddress(address)) {
return null;
}
String addressString = address.toString();
try {
return InetAddress.getByName(addressString);
} catch (UnknownHostException e) {
// Should never happen.
throw new AssertionError(e);
}
}
public static DnsName reverseIpAddressOf(Inet6Address inet6Address) {
final String ipAddress = inet6Address.getHostAddress();
final String[] ipAddressParts = ipAddress.split(":");
String[] parts = new String[32];
int currentPartNum = 0;
for (int i = ipAddressParts.length - 1; i >= 0; i--) {
final String currentPart = ipAddressParts[i];
final int missingPlaces = 4 - currentPart.length();
for (int j = 0; j < missingPlaces; j++) {
parts[currentPartNum++] = "0";
}
for (int j = 0; j < currentPart.length(); j++) {
parts[currentPartNum++] = Character.toString(currentPart.charAt(j));
}
}
return DnsName.from(parts);
}
public static DnsName reverseIpAddressOf(Inet4Address inet4Address) {
final String[] ipAddressParts = inet4Address.getHostAddress().split("\\.");
assert ipAddressParts.length == 4;
return DnsName.from(ipAddressParts);
}
}