forked from I2P_Developers/i2p.i2p
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:
@ -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:
|
||||
|
@ -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.*" />
|
||||
|
99
core/java/src/org/minidns/constants/DnssecConstants.java
Normal file
99
core/java/src/org/minidns/constants/DnssecConstants.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
25
core/java/src/org/minidns/dnslabel/ALabel.java
Normal file
25
core/java/src/org/minidns/dnslabel/ALabel.java
Normal 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);
|
||||
}
|
||||
}
|
186
core/java/src/org/minidns/dnslabel/DnsLabel.java
Normal file
186
core/java/src/org/minidns/dnslabel/DnsLabel.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
19
core/java/src/org/minidns/dnslabel/FakeALabel.java
Normal file
19
core/java/src/org/minidns/dnslabel/FakeALabel.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
61
core/java/src/org/minidns/dnslabel/LdhLabel.java
Normal file
61
core/java/src/org/minidns/dnslabel/LdhLabel.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
35
core/java/src/org/minidns/dnslabel/NonLdhLabel.java
Normal file
35
core/java/src/org/minidns/dnslabel/NonLdhLabel.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
34
core/java/src/org/minidns/dnslabel/NonReservedLdhLabel.java
Normal file
34
core/java/src/org/minidns/dnslabel/NonReservedLdhLabel.java
Normal 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);
|
||||
}
|
||||
}
|
23
core/java/src/org/minidns/dnslabel/OtherNonLdhLabel.java
Normal file
23
core/java/src/org/minidns/dnslabel/OtherNonLdhLabel.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
36
core/java/src/org/minidns/dnslabel/ReservedLdhLabel.java
Normal file
36
core/java/src/org/minidns/dnslabel/ReservedLdhLabel.java
Normal 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) == '-';
|
||||
}
|
||||
}
|
26
core/java/src/org/minidns/dnslabel/UnderscoreLabel.java
Normal file
26
core/java/src/org/minidns/dnslabel/UnderscoreLabel.java
Normal 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) == '_';
|
||||
}
|
||||
}
|
48
core/java/src/org/minidns/dnslabel/XnLabel.java
Normal file
48
core/java/src/org/minidns/dnslabel/XnLabel.java
Normal 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");
|
||||
}
|
||||
}
|
1279
core/java/src/org/minidns/dnsmessage/DnsMessage.java
Normal file
1279
core/java/src/org/minidns/dnsmessage/DnsMessage.java
Normal file
File diff suppressed because it is too large
Load Diff
180
core/java/src/org/minidns/dnsmessage/Question.java
Normal file
180
core/java/src/org/minidns/dnsmessage/Question.java
Normal 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();
|
||||
}
|
||||
}
|
606
core/java/src/org/minidns/dnsname/DnsName.java
Normal file
606
core/java/src/org/minidns/dnsname/DnsName.java
Normal 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(".");
|
||||
}
|
||||
}
|
@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
235
core/java/src/org/minidns/edns/Edns.java
Normal file
235
core/java/src/org/minidns/edns/Edns.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
82
core/java/src/org/minidns/edns/EdnsOption.java
Normal file
82
core/java/src/org/minidns/edns/EdnsOption.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
47
core/java/src/org/minidns/edns/Nsid.java
Normal file
47
core/java/src/org/minidns/edns/Nsid.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
37
core/java/src/org/minidns/edns/UnknownEdnsOption.java
Normal file
37
core/java/src/org/minidns/edns/UnknownEdnsOption.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
36
core/java/src/org/minidns/idna/DefaultIdnaTransformator.java
Normal file
36
core/java/src/org/minidns/idna/DefaultIdnaTransformator.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
19
core/java/src/org/minidns/idna/IdnaTransformator.java
Normal file
19
core/java/src/org/minidns/idna/IdnaTransformator.java
Normal 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);
|
||||
|
||||
}
|
31
core/java/src/org/minidns/idna/MiniDnsIdna.java
Normal file
31
core/java/src/org/minidns/idna/MiniDnsIdna.java
Normal 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;
|
||||
}
|
||||
}
|
16
core/java/src/org/minidns/package.html
Normal file
16
core/java/src/org/minidns/package.html
Normal 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>
|
68
core/java/src/org/minidns/record/A.java
Normal file
68
core/java/src/org/minidns/record/A.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
67
core/java/src/org/minidns/record/AAAA.java
Normal file
67
core/java/src/org/minidns/record/AAAA.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
42
core/java/src/org/minidns/record/CNAME.java
Normal file
42
core/java/src/org/minidns/record/CNAME.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
43
core/java/src/org/minidns/record/DLV.java
Normal file
43
core/java/src/org/minidns/record/DLV.java
Normal 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;
|
||||
}
|
||||
}
|
44
core/java/src/org/minidns/record/DNAME.java
Normal file
44
core/java/src/org/minidns/record/DNAME.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
194
core/java/src/org/minidns/record/DNSKEY.java
Normal file
194
core/java/src/org/minidns/record/DNSKEY.java
Normal 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;
|
||||
}
|
||||
}
|
48
core/java/src/org/minidns/record/DS.java
Normal file
48
core/java/src/org/minidns/record/DS.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
107
core/java/src/org/minidns/record/Data.java
Normal file
107
core/java/src/org/minidns/record/Data.java
Normal 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);
|
||||
}
|
||||
}
|
156
core/java/src/org/minidns/record/DelegatingDnssecRR.java
Normal file
156
core/java/src/org/minidns/record/DelegatingDnssecRR.java
Normal 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);
|
||||
}
|
||||
}
|
78
core/java/src/org/minidns/record/InternetAddressRR.java
Normal file
78
core/java/src/org/minidns/record/InternetAddressRR.java
Normal 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);
|
||||
}
|
||||
}
|
76
core/java/src/org/minidns/record/MX.java
Normal file
76
core/java/src/org/minidns/record/MX.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
38
core/java/src/org/minidns/record/NS.java
Normal file
38
core/java/src/org/minidns/record/NS.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
161
core/java/src/org/minidns/record/NSEC.java
Normal file
161
core/java/src/org/minidns/record/NSEC.java
Normal 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;
|
||||
}
|
||||
}
|
211
core/java/src/org/minidns/record/NSEC3.java
Normal file
211
core/java/src/org/minidns/record/NSEC3.java
Normal 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);
|
||||
}
|
||||
}
|
101
core/java/src/org/minidns/record/NSEC3PARAM.java
Normal file
101
core/java/src/org/minidns/record/NSEC3PARAM.java
Normal 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;
|
||||
}
|
||||
}
|
60
core/java/src/org/minidns/record/OPENPGPKEY.java
Normal file
60
core/java/src/org/minidns/record/OPENPGPKEY.java
Normal 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();
|
||||
}
|
||||
}
|
72
core/java/src/org/minidns/record/OPT.java
Normal file
72
core/java/src/org/minidns/record/OPT.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
core/java/src/org/minidns/record/PTR.java
Normal file
42
core/java/src/org/minidns/record/PTR.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
197
core/java/src/org/minidns/record/RRSIG.java
Normal file
197
core/java/src/org/minidns/record/RRSIG.java
Normal 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();
|
||||
}
|
||||
}
|
50
core/java/src/org/minidns/record/RRWithTarget.java
Normal file
50
core/java/src/org/minidns/record/RRWithTarget.java
Normal 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;
|
||||
}
|
||||
}
|
628
core/java/src/org/minidns/record/Record.java
Normal file
628
core/java/src/org/minidns/record/Record.java
Normal 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;
|
||||
}
|
||||
}
|
116
core/java/src/org/minidns/record/SOA.java
Normal file
116
core/java/src/org/minidns/record/SOA.java
Normal 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();
|
||||
}
|
||||
}
|
114
core/java/src/org/minidns/record/SRV.java
Normal file
114
core/java/src/org/minidns/record/SRV.java
Normal 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;
|
||||
}
|
||||
}
|
158
core/java/src/org/minidns/record/TLSA.java
Normal file
158
core/java/src/org/minidns/record/TLSA.java
Normal 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);
|
||||
}
|
||||
}
|
111
core/java/src/org/minidns/record/TXT.java
Normal file
111
core/java/src/org/minidns/record/TXT.java
Normal 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() + "\"";
|
||||
}
|
||||
|
||||
}
|
45
core/java/src/org/minidns/record/UNKNOWN.java
Normal file
45
core/java/src/org/minidns/record/UNKNOWN.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
41
core/java/src/org/minidns/util/Base32.java
Normal file
41
core/java/src/org/minidns/util/Base32.java
Normal 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);
|
||||
}
|
||||
}
|
38
core/java/src/org/minidns/util/Base64.java
Normal file
38
core/java/src/org/minidns/util/Base64.java
Normal 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);
|
||||
}
|
||||
}
|
22
core/java/src/org/minidns/util/Hex.java
Normal file
22
core/java/src/org/minidns/util/Hex.java
Normal 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;
|
||||
}
|
||||
}
|
116
core/java/src/org/minidns/util/InetAddressUtil.java
Normal file
116
core/java/src/org/minidns/util/InetAddressUtil.java
Normal 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user