From 2a66a70a4f7bb46efb1fbf53c3d79b99d80d6590 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 6 Nov 2017 16:55:24 +0100 Subject: [PATCH] Added xbill derrived DNS - WIP --- src/dorkbox/network/dns/Compression.java | 85 ++ src/dorkbox/network/dns/DnsInput.java | 237 +++ src/dorkbox/network/dns/DnsOutput.java | 221 +++ src/dorkbox/network/dns/Mnemonic.java | 222 +++ src/dorkbox/network/dns/Name.java | 989 +++++++++++++ .../network/dns/constants/DnsClass.java | 132 ++ .../network/dns/constants/DnsOpCode.java | 73 + .../network/dns/constants/DnsRecordType.java | 565 ++++++++ .../dns/constants/DnsResponseCode.java | 180 +++ .../network/dns/constants/DnsSection.java | 115 ++ src/dorkbox/network/dns/constants/Flags.java | 105 ++ .../dns/decoder/MailExchangerDecoder.java | 34 - .../network/dns/decoder/RecordDecoder.java | 26 - .../network/dns/decoder/ServiceDecoder.java | 36 - .../dns/decoder/StartOfAuthorityDecoder.java | 40 - .../network/dns/decoder/TextDecoder.java | 42 - .../exceptions/InvalidDClassException.java | 19 + .../dns/exceptions/InvalidTTLException.java | 19 + .../dns/exceptions/InvalidTypeException.java | 19 + .../dns/exceptions/NameTooLongException.java | 25 + .../dns/exceptions/RelativeNameException.java | 27 + .../dns/exceptions/TextParseException.java | 26 + .../dns/exceptions/WireParseException.java | 32 + .../dns/exceptions/ZoneTransferException.java | 24 + .../network/dns/handlers/DnsHandler.java | 17 + .../dns/handlers/DnsMessageDecoder.java | 46 + .../dns/handlers/DnsMessageEncoder.java | 76 + .../dns/handlers/DnsServerHandler.java | 56 + .../dns/handlers/ForwardingHandler.java | 156 ++ .../dns/record/MailExchangerRecord.java | 51 - .../network/dns/record/ServiceRecord.java | 100 -- .../dns/record/StartOfAuthorityRecord.java | 129 -- src/dorkbox/network/dns/records/A6Record.java | 147 ++ .../network/dns/records/AAAARecord.java | 108 ++ .../network/dns/records/AFSDBRecord.java | 53 + .../network/dns/records/APLRecord.java | 305 ++++ src/dorkbox/network/dns/records/ARecord.java | 106 ++ .../network/dns/records/CAARecord.java | 122 ++ .../network/dns/records/CERTRecord.java | 257 ++++ .../network/dns/records/CNAMERecord.java | 52 + .../dns/records/ClientSubnetOption.java | 187 +++ .../network/dns/records/DHCIDRecord.java | 74 + .../network/dns/records/DLVRecord.java | 136 ++ .../network/dns/records/DNAMERecord.java | 52 + .../network/dns/records/DNSKEYRecord.java | 107 ++ src/dorkbox/network/dns/records/DNSSEC.java | 1278 +++++++++++++++++ src/dorkbox/network/dns/records/DSRecord.java | 170 +++ .../network/dns/records/DnsMessage.java | 1025 +++++++++++++ .../network/dns/records/DnsRecord.java | 823 +++++++++++ .../dns/records/DnsTypeProtoAssignment.java | 78 + .../network/dns/records/EDNSOption.java | 236 +++ .../network/dns/records/EmptyRecord.java | 47 + .../network/dns/records/ExtendedFlags.java | 51 + .../network/dns/records/GPOSRecord.java | 191 +++ .../dns/records/GenericEDNSOption.java | 51 + .../network/dns/records/HINFORecord.java | 102 ++ src/dorkbox/network/dns/records/Header.java | 343 +++++ .../network/dns/records/IPSECKEYRecord.java | 253 ++++ .../network/dns/records/ISDNRecord.java | 118 ++ src/dorkbox/network/dns/records/KEYBase.java | 175 +++ .../network/dns/records/KEYRecord.java | 421 ++++++ src/dorkbox/network/dns/records/KXRecord.java | 60 + .../network/dns/records/LOCRecord.java | 349 +++++ src/dorkbox/network/dns/records/MBRecord.java | 50 + src/dorkbox/network/dns/records/MDRecord.java | 51 + src/dorkbox/network/dns/records/MFRecord.java | 51 + src/dorkbox/network/dns/records/MGRecord.java | 45 + .../network/dns/records/MINFORecord.java | 98 ++ src/dorkbox/network/dns/records/MRRecord.java | 45 + src/dorkbox/network/dns/records/MXRecord.java | 68 + .../network/dns/records/NAPTRRecord.java | 174 +++ .../network/dns/records/NSAPRecord.java | 120 ++ .../network/dns/records/NSAP_PTRRecord.java | 45 + .../network/dns/records/NSEC3PARAMRecord.java | 188 +++ .../network/dns/records/NSEC3Record.java | 301 ++++ .../network/dns/records/NSECRecord.java | 113 ++ .../network/dns/records/NSIDOption.java | 30 + src/dorkbox/network/dns/records/NSRecord.java | 50 + .../network/dns/records/NULLRecord.java | 78 + .../network/dns/records/NXTRecord.java | 129 ++ .../network/dns/records/OPENPGPKEYRecord.java | 88 ++ .../network/dns/records/OPTRecord.java | 232 +++ .../network/dns/records/PTRRecord.java | 45 + src/dorkbox/network/dns/records/PXRecord.java | 109 ++ src/dorkbox/network/dns/records/RPRecord.java | 95 ++ .../network/dns/records/RRSIGRecord.java | 61 + src/dorkbox/network/dns/records/RRset.java | 308 ++++ src/dorkbox/network/dns/records/RTRecord.java | 54 + src/dorkbox/network/dns/records/SIG0.java | 84 ++ src/dorkbox/network/dns/records/SIGBase.java | 229 +++ .../network/dns/records/SIGRecord.java | 61 + .../network/dns/records/SMIMEARecord.java | 178 +++ .../network/dns/records/SOARecord.java | 186 +++ .../network/dns/records/SPFRecord.java | 52 + .../network/dns/records/SRVRecord.java | 130 ++ .../network/dns/records/SSHFPRecord.java | 123 ++ .../dns/records/SingleCompressedNameBase.java | 34 + .../network/dns/records/SingleNameBase.java | 66 + .../network/dns/records/TKEYRecord.java | 285 ++++ .../network/dns/records/TLSARecord.java | 176 +++ src/dorkbox/network/dns/records/TSIG.java | 780 ++++++++++ .../network/dns/records/TSIGRecord.java | 264 ++++ src/dorkbox/network/dns/records/TTL.java | 146 ++ src/dorkbox/network/dns/records/TXTBase.java | 138 ++ .../network/dns/records/TXTRecord.java | 52 + .../network/dns/records/TypeBitmap.java | 166 +++ .../network/dns/records/U16NameBase.java | 78 + .../network/dns/records/UNKRecord.java | 65 + .../network/dns/records/URIRecord.java | 120 ++ src/dorkbox/network/dns/records/Update.java | 322 +++++ .../network/dns/records/WKSRecord.java | 861 +++++++++++ .../network/dns/records/X25Record.java | 96 ++ src/dorkbox/network/dns/utils/Address.java | 501 +++++++ .../network/dns/utils/FormattedTime.java | 90 ++ src/dorkbox/network/dns/utils/Options.java | 143 ++ src/dorkbox/network/dns/utils/ReverseMap.java | 153 ++ src/dorkbox/network/dns/utils/Tokenizer.java | 888 ++++++++++++ src/dorkbox/network/dns/utils/base16.java | 80 ++ src/dorkbox/network/dns/utils/base32.java | 229 +++ ...NoopDnsQueryLifecycleObserverFactory.java} | 26 +- 120 files changed, 20361 insertions(+), 470 deletions(-) create mode 100644 src/dorkbox/network/dns/Compression.java create mode 100644 src/dorkbox/network/dns/DnsInput.java create mode 100644 src/dorkbox/network/dns/DnsOutput.java create mode 100644 src/dorkbox/network/dns/Mnemonic.java create mode 100644 src/dorkbox/network/dns/Name.java create mode 100644 src/dorkbox/network/dns/constants/DnsClass.java create mode 100644 src/dorkbox/network/dns/constants/DnsOpCode.java create mode 100644 src/dorkbox/network/dns/constants/DnsRecordType.java create mode 100644 src/dorkbox/network/dns/constants/DnsResponseCode.java create mode 100644 src/dorkbox/network/dns/constants/DnsSection.java create mode 100644 src/dorkbox/network/dns/constants/Flags.java delete mode 100644 src/dorkbox/network/dns/decoder/MailExchangerDecoder.java delete mode 100644 src/dorkbox/network/dns/decoder/RecordDecoder.java delete mode 100644 src/dorkbox/network/dns/decoder/ServiceDecoder.java delete mode 100644 src/dorkbox/network/dns/decoder/StartOfAuthorityDecoder.java delete mode 100644 src/dorkbox/network/dns/decoder/TextDecoder.java create mode 100644 src/dorkbox/network/dns/exceptions/InvalidDClassException.java create mode 100644 src/dorkbox/network/dns/exceptions/InvalidTTLException.java create mode 100644 src/dorkbox/network/dns/exceptions/InvalidTypeException.java create mode 100644 src/dorkbox/network/dns/exceptions/NameTooLongException.java create mode 100644 src/dorkbox/network/dns/exceptions/RelativeNameException.java create mode 100644 src/dorkbox/network/dns/exceptions/TextParseException.java create mode 100644 src/dorkbox/network/dns/exceptions/WireParseException.java create mode 100644 src/dorkbox/network/dns/exceptions/ZoneTransferException.java create mode 100644 src/dorkbox/network/dns/handlers/DnsHandler.java create mode 100644 src/dorkbox/network/dns/handlers/DnsMessageDecoder.java create mode 100644 src/dorkbox/network/dns/handlers/DnsMessageEncoder.java create mode 100644 src/dorkbox/network/dns/handlers/DnsServerHandler.java create mode 100644 src/dorkbox/network/dns/handlers/ForwardingHandler.java delete mode 100644 src/dorkbox/network/dns/record/MailExchangerRecord.java delete mode 100644 src/dorkbox/network/dns/record/ServiceRecord.java delete mode 100644 src/dorkbox/network/dns/record/StartOfAuthorityRecord.java create mode 100644 src/dorkbox/network/dns/records/A6Record.java create mode 100644 src/dorkbox/network/dns/records/AAAARecord.java create mode 100644 src/dorkbox/network/dns/records/AFSDBRecord.java create mode 100644 src/dorkbox/network/dns/records/APLRecord.java create mode 100644 src/dorkbox/network/dns/records/ARecord.java create mode 100644 src/dorkbox/network/dns/records/CAARecord.java create mode 100644 src/dorkbox/network/dns/records/CERTRecord.java create mode 100644 src/dorkbox/network/dns/records/CNAMERecord.java create mode 100644 src/dorkbox/network/dns/records/ClientSubnetOption.java create mode 100644 src/dorkbox/network/dns/records/DHCIDRecord.java create mode 100644 src/dorkbox/network/dns/records/DLVRecord.java create mode 100644 src/dorkbox/network/dns/records/DNAMERecord.java create mode 100644 src/dorkbox/network/dns/records/DNSKEYRecord.java create mode 100644 src/dorkbox/network/dns/records/DNSSEC.java create mode 100644 src/dorkbox/network/dns/records/DSRecord.java create mode 100644 src/dorkbox/network/dns/records/DnsMessage.java create mode 100644 src/dorkbox/network/dns/records/DnsRecord.java create mode 100644 src/dorkbox/network/dns/records/DnsTypeProtoAssignment.java create mode 100644 src/dorkbox/network/dns/records/EDNSOption.java create mode 100644 src/dorkbox/network/dns/records/EmptyRecord.java create mode 100644 src/dorkbox/network/dns/records/ExtendedFlags.java create mode 100644 src/dorkbox/network/dns/records/GPOSRecord.java create mode 100644 src/dorkbox/network/dns/records/GenericEDNSOption.java create mode 100644 src/dorkbox/network/dns/records/HINFORecord.java create mode 100644 src/dorkbox/network/dns/records/Header.java create mode 100644 src/dorkbox/network/dns/records/IPSECKEYRecord.java create mode 100644 src/dorkbox/network/dns/records/ISDNRecord.java create mode 100644 src/dorkbox/network/dns/records/KEYBase.java create mode 100644 src/dorkbox/network/dns/records/KEYRecord.java create mode 100644 src/dorkbox/network/dns/records/KXRecord.java create mode 100644 src/dorkbox/network/dns/records/LOCRecord.java create mode 100644 src/dorkbox/network/dns/records/MBRecord.java create mode 100644 src/dorkbox/network/dns/records/MDRecord.java create mode 100644 src/dorkbox/network/dns/records/MFRecord.java create mode 100644 src/dorkbox/network/dns/records/MGRecord.java create mode 100644 src/dorkbox/network/dns/records/MINFORecord.java create mode 100644 src/dorkbox/network/dns/records/MRRecord.java create mode 100644 src/dorkbox/network/dns/records/MXRecord.java create mode 100644 src/dorkbox/network/dns/records/NAPTRRecord.java create mode 100644 src/dorkbox/network/dns/records/NSAPRecord.java create mode 100644 src/dorkbox/network/dns/records/NSAP_PTRRecord.java create mode 100644 src/dorkbox/network/dns/records/NSEC3PARAMRecord.java create mode 100644 src/dorkbox/network/dns/records/NSEC3Record.java create mode 100644 src/dorkbox/network/dns/records/NSECRecord.java create mode 100644 src/dorkbox/network/dns/records/NSIDOption.java create mode 100644 src/dorkbox/network/dns/records/NSRecord.java create mode 100644 src/dorkbox/network/dns/records/NULLRecord.java create mode 100644 src/dorkbox/network/dns/records/NXTRecord.java create mode 100644 src/dorkbox/network/dns/records/OPENPGPKEYRecord.java create mode 100644 src/dorkbox/network/dns/records/OPTRecord.java create mode 100644 src/dorkbox/network/dns/records/PTRRecord.java create mode 100644 src/dorkbox/network/dns/records/PXRecord.java create mode 100644 src/dorkbox/network/dns/records/RPRecord.java create mode 100644 src/dorkbox/network/dns/records/RRSIGRecord.java create mode 100644 src/dorkbox/network/dns/records/RRset.java create mode 100644 src/dorkbox/network/dns/records/RTRecord.java create mode 100644 src/dorkbox/network/dns/records/SIG0.java create mode 100644 src/dorkbox/network/dns/records/SIGBase.java create mode 100644 src/dorkbox/network/dns/records/SIGRecord.java create mode 100644 src/dorkbox/network/dns/records/SMIMEARecord.java create mode 100644 src/dorkbox/network/dns/records/SOARecord.java create mode 100644 src/dorkbox/network/dns/records/SPFRecord.java create mode 100644 src/dorkbox/network/dns/records/SRVRecord.java create mode 100644 src/dorkbox/network/dns/records/SSHFPRecord.java create mode 100644 src/dorkbox/network/dns/records/SingleCompressedNameBase.java create mode 100644 src/dorkbox/network/dns/records/SingleNameBase.java create mode 100644 src/dorkbox/network/dns/records/TKEYRecord.java create mode 100644 src/dorkbox/network/dns/records/TLSARecord.java create mode 100644 src/dorkbox/network/dns/records/TSIG.java create mode 100644 src/dorkbox/network/dns/records/TSIGRecord.java create mode 100644 src/dorkbox/network/dns/records/TTL.java create mode 100644 src/dorkbox/network/dns/records/TXTBase.java create mode 100644 src/dorkbox/network/dns/records/TXTRecord.java create mode 100644 src/dorkbox/network/dns/records/TypeBitmap.java create mode 100644 src/dorkbox/network/dns/records/U16NameBase.java create mode 100644 src/dorkbox/network/dns/records/UNKRecord.java create mode 100644 src/dorkbox/network/dns/records/URIRecord.java create mode 100644 src/dorkbox/network/dns/records/Update.java create mode 100644 src/dorkbox/network/dns/records/WKSRecord.java create mode 100644 src/dorkbox/network/dns/records/X25Record.java create mode 100644 src/dorkbox/network/dns/utils/Address.java create mode 100644 src/dorkbox/network/dns/utils/FormattedTime.java create mode 100644 src/dorkbox/network/dns/utils/Options.java create mode 100644 src/dorkbox/network/dns/utils/ReverseMap.java create mode 100644 src/dorkbox/network/dns/utils/Tokenizer.java create mode 100644 src/dorkbox/network/dns/utils/base16.java create mode 100644 src/dorkbox/network/dns/utils/base32.java rename src/{dorkbox/network/dns/decoder/DomainDecoder.java => io/nettyxbill/resolver/dns/NoopDnsQueryLifecycleObserverFactory.java} (51%) diff --git a/src/dorkbox/network/dns/Compression.java b/src/dorkbox/network/dns/Compression.java new file mode 100644 index 00000000..a396181a --- /dev/null +++ b/src/dorkbox/network/dns/Compression.java @@ -0,0 +1,85 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns; + +import dorkbox.network.dns.records.DnsMessage; +import dorkbox.network.dns.utils.Options; + +/** + * DNS Name Compression object. + * + * @author Brian Wellington + * @see DnsMessage + * @see Name + */ + +public +class Compression { + + private static final int TABLE_SIZE = 17; + private static final int MAX_POINTER = 0x3FFF; + private Entry[] table; + private boolean verbose = Options.check("verbosecompression"); + + + private static + class Entry { + Name name; + int pos; + Entry next; + } + + /** + * Creates a new Compression object. + */ + public + Compression() { + table = new Entry[TABLE_SIZE]; + } + + /** + * Adds a compression entry mapping a name to a position in a message. + * + * @param pos The position at which the name is added. + * @param name The name being added to the message. + */ + public + void add(int pos, Name name) { + if (pos > MAX_POINTER) { + return; + } + int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; + Entry entry = new Entry(); + entry.name = name; + entry.pos = pos; + entry.next = table[row]; + table[row] = entry; + if (verbose) { + System.err.println("Adding " + name + " at " + pos); + } + } + + /** + * Retrieves the position of the given name, if it has been previously + * included in the message. + * + * @param name The name to find in the compression table. + * + * @return The position of the name, or -1 if not found. + */ + public + int get(Name name) { + int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; + int pos = -1; + for (Entry entry = table[row]; entry != null; entry = entry.next) { + if (entry.name.equals(name)) { + pos = entry.pos; + } + } + if (verbose) { + System.err.println("Looking for " + name + ", found " + pos); + } + return pos; + } + +} diff --git a/src/dorkbox/network/dns/DnsInput.java b/src/dorkbox/network/dns/DnsInput.java new file mode 100644 index 00000000..6139dbfa --- /dev/null +++ b/src/dorkbox/network/dns/DnsInput.java @@ -0,0 +1,237 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns; + +import dorkbox.network.dns.exceptions.WireParseException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * An class for parsing DNS messages. + * + * @author Brian Wellington + */ + +public +class DnsInput { + + private ByteBuf byteBuf; + private int savedActiveIndex = -1; + private boolean marked = false; + + /** + * Creates a new DnsInput + * + * @param input The byte array to read from + */ + public + DnsInput(byte[] input) { + byteBuf = Unpooled.wrappedBuffer(input); + } + + /** + * Creates a new DnsInput from the given {@link ByteBuf} + * + * @param byteBuf The ByteBuf + */ + public + DnsInput(ByteBuf byteBuf) { + this.byteBuf = byteBuf; + } + + /** + * Returns the current position, for reading only + */ + public + int readIndex() { + return byteBuf.readerIndex(); + } + + /** + * NOTE: "Active" restricts operations to a specific part of the buffer, defined by the current position + length. Operations that + * extend BEYOND this section are denied by virtue that we set the max readable length in the underlying ByteBuf. + * + * Marks the following bytes in the stream as active, and saves it's state (so it can be restored later) + * + * @param len The number of bytes in the active region. + * + * @throws IllegalArgumentException The number of bytes in the active region + * is longer than the remainder of the input. + */ + public + void setActive(int len) { + savedActiveIndex = byteBuf.writerIndex(); + + if (len > byteBuf.readableBytes()) { + throw new IllegalArgumentException("cannot set active " + "region past end of input"); + } + + byteBuf.writerIndex(byteBuf.readerIndex() + len); + } + + /** + * Restores the previously set active region. + */ + public + void restoreActive() { + if (savedActiveIndex > -1) { + byteBuf.writerIndex(savedActiveIndex); + savedActiveIndex = -1; + } + } + + /** + * Resets the current position of the input stream to the specified index, + * and clears the active region. + * + * @param index The position to continue parsing at. + * + * @throws IllegalArgumentException The index is not within the input. + */ + public + void jump(int index) { + if (index >= byteBuf.capacity()) { + throw new IllegalArgumentException("cannot jump past " + "end of input"); + } + byteBuf.readerIndex(index); + + restoreActive(); + } + + /** + * Saves the current state of the input stream. Both the current position and + * the end of the active region are saved. + * + * @throws IllegalArgumentException The index is not within the input. + */ + public + void save() { + marked = true; + byteBuf.markReaderIndex(); + } + + /** + * Restores the input stream to its state before the call to {@link #save}. + */ + public + void restore() { + if (!marked) { + throw new IllegalStateException("Not marked first"); + } + byteBuf.resetReaderIndex(); + } + + private + void require(int n) throws WireParseException { + if (n > remaining()) { + throw new WireParseException("end of input"); + } + } + + /** + * Returns the number of bytes that can be read from this stream before + * reaching the end. + */ + public + int remaining() { + return byteBuf.readableBytes(); + } + + /** + * Reads an unsigned 8 bit value from the stream, as an int. + * + * @return An unsigned 8 bit value. + * + * @throws WireParseException The end of the stream was reached. + */ + public + int readU8() throws WireParseException { + require(1); + return byteBuf.readUnsignedByte(); + } + + /** + * Reads an unsigned 16 bit value from the stream, as an int. + * + * @return An unsigned 16 bit value. + * + * @throws WireParseException The end of the stream was reached. + */ + public + int readU16() throws WireParseException { + require(2); + return byteBuf.readUnsignedShort(); + } + + /** + * Reads an unsigned 32 bit value from the stream, as a long. + * + * @return An unsigned 32 bit value. + * + * @throws WireParseException The end of the stream was reached. + */ + public + long readU32() throws WireParseException { + require(4); + return byteBuf.readUnsignedInt(); + } + + /** + * Reads a byte array of a specified length from the stream into an existing + * array. + * + * @param b The array to read into. + * @param off The offset of the array to start copying data into. + * @param len The number of bytes to copy. + * + * @throws WireParseException The end of the stream was reached. + */ + public + void readByteArray(byte[] b, int off, int len) throws WireParseException { + require(len); + byteBuf.readBytes(b, off, len); + } + + /** + * Reads a byte array of a specified length from the stream. + * + * @return The byte array. + * + * @throws WireParseException The end of the stream was reached. + */ + public + byte[] readByteArray(int len) throws WireParseException { + require(len); + byte[] out = new byte[len]; + byteBuf.readBytes(out, 0, len); + return out; + } + + /** + * Reads a byte array consisting of the remainder of the stream (or the + * active region, if one is set. + * + * @return The byte array. + */ + public + byte[] readByteArray() { + int len = remaining(); + byte[] out = new byte[len]; + byteBuf.readBytes(out, 0, len); + return out; + } + + /** + * Reads a counted string from the stream. A counted string is a one byte + * value indicating string length, followed by bytes of data. + * + * @return A byte array containing the string. + * + * @throws WireParseException The end of the stream was reached. + */ + public + byte[] readCountedString() throws WireParseException { + int len = readU8(); + return readByteArray(len); + } +} diff --git a/src/dorkbox/network/dns/DnsOutput.java b/src/dorkbox/network/dns/DnsOutput.java new file mode 100644 index 00000000..08cb1265 --- /dev/null +++ b/src/dorkbox/network/dns/DnsOutput.java @@ -0,0 +1,221 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * A class for rendering DNS messages. + * + * @author Brian Wellington + */ + + +public +class DnsOutput { + + private ByteBuf byteBuf; + private boolean marked = false; + + /** + * Create a new DnsOutput + */ + public + DnsOutput() { + this(32); + } + + /** + * Create a new DnsOutput with a specified size. + * + * @param size The initial size + */ + public + DnsOutput(int size) { + this(Unpooled.buffer(size)); + } + + /** + * Create a new DnsOutput with a specified ByteBuf. + * + * @param byteBuf The ByteBuf to use + */ + public + DnsOutput(ByteBuf byteBuf) { + this.byteBuf = byteBuf; + } + + /** + * Returns the current position. + */ + public + int current() { + return byteBuf.writerIndex(); + } + + /** + * Resets the current position of the output stream to the specified index. + * + * @param index The new current position. + * + * @throws IllegalArgumentException The index is not within the output. + */ + public + void jump(int index) { + if (index >= byteBuf.writerIndex()) { + // we haven't written data to this point yet, and the contract for jump() is that it can only jump to a PREVIOUSLY written spot + throw new IllegalArgumentException("Unable to jump to invalid position " + index + ". Max is " + byteBuf.writerIndex()); + } + + byteBuf.writerIndex(index); + } + + /** + * Saves the current state of the output stream. + * + * @throws IllegalArgumentException The index is not within the output. + */ + public + void save() { + marked = true; + byteBuf.markWriterIndex(); + } + + /** + * Restores the input stream to its state before the call to {@link #save}. + */ + public + void restore() { + if (!marked) { + throw new IllegalStateException("Not marked first"); + } + byteBuf.resetWriterIndex(); + marked = false; + } + + /** + * Writes an unsigned 8 bit value to the stream. + * + * @param val The value to be written + */ + public + void writeU8(int val) { + check(val, 8); + + byteBuf.ensureWritable(1); + byteBuf.writeByte(val); + } + + private + void check(long val, int bits) { + long max = 1; + max <<= bits; + if (val < 0 || val > max) { + throw new IllegalArgumentException(val + " out of range for " + bits + " bit value"); + } + } + + /** + * Writes an unsigned 16 bit value to the stream. + * + * @param val The value to be written + */ + public + void writeU16(int val) { + check(val, 16); + + byteBuf.ensureWritable(2); + byteBuf.writeShort(val); + } + + /** + * Writes an unsigned 16 bit value to the specified position in the stream. + * + * @param val The value to be written + * @param where The position to write the value. + */ + public + void writeU16At(int val, int where) { + check(val, 16); + + // save and set both the read/write index, otherwise if the read index is > write index, errors happen. + int saved = byteBuf.writerIndex(); + int readSaved = byteBuf.readerIndex(); + + byteBuf.setIndex(where, where); + byteBuf.ensureWritable(2); + byteBuf.writeShort(val); + + byteBuf.writerIndex(saved); + byteBuf.readerIndex(readSaved); + } + + /** + * Writes an unsigned 32 bit value to the stream. + * + * @param val The value to be written + */ + public + void writeU32(long val) { + check(val, 32); + + byteBuf.ensureWritable(4); + byteBuf.writeInt((int) val); + } + + /** + * Writes a byte array to the stream. + * + * @param b The array to write. + */ + public + void writeByteArray(byte[] b) { + writeByteArray(b, 0, b.length); + } + + /** + * Writes a byte array to the stream. + * + * @param b The array to write. + * @param off The offset of the array to start copying data from. + * @param len The number of bytes to write. + */ + public + void writeByteArray(byte[] b, int off, int len) { + byteBuf.ensureWritable(len); + byteBuf.writeBytes(b, off, len); + } + + /** + * Writes a counted string from the stream. A counted string is a one byte + * value indicating string length, followed by bytes of data. + * + * @param s The string to write. + */ + public + void writeCountedString(byte[] s) { + if (s.length > 0xFF) { + throw new IllegalArgumentException("Invalid counted string"); + } + + byteBuf.ensureWritable(1 + s.length); + byteBuf.writeByte(s.length); + byteBuf.writeBytes(s, 0, s.length); + } + + /** + * Returns a byte array containing the current contents of the stream. + */ + public + byte[] toByteArray() { + byte[] out = new byte[byteBuf.writerIndex()]; + byteBuf.readBytes(out, 0, out.length); + return out; + } + + public + ByteBuf getByteBuf() { + return byteBuf; + } +} diff --git a/src/dorkbox/network/dns/Mnemonic.java b/src/dorkbox/network/dns/Mnemonic.java new file mode 100644 index 00000000..33c0587d --- /dev/null +++ b/src/dorkbox/network/dns/Mnemonic.java @@ -0,0 +1,222 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns; + +import java.util.HashMap; + +/** + * A utility class for converting between numeric codes and mnemonics + * for those codes. Mnemonics are case insensitive. + * + * @author Brian Wellington + */ +public +class Mnemonic { + + private static Integer cachedInts[] = new Integer[64]; + /* Strings are case-sensitive. */ + public static final int CASE_SENSITIVE = 1; + /* Strings will be stored/searched for in uppercase. */ + public static final int CASE_UPPER = 2; + /* Strings will be stored/searched for in lowercase. */ + public static final int CASE_LOWER = 3; + + + private HashMap strings; + private HashMap values; + private String description; + private int wordcase; + private String prefix; + private int max; + private boolean numericok; + + static { + for (int i = 0; i < cachedInts.length; i++) { + cachedInts[i] = new Integer(i); + } + } + + /** + * Creates a new Mnemonic table. + * + * @param description A short description of the mnemonic to use when + * @param wordcase Whether to convert strings into uppercase, lowercase, + * or leave them unchanged. + * throwing exceptions. + */ + public + Mnemonic(String description, int wordcase) { + this.description = description; + this.wordcase = wordcase; + strings = new HashMap(); + values = new HashMap(); + max = Integer.MAX_VALUE; + } + + /** + * Sets the maximum numeric value + */ + public + void setMaximum(int max) { + this.max = max; + } + + /** + * Sets the prefix to use when converting to and from values that don't + * have mnemonics. + */ + public + void setPrefix(String prefix) { + this.prefix = sanitize(prefix); + } + + /* Converts a String to the correct case. */ + private + String sanitize(String str) { + if (wordcase == CASE_UPPER) { + return str.toUpperCase(); + } + else if (wordcase == CASE_LOWER) { + return str.toLowerCase(); + } + return str; + } + + /** + * Sets whether numeric values stored in strings are acceptable. + */ + public + void setNumericAllowed(boolean numeric) { + this.numericok = numeric; + } + + /** + * Defines the text representation of a numeric value. + * + * @param val The numeric value + * @param string The text string + */ + public + void add(int val, String string) { + check(val); + Integer value = toInteger(val); + string = sanitize(string); + strings.put(string, value); + values.put(value, string); + } + + /** + * Converts an int into a possibly cached Integer. + */ + public static + Integer toInteger(int val) { + if (val >= 0 && val < cachedInts.length) { + return (cachedInts[val]); + } + return new Integer(val); + } + + /** + * Checks that a numeric value is within the range [0..max] + */ + public + void check(int val) { + if (val < 0 || val > max) { + throw new IllegalArgumentException(description + " " + val + "is out of range"); + } + } + + /** + * Defines an additional text representation of a numeric value. This will + * be used by getValue(), but not getText(). + * + * @param val The numeric value + * @param string The text string + */ + public + void addAlias(int val, String string) { + check(val); + Integer value = toInteger(val); + string = sanitize(string); + strings.put(string, value); + } + + /** + * Copies all mnemonics from one table into another. + * + * @param source The Mnemonic source to add from + * + * @throws IllegalArgumentException The wordcases of the Mnemonics do not + * match. + */ + public + void addAll(Mnemonic source) { + if (wordcase != source.wordcase) { + throw new IllegalArgumentException(source.description + ": wordcases do not match"); + } + strings.putAll(source.strings); + values.putAll(source.values); + } + + /** + * Gets the text mnemonic corresponding to a numeric value. + * + * @param val The numeric value + * + * @return The corresponding text mnemonic. + */ + public + String getText(int val) { + check(val); + String str = (String) values.get(toInteger(val)); + if (str != null) { + return str; + } + str = Integer.toString(val); + if (prefix != null) { + return prefix + str; + } + return str; + } + + /** + * Gets the numeric value corresponding to a text mnemonic. + * + * @param str The text mnemonic + * + * @return The corresponding numeric value, or -1 if there is none + */ + public + int getValue(String str) { + str = sanitize(str); + Integer value = (Integer) strings.get(str); + if (value != null) { + return value.intValue(); + } + if (prefix != null) { + if (str.startsWith(prefix)) { + int val = parseNumeric(str.substring(prefix.length())); + if (val >= 0) { + return val; + } + } + } + if (numericok) { + return parseNumeric(str); + } + return -1; + } + + private + int parseNumeric(String s) { + try { + int val = Integer.parseInt(s); + if (val >= 0 && val <= max) { + return val; + } + } catch (NumberFormatException e) { + } + return -1; + } + +} diff --git a/src/dorkbox/network/dns/Name.java b/src/dorkbox/network/dns/Name.java new file mode 100644 index 00000000..6b888c37 --- /dev/null +++ b/src/dorkbox/network/dns/Name.java @@ -0,0 +1,989 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns; + +import java.io.IOException; +import java.io.Serializable; +import java.text.DecimalFormat; + +import dorkbox.network.dns.exceptions.NameTooLongException; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.records.DNAMERecord; +import dorkbox.network.dns.utils.Options; + +/** + * A representation of a domain name. It may either be absolute (fully + * qualified) or relative. + * + * @author Brian Wellington + */ + +public +class Name implements Comparable, Serializable { + + private static final long serialVersionUID = -7257019940971525644L; + + private static final int LABEL_NORMAL = 0; + private static final int LABEL_COMPRESSION = 0xC0; + private static final int LABEL_MASK = 0xC0; + + /* The name data */ + private byte[] name; + + /* + * Effectively an 8 byte array, where the low order byte stores the number + * of labels and the 7 higher order bytes store per-label offsets. + */ + private long offsets; + + /* Precomputed hashcode. */ + private int hashcode; + + private static final byte[] emptyLabel = new byte[] {(byte) 0}; + private static final byte[] wildLabel = new byte[] {(byte) 1, (byte) '*'}; + + /** + * The root name + */ + public static final Name root; + + /** + * The root name + */ + public static final Name empty; + + /** + * The maximum length of a Name + */ + private static final int MAXNAME = 255; + + /** + * The maximum length of a label a Name + */ + private static final int MAXLABEL = 63; + + /** + * The maximum number of labels in a Name + */ + private static final int MAXLABELS = 128; + + /** + * The maximum number of cached offsets + */ + private static final int MAXOFFSETS = 7; + + /* Used for printing non-printable characters */ + private static final DecimalFormat byteFormat = new DecimalFormat(); + + /* Used to efficiently convert bytes to lowercase */ + private static final byte lowercase[] = new byte[256]; + + /* Used in wildcard names. */ + private static final Name wild; + + static { + byteFormat.setMinimumIntegerDigits(3); + for (int i = 0; i < lowercase.length; i++) { + if (i < 'A' || i > 'Z') { + lowercase[i] = (byte) i; + } + else { + lowercase[i] = (byte) (i - 'A' + 'a'); + } + } + root = new Name(); + root.appendSafe(emptyLabel, 0, 1); + empty = new Name(); + empty.name = new byte[0]; + wild = new Name(); + wild.appendSafe(wildLabel, 0, 1); + } + + private + Name() { + } + + private + void setoffset(int n, int offset) { + if (n >= MAXOFFSETS) { + return; + } + int shift = 8 * (7 - n); + offsets &= (~(0xFFL << shift)); + offsets |= ((long) offset << shift); + } + + private + int offset(int n) { + if (n == 0 && getlabels() == 0) { + return 0; + } + if (n < 0 || n >= getlabels()) { + throw new IllegalArgumentException("label out of range"); + } + if (n < MAXOFFSETS) { + int shift = 8 * (7 - n); + return ((int) (offsets >>> shift) & 0xFF); + } + else { + int pos = offset(MAXOFFSETS - 1); + for (int i = MAXOFFSETS - 1; i < n; i++) { + pos += (name[pos] + 1); + } + return (pos); + } + } + + private + void setlabels(int labels) { + offsets &= ~(0xFF); + offsets |= labels; + } + + private static + void copy(Name src, Name dst) { + if (src.offset(0) == 0) { + dst.name = src.name; + dst.offsets = src.offsets; + } + else { + int offset0 = src.offset(0); + int namelen = src.name.length - offset0; + int labels = src.labels(); + dst.name = new byte[namelen]; + System.arraycopy(src.name, offset0, dst.name, 0, namelen); + for (int i = 0; i < labels && i < MAXOFFSETS; i++) { + dst.setoffset(i, src.offset(i) - offset0); + } + dst.setlabels(labels); + } + } + + private + void append(byte[] array, int start, int n) throws NameTooLongException { + int length = (name == null ? 0 : (name.length - offset(0))); + int alength = 0; + for (int i = 0, pos = start; i < n; i++) { + int len = array[pos]; + if (len > MAXLABEL) { + throw new IllegalStateException("invalid label"); + } + len++; + pos += len; + alength += len; + } + int newlength = length + alength; + if (newlength > MAXNAME) { + throw new NameTooLongException(); + } + int labels = getlabels(); + int newlabels = labels + n; + if (newlabels > MAXLABELS) { + throw new IllegalStateException("too many labels"); + } + byte[] newname = new byte[newlength]; + if (length != 0) { + System.arraycopy(name, offset(0), newname, 0, length); + } + System.arraycopy(array, start, newname, length, alength); + name = newname; + for (int i = 0, pos = length; i < n; i++) { + setoffset(labels + i, pos); + pos += (newname[pos] + 1); + } + setlabels(newlabels); + } + + private static + TextParseException parseException(String str, String message) { + return new TextParseException("'" + str + "': " + message); + } + + private + void appendFromString(String fullName, byte[] array, int start, int n) throws TextParseException { + try { + append(array, start, n); + } catch (NameTooLongException e) { + throw parseException(fullName, "Name too long"); + } + } + + private + void appendSafe(byte[] array, int start, int n) { + try { + append(array, start, n); + } catch (NameTooLongException e) { + } + } + + /** + * Create a new name from a string and an origin. This does not automatically + * make the name absolute; it will be absolute if it has a trailing dot or an + * absolute origin is appended. + * + * @param s The string to be converted + * @param origin If the name is not absolute, the origin to be appended. + * + * @throws TextParseException The name is invalid. + */ + public + Name(String s, Name origin) throws TextParseException { + if (s.equals("")) { + throw parseException(s, "empty name"); + } + else if (s.equals("@")) { + if (origin == null) { + copy(empty, this); + } + else { + copy(origin, this); + } + return; + } + else if (s.equals(".")) { + copy(root, this); + return; + } + int labelstart = -1; + int pos = 1; + byte[] label = new byte[MAXLABEL + 1]; + boolean escaped = false; + int digits = 0; + int intval = 0; + boolean absolute = false; + for (int i = 0; i < s.length(); i++) { + byte b = (byte) s.charAt(i); + if (escaped) { + if (b >= '0' && b <= '9' && digits < 3) { + digits++; + intval *= 10; + intval += (b - '0'); + if (intval > 255) { + throw parseException(s, "bad escape"); + } + if (digits < 3) { + continue; + } + b = (byte) intval; + } + else if (digits > 0 && digits < 3) { + throw parseException(s, "bad escape"); + } + if (pos > MAXLABEL) { + throw parseException(s, "label too long"); + } + labelstart = pos; + label[pos++] = b; + escaped = false; + } + else if (b == '\\') { + escaped = true; + digits = 0; + intval = 0; + } + else if (b == '.') { + if (labelstart == -1) { + throw parseException(s, "invalid empty label"); + } + label[0] = (byte) (pos - 1); + appendFromString(s, label, 0, 1); + labelstart = -1; + pos = 1; + } + else { + if (labelstart == -1) { + labelstart = i; + } + if (pos > MAXLABEL) { + throw parseException(s, "label too long"); + } + label[pos++] = b; + } + } + if (digits > 0 && digits < 3) { + throw parseException(s, "bad escape"); + } + if (escaped) { + throw parseException(s, "bad escape"); + } + if (labelstart == -1) { + appendFromString(s, emptyLabel, 0, 1); + absolute = true; + } + else { + label[0] = (byte) (pos - 1); + appendFromString(s, label, 0, 1); + } + if (origin != null && !absolute) { + appendFromString(s, origin.name, origin.offset(0), origin.getlabels()); + } + } + + /** + * Create a new name from a string. This does not automatically make the name + * absolute; it will be absolute if it has a trailing dot. + * + * @param s The string to be converted + * + * @throws TextParseException The name is invalid. + */ + public + Name(String s) throws TextParseException { + this(s, null); + } + + /** + * Create a new name from a string. This does not automatically make the name + * absolute; it will be absolute if it has a trailing dot. This is identical + * to the constructor, except that it will avoid creating new objects in some + * cases. + * + * @param s The string to be converted + * + * @throws TextParseException The name is invalid. + */ + public static + Name fromString(String s) throws TextParseException { + return fromString(s, null); + } + + /** + * Create a new name from a string and an origin. This does not automatically + * make the name absolute; it will be absolute if it has a trailing dot or an + * absolute origin is appended. This is identical to the constructor, except + * that it will avoid creating new objects in some cases. + * + * @param s The string to be converted + * @param origin If the name is not absolute, the origin to be appended. + * + * @throws TextParseException The name is invalid. + */ + public static + Name fromString(String s, Name origin) throws TextParseException { + if (s.equals("@") && origin != null) { + return origin; + } + else if (s.equals(".")) { + return (root); + } + + return new Name(s, origin); + } + + /** + * Create a new name from a constant string. This should only be used when + * the name is known to be good - that is, when it is constant. + * + * @param s The string to be converted + * + * @throws IllegalArgumentException The name is invalid. + */ + public static + Name fromConstantString(String s) { + try { + return fromString(s, null); + } catch (TextParseException e) { + throw new IllegalArgumentException("Invalid name '" + s + "'"); + } + } + + /** + * Create a new name from DNS a wire format message + * + * @param in A stream containing the DNS message which is currently + * positioned at the start of the name to be read. + */ + public + Name(DnsInput in) throws WireParseException { + int len, pos; + boolean done = false; + byte[] label = new byte[MAXLABEL + 1]; + boolean savedState = false; + + while (!done) { + len = in.readU8(); + switch (len & LABEL_MASK) { + case LABEL_NORMAL: + if (getlabels() >= MAXLABELS) { + throw new WireParseException("too many labels"); + } + if (len == 0) { + append(emptyLabel, 0, 1); + done = true; + } + else { + label[0] = (byte) len; + in.readByteArray(label, 1, len); + append(label, 0, 1); + } + break; + case LABEL_COMPRESSION: + pos = in.readU8(); + pos += ((len & ~LABEL_MASK) << 8); + if (Options.check("verbosecompression")) { + System.err.println("currently " + in.readIndex() + ", pointer to " + pos); + } + + if (pos >= in.readIndex() - 2) { + throw new WireParseException("bad compression"); + } + if (!savedState) { + in.save(); + savedState = true; + } + in.jump(pos); + if (Options.check("verbosecompression")) { + System.err.println("current name '" + this + "', seeking to " + pos); + } + break; + default: + throw new WireParseException("bad label type"); + } + } + if (savedState) { + in.restore(); + } + } + + /** + * Create a new name from DNS wire format + * + * @param b A byte array containing the wire format of the name. + */ + public + Name(byte[] b) throws IOException { + this(new DnsInput(b)); + } + + /** + * Create a new name by removing labels from the beginning of an existing Name + * + * @param src An existing Name + * @param n The number of labels to remove from the beginning in the copy + */ + public + Name(Name src, int n) { + int slabels = src.labels(); + if (n > slabels) { + throw new IllegalArgumentException("attempted to remove too " + "many labels"); + } + name = src.name; + setlabels(slabels - n); + for (int i = 0; i < MAXOFFSETS && i < slabels - n; i++) { + setoffset(i, src.offset(i + n)); + } + } + + /** + * Creates a new name by concatenating two existing names. + * + * @param prefix The prefix name. + * @param suffix The suffix name. + * + * @return The concatenated name. + * + * @throws NameTooLongException The name is too long. + */ + public static + Name concatenate(Name prefix, Name suffix) throws NameTooLongException { + if (prefix.isAbsolute()) { + return (prefix); + } + Name newname = new Name(); + copy(prefix, newname); + newname.append(suffix.name, suffix.offset(0), suffix.getlabels()); + return newname; + } + + /** + * If this name is a subdomain of origin, return a new name relative to + * origin with the same value. Otherwise, return the existing name. + * + * @param origin The origin to remove. + * + * @return The possibly relativized name. + */ + public + Name relativize(Name origin) { + if (origin == null || !subdomain(origin)) { + return this; + } + Name newname = new Name(); + copy(this, newname); + int length = length() - origin.length(); + int labels = newname.labels() - origin.labels(); + newname.setlabels(labels); + newname.name = new byte[length]; + System.arraycopy(name, offset(0), newname.name, 0, length); + return newname; + } + + /** + * Generates a new Name with the first n labels replaced by a wildcard + * + * @return The wildcard name + */ + public + Name wild(int n) { + if (n < 1) { + throw new IllegalArgumentException("must replace 1 or more " + "labels"); + } + try { + Name newname = new Name(); + copy(wild, newname); + newname.append(name, offset(n), getlabels() - n); + return newname; + } catch (NameTooLongException e) { + throw new IllegalStateException("Name.wild: concatenate failed"); + } + } + + /** + * Returns a canonicalized version of the Name (all lowercase). This may be + * the same name, if the input Name is already canonical. + */ + public + Name canonicalize() { + boolean canonical = true; + for (int i = 0; i < name.length; i++) { + if (lowercase[name[i] & 0xFF] != name[i]) { + canonical = false; + break; + } + } + if (canonical) { + return this; + } + + Name newname = new Name(); + newname.appendSafe(name, offset(0), getlabels()); + for (int i = 0; i < newname.name.length; i++) { + newname.name[i] = lowercase[newname.name[i] & 0xFF]; + } + + return newname; + } + + /** + * Generates a new Name to be used when following a DNAME. + * + * @param dname The DNAME record to follow. + * + * @return The constructed name. + * + * @throws NameTooLongException The resulting name is too long. + */ + public + Name fromDNAME(DNAMERecord dname) throws NameTooLongException { + Name dnameowner = dname.getName(); + Name dnametarget = dname.getTarget(); + if (!subdomain(dnameowner)) { + return null; + } + + int plabels = labels() - dnameowner.labels(); + int plength = length() - dnameowner.length(); + int pstart = offset(0); + + int dlabels = dnametarget.labels(); + int dlength = dnametarget.length(); + + if (plength + dlength > MAXNAME) { + throw new NameTooLongException(); + } + + Name newname = new Name(); + newname.setlabels(plabels + dlabels); + newname.name = new byte[plength + dlength]; + System.arraycopy(name, pstart, newname.name, 0, plength); + System.arraycopy(dnametarget.name, 0, newname.name, plength, dlength); + + for (int i = 0, pos = 0; i < MAXOFFSETS && i < plabels + dlabels; i++) { + newname.setoffset(i, pos); + pos += (newname.name[pos] + 1); + } + return newname; + } + + /** + * Is this name a wildcard? + */ + public + boolean isWild() { + if (labels() == 0) { + return false; + } + return (name[0] == (byte) 1 && name[1] == (byte) '*'); + } + + /** + * The number of labels in the name. + */ + public + int labels() { + return getlabels(); + } + + private + int getlabels() { + return (int) (offsets & 0xFF); + } + + /** + * Is this name absolute? + */ + public + boolean isAbsolute() { + int nlabels = labels(); + if (nlabels == 0) { + return false; + } + return name[offset(nlabels - 1)] == 0; + } + + /** + * The length of the name. + */ + public + short length() { + if (getlabels() == 0) { + return 0; + } + return (short) (name.length - offset(0)); + } + + /** + * Is the current Name a subdomain of the specified name? + */ + public + boolean subdomain(Name domain) { + int labels = labels(); + int dlabels = domain.labels(); + if (dlabels > labels) { + return false; + } + if (dlabels == labels) { + return equals(domain); + } + return domain.equals(name, offset(labels - dlabels)); + } + + private + String byteString(byte[] array, int pos) { + StringBuilder sb = new StringBuilder(); + int len = array[pos++]; + + for (int i = pos; i < pos + len; i++) { + int b = array[i] & 0xFF; + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + sb.append(byteFormat.format(b)); + } + else if (b == '"' || b == '(' || b == ')' || b == '.' || b == ';' || b == '\\' || b == '@' || b == '$') { + sb.append('\\'); + sb.append((char) b); + } + else { + sb.append((char) b); + } + } + + return sb.toString(); + } + + /** + * Convert a Name to a String + * + * @param omitFinalDot If true, and the name is absolute, omit the final dot. + * + * @return The representation of this name as a (printable) String. + */ + public + String toString(boolean omitFinalDot) { + int labels = labels(); + if (labels == 0) { + return "@"; + } + else if (labels == 1 && name[offset(0)] == 0) { + return "."; + } + StringBuilder sb = new StringBuilder(); + + for (int i = 0, pos = offset(0); i < labels; i++) { + int len = name[pos]; + if (len > MAXLABEL) { + throw new IllegalStateException("invalid label"); + } + if (len == 0) { + if (!omitFinalDot) { + sb.append('.'); + } + break; + } + if (i > 0) { + sb.append('.'); + } + sb.append(byteString(name, pos)); + pos += (1 + len); + } + + return sb.toString(); + } + + /** + * Retrieve the nth label of a Name. This makes a copy of the label; changing + * this does not change the Name. + * + * @param n The label to be retrieved. The first label is 0. + */ + public + byte[] getLabel(int n) { + int pos = offset(n); + byte len = (byte) (name[pos] + 1); + byte[] label = new byte[len]; + System.arraycopy(name, pos, label, 0, len); + return label; + } + + /** + * Convert the nth label in a Name to a String + * + * @param n The label to be converted to a (printable) String. The first + * label is 0. + */ + public + String getLabelString(int n) { + int pos = offset(n); + return byteString(name, pos); + } + + /** + * Emit a Name in DNS wire format + * + * @param out The output stream containing the DNS message. + * @param c The compression context, or null of no compression is desired. + * + * @throws IllegalArgumentException The name is not absolute. + */ + public + void toWire(DnsOutput out, Compression c) { + if (!isAbsolute()) { + throw new IllegalArgumentException("toWire() called on " + "non-absolute name"); + } + + int labels = labels(); + for (int i = 0; i < labels - 1; i++) { + Name tname; + if (i == 0) { + tname = this; + } + else { + tname = new Name(this, i); + } + int pos = -1; + if (c != null) { + pos = c.get(tname); + } + if (pos >= 0) { + pos |= (LABEL_MASK << 8); + out.writeU16(pos); + return; + } + else { + if (c != null) { + c.add(out.current(), tname); + } + int off = offset(i); + out.writeByteArray(name, off, name[off] + 1); + } + } + out.writeU8(0); + } + + /** + * Emit a Name in DNS wire format + * + * @throws IllegalArgumentException The name is not absolute. + */ + public + byte[] toWire() { + DnsOutput out = new DnsOutput(); + toWire(out, null); + return out.toByteArray(); + } + + /** + * Emit a Name in canonical DNS wire format (all lowercase) + * + * @param out The output stream to which the message is written. + */ + public + void toWireCanonical(DnsOutput out) { + byte[] b = toWireCanonical(); + out.writeByteArray(b); + } + + /** + * Emit a Name in canonical DNS wire format (all lowercase) + * + * @return The canonical form of the name. + */ + public + byte[] toWireCanonical() { + int labels = labels(); + if (labels == 0) { + return (new byte[0]); + } + byte[] b = new byte[name.length - offset(0)]; + for (int i = 0, spos = offset(0), dpos = 0; i < labels; i++) { + int len = name[spos]; + if (len > MAXLABEL) { + throw new IllegalStateException("invalid label"); + } + b[dpos++] = name[spos++]; + for (int j = 0; j < len; j++) { + b[dpos++] = lowercase[(name[spos++] & 0xFF)]; + } + } + return b; + } + + /** + * Emit a Name in DNS wire format + * + * @param out The output stream containing the DNS message. + * @param c The compression context, or null of no compression is desired. + * @param canonical If true, emit the name in canonicalized form + * (all lowercase). + * + * @throws IllegalArgumentException The name is not absolute. + */ + public + void toWire(DnsOutput out, Compression c, boolean canonical) { + if (canonical) { + toWireCanonical(out); + } + else { + toWire(out, c); + } + } + + private + boolean equals(byte[] b, int bpos) { + int labels = labels(); + for (int i = 0, pos = offset(0); i < labels; i++) { + if (name[pos] != b[bpos]) { + return false; + } + int len = name[pos++]; + bpos++; + if (len > MAXLABEL) { + throw new IllegalStateException("invalid label"); + } + for (int j = 0; j < len; j++) { + if (lowercase[(name[pos++] & 0xFF)] != lowercase[(b[bpos++] & 0xFF)]) { + return false; + } + } + } + return true; + } + + /** + * Computes a hashcode based on the value + */ + public + int hashCode() { + if (hashcode != 0) { + return (hashcode); + } + int code = 0; + for (int i = offset(0); i < name.length; i++) { + code += ((code << 3) + lowercase[(name[i] & 0xFF)]); + } + hashcode = code; + return hashcode; + } + + /** + * Are these two Names equivalent? + */ + public + boolean equals(Object arg) { + if (arg == this) { + return true; + } + if (arg == null || !(arg instanceof Name)) { + return false; + } + Name d = (Name) arg; + if (d.hashcode == 0) { + d.hashCode(); + } + if (hashcode == 0) { + hashCode(); + } + if (d.hashcode != hashcode) { + return false; + } + if (d.labels() != labels()) { + return false; + } + return equals(d.name, d.offset(0)); + } + + /** + * Convert a Name to a String + * + * @return The representation of this name as a (printable) String. + */ + public + String toString() { + return toString(false); + } + + /** + * Compares this Name to another Object. + * + * @param o The Object to be compared. + * + * @return The value 0 if the argument is a name equivalent to this name; + * a value less than 0 if the argument is less than this name in the canonical + * ordering, and a value greater than 0 if the argument is greater than this + * name in the canonical ordering. + * + * @throws ClassCastException if the argument is not a Name. + */ + @Override + public + int compareTo(Object o) { + Name arg = (Name) o; + + if (this == arg) { + return (0); + } + + int labels = labels(); + int alabels = arg.labels(); + int compares = labels > alabels ? alabels : labels; + + for (int i = 1; i <= compares; i++) { + int start = offset(labels - i); + int astart = arg.offset(alabels - i); + int length = name[start]; + int alength = arg.name[astart]; + for (int j = 0; j < length && j < alength; j++) { + int n = lowercase[(name[j + start + 1]) & 0xFF] - lowercase[(arg.name[j + astart + 1]) & 0xFF]; + if (n != 0) { + return (n); + } + } + if (length != alength) { + return (length - alength); + } + } + return (labels - alabels); + } + +} diff --git a/src/dorkbox/network/dns/constants/DnsClass.java b/src/dorkbox/network/dns/constants/DnsClass.java new file mode 100644 index 00000000..dce2cf40 --- /dev/null +++ b/src/dorkbox/network/dns/constants/DnsClass.java @@ -0,0 +1,132 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.exceptions.InvalidDClassException; + +/** + * Constants and functions relating to DNS classes. This is called DnsClass to avoid confusion with Class. + * + * @author Brian Wellington + */ +public final +class DnsClass { + + /** + * Internet DNS resource record class: {@code IN} + */ + public static final int IN = 1; + + /** + * Computer Science Network network DNS resource record class: {@code CSNET}. It was never installed as a top-level domain + * in the Domain Name System, but parsed in the message routing logic of mail transport agents (MTA). It was introduced in 1985. + */ + public static final int CS = 2; + + /** + * Computer Science Network network DNS resource record class: {@code CSNET}. It was never installed as a top-level domain + * in the Domain Name System, but parsed in the message routing logic of mail transport agents (MTA). It was introduced in 1985. + */ + public static final int CSNET = 2; + + /** + * Chaos network DNS resource record class: {@code CH} (MIT) + */ + public static final int CH = 3; + + /** + * Chaos network DNS resource record class: {@code CHAOS} (MIT, alternate name) + */ + public static final int CHAOS = 3; + + /** + * Hesiod DNS resource record class: {@code HS} (MIT) + */ + public static final int HS = 4; + + /** + * Hesiod DNS resource record class: {@code HESIOD} (MIT, alternate name) + */ + public static final int HESIOD = 4; + + /** + * Special value used in dynamic update messages + */ + public static final int NONE = 254; + + /** + * Matches any class + */ + public static final int ANY = 255; + + + + private static Mnemonic classes = new DClassMnemonic(); + + + private static + class DClassMnemonic extends Mnemonic { + DClassMnemonic() { + super("DnsClass", CASE_UPPER); + setPrefix("CLASS"); + } + + @Override + public + void check(int val) { + DnsClass.check(val); + } + } + + + static { + classes.add(IN, "IN"); + classes.add(CS, "CS"); + classes.addAlias(CSNET, "CSNET"); + classes.add(CH, "CH"); + classes.addAlias(CH, "CHAOS"); + classes.add(HS, "HS"); + classes.addAlias(HS, "HESIOD"); + classes.add(NONE, "NONE"); + classes.add(ANY, "ANY"); + } + + private + DnsClass() {} + + /** + * Checks that a numeric DnsClass is valid. + * + * @throws InvalidDClassException The class is out of range. + */ + public static + void check(int i) { + if (i < 0 || i > 0xFFFF) { + throw new InvalidDClassException(i); + } + } + + /** + * Converts a numeric DnsClass into a String + * + * @return The canonical string representation of the class + * + * @throws InvalidDClassException The class is out of range. + */ + public static + String string(int i) { + return classes.getText(i); + } + + /** + * Converts a String representation of a DnsClass into its numeric value + * + * @return The class code, or -1 on error. + */ + public static + int value(String s) { + return classes.getValue(s); + } + +} diff --git a/src/dorkbox/network/dns/constants/DnsOpCode.java b/src/dorkbox/network/dns/constants/DnsOpCode.java new file mode 100644 index 00000000..ad8918f6 --- /dev/null +++ b/src/dorkbox/network/dns/constants/DnsOpCode.java @@ -0,0 +1,73 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import dorkbox.network.dns.Mnemonic; + +/** + * Constants and functions relating to DNS opcodes + * + * @author Brian Wellington + */ + +public final +class DnsOpCode { + + /** + * A standard query + */ + public static final int QUERY = 0; + + /** + * An inverse query (deprecated) + */ + public static final int IQUERY = 1; + + /** + * A server status request (not used) + */ + public static final int STATUS = 2; + + /** + * A message from a primary to a secondary server to initiate a zone transfer + */ + public static final int NOTIFY = 4; + + /** + * A dynamic update message + */ + public static final int UPDATE = 5; + + private static Mnemonic opcodes = new Mnemonic("DNS DnsOpCode", Mnemonic.CASE_UPPER); + + static { + opcodes.setMaximum(0xF); + opcodes.setPrefix("RESERVED"); + opcodes.setNumericAllowed(true); + + opcodes.add(QUERY, "QUERY"); + opcodes.add(IQUERY, "IQUERY"); + opcodes.add(STATUS, "STATUS"); + opcodes.add(NOTIFY, "NOTIFY"); + opcodes.add(UPDATE, "UPDATE"); + } + + private + DnsOpCode() {} + + /** + * Converts a numeric DnsOpCode into a String + */ + public static + String string(int i) { + return opcodes.getText(i); + } + + /** + * Converts a String representation of an DnsOpCode into its numeric value + */ + public static + int value(String s) { + return opcodes.getValue(s); + } +} diff --git a/src/dorkbox/network/dns/constants/DnsRecordType.java b/src/dorkbox/network/dns/constants/DnsRecordType.java new file mode 100644 index 00000000..db6382e2 --- /dev/null +++ b/src/dorkbox/network/dns/constants/DnsRecordType.java @@ -0,0 +1,565 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import java.util.HashMap; + +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.exceptions.InvalidTypeException; +import dorkbox.network.dns.records.DnsRecord; +import dorkbox.network.dns.records.DnsTypeProtoAssignment; + +/** + * Constants and functions relating to DNS Types + * + * @author Brian Wellington + */ + +public final +class DnsRecordType { + + /** + * Address record RFC 1035 Returns a 32-bit IPv4 address, most commonly used + * to map hostnames to an IP address of the host, but also used for DNSBLs, + * storing subnet masks in RFC 1101, etc. + */ + public static final int A = 1; + + /** + * Name server record RFC 1035 Delegates a DNS zone to use the given + * authoritative name servers + */ + public static final int NS = 2; + + /** + * Mail destination. MD specifies the final destination to which a message addressed to a given domain name should be delivered + * (Obsolete, as it's behavior has been replaced by MX) + */ + @Deprecated + public static final int MD = 3; + + /** + * Mail forwarder. MF specifies a host that would forward mail on to the eventual destination, should that destination be unreachable. + * (Obsolete, as it's behavior has been replaced by MX) + */ + @Deprecated + public static final int MF = 4; + + /** + * Canonical name (alias) record RFC 1035 Alias of one name to another: the DNS + * lookup will continue by retrying the lookup with the new name. + */ + public static final int CNAME = 5; + + /** + * Start of [a zone of] authority record RFC 1035 and RFC 2308 Specifies + * authoritative information about a DNS zone, including the primary name + * server, the email of the domain administrator, the domain serial number, + * and several timers relating to refreshing the zone. + */ + public static final int SOA = 6; + + /** + * Mailbox domain name RFC 1035. EXPERIMENTAL. A which specifies a host which has the + * specified mailbox. + */ + public static final int MB = 7; + + /** + * Mail group member RFC 1035. EXPERIMENTAL. A which specifies a mailbox which is a + * member of the mail group specified by the domain name. MG records cause no additional section processing. + */ + public static final int MG = 8; + + /** + * Mail rename name RFC 1035. EXPERIMENTAL. A which specifies a mailbox which is the + * proper rename of the specified mailbox. + */ + public static final int MR = 9; + + /** + * Null record RFC 1035. EXPERIMENTAL. Anything at all may be in the RDATA field so long as it is 65535 octets + * or less + */ + public static final int NULL = 10; + + /** + * The WKS record RFC 1035 is used to describe the well known services supported by + * a particular protocol on a particular internet address. + */ + public static final int WKS = 11; + + /** + * Pointer record RFC 1035 Pointer to a canonical name. Unlike a CNAME, DNS + * processing does NOT proceed, just the name is returned. The most common + * use is for implementing reverse DNS lookups, but other uses include such + * things as DNS-SD. + */ + public static final int PTR = 12; + + /** + * Host information HINFO RFC 1035. records are used to acquire general information about a host. The + * main use is for protocols such as FTP that can use special procedures when talking between machines + * or operating systems of the same type. + */ + public static final int HINFO = 13; + + /** + * Mailbox or mail list information RFC 1035. EXPERIMENTAL. MINFO records cause no additional section processing. Although these + * records can be associated with a simple mailbox, they are usually used with a mailing list. + */ + public static final int MINFO = 14; + + /** + * Mail exchange (routing) record RFC 1035 Maps a domain name to a list of message transfer agents for that domain. + */ + public static final int MX = 15; + + /** + * Text record RFC 1035 Originally for arbitrary human-readable text in a + * DNS record. Since the early 1990s, however, this record more often + * carries machine-readable data, such as specified by RFC 1464, + * opportunistic encryption, Sender Policy Framework, DKIM, DMARC DNS-SD, + * etc. + */ + public static final int TXT = 16; + + /** + * Responsible person record RFC 1183 Information about the responsible + * person(s) for the domain. Usually an email address with the @ replaced by + * a . + */ + public static final int RP = 17; + + /** + * AFS cell database record RFC 1183 Location of database servers of an AFS cell. + * This record is commonly used by AFS clients to contact AFS cells outside + * their local domain. A subtype of this record is used by the obsolete + * DCE/DFS file system. + */ + public static final int AFSDB = 18; + + /** + * X.25 calling address + */ + public static final int X25 = 19; + + /** + * ISDN calling address + */ + public static final int ISDN = 20; + + /** + * Router + */ + public static final int RT = 21; + + /** + * NSAP address + */ + public static final int NSAP = 22; + + /** + * Reverse NSAP address (deprecated) + */ + @Deprecated + public static final int NSAP_PTR = 23; + + /** + * Signature record RFC 2535 Signature record used in SIG(0) (RFC 2931) and + * TKEY (RFC 2930). RFC 3755 designated RRSIG as the replacement for SIG for + * use within DNSSEC. + */ + public static final int SIG = 24; + + /** + * key record RFC 2535 and RFC 2930 Used only for SIG(0) (RFC 2931) and TKEY + * (RFC 2930). RFC 3445 eliminated their use for application keys and + * limited their use to DNSSEC. RFC 3755 designates DNSKEY as the + * replacement within DNSSEC. RFC 4025 designates IPSECKEY as the + * replacement for use with IPsec. + */ + public static final int KEY = 25; + + /** + * X.400 mail mapping + */ + public static final int PX = 26; + + /** + * Geographical position (withdrawn) + */ + @Deprecated + public static final int GPOS = 27; + + /** + * IPv6 address record RFC 3596 Returns a 128-bit IPv6 address, most + * commonly used to map hostnames to an IP address of the host. + */ + public static final int AAAA = 28; + + /** + * Location record RFC 1876 Specifies a geographical location associated + * with a domain name. + */ + public static final int LOC = 29; + + /** + * Next valid name in zone + */ + public static final int NXT = 30; + + /** + * Endpoint identifier + */ + public static final int EID = 31; + + /** + * Nimrod locator + */ + public static final int NIMLOC = 32; + + /** + * Service selection locator RFC 2782 Generalized service location record, used for + * newer protocols instead of creating protocol-specific records such as MX. + */ + public static final int SRV = 33; + + /** + * ATM address + */ + public static final int ATMA = 34; + + /** + * Naming Authority Pointer record RFC 3403 Allows regular expression based + * rewriting of domain names which can then be used as URIs, further domain + * names to lookups, etc. + */ + public static final int NAPTR = 35; + + /** + * Key eXchanger record RFC 2230 Used with some cryptographic systems (not + * including DNSSEC) to identify a key management agent for the associated + * domain-name. Note that this has nothing to do with DNS Security. It is + * Informational status, rather than being on the IETF standards-track. It + * has always had limited deployment, but is still in use. + */ + public static final int KX = 36; + + /** + * Certificate record RFC 4398 Stores PKIX, SPKI, PGP, etc. + */ + public static final int CERT = 37; + + /** + * IPv6 address (experimental) + */ + public static final int A6 = 38; + + /** + * Delegation name record RFC 2672 DNAME creates an alias for a name and all + * its subnames, unlike CNAME, which aliases only the exact name in its + * label. Like the CNAME record, the DNS lookup will continue by retrying + * the lookup with the new name. This is also known as Non-terminal name redirection + */ + public static final int DNAME = 39; + + /** + * Options - contains EDNS metadata. Option record RFC 2671 This is a pseudo DNS + * record type needed to support EDNS. + */ + public static final int OPT = 41; + + /** + * Address Prefix List record RFC 3123 Specify lists of address ranges, e.g. + * in CIDR format, for various address families. Experimental. + */ + public static final int APL = 42; + + /** + * Delegation signer record RFC 4034 The record used to identify the DNSSEC + * signing key of a delegated zone. + */ + public static final int DS = 43; + + /** + * SSH Public Key Fingerprint record RFC 4255 Resource record for publishing + * SSH public host key fingerprints in the DNS System, in order to aid in + * verifying the authenticity of the host. RFC 6594 defines ECC SSH keys and + * SHA-256 hashes. See the IANA SSHFP RR parameters registry for details. + */ + public static final int SSHFP = 44; + + /** + * IPsec Key record RFC 4025 Key record that can be used with IPsec. + */ + public static final int IPSECKEY = 45; + + /** + * Resource Record Signature. DNSSEC signature record RFC 4034 Signature for a DNSSEC-secured record + * set. Uses the same format as the SIG record. + */ + public static final int RRSIG = 46; + + /** + * Next Secure Name. Next-Secure record RFC 4034 Part of DNSSEC, used to prove a name does not + * exist. Uses the same format as the (obsolete) NXT record. + */ + public static final int NSEC = 47; + + /** + * DNSSEC Key record RFC 4034 The key record used in DNSSEC. Uses the same + * format as the KEY record. + */ + public static final int DNSKEY = 48; + + /** + * Dynamic Host Configuration Protocol (DHCP) ID. DHCP identifier record RFC 4701 + * Used in conjunction with the FQDN option to DHCP. + */ + public static final int DHCID = 49; + + /** + * Next SECure, 3rd edition, RFC 5155. An extension to DNSSEC that allows proof + * of nonexistence for a name without permitting zonewalking. + */ + public static final int NSEC3 = 50; + + /** + * NSEC3 parameters record RFC 5155 Parameter record for use with NSEC3. + */ + public static final int NSEC3PARAM = 51; + + /** + * Transport Layer Security Authentication, draft-ietf-dane-protocol-23. + * TLSA certificate association record RFC 6698 A record for DNS-based + * Authentication of Named Entities (DANE). RFC 6698 defines The TLSA DNS + * resource record is used to associate a TLS server certificate or public + * key with the domain name where the record is found, thus forming a 'TLSA + * certificate association'. + */ + public static final int TLSA = 52; + + /** + * S/MIME cert association, draft-ietf-dane-smime + */ + public static final int SMIMEA = 53; + + + /** + * Host Identity Protocol record RFC 5205 Method of separating the end-point + * identifier and locator roles of IP addresses. + */ + public static final int HIP = 55; + + /** + * OpenPGP Key, RFC 7929 + */ + public static final int OPENPGPKEY = 61; + + /** + * Sender Policy Framework (experimental) record RFC 4408 Specified as part of the SPF + * protocol as an alternative to of storing SPF data in TXT records. Uses + * the same format as the earlier TXT record. + */ + public static final int SPF = 99; + + /** + * Transaction key - used to compute a shared secret or exchange a key. + * Secret key record RFC 2930 A method of providing keying material to be + * used with TSIG that is encrypted under the public key in an accompanying + * KEY RR.. + */ + public static final int TKEY = 249; + + /** + * Transaction Signature record RFC 2845 Can be used to authenticate dynamic + * updates as coming from an approved client, or to authenticate responses + * as coming from an approved recursive name server similar to DNSSEC. + */ + public static final int TSIG = 250; + + /** + * Incremental Zone Transfer record RFC 1996 Requests a zone transfer of the + * given zone but only differences from a previous serial number. This + * request may be ignored and a full (AXFR) sent in response if the + * authoritative server is unable to fulfill the request due to + * configuration or lack of required deltas. + */ + public static final int IXFR = 251; + + /** + * Authoritative Zone Transfer record RFC 1035 Transfer entire zone file + * from the master name server to secondary name servers. + */ + public static final int AXFR = 252; + + /** + * Transfer mailbox records + */ + public static final int MAILB = 253; + + /** + * Transfer mail agent records + */ + public static final int MAILA = 254; + + /** + * Matches any type + * + * All cached records RFC 1035 Returns all records of all types known to the + * name server. If the name server does not have any information on the + * name, the request will be forwarded on. The records returned may not be + * complete. For example, if there is both an A and an MX for a name, but + * the name server has only the A record cached, only the A record will be + * returned. Sometimes referred to as ANY, for example in Windows nslookup + * and Wireshark. + */ + public static final int ANY = 255; + + /** + * URI + * + * @see draft-faltstrom-uri-14 + */ + public static final int URI = 256; + + /** + * Certification Authority Authorization, RFC 6844. CA pinning, + * constraining acceptable CAs for a host/domain. + */ + public static final int CAA = 257; + + /** + * DNSSEC Trust Authorities record N/A Part of a deployment proposal for + * DNSSEC without a signed DNS root. See the IANA database and Weiler Spec + * for details. Uses the same format as the DS record. + */ + public static final int TA = 32768; + + /** + * DNSSEC Lookaside Validation, RFC 4431. For publishing DNSSEC trust + * anchors outside of the DNS delegation chain. Uses the same format as the + * DS record. RFC 5074 describes a way of using these records. + */ + public static final int DLV = 32769; + private static TypeMnemonic types = new TypeMnemonic(); + + + public static + class TypeMnemonic extends Mnemonic { + private HashMap objects; + + public + TypeMnemonic() { + super("DnsRecordType", CASE_UPPER); + setPrefix("TYPE"); + objects = new HashMap(); + } + + public + void add(int val, String str, DnsRecord proto) { + super.add(val, str); + objects.put(Mnemonic.toInteger(val), proto); + } + + public + T getProto(int val) { + check(val); + return (T) objects.get(toInteger(val)); + } + + @Override + public + void check(int val) { + DnsRecordType.check(val); + } + } + + static { + // this is so we don't have to make each type constructor public + DnsTypeProtoAssignment.assign(types); + } + + private + DnsRecordType() { + } + + /** + * Checks that a numeric DnsRecordType is valid. + * + * @throws InvalidTypeException The type is out of range. + */ + public static + void check(int val) { + if (val < 0 || val > 0xFFFF) { + throw new InvalidTypeException(val); + } + } + + /** + * Converts a numeric DnsRecordType into a String + * + * @param val The type value. + * + * @return The canonical string representation of the type + * + * @throws InvalidTypeException The type is out of range. + */ + public static + String string(int val) { + return types.getText(val); + } + + /** + * Converts a String representation of an DnsRecordType into its numeric value + * + * @return The type code, or -1 on error. + */ + public static + int value(String s) { + return value(s, false); + } + + /** + * Converts a String representation of an DnsRecordType into its numeric value. + * + * @param s The string representation of the type + * @param numberok Whether a number will be accepted or not. + * + * @return The type code, or -1 on error. + */ + public static + int value(String s, boolean numberok) { + int val = types.getValue(s); + if (val == -1 && numberok) { + val = types.getValue("TYPE" + s); + } + return val; + } + + public static + T getProto(int val) { + return types.getProto(val); + } + + /** + * Is this type valid for a record (a non-meta type)? + */ + public static + boolean isRR(int type) { + switch (type) { + case OPT: + case TKEY: + case TSIG: + case IXFR: + case AXFR: + case MAILB: + case MAILA: + case ANY: + return false; + default: + return true; + } + } +} diff --git a/src/dorkbox/network/dns/constants/DnsResponseCode.java b/src/dorkbox/network/dns/constants/DnsResponseCode.java new file mode 100644 index 00000000..22af7740 --- /dev/null +++ b/src/dorkbox/network/dns/constants/DnsResponseCode.java @@ -0,0 +1,180 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import dorkbox.network.dns.Mnemonic; + +/** + * Constants and functions relating to DNS rcodes (error values) + * + * @author Brian Wellington + */ + +public final +class DnsResponseCode { + + private static Mnemonic rcodes = new Mnemonic("DNS DnsResponseCode", Mnemonic.CASE_UPPER); + + private static Mnemonic tsigrcodes = new Mnemonic("TSIG rcode", Mnemonic.CASE_UPPER); + + /** + * No error + */ + public static final int NOERROR = 0; + + /** + * Format error + */ + public static final int FORMERR = 1; + + /** + * Server failure + */ + public static final int SERVFAIL = 2; + + /** + * The name does not exist + */ + public static final int NXDOMAIN = 3; + + /** + * The operation requested is not implemented + */ + public static final int NOTIMP = 4; + + /** + * Deprecated synonym for NOTIMP. + */ + public static final int NOTIMPL = 4; + + /** + * The operation was refused by the server + */ + public static final int REFUSED = 5; + + /** + * The name exists + */ + public static final int YXDOMAIN = 6; + + /** + * The RRset (name, type) exists + */ + public static final int YXRRSET = 7; + + /** + * The RRset (name, type) does not exist + */ + public static final int NXRRSET = 8; + + /** + * The requestor is not authorized to perform this operation + */ + public static final int NOTAUTH = 9; + + /** + * The zone specified is not a zone + */ + public static final int NOTZONE = 10; + + + +/* EDNS extended rcodes */ + /** + * Unsupported EDNS level + */ + public static final int BADVERS = 16; + + + +/* TSIG/TKEY only rcodes */ + /** + * The signature is invalid (TSIG/TKEY extended error) + */ + public static final int BADSIG = 16; + + /** + * The key is invalid (TSIG/TKEY extended error) + */ + public static final int BADKEY = 17; + + /** + * The time is out of range (TSIG/TKEY extended error) + */ + public static final int BADTIME = 18; + + /** + * The mode is invalid (TKEY extended error) + */ + public static final int BADMODE = 19; + + /** + * The 'BADNAME' DNS RCODE (20), as defined in RFC2930. + */ + public static final int BADNAME = 20; + + /** + * The 'BADALG' DNS RCODE (21), as defined in RFC2930. + */ + public static final int BADALG = 21; + + static { + rcodes.setMaximum(0xFFF); + rcodes.setPrefix("RESERVED"); + rcodes.setNumericAllowed(true); + + rcodes.add(NOERROR, "NOERROR"); + rcodes.add(FORMERR, "FORMERR"); + rcodes.add(SERVFAIL, "SERVFAIL"); + rcodes.add(NXDOMAIN, "NXDOMAIN"); + rcodes.add(NOTIMP, "NOTIMP"); + rcodes.addAlias(NOTIMP, "NOTIMPL"); + rcodes.add(REFUSED, "REFUSED"); + rcodes.add(YXDOMAIN, "YXDOMAIN"); + rcodes.add(YXRRSET, "YXRRSET"); + rcodes.add(NXRRSET, "NXRRSET"); + rcodes.add(NOTAUTH, "NOTAUTH"); + rcodes.add(NOTZONE, "NOTZONE"); + rcodes.add(BADVERS, "BADVERS"); + + tsigrcodes.setMaximum(0xFFFF); + tsigrcodes.setPrefix("RESERVED"); + tsigrcodes.setNumericAllowed(true); + tsigrcodes.addAll(rcodes); + + tsigrcodes.add(BADSIG, "BADSIG"); + tsigrcodes.add(BADKEY, "BADKEY"); + tsigrcodes.add(BADTIME, "BADTIME"); + tsigrcodes.add(BADMODE, "BADMODE"); + tsigrcodes.add(BADNAME, "BADNAME"); + tsigrcodes.add(BADALG, "BADALG"); + } + + private + DnsResponseCode() {} + + /** + * Converts a numeric DnsResponseCode into a String + */ + public static + String string(int i) { + return rcodes.getText(i); + } + + /** + * Converts a numeric TSIG extended DnsResponseCode into a String + */ + public static + String TSIGstring(int i) { + return tsigrcodes.getText(i); + } + + /** + * Converts a String representation of an DnsResponseCode into its numeric value + */ + public static + int value(String s) { + return rcodes.getValue(s); + } + +} diff --git a/src/dorkbox/network/dns/constants/DnsSection.java b/src/dorkbox/network/dns/constants/DnsSection.java new file mode 100644 index 00000000..6c52f06c --- /dev/null +++ b/src/dorkbox/network/dns/constants/DnsSection.java @@ -0,0 +1,115 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import dorkbox.network.dns.Mnemonic; + +/** + * Constants and functions relating to DNS message sections + * + * @author Brian Wellington + */ + +public final +class DnsSection { + public static final int TOTAL_SECTION_COUNT = 4; + + /** + * The question (first) section + */ + public static final int QUESTION = 0; + + /** + * The answer (second) section + */ + public static final int ANSWER = 1; + + /** + * The authority (third) section + */ + public static final int AUTHORITY = 2; + + /** + * The additional (fourth) section + */ + public static final int ADDITIONAL = 3; + +/* Aliases for dynamic update */ + /** + * The zone (first) section of a dynamic update message + */ + public static final int ZONE = 0; + + /** + * The prerequisite (second) section of a dynamic update message + */ + public static final int PREREQ = 1; + + /** + * The update (third) section of a dynamic update message + */ + public static final int UPDATE = 2; + + private static Mnemonic sections = new Mnemonic("DnsMessage DnsSection", Mnemonic.CASE_LOWER); + private static String[] longSections = new String[4]; + private static String[] updateSections = new String[4]; + + static { + sections.setMaximum(3); + sections.setNumericAllowed(true); + + sections.add(QUESTION, "qd"); + sections.add(ANSWER, "an"); + sections.add(AUTHORITY, "au"); + sections.add(ADDITIONAL, "ad"); + + longSections[QUESTION] = "QUESTIONS"; + longSections[ANSWER] = "ANSWERS"; + longSections[AUTHORITY] = "AUTHORITY RECORDS"; + longSections[ADDITIONAL] = "ADDITIONAL RECORDS"; + + updateSections[ZONE] = "ZONE"; + updateSections[PREREQ] = "PREREQUISITES"; + updateSections[UPDATE] = "UPDATE RECORDS"; + updateSections[ADDITIONAL] = "ADDITIONAL RECORDS"; + } + + private + DnsSection() {} + + /** + * Converts a numeric DnsSection into an abbreviation String + */ + public static + String string(int i) { + return sections.getText(i); + } + + /** + * Converts a numeric DnsSection into a full description String + */ + public static + String longString(int i) { + sections.check(i); + return longSections[i]; + } + + /** + * Converts a numeric DnsSection into a full description String for an update + * DnsMessage. + */ + public static + String updString(int i) { + sections.check(i); + return updateSections[i]; + } + + /** + * Converts a String representation of a DnsSection into its numeric value + */ + public static + int value(String s) { + return sections.getValue(s); + } + +} diff --git a/src/dorkbox/network/dns/constants/Flags.java b/src/dorkbox/network/dns/constants/Flags.java new file mode 100644 index 00000000..ec655054 --- /dev/null +++ b/src/dorkbox/network/dns/constants/Flags.java @@ -0,0 +1,105 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.constants; + +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.records.ExtendedFlags; + +/** + * Constants and functions relating to flags in the DNS header. + * + * @author Brian Wellington + */ + +public final +class Flags { + + private static Mnemonic flags = new Mnemonic("DNS Header Flag", Mnemonic.CASE_LOWER); + + /** + * query/response + */ + public static final byte QR = 0; + + /** + * authoritative answer + */ + public static final byte AA = 5; + + /** + * truncated + */ + public static final byte TC = 6; + + /** + * recursion desired + */ + public static final byte RD = 7; + + /** + * recursion available + */ + public static final byte RA = 8; + + /** + * authenticated data + */ + public static final byte AD = 10; + + /** + * (security) checking disabled + */ + public static final byte CD = 11; + + /** + * dnssec ok (extended) + */ + public static final int DO = ExtendedFlags.DO; + + static { + flags.setMaximum(0xF); + flags.setPrefix("FLAG"); + flags.setNumericAllowed(true); + + flags.add(QR, "qr"); + flags.add(AA, "aa"); + flags.add(TC, "tc"); + flags.add(RD, "rd"); + flags.add(RA, "ra"); + flags.add(AD, "ad"); + flags.add(CD, "cd"); + } + + private + Flags() {} + + /** + * Converts a numeric Flag into a String + */ + public static + String string(int i) { + return flags.getText(i); + } + + /** + * Converts a String representation of an Flag into its numeric value + */ + public static + int value(String s) { + return flags.getValue(s); + } + + /** + * Indicates if a bit in the flags field is a flag or not. If it's part of + * the rcode or opcode, it's not. + */ + public static + boolean isFlag(int index) { + flags.check(index); + if ((index >= 1 && index <= 4) || (index >= 12)) { + return false; + } + return true; + } + +} diff --git a/src/dorkbox/network/dns/decoder/MailExchangerDecoder.java b/src/dorkbox/network/dns/decoder/MailExchangerDecoder.java deleted file mode 100644 index 048c1054..00000000 --- a/src/dorkbox/network/dns/decoder/MailExchangerDecoder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.decoder; - -import dorkbox.network.dns.record.MailExchangerRecord; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.resolver.dns.DnsNameResolverAccess; - -public -class MailExchangerDecoder implements RecordDecoder { - - @Override - public - MailExchangerRecord decode(final DnsRecord record, final ByteBuf response) { - int priority = response.readUnsignedShort(); - - String name = DnsNameResolverAccess.decodeDomainName(response); - return new MailExchangerRecord(priority, name); - } -} diff --git a/src/dorkbox/network/dns/decoder/RecordDecoder.java b/src/dorkbox/network/dns/decoder/RecordDecoder.java deleted file mode 100644 index 28401096..00000000 --- a/src/dorkbox/network/dns/decoder/RecordDecoder.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.decoder; - -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.DecoderException; -import io.netty.handler.codec.dns.DnsRecord; - -public -interface RecordDecoder { - - T decode(final DnsRecord record, final ByteBuf response) throws DecoderException; -} diff --git a/src/dorkbox/network/dns/decoder/ServiceDecoder.java b/src/dorkbox/network/dns/decoder/ServiceDecoder.java deleted file mode 100644 index a97f1371..00000000 --- a/src/dorkbox/network/dns/decoder/ServiceDecoder.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.decoder; - -import dorkbox.network.dns.record.ServiceRecord; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.resolver.dns.DnsNameResolverAccess; - -public -class ServiceDecoder implements RecordDecoder { - - @Override - public - ServiceRecord decode(final DnsRecord record, final ByteBuf response) { - int priority = response.readShort(); - int weight = response.readShort(); - int port = response.readUnsignedShort(); - String target = DnsNameResolverAccess.decodeDomainName(response); - - return new ServiceRecord(record.name(), priority, weight, port, target); - } -} diff --git a/src/dorkbox/network/dns/decoder/StartOfAuthorityDecoder.java b/src/dorkbox/network/dns/decoder/StartOfAuthorityDecoder.java deleted file mode 100644 index e8214a63..00000000 --- a/src/dorkbox/network/dns/decoder/StartOfAuthorityDecoder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.decoder; - -import dorkbox.network.dns.record.StartOfAuthorityRecord; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.resolver.dns.DnsNameResolverAccess; - -public -class StartOfAuthorityDecoder implements RecordDecoder { - - @Override - public - StartOfAuthorityRecord decode(final DnsRecord record, final ByteBuf response) { - String primaryName = DnsNameResolverAccess.decodeDomainName(response); - String personName = DnsNameResolverAccess.decodeDomainName(response); - - long serial = response.readUnsignedInt(); - int refresh = response.readInt(); - int retry = response.readInt(); - int expire = response.readInt(); - long minimum = response.readUnsignedInt(); - - return new StartOfAuthorityRecord(primaryName, personName, serial, refresh, retry, expire, minimum); - } -} diff --git a/src/dorkbox/network/dns/decoder/TextDecoder.java b/src/dorkbox/network/dns/decoder/TextDecoder.java deleted file mode 100644 index 432da7eb..00000000 --- a/src/dorkbox/network/dns/decoder/TextDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.decoder; - -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.util.CharsetUtil; - -import java.util.ArrayList; -import java.util.List; - -public -class TextDecoder implements RecordDecoder> { - - @Override - public - List decode(final DnsRecord record, final ByteBuf response) { - List list = new ArrayList(); - - int index = response.readerIndex(); - while (index < response.writerIndex()) { - int len = response.getUnsignedByte(index++); - list.add(response.toString(index, len, CharsetUtil.UTF_8)); - index += len; - } - - return list; - } -} diff --git a/src/dorkbox/network/dns/exceptions/InvalidDClassException.java b/src/dorkbox/network/dns/exceptions/InvalidDClassException.java new file mode 100644 index 00000000..e115670b --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/InvalidDClassException.java @@ -0,0 +1,19 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +/** + * An exception thrown when an invalid dclass code is specified. + * + * @author Brian Wellington + */ + +public +class InvalidDClassException extends IllegalArgumentException { + + public + InvalidDClassException(int dclass) { + super("Invalid DNS class: " + dclass); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/InvalidTTLException.java b/src/dorkbox/network/dns/exceptions/InvalidTTLException.java new file mode 100644 index 00000000..c1deb810 --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/InvalidTTLException.java @@ -0,0 +1,19 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +/** + * An exception thrown when an invalid TTL is specified. + * + * @author Brian Wellington + */ + +public +class InvalidTTLException extends IllegalArgumentException { + + public + InvalidTTLException(long ttl) { + super("Invalid DNS TTL: " + ttl); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/InvalidTypeException.java b/src/dorkbox/network/dns/exceptions/InvalidTypeException.java new file mode 100644 index 00000000..eae90e74 --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/InvalidTypeException.java @@ -0,0 +1,19 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +/** + * An exception thrown when an invalid type code is specified. + * + * @author Brian Wellington + */ + +public +class InvalidTypeException extends IllegalArgumentException { + + public + InvalidTypeException(int type) { + super("Invalid DNS type: " + type); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/NameTooLongException.java b/src/dorkbox/network/dns/exceptions/NameTooLongException.java new file mode 100644 index 00000000..2841098f --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/NameTooLongException.java @@ -0,0 +1,25 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +/** + * An exception thrown when a name is longer than the maximum length of a DNS + * name. + * + * @author Brian Wellington + */ + +public +class NameTooLongException extends WireParseException { + + public + NameTooLongException() { + super(); + } + + public + NameTooLongException(String s) { + super(s); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/RelativeNameException.java b/src/dorkbox/network/dns/exceptions/RelativeNameException.java new file mode 100644 index 00000000..3f729aaf --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/RelativeNameException.java @@ -0,0 +1,27 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +import dorkbox.network.dns.Name; + +/** + * An exception thrown when a relative name is passed as an argument to + * a method requiring an absolute name. + * + * @author Brian Wellington + */ + +public +class RelativeNameException extends IllegalArgumentException { + + public + RelativeNameException(Name name) { + super("'" + name + "' is not an absolute name"); + } + + public + RelativeNameException(String s) { + super(s); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/TextParseException.java b/src/dorkbox/network/dns/exceptions/TextParseException.java new file mode 100644 index 00000000..186fb37f --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/TextParseException.java @@ -0,0 +1,26 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +import java.io.IOException; + +/** + * An exception thrown when unable to parse text. + * + * @author Brian Wellington + */ + +public +class TextParseException extends IOException { + + public + TextParseException() { + super(); + } + + public + TextParseException(String s) { + super(s); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/WireParseException.java b/src/dorkbox/network/dns/exceptions/WireParseException.java new file mode 100644 index 00000000..f8ea4ce9 --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/WireParseException.java @@ -0,0 +1,32 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +import java.io.IOException; + +/** + * An exception thrown when a DNS message is invalid. + * + * @author Brian Wellington + */ + +public +class WireParseException extends IOException { + + public + WireParseException() { + super(); + } + + public + WireParseException(String s) { + super(s); + } + + public + WireParseException(String s, Throwable cause) { + super(s); + initCause(cause); + } + +} diff --git a/src/dorkbox/network/dns/exceptions/ZoneTransferException.java b/src/dorkbox/network/dns/exceptions/ZoneTransferException.java new file mode 100644 index 00000000..37e47325 --- /dev/null +++ b/src/dorkbox/network/dns/exceptions/ZoneTransferException.java @@ -0,0 +1,24 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.exceptions; + +/** + * An exception thrown when a zone transfer fails. + * + * @author Brian Wellington + */ + +public +class ZoneTransferException extends Exception { + + public + ZoneTransferException() { + super(); + } + + public + ZoneTransferException(String s) { + super(s); + } + +} diff --git a/src/dorkbox/network/dns/handlers/DnsHandler.java b/src/dorkbox/network/dns/handlers/DnsHandler.java new file mode 100644 index 00000000..40a60508 --- /dev/null +++ b/src/dorkbox/network/dns/handlers/DnsHandler.java @@ -0,0 +1,17 @@ +package dorkbox.network.dns.handlers; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.DatagramChannel; + +public +class DnsHandler extends ChannelInitializer { + @Override + protected + void initChannel(DatagramChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new DnsMessageEncoder()); + pipeline.addLast(new DnsMessageDecoder()); + // pipeline.addLast(new DNSMessageDecoder()); + } +} diff --git a/src/dorkbox/network/dns/handlers/DnsMessageDecoder.java b/src/dorkbox/network/dns/handlers/DnsMessageDecoder.java new file mode 100644 index 00000000..b4e1a4bb --- /dev/null +++ b/src/dorkbox/network/dns/handlers/DnsMessageDecoder.java @@ -0,0 +1,46 @@ +package dorkbox.network.dns.handlers; + +import java.util.List; + +import org.handwerkszeug.dns.DNSMessage; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.MessageToMessageDecoder; + + +public +class DnsMessageDecoder extends MessageToMessageDecoder { + @Override + public + void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception { + // Channel channel = context.channel(); + + System.err.println("POW! "); + cause.printStackTrace(); + // this.logger.error("Unexpected exception while trying to send/receive data on Client remote (network) channel. ({})" + + // System.getProperty("line.separator"), channel.remoteAddress(), cause); + // if (channel.isOpen()) { + // channel.close(); + // } + } + + @Override + protected + void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) throws Exception { + System.err.println("READING MESSAGE"); + final ByteBuf buf = packet.content(); + + boolean success = false; + try { + DNSMessage dnsMessage = new DNSMessage(buf); + out.add(dnsMessage); + success = true; + } finally { + if (!success) { + buf.release(); + } + } + } +} diff --git a/src/dorkbox/network/dns/handlers/DnsMessageEncoder.java b/src/dorkbox/network/dns/handlers/DnsMessageEncoder.java new file mode 100644 index 00000000..db24a7db --- /dev/null +++ b/src/dorkbox/network/dns/handlers/DnsMessageEncoder.java @@ -0,0 +1,76 @@ +package dorkbox.network.dns.handlers; + +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.records.DnsMessage; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + * An encoder which serializes a Java object into a {@link ByteBuf}. + *

+ * Please note that the serialized form this encoder produces is not + * compatible with the standard {@link ObjectInputStream}. Please use + * {@link ObjectDecoder} or {@link ObjectDecoderInputStream} to ensure the + * interoperability with this encoder. + */ + + +@ChannelHandler.Sharable +public +class DnsMessageEncoder extends MessageToByteEncoder { + + + // public + // class ObjectEncoder extends MessageToByteEncoder { + // private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; + + // @Override + // protected + // void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { + // int startIdx = out.writerIndex(); + // + // ByteBufOutputStream bout = new ByteBufOutputStream(out); + // ObjectOutputStream oout = null; + // try { + // bout.write(LENGTH_PLACEHOLDER); + // oout = new CompactObjectOutputStream(bout); + // oout.writeObject(msg); + // oout.flush(); + // } finally { + // if (oout != null) { + // oout.close(); + // } + // else { + // bout.close(); + // } + // } + // + // int endIdx = out.writerIndex(); + // + // out.setInt(startIdx, endIdx - startIdx - 4); + // } + + @Override + protected + void encode(final ChannelHandlerContext ctx, final DnsMessage msg, final ByteBuf out) throws Exception { + System.err.println("WRITING MESSAGE"); + final DnsOutput outd = new DnsOutput(out); + msg.toWire(outd); + } + + @Override + public + void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception { + // Channel channel = context.channel(); + + System.err.println("POW! "); + cause.printStackTrace(); + // this.logger.error("Unexpected exception while trying to send/receive data on Client remote (network) channel. ({})" + + // System.getProperty("line.separator"), channel.remoteAddress(), cause); + // if (channel.isOpen()) { + // channel.close(); + // } + } +} diff --git a/src/dorkbox/network/dns/handlers/DnsServerHandler.java b/src/dorkbox/network/dns/handlers/DnsServerHandler.java new file mode 100644 index 00000000..fd135b91 --- /dev/null +++ b/src/dorkbox/network/dns/handlers/DnsServerHandler.java @@ -0,0 +1,56 @@ +package dorkbox.network.dns.handlers; + +import org.handwerkszeug.dns.server.DNSMessageDecoder; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelPipeline; + +/** + * + */ +public +class DnsServerHandler extends ChannelInboundHandlerAdapter { + + protected DNSMessageDecoder decoder = new DNSMessageDecoder(); + + public + DnsServerHandler(final String threadName) { + } + + @Override + public final + void channelRegistered(final ChannelHandlerContext context) throws Exception { + boolean success = false; + try { + initChannel(context.channel()); + context.fireChannelRegistered(); + success = true; + } catch (Throwable t) { + // this.logger.error("Failed to initialize a channel. Closing: {}", context.channel(), t); + } finally { + if (!success) { + context.close(); + } + } + } + + /** + * STEP 1: Channel is first created + */ + protected + void initChannel(final Channel channel) { + ChannelPipeline pipeline = channel.pipeline(); + + /////////////////////// + // DECODE (or upstream) + /////////////////////// + pipeline.addLast("decoder", this.decoder); + + // ENCODE (or downstream) + ///////////////////////// + pipeline.addLast("fowarder", new ForwardingHandler()); + // pipeline.addLast("fowarder", new ForwardingHandler(this.config, this.clientChannelFactory)); + } +} diff --git a/src/dorkbox/network/dns/handlers/ForwardingHandler.java b/src/dorkbox/network/dns/handlers/ForwardingHandler.java new file mode 100644 index 00000000..f7207e0f --- /dev/null +++ b/src/dorkbox/network/dns/handlers/ForwardingHandler.java @@ -0,0 +1,156 @@ +package dorkbox.network.dns.handlers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOutboundHandlerAdapter; + +public class ForwardingHandler extends ChannelOutboundHandlerAdapter { + + static final Logger LOG = LoggerFactory.getLogger(ForwardingHandler.class); + + // protected ServerConfiguration config; + // protected ChannelFactory clientChannelFactory; + + // public + // ForwardingHandler(ServerConfiguration config, + // ChannelFactory clientChannelFactory) { + // this.config = config; + // this.clientChannelFactory = clientChannelFactory; + // } + + @Override + public + void read(final ChannelHandlerContext ctx) throws Exception { + super.read(ctx); + } + + + + // @Override + // public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) + // throws Exception { + // final DNSMessage original = DNSMessage.class.cast(e.getMessage()); + // + // ClientBootstrap cb = new ClientBootstrap(this.clientChannelFactory); + // cb.setOption("broadcast", "false"); + // cb.setPipelineFactory(new ChannelPipelineFactory() { + // @Override + // public ChannelPipeline getPipeline() throws Exception { + // return Channels.pipeline(new ClientHanler(original, e + // .getChannel(), e.getRemoteAddress())); + // } + // }); + // + // List newlist = new ArrayList( + // this.config.getForwarders()); + // sendRequest(e, original, cb, newlist); + // } + // + // protected void sendRequest(final MessageEvent e, final DNSMessage original, final ClientBootstrap bootstrap, final List forwarders) { + // if (0 < forwarders.size()) { + // SocketAddress sa = forwarders.remove(0); + // LOG.debug("send to {}", sa); + // + // ChannelFuture f = bootstrap.connect(sa); + // ChannelBuffer newone = ChannelBuffers.buffer(512); + // DNSMessage msg = new DNSMessage(original); + // msg.write(newone); + // newone.resetReaderIndex(); + // final Channel c = f.getChannel(); + // + // if (LOG.isDebugEnabled()) { + // LOG.debug( + // "STATUS : [isOpen/isConnected/isWritable {}] {} {}", + // new Object[] { + // new boolean[] { c.isOpen(), c.isConnected(), + // c.isWritable() }, c.getRemoteAddress(), + // c.getClass() }); + // } + // + // c.write(newone, sa).addListener(new ChannelFutureListener() { + // @Override + // public void operationComplete(ChannelFuture future) + // throws Exception { + // LOG.debug("request complete isSuccess : {}", + // future.isSuccess()); + // if (future.isSuccess() == false) { + // if (0 < forwarders.size()) { + // sendRequest(e, original, bootstrap, forwarders); + // } else { + // original.header().rcode(RCode.ServFail); + // ChannelBuffer buffer = ChannelBuffers.buffer(512); + // original.write(buffer); + // // close inbound channel + // e.getChannel().write(buffer) + // .addListener(ChannelFutureListener.CLOSE); + // } + // } + // } + // }); + // + // // f.awaitUninterruptibly(30, TimeUnit.SECONDS); + // } + // } + + @Override + public + void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { + LOG.error("ForwardingHandler#exceptionCaught"); + LOG.error(cause.getMessage(), cause); + } + + + protected class ClientHanler extends ChannelInboundHandlerAdapter { + + // protected DNSMessage original; + // + // protected Channel originalChannel; + // + // protected SocketAddress originalAddress; + // + // public ClientHanler(DNSMessage msg, Channel c, SocketAddress sa) { + // this.original = msg; + // this.originalChannel = c; + // this.originalAddress = sa; + // } + // + + + + + + + // @Override + // public void messageReceived(ChannelHandlerContext ctx, final MessageEvent e) throws Exception { + // LOG.debug("ClientHanler#messageReceived"); + // ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); + // DNSMessage msg = new DNSMessage(buffer); + // msg.header().id(this.original.header().id()); + // ChannelBuffer newone = ChannelBuffers.buffer(buffer.capacity()); + // msg.write(newone); + // newone.resetReaderIndex(); + // this.originalChannel.write(newone, this.originalAddress) + // .addListener(new ChannelFutureListener() { + // @Override + // public void operationComplete(ChannelFuture future) + // throws Exception { + // e.getChannel().close(); + // } + // }); + // } + // + + @Override + public + void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { + LOG.error("ClientHanler#exceptionCaught"); + LOG.error(cause.getMessage(), cause); + // e.getFuture() + // .setFailure(t); + ctx.channel().close(); + } + } +} diff --git a/src/dorkbox/network/dns/record/MailExchangerRecord.java b/src/dorkbox/network/dns/record/MailExchangerRecord.java deleted file mode 100644 index 21834fc2..00000000 --- a/src/dorkbox/network/dns/record/MailExchangerRecord.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.record; - -/** - * Represents an MX record. - */ -public -class MailExchangerRecord { - - private final int priority; - private final String name; - - /** - * @param priority lower is more preferred - * @param name e-mail address in the format admin.example.com, which - * represents admin@example.com - */ - public - MailExchangerRecord(int priority, String name) { - this.priority = priority; - this.name = name; - } - - public - int priority() { - return this.priority; - } - - /** - * Returns the MX (an e-mail address) in the format - * admin.example.com, which represents admin@example.com. - */ - public - String name() { - return this.name; - } -} diff --git a/src/dorkbox/network/dns/record/ServiceRecord.java b/src/dorkbox/network/dns/record/ServiceRecord.java deleted file mode 100644 index 7a723b8d..00000000 --- a/src/dorkbox/network/dns/record/ServiceRecord.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.record; - -/** - * Represents an SRV record. - *

- * This is the location (or hostname and port), of servers for specified services. For example, a service "http" - * may be running on the server "example.com" on port 80. - */ -public -class ServiceRecord { - - private final int priority; - private final int weight; - private final int port; - private final String name; - private final String protocol; - private final String service; - private final String target; - - /** - * @param fullPath the name first read in the SRV record which contains the - * service, the protocol, and the name of the server being - * queried. The format is {@code _service._protocol.hostname}, or - * for example {@code _http._tcp.example.com} - * @param priority relative priority of this service, range 0-65535 (lower is - * higher priority) - * @param weight determines how often multiple services will be used in the - * event they have the same priority (greater weight means - * service is used more often) - * @param port the port for the service - * @param target the name of the host for the service - */ - public - ServiceRecord(String fullPath, int priority, int weight, int port, String target) { - String[] parts = fullPath.split("\\.", 3); - this.service = parts[0]; - this.protocol = parts[1]; - this.name = parts[2]; - this.priority = priority; - this.weight = weight; - this.port = port; - this.target = target; - } - - public - int priority() { - return this.priority; - } - - public - int weight() { - return this.weight; - } - - public - int port() { - return this.port; - } - - public - String name() { - return this.name; - } - - public - String protocol() { - return this.protocol; - } - - /** - * @return the service's name (i.e. "_http"). - */ - public - String service() { - return this.service; - } - - /** - * @return he name of the host for the service. - */ - public - String target() { - return this.target; - } -} diff --git a/src/dorkbox/network/dns/record/StartOfAuthorityRecord.java b/src/dorkbox/network/dns/record/StartOfAuthorityRecord.java deleted file mode 100644 index 1b25ff64..00000000 --- a/src/dorkbox/network/dns/record/StartOfAuthorityRecord.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package dorkbox.network.dns.record; - -/** - * Represents an SOA record. - *

- * There can only be one SOA record per zone. - */ -public -class StartOfAuthorityRecord { - - private final String primaryNameServer; - private final String responsiblePerson; - private final long serial; - private final int refreshTime; - private final int retryTime; - private final int expireTime; - private final long minimumTtl; - - /** - * @param primaryNameServer any name server that will respond authoritatively for the domain - * @param responsiblePerson e-mail address of person responsible for this zone - * @param serial a serial number that must be incremented when changes are - * made. Recommended format is YYYYMMDDnn. For example, if the - * primary name server is changed on June 19, 2013, then the - * serial would be 2013061901. If it is changed again on the same - * day it would be 2013061902 - * @param refreshTime number of seconds a secondary name server waits, after getting - * a copy of the zone, before it checks the zone again for - * changes - * @param retryTime number of seconds to wait after a failed refresh attempt - * before another attempt to refresh is made - * @param expireTime number of seconds secondary name server can hold information - * before it is considered not authoritative - * @param minimumTtl number of seconds that records in the zone are valid for (if a - * record has a higher TTL, it overrides this value which is just - * a minimum) - */ - public - StartOfAuthorityRecord(String primaryNameServer, - String responsiblePerson, - long serial, - int refreshTime, - int retryTime, - int expireTime, - long minimumTtl) { - - this.primaryNameServer = primaryNameServer; - this.responsiblePerson = responsiblePerson; - this.serial = serial; - this.refreshTime = refreshTime; - this.retryTime = retryTime; - this.expireTime = expireTime; - this.minimumTtl = minimumTtl; - } - - /** - * Returns the primary name server. - */ - public - String primaryNameServer() { - return this.primaryNameServer; - } - - /** - * Returns the responsible person's e-mail. - */ - public - String responsiblePerson() { - return this.responsiblePerson; - } - - /** - * Returns the zone's serial number, usually in format YYYYMMDDnn. - */ - public - long serial() { - return this.serial; - } - - /** - * Returns time between refreshes for secondary name servers. - */ - public - int refreshTime() { - return this.refreshTime; - } - - /** - * Returns time between retries for failed refresh attempts. - */ - public - int retryTime() { - return this.retryTime; - } - - /** - * Returns time before information stored in secondary name servers becomes - * non authoritative. - */ - public - int expireTime() { - return this.expireTime; - } - - /** - * Returns the minimum TTL for records in the zone (if the record has a - * higher TTL, that value should be used as the TTL). - */ - public - long minimumTtl() { - return this.minimumTtl; - } - -} diff --git a/src/dorkbox/network/dns/records/A6Record.java b/src/dorkbox/network/dns/records/A6Record.java new file mode 100644 index 00000000..214bac7d --- /dev/null +++ b/src/dorkbox/network/dns/records/A6Record.java @@ -0,0 +1,147 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * A6 Record - maps a domain name to an IPv6 address (experimental) + * + * @author Brian Wellington + */ + +public +class A6Record extends DnsRecord { + + private static final long serialVersionUID = -8815026887337346789L; + + private int prefixBits; + private InetAddress suffix; + private Name prefix; + + A6Record() {} + + @Override + DnsRecord getObject() { + return new A6Record(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + prefixBits = in.readU8(); + int suffixbits = 128 - prefixBits; + int suffixbytes = (suffixbits + 7) / 8; + if (prefixBits < 128) { + byte[] bytes = new byte[16]; + in.readByteArray(bytes, 16 - suffixbytes, suffixbytes); + suffix = InetAddress.getByAddress(bytes); + } + if (prefixBits > 0) { + prefix = new Name(in); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(prefixBits); + if (suffix != null) { + int suffixbits = 128 - prefixBits; + int suffixbytes = (suffixbits + 7) / 8; + byte[] data = suffix.getAddress(); + out.writeByteArray(data, 16 - suffixbytes, suffixbytes); + } + if (prefix != null) { + prefix.toWire(out, null, canonical); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(prefixBits); + if (suffix != null) { + sb.append(" "); + sb.append(suffix.getHostAddress()); + } + if (prefix != null) { + sb.append(" "); + sb.append(prefix); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + prefixBits = st.getUInt8(); + if (prefixBits > 128) { + throw st.exception("prefix bits must be [0..128]"); + } + else if (prefixBits < 128) { + String s = st.getString(); + try { + suffix = Address.getByAddress(s, Address.IPv6); + } catch (UnknownHostException e) { + throw st.exception("invalid IPv6 address: " + s); + } + } + if (prefixBits > 0) { + prefix = st.getName(origin); + } + } + + /** + * Creates an A6 Record from the given data + * + * @param prefixBits The number of bits in the address prefix + * @param suffix The address suffix + * @param prefix The name of the prefix + */ + public + A6Record(Name name, int dclass, long ttl, int prefixBits, InetAddress suffix, Name prefix) { + super(name, DnsRecordType.A6, dclass, ttl); + this.prefixBits = checkU8("prefixBits", prefixBits); + if (suffix != null && Address.familyOf(suffix) != Address.IPv6) { + throw new IllegalArgumentException("invalid IPv6 address"); + } + this.suffix = suffix; + if (prefix != null) { + this.prefix = checkName("prefix", prefix); + } + } + + /** + * Returns the number of bits in the prefix + */ + public + int getPrefixBits() { + return prefixBits; + } + + /** + * Returns the address suffix + */ + public + InetAddress getSuffix() { + return suffix; + } + + /** + * Returns the address prefix + */ + public + Name getPrefix() { + return prefix; + } + +} diff --git a/src/dorkbox/network/dns/records/AAAARecord.java b/src/dorkbox/network/dns/records/AAAARecord.java new file mode 100644 index 00000000..7fd489a3 --- /dev/null +++ b/src/dorkbox/network/dns/records/AAAARecord.java @@ -0,0 +1,108 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * IPv6 Address Record - maps a domain name to an IPv6 address + * + * @author Brian Wellington + */ + +public +class AAAARecord extends DnsRecord { + + private static final long serialVersionUID = -4588601512069748050L; + + private byte[] address; + + AAAARecord() {} + + @Override + DnsRecord getObject() { + return new AAAARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + address = in.readByteArray(16); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(address); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + InetAddress addr; + try { + addr = InetAddress.getByAddress(null, address); + } catch (UnknownHostException e) { + return; + } + if (addr.getAddress().length == 4) { + // Deal with Java's broken handling of mapped IPv4 addresses. + sb.append("0:0:0:0:0:ffff:"); + int high = ((address[12] & 0xFF) << 8) + (address[13] & 0xFF); + int low = ((address[14] & 0xFF) << 8) + (address[15] & 0xFF); + sb.append(Integer.toHexString(high)); + sb.append(':'); + sb.append(Integer.toHexString(low)); + + return; + } + + sb.append(addr.getHostAddress()); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + address = st.getAddressBytes(Address.IPv6); + } + + /** + * Creates an AAAA Record from the given data + * + * @param address The address suffix + */ + public + AAAARecord(Name name, int dclass, long ttl, InetAddress address) { + super(name, DnsRecordType.AAAA, dclass, ttl); + if (Address.familyOf(address) != Address.IPv6) { + throw new IllegalArgumentException("invalid IPv6 address"); + } + this.address = address.getAddress(); + } + + /** + * Returns the address + */ + public + InetAddress getAddress() { + try { + if (name == null) { + return InetAddress.getByAddress(address); + } + else { + return InetAddress.getByAddress(name.toString(true), address); + } + } catch (UnknownHostException e) { + return null; + } + } +} diff --git a/src/dorkbox/network/dns/records/AFSDBRecord.java b/src/dorkbox/network/dns/records/AFSDBRecord.java new file mode 100644 index 00000000..900852df --- /dev/null +++ b/src/dorkbox/network/dns/records/AFSDBRecord.java @@ -0,0 +1,53 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * AFS Data Base Record - maps a domain name to the name of an AFS cell + * database server. + * + * @author Brian Wellington + */ + +public +class AFSDBRecord extends U16NameBase { + + private static final long serialVersionUID = 3034379930729102437L; + + AFSDBRecord() {} + + @Override + DnsRecord getObject() { + return new AFSDBRecord(); + } + + /** + * Creates an AFSDB Record from the given data. + * + * @param subtype Indicates the type of service provided by the host. + * @param host The host providing the service. + */ + public + AFSDBRecord(Name name, int dclass, long ttl, int subtype, Name host) { + super(name, DnsRecordType.AFSDB, dclass, ttl, subtype, "subtype", host, "host"); + } + + /** + * Gets the subtype indicating the service provided by the host. + */ + public + int getSubtype() { + return getU16Field(); + } + + /** + * Gets the host providing service for the domain. + */ + public + Name getHost() { + return getNameField(); + } +} diff --git a/src/dorkbox/network/dns/records/APLRecord.java b/src/dorkbox/network/dns/records/APLRecord.java new file mode 100644 index 00000000..920436b8 --- /dev/null +++ b/src/dorkbox/network/dns/records/APLRecord.java @@ -0,0 +1,305 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * APL - Address Prefix List. See RFC 3123. + * + * @author Brian Wellington + */ + +/* + * Note: this currently uses the same constants as the Address class; + * this could change if more constants are defined for APL records. + */ + +public +class APLRecord extends DnsRecord { + + private static final long serialVersionUID = -1348173791712935864L; + private List elements; + + + public static + class Element { + public final int family; + public final boolean negative; + public final int prefixLength; + public final Object address; + + /** + * Creates an APL element corresponding to an IPv4 or IPv6 prefix. + * + * @param negative Indicates if this prefix is a negation. + * @param address The IPv4 or IPv6 address. + * @param prefixLength The length of this prefix, in bits. + * + * @throws IllegalArgumentException The prefix length is invalid. + */ + public + Element(boolean negative, InetAddress address, int prefixLength) { + this(Address.familyOf(address), negative, address, prefixLength); + } + + private + Element(int family, boolean negative, Object address, int prefixLength) { + this.family = family; + this.negative = negative; + this.address = address; + this.prefixLength = prefixLength; + if (!validatePrefixLength(family, prefixLength)) { + throw new IllegalArgumentException("invalid prefix " + "length"); + } + } + + public + int hashCode() { + return address.hashCode() + prefixLength + (negative ? 1 : 0); + } + + public + boolean equals(Object arg) { + if (arg == null || !(arg instanceof Element)) { + return false; + } + Element elt = (Element) arg; + return (family == elt.family && negative == elt.negative && prefixLength == elt.prefixLength && address.equals(elt.address)); + } + + public + String toString() { + StringBuilder sb = new StringBuilder(); + if (negative) { + sb.append("!"); + } + sb.append(family); + sb.append(":"); + if (family == Address.IPv4 || family == Address.IPv6) { + sb.append(((InetAddress) address).getHostAddress()); + } + else { + sb.append(base16.toString((byte[]) address)); + } + sb.append("/"); + sb.append(prefixLength); + return sb.toString(); + } + } + + APLRecord() {} + + @Override + DnsRecord getObject() { + return new APLRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + elements = new ArrayList(1); + while (in.remaining() != 0) { + int family = in.readU16(); + int prefix = in.readU8(); + int length = in.readU8(); + boolean negative = (length & 0x80) != 0; + length &= ~0x80; + + byte[] data = in.readByteArray(length); + Element element; + if (!validatePrefixLength(family, prefix)) { + throw new WireParseException("invalid prefix length"); + } + + if (family == Address.IPv4 || family == Address.IPv6) { + data = parseAddress(data, Address.addressLength(family)); + InetAddress addr = InetAddress.getByAddress(data); + element = new Element(negative, addr, prefix); + } + else { + element = new Element(family, negative, data, prefix); + } + elements.add(element); + + } + } + + private static + boolean validatePrefixLength(int family, int prefixLength) { + if (prefixLength < 0 || prefixLength >= 256) { + return false; + } + if ((family == Address.IPv4 && prefixLength > 32) || (family == Address.IPv6 && prefixLength > 128)) { + return false; + } + return true; + } + + private static + byte[] parseAddress(byte[] in, int length) throws WireParseException { + if (in.length > length) { + throw new WireParseException("invalid address length"); + } + if (in.length == length) { + return in; + } + byte[] out = new byte[length]; + System.arraycopy(in, 0, out, 0, in.length); + return out; + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Element element = (Element) it.next(); + int length = 0; + byte[] data; + if (element.family == Address.IPv4 || element.family == Address.IPv6) { + InetAddress addr = (InetAddress) element.address; + data = addr.getAddress(); + length = addressLength(data); + } + else { + data = (byte[]) element.address; + length = data.length; + } + int wlength = length; + if (element.negative) { + wlength |= 0x80; + } + out.writeU16(element.family); + out.writeU8(element.prefixLength); + out.writeU8(wlength); + out.writeByteArray(data, 0, length); + } + } + + @Override + void rrToString(StringBuilder sb) { + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Element element = (Element) it.next(); + sb.append(element); + if (it.hasNext()) { + sb.append(" "); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + elements = new ArrayList(1); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) { + break; + } + + boolean negative = false; + int family = 0; + int prefix = 0; + + String s = t.value; + int start = 0; + if (s.startsWith("!")) { + negative = true; + start = 1; + } + int colon = s.indexOf(':', start); + if (colon < 0) { + throw st.exception("invalid address prefix element"); + } + int slash = s.indexOf('/', colon); + if (slash < 0) { + throw st.exception("invalid address prefix element"); + } + + String familyString = s.substring(start, colon); + String addressString = s.substring(colon + 1, slash); + String prefixString = s.substring(slash + 1); + + try { + family = Integer.parseInt(familyString); + } catch (NumberFormatException e) { + throw st.exception("invalid family"); + } + if (family != Address.IPv4 && family != Address.IPv6) { + throw st.exception("unknown family"); + } + + try { + prefix = Integer.parseInt(prefixString); + } catch (NumberFormatException e) { + throw st.exception("invalid prefix length"); + } + + if (!validatePrefixLength(family, prefix)) { + throw st.exception("invalid prefix length"); + } + + byte[] bytes = Address.toByteArray(addressString, family); + if (bytes == null) { + throw st.exception("invalid IP address " + addressString); + } + + InetAddress address = InetAddress.getByAddress(bytes); + elements.add(new Element(negative, address, prefix)); + } + st.unget(); + } + + private static + int addressLength(byte[] addr) { + for (int i = addr.length - 1; i >= 0; i--) { + if (addr[i] != 0) { + return i + 1; + } + } + return 0; + } + + /** + * Creates an APL Record from the given data. + * + * @param elements The list of APL elements. + */ + public + APLRecord(Name name, int dclass, long ttl, List elements) { + super(name, DnsRecordType.APL, dclass, ttl); + this.elements = new ArrayList(elements.size()); + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Object o = it.next(); + if (!(o instanceof Element)) { + throw new IllegalArgumentException("illegal element"); + } + Element element = (Element) o; + if (element.family != Address.IPv4 && element.family != Address.IPv6) { + throw new IllegalArgumentException("unknown family"); + } + this.elements.add(element); + + } + } + + /** + * Returns the list of APL elements. + */ + public + List getElements() { + return elements; + } + +} diff --git a/src/dorkbox/network/dns/records/ARecord.java b/src/dorkbox/network/dns/records/ARecord.java new file mode 100644 index 00000000..9d96b9fb --- /dev/null +++ b/src/dorkbox/network/dns/records/ARecord.java @@ -0,0 +1,106 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Address Record - maps a domain name to an Internet address + * + * @author Brian Wellington + */ + +public +class ARecord extends DnsRecord { + + private static final long serialVersionUID = -2172609200849142323L; + + private int addr; + + ARecord() {} + + @Override + DnsRecord getObject() { + return new ARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + addr = fromArray(in.readByteArray(4)); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU32(((long) addr) & 0xFFFFFFFFL); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(Address.toDottedQuad(toArray(addr))); + } + + private static + byte[] toArray(int addr) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) ((addr >>> 24) & 0xFF); + bytes[1] = (byte) ((addr >>> 16) & 0xFF); + bytes[2] = (byte) ((addr >>> 8) & 0xFF); + bytes[3] = (byte) (addr & 0xFF); + return bytes; + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + addr = fromArray(st.getAddressBytes(Address.IPv4)); + } + + private static + int fromArray(byte[] array) { + return (((array[0] & 0xFF) << 24) | ((array[1] & 0xFF) << 16) | ((array[2] & 0xFF) << 8) | (array[3] & 0xFF)); + } + + /** + * Creates an A Record from the given data + * + * @param address The address that the name refers to + */ + public + ARecord(Name name, int dclass, long ttl, InetAddress address) { + super(name, DnsRecordType.A, dclass, ttl); + if (Address.familyOf(address) != Address.IPv4) { + throw new IllegalArgumentException("invalid IPv4 address"); + } + addr = fromArray(address.getAddress()); + } + + /** + * Returns the Internet address + */ + public + InetAddress getAddress() { + try { + if (name == null) { + return InetAddress.getByAddress(toArray(addr)); + } + else { + return InetAddress.getByAddress(name.toString(true), toArray(addr)); + } + } catch (UnknownHostException e) { + return null; + } + } + +} diff --git a/src/dorkbox/network/dns/records/CAARecord.java b/src/dorkbox/network/dns/records/CAARecord.java new file mode 100644 index 00000000..864713f8 --- /dev/null +++ b/src/dorkbox/network/dns/records/CAARecord.java @@ -0,0 +1,122 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Certification Authority Authorization + * + * @author Brian Wellington + */ + +public +class CAARecord extends DnsRecord { + + private static final long serialVersionUID = 8544304287274216443L; + private int flags; + private byte[] tag; + private byte[] value; + + + public static + class Flags { + public static final int IssuerCritical = 128; + + private + Flags() {} + } + + CAARecord() {} + + @Override + DnsRecord getObject() { + return new CAARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + flags = in.readU8(); + tag = in.readCountedString(); + value = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(flags); + out.writeCountedString(tag); + out.writeByteArray(value); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(flags); + sb.append(" "); + sb.append(byteArrayToString(tag, false)); + sb.append(" "); + sb.append(byteArrayToString(value, true)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + flags = st.getUInt8(); + try { + tag = byteArrayFromString(st.getString()); + value = byteArrayFromString(st.getString()); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + } + + /** + * Creates an CAA Record from the given data. + * + * @param flags The flags. + * @param tag The tag. + * @param value The value. + */ + public + CAARecord(Name name, int dclass, long ttl, int flags, String tag, String value) { + super(name, DnsRecordType.CAA, dclass, ttl); + this.flags = checkU8("flags", flags); + try { + this.tag = byteArrayFromString(tag); + this.value = byteArrayFromString(value); + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * Returns the flags. + */ + public + int getFlags() { + return flags; + } + + /** + * Returns the tag. + */ + public + String getTag() { + return byteArrayToString(tag, false); + } + + /** + * Returns the value + */ + public + String getValue() { + return byteArrayToString(value, false); + } + +} diff --git a/src/dorkbox/network/dns/records/CERTRecord.java b/src/dorkbox/network/dns/records/CERTRecord.java new file mode 100644 index 00000000..531f3cb9 --- /dev/null +++ b/src/dorkbox/network/dns/records/CERTRecord.java @@ -0,0 +1,257 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * Certificate Record - Stores a certificate associated with a name. The + * certificate might also be associated with a KEYRecord. + * + * @author Brian Wellington + * @see KEYRecord + */ + +public +class CERTRecord extends DnsRecord { + + /** + * PKIX (X.509v3) + */ + public static final int PKIX = CertificateType.PKIX; + /** + * Simple Public Key Infrastructure + */ + public static final int SPKI = CertificateType.SPKI; + /** + * Pretty Good Privacy + */ + public static final int PGP = CertificateType.PGP; + /** + * Certificate format defined by URI + */ + public static final int URI = CertificateType.URI; + /** + * Certificate format defined by IOD + */ + public static final int OID = CertificateType.OID; + private static final long serialVersionUID = 4763014646517016835L; + private int certType, keyTag; + private int alg; + private byte[] cert; + + + public static + class CertificateType { + /** + * PKIX (X.509v3) + */ + public static final int PKIX = 1; + /** + * Simple Public Key Infrastructure + */ + public static final int SPKI = 2; + /** + * Pretty Good Privacy + */ + public static final int PGP = 3; + /** + * URL of an X.509 data object + */ + public static final int IPKIX = 4; + /** + * URL of an SPKI certificate + */ + public static final int ISPKI = 5; + /** + * Fingerprint and URL of an OpenPGP packet + */ + public static final int IPGP = 6; + /** + * Attribute Certificate + */ + public static final int ACPKIX = 7; + /** + * URL of an Attribute Certificate + */ + public static final int IACPKIX = 8; + /** + * Certificate format defined by URI + */ + public static final int URI = 253; + /** + * Certificate format defined by OID + */ + public static final int OID = 254; + private static Mnemonic types = new Mnemonic("Certificate type", Mnemonic.CASE_UPPER); + + /** + * Certificate type identifiers. See RFC 4398 for more detail. + */ + + private + CertificateType() {} + + static { + types.setMaximum(0xFFFF); + types.setNumericAllowed(true); + + types.add(PKIX, "PKIX"); + types.add(SPKI, "SPKI"); + types.add(PGP, "PGP"); + types.add(PKIX, "IPKIX"); + types.add(SPKI, "ISPKI"); + types.add(PGP, "IPGP"); + types.add(PGP, "ACPKIX"); + types.add(PGP, "IACPKIX"); + types.add(URI, "URI"); + types.add(OID, "OID"); + } + + /** + * Converts a certificate type into its textual representation + */ + public static + String string(int type) { + return types.getText(type); + } + + /** + * Converts a textual representation of an certificate type into its + * numeric code. Integers in the range 0..65535 are also accepted. + * + * @param s The textual representation of the algorithm + * + * @return The algorithm code, or -1 on error. + */ + public static + int value(String s) { + return types.getValue(s); + } + } + + CERTRecord() {} + + @Override + DnsRecord getObject() { + return new CERTRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + certType = in.readU16(); + keyTag = in.readU16(); + alg = in.readU8(); + cert = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(certType); + out.writeU16(keyTag); + out.writeU8(alg); + out.writeByteArray(cert); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(certType); + sb.append(" "); + sb.append(keyTag); + sb.append(" "); + sb.append(alg); + + if (cert != null) { + if (Options.check("multiline")) { + sb.append(" ("); + sb.append(OS.LINE_SEPARATOR); + + sb.append(Base64Fast.formatString(Base64Fast.encode2(cert), 64, "\t", true)); + } + else { + sb.append(" "); + sb.append(Base64Fast.encode2(cert)); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String certTypeString = st.getString(); + certType = CertificateType.value(certTypeString); + if (certType < 0) { + throw st.exception("Invalid certificate type: " + certTypeString); + } + keyTag = st.getUInt16(); + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) { + throw st.exception("Invalid algorithm: " + algString); + } + cert = st.getBase64(); + } + + /** + * Creates a CERT Record from the given data + * + * @param certType The type of certificate (see constants) + * @param keyTag The ID of the associated KEYRecord, if present + * @param alg The algorithm of the associated KEYRecord, if present + * @param cert Binary data representing the certificate + */ + public + CERTRecord(Name name, int dclass, long ttl, int certType, int keyTag, int alg, byte[] cert) { + super(name, DnsRecordType.CERT, dclass, ttl); + this.certType = checkU16("certType", certType); + this.keyTag = checkU16("keyTag", keyTag); + this.alg = checkU8("alg", alg); + this.cert = cert; + } + + /** + * Returns the type of certificate + */ + public + int getCertType() { + return certType; + } + + /** + * Returns the ID of the associated KEYRecord, if present + */ + public + int getKeyTag() { + return keyTag; + } + + /** + * Returns the algorithm of the associated KEYRecord, if present + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the binary representation of the certificate + */ + public + byte[] getCert() { + return cert; + } + +} diff --git a/src/dorkbox/network/dns/records/CNAMERecord.java b/src/dorkbox/network/dns/records/CNAMERecord.java new file mode 100644 index 00000000..2844ac76 --- /dev/null +++ b/src/dorkbox/network/dns/records/CNAMERecord.java @@ -0,0 +1,52 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * CNAME Record - maps an alias to its real name + * + * @author Brian Wellington + */ + +public +class CNAMERecord extends SingleCompressedNameBase { + + private static final long serialVersionUID = -4020373886892538580L; + + CNAMERecord() {} + + @Override + DnsRecord getObject() { + return new CNAMERecord(); + } + + /** + * Creates a new CNAMERecord with the given data + * + * @param alias The name to which the CNAME alias points + */ + public + CNAMERecord(Name name, int dclass, long ttl, Name alias) { + super(name, DnsRecordType.CNAME, dclass, ttl, alias, "alias"); + } + + /** + * Gets the target of the CNAME Record + */ + public + Name getTarget() { + return getSingleName(); + } + + /** + * Gets the alias specified by the CNAME Record + */ + public + Name getAlias() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/ClientSubnetOption.java b/src/dorkbox/network/dns/records/ClientSubnetOption.java new file mode 100644 index 00000000..e37f60de --- /dev/null +++ b/src/dorkbox/network/dns/records/ClientSubnetOption.java @@ -0,0 +1,187 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Address; + +/** + * The Client Subnet EDNS Option, defined in + * http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-00 + * ("Client subnet in DNS requests"). + *

+ * The option is used to convey information about the IP address of the + * originating client, so that an authoritative server can make decisions + * based on this address, rather than the address of the intermediate + * caching name server. + *

+ * The option is transmitted as part of an OPTRecord in the additional section + * of a DNS message, as defined by RFC 2671 (EDNS0). + *

+ * The wire format of the option contains a 2-byte length field (1 for IPv4, 2 + * for IPv6), a 1-byte source netmask, a 1-byte scope netmask, and an address + * truncated to the source netmask length (where the final octet is padded with + * bits set to 0) + * + * @author Brian Wellington + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + * @see OPTRecord + */ +public +class ClientSubnetOption extends EDNSOption { + + private static final long serialVersionUID = -3868158449890266347L; + + private int family; + private int sourceNetmask; + private int scopeNetmask; + private InetAddress address; + + ClientSubnetOption() { + super(EDNSOption.Code.CLIENT_SUBNET); + } + + /** + * Construct a Client Subnet option with scope netmask set to 0. + * + * @param sourceNetmask The length of the netmask pertaining to the query. + * In replies, it mirrors the same value as in the requests. + * @param address The address of the client. + * + * @see ClientSubnetOption + */ + public + ClientSubnetOption(int sourceNetmask, InetAddress address) { + this(sourceNetmask, 0, address); + } + + /** + * Construct a Client Subnet option. Note that the number of significant bits + * in the address must not be greater than the supplied source netmask. There + * may also be issues related to Java's handling of mapped addresses + * + * @param sourceNetmask The length of the netmask pertaining to the query. + * In replies, it mirrors the same value as in the requests. + * @param scopeNetmask The length of the netmask pertaining to the reply. + * In requests, it MUST be set to 0. In responses, this may or may not match + * the source netmask. + * @param address The address of the client. + */ + public + ClientSubnetOption(int sourceNetmask, int scopeNetmask, InetAddress address) { + super(EDNSOption.Code.CLIENT_SUBNET); + + this.family = Address.familyOf(address); + this.sourceNetmask = checkMaskLength("source netmask", this.family, sourceNetmask); + this.scopeNetmask = checkMaskLength("scope netmask", this.family, scopeNetmask); + this.address = Address.truncate(address, sourceNetmask); + + if (!address.equals(this.address)) { + throw new IllegalArgumentException("source netmask is not " + "valid for address"); + } + } + + private static + int checkMaskLength(String field, int family, int val) { + int max = Address.addressLength(family) * 8; + if (val < 0 || val > max) { + throw new IllegalArgumentException("\"" + field + "\" " + val + " must be in the range " + "[0.." + max + "]"); + } + return val; + } + + /** + * Returns the family of the network address. This will be either IPv4 (1) + * or IPv6 (2). + */ + public + int getFamily() { + return family; + } + + /** + * Returns the source netmask. + */ + public + int getSourceNetmask() { + return sourceNetmask; + } + + /** + * Returns the scope netmask. + */ + public + int getScopeNetmask() { + return scopeNetmask; + } + + /** + * Returns the IP address of the client. + */ + public + InetAddress getAddress() { + return address; + } + + @Override + void optionFromWire(DnsInput in) throws WireParseException { + family = in.readU16(); + if (family != Address.IPv4 && family != Address.IPv6) { + throw new WireParseException("unknown address family"); + } + sourceNetmask = in.readU8(); + if (sourceNetmask > Address.addressLength(family) * 8) { + throw new WireParseException("invalid source netmask"); + } + scopeNetmask = in.readU8(); + if (scopeNetmask > Address.addressLength(family) * 8) { + throw new WireParseException("invalid scope netmask"); + } + + // Read the truncated address + byte[] addr = in.readByteArray(); + if (addr.length != (sourceNetmask + 7) / 8) { + throw new WireParseException("invalid address"); + } + + // Convert it to a full length address. + byte[] fulladdr = new byte[Address.addressLength(family)]; + System.arraycopy(addr, 0, fulladdr, 0, addr.length); + + try { + address = InetAddress.getByAddress(fulladdr); + } catch (UnknownHostException e) { + throw new WireParseException("invalid address", e); + } + + InetAddress tmp = Address.truncate(address, sourceNetmask); + if (!tmp.equals(address)) { + throw new WireParseException("invalid padding"); + } + } + + @Override + void optionToWire(DnsOutput out) { + out.writeU16(family); + out.writeU8(sourceNetmask); + out.writeU8(scopeNetmask); + out.writeByteArray(address.getAddress(), 0, (sourceNetmask + 7) / 8); + } + + @Override + String optionToString() { + StringBuilder sb = new StringBuilder(); + sb.append(address.getHostAddress()); + sb.append("/"); + sb.append(sourceNetmask); + sb.append(", scope netmask "); + sb.append(scopeNetmask); + return sb.toString(); + } + +} diff --git a/src/dorkbox/network/dns/records/DHCIDRecord.java b/src/dorkbox/network/dns/records/DHCIDRecord.java new file mode 100644 index 00000000..192782c0 --- /dev/null +++ b/src/dorkbox/network/dns/records/DHCIDRecord.java @@ -0,0 +1,74 @@ +// Copyright (c) 2008 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; + +/** + * DHCID - Dynamic Host Configuration Protocol (DHCP) ID (RFC 4701) + * + * @author Brian Wellington + */ + +public +class DHCIDRecord extends DnsRecord { + + private static final long serialVersionUID = -8214820200808997707L; + + private byte[] data; + + DHCIDRecord() {} + + @Override + DnsRecord getObject() { + return new DHCIDRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + data = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(Base64Fast.encode2(data)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + data = st.getBase64(); + } + + /** + * Creates an DHCID Record from the given data + * + * @param data The binary data, which is opaque to DNS. + */ + public + DHCIDRecord(Name name, int dclass, long ttl, byte[] data) { + super(name, DnsRecordType.DHCID, dclass, ttl); + this.data = data; + } + + /** + * Returns the binary data. + */ + public + byte[] getData() { + return data; + } + +} diff --git a/src/dorkbox/network/dns/records/DLVRecord.java b/src/dorkbox/network/dns/records/DLVRecord.java new file mode 100644 index 00000000..fbab2d71 --- /dev/null +++ b/src/dorkbox/network/dns/records/DLVRecord.java @@ -0,0 +1,136 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * DLV - contains a Delegation Lookaside Validation record, which acts + * as the equivalent of a DS record in a lookaside zone. + * + * @author David Blacka + * @author Brian Wellington + * @see DNSSEC + * @see DSRecord + */ + +public +class DLVRecord extends DnsRecord { + + public static final int SHA1_DIGEST_ID = DSRecord.Digest.SHA1; + public static final int SHA256_DIGEST_ID = DSRecord.Digest.SHA1; + + private static final long serialVersionUID = 1960742375677534148L; + + private int footprint; + private int alg; + private int digestid; + private byte[] digest; + + DLVRecord() {} + + @Override + DnsRecord getObject() { + return new DLVRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + footprint = in.readU16(); + alg = in.readU8(); + digestid = in.readU8(); + digest = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(footprint); + out.writeU8(alg); + out.writeU8(digestid); + if (digest != null) { + out.writeByteArray(digest); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(footprint); + sb.append(" "); + sb.append(alg); + sb.append(" "); + sb.append(digestid); + if (digest != null) { + sb.append(" "); + sb.append(base16.toString(digest)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + footprint = st.getUInt16(); + alg = st.getUInt8(); + digestid = st.getUInt8(); + digest = st.getHex(); + } + + /** + * Creates a DLV Record from the given data + * + * @param footprint The original KEY record's footprint (keyid). + * @param alg The original key algorithm. + * @param digestid The digest id code. + * @param digest A hash of the original key. + */ + public + DLVRecord(Name name, int dclass, long ttl, int footprint, int alg, int digestid, byte[] digest) { + super(name, DnsRecordType.DLV, dclass, ttl); + this.footprint = checkU16("footprint", footprint); + this.alg = checkU8("alg", alg); + this.digestid = checkU8("digestid", digestid); + this.digest = digest; + } + + /** + * Returns the key's algorithm. + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the key's Digest ID. + */ + public + int getDigestID() { + return digestid; + } + + /** + * Returns the binary hash of the key. + */ + public + byte[] getDigest() { + return digest; + } + + /** + * Returns the key's footprint. + */ + public + int getFootprint() { + return footprint; + } + +} diff --git a/src/dorkbox/network/dns/records/DNAMERecord.java b/src/dorkbox/network/dns/records/DNAMERecord.java new file mode 100644 index 00000000..80bbf480 --- /dev/null +++ b/src/dorkbox/network/dns/records/DNAMERecord.java @@ -0,0 +1,52 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * DNAME Record - maps a nonterminal alias (subtree) to a different domain + * + * @author Brian Wellington + */ + +public +class DNAMERecord extends SingleNameBase { + + private static final long serialVersionUID = 2670767677200844154L; + + DNAMERecord() {} + + @Override + DnsRecord getObject() { + return new DNAMERecord(); + } + + /** + * Creates a new DNAMERecord with the given data + * + * @param alias The name to which the DNAME alias points + */ + public + DNAMERecord(Name name, int dclass, long ttl, Name alias) { + super(name, DnsRecordType.DNAME, dclass, ttl, alias, "alias"); + } + + /** + * Gets the target of the DNAME Record + */ + public + Name getTarget() { + return getSingleName(); + } + + /** + * Gets the alias specified by the DNAME Record + */ + public + Name getAlias() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/DNSKEYRecord.java b/src/dorkbox/network/dns/records/DNSKEYRecord.java new file mode 100644 index 00000000..ed648bcc --- /dev/null +++ b/src/dorkbox/network/dns/records/DNSKEYRecord.java @@ -0,0 +1,107 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.PublicKey; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Key - contains a cryptographic public key for use by DNS. + * The data can be converted to objects implementing + * java.security.interfaces.PublicKey + * + * @author Brian Wellington + * @see DNSSEC + */ + +public +class DNSKEYRecord extends KEYBase { + + private static final long serialVersionUID = -8679800040426675002L; + + + public static + class Protocol { + /** + * Key will be used for DNSSEC + */ + public static final int DNSSEC = 3; + + private + Protocol() {} + } + + + public static + class Flags { + /** + * Key is a zone key + */ + public static final int ZONE_KEY = 0x100; + /** + * Key is a secure entry point key + */ + public static final int SEP_KEY = 0x1; + /** + * Key has been revoked + */ + public static final int REVOKE = 0x80; + + private + Flags() {} + } + + DNSKEYRecord() {} + + @Override + DnsRecord getObject() { + return new DNSKEYRecord(); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + flags = st.getUInt16(); + proto = st.getUInt8(); + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) { + throw st.exception("Invalid algorithm: " + algString); + } + key = st.getBase64(); + } + + /** + * Creates a DNSKEY Record from the given data + * + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key Binary representation of the key + */ + public + DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, byte[] key) { + super(name, DnsRecordType.DNSKEY, dclass, ttl, flags, proto, alg, key); + } + + /** + * Creates a DNSKEY Record from the given data + * + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key The key as a PublicKey + * + * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS + * format. + */ + public + DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, PublicKey key) throws DNSSEC.DNSSECException { + super(name, DnsRecordType.DNSKEY, dclass, ttl, flags, proto, alg, DNSSEC.fromPublicKey(key, alg)); + publicKey = key; + } + +} diff --git a/src/dorkbox/network/dns/records/DNSSEC.java b/src/dorkbox/network/dns/records/DNSSEC.java new file mode 100644 index 00000000..a2706419 --- /dev/null +++ b/src/dorkbox/network/dns/records/DNSSEC.java @@ -0,0 +1,1278 @@ +// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsSection; + +/** + * Constants and methods relating to DNSSEC. + *

+ * DNSSEC provides authentication for DNS information. + * + * @author Brian Wellington + * @see RRSIGRecord + * @see DNSKEYRecord + * @see RRset + */ + +public +class DNSSEC { + + // RFC 4357 DnsSection 11.4 + private static final ECKeyInfo GOST = new ECKeyInfo(32, + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94", + "A6", + "1", + "8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893"); + // RFC 5114 DnsSection 2.6 + private static final ECKeyInfo ECDSA_P256 = new ECKeyInfo(32, + "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", + "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", + "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", + "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"); + // RFC 5114 DnsSection 2.7 + private static final ECKeyInfo ECDSA_P384 = new ECKeyInfo(48, + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", + "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", + "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", + "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973"); + private static final int ASN1_SEQ = 0x30; + private static final int ASN1_INT = 0x2; + private static final int DSA_LEN = 20; + + + public static + class Algorithm { + /** + * RSA/MD5 public key (deprecated) + */ + public static final int RSAMD5 = 1; + /** + * Diffie Hellman key + */ + public static final int DH = 2; + /** + * DSA public key + */ + public static final int DSA = 3; + /** + * RSA/SHA1 public key + */ + public static final int RSASHA1 = 5; + /** + * DSA/SHA1, NSEC3-aware public key + */ + public static final int DSA_NSEC3_SHA1 = 6; + /** + * RSA/SHA1, NSEC3-aware public key + */ + public static final int RSA_NSEC3_SHA1 = 7; + /** + * RSA/SHA256 public key + */ + public static final int RSASHA256 = 8; + /** + * RSA/SHA512 public key + */ + public static final int RSASHA512 = 10; + /** + * GOST R 34.10-2001. + * This requires an external cryptography provider, + * such as BouncyCastle. + */ + public static final int ECC_GOST = 12; + /** + * ECDSA Curve P-256 with SHA-256 public key + **/ + public static final int ECDSAP256SHA256 = 13; + /** + * ECDSA Curve P-384 with SHA-384 public key + **/ + public static final int ECDSAP384SHA384 = 14; + /** + * Indirect keys; the actual key is elsewhere. + */ + public static final int INDIRECT = 252; + /** + * Private algorithm, specified by domain name + */ + public static final int PRIVATEDNS = 253; + /** + * Private algorithm, specified by OID + */ + public static final int PRIVATEOID = 254; + private static Mnemonic algs = new Mnemonic("DNSSEC algorithm", Mnemonic.CASE_UPPER); + + private + Algorithm() {} + + static { + algs.setMaximum(0xFF); + algs.setNumericAllowed(true); + + algs.add(RSAMD5, "RSAMD5"); + algs.add(DH, "DH"); + algs.add(DSA, "DSA"); + algs.add(RSASHA1, "RSASHA1"); + algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1"); + algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1"); + algs.add(RSASHA256, "RSASHA256"); + algs.add(RSASHA512, "RSASHA512"); + algs.add(ECC_GOST, "ECC-GOST"); + algs.add(ECDSAP256SHA256, "ECDSAP256SHA256"); + algs.add(ECDSAP384SHA384, "ECDSAP384SHA384"); + algs.add(INDIRECT, "INDIRECT"); + algs.add(PRIVATEDNS, "PRIVATEDNS"); + algs.add(PRIVATEOID, "PRIVATEOID"); + } + + /** + * Converts an algorithm into its textual representation + */ + public static + String string(int alg) { + return algs.getText(alg); + } + + /** + * Converts a textual representation of an algorithm into its numeric + * code. Integers in the range 0..255 are also accepted. + * + * @param s The textual representation of the algorithm + * + * @return The algorithm code, or -1 on error. + */ + public static + int value(String s) { + return algs.getValue(s); + } + } + + + private + DNSSEC() { } + + /** + * Creates a byte array containing the concatenation of the fields of the + * SIG(0) record and the message to be signed. This does not perform + * a cryptographic digest. + * + * @param sig The SIG record used to sign the rrset. + * @param msg The message to be signed. + * @param previous If this is a response, the signature from the query. + * + * @return The data to be cryptographically signed. + */ + public static + byte[] digestMessage(SIGRecord sig, DnsMessage msg, byte[] previous) { + DnsOutput out = new DnsOutput(); + digestSIG(out, sig); + + if (previous != null) { + out.writeByteArray(previous); + } + + msg.toWire(out); + return out.toByteArray(); + } + + private static + void digestSIG(DnsOutput out, SIGBase sig) { + out.writeU16(sig.getTypeCovered()); + out.writeU8(sig.getAlgorithm()); + out.writeU8(sig.getLabels()); + out.writeU32(sig.getOrigTTL()); + out.writeU32(sig.getExpire() + .getTime() / 1000); + out.writeU32(sig.getTimeSigned() + .getTime() / 1000); + out.writeU16(sig.getFootprint()); + sig.getSigner() + .toWireCanonical(out); + } + + + /** + * A DNSSEC exception. + */ + public static + class DNSSECException extends Exception { + DNSSECException(String s) { + super(s); + } + } + + + /** + * An algorithm is unsupported by this DNSSEC implementation. + */ + public static + class UnsupportedAlgorithmException extends DNSSECException { + UnsupportedAlgorithmException(int alg) { + super("Unsupported algorithm: " + alg); + } + } + + + /** + * The cryptographic data in a DNSSEC key is malformed. + */ + public static + class MalformedKeyException extends DNSSECException { + MalformedKeyException(KEYBase rec) { + super("Invalid key data: " + asString(rec)); + } + + private static + String asString(final KEYBase rec) { + StringBuilder stringBuilder = new StringBuilder(); + rec.rdataToString(stringBuilder); + return stringBuilder.toString(); + } + } + + + /** + * A DNSSEC verification failed because fields in the DNSKEY and RRSIG records + * do not match. + */ + public static + class KeyMismatchException extends DNSSECException { + private KEYBase key; + private SIGBase sig; + + KeyMismatchException(KEYBase key, SIGBase sig) { + super("key " + key.getName() + "/" + DNSSEC.Algorithm.string(key.getAlgorithm()) + "/" + key.getFootprint() + " " + + "does not match signature " + sig.getSigner() + "/" + DNSSEC.Algorithm.string(sig.getAlgorithm()) + "/" + + sig.getFootprint()); + } + } + + + /** + * A DNSSEC verification failed because the signature has expired. + */ + public static + class SignatureExpiredException extends DNSSECException { + private Date when, now; + + SignatureExpiredException(Date when, Date now) { + super("signature expired"); + this.when = when; + this.now = now; + } + + /** + * @return When the signature expired + */ + public + Date getExpiration() { + return when; + } + + /** + * @return When the verification was attempted + */ + public + Date getVerifyTime() { + return now; + } + } + + + /** + * A DNSSEC verification failed because the signature has not yet become valid. + */ + public static + class SignatureNotYetValidException extends DNSSECException { + private Date when, now; + + SignatureNotYetValidException(Date when, Date now) { + super("signature is not yet valid"); + this.when = when; + this.now = now; + } + + /** + * @return When the signature will become valid + */ + public + Date getExpiration() { + return when; + } + + /** + * @return When the verification was attempted + */ + public + Date getVerifyTime() { + return now; + } + } + + + /** + * A DNSSEC verification failed because the cryptographic signature + * verification failed. + */ + public static + class SignatureVerificationException extends DNSSECException { + SignatureVerificationException() { + super("signature verification failed"); + } + } + + + /** + * The key data provided is inconsistent. + */ + public static + class IncompatibleKeyException extends IllegalArgumentException { + IncompatibleKeyException() { + super("incompatible keys"); + } + } + + + /** + * No signature was found. + */ + public static + class NoSignatureException extends DNSSECException { + NoSignatureException() { + super("no signature found"); + } + } + + private static + byte[] trimByteArray(byte[] array) { + if (array[0] == 0) { + byte trimmedArray[] = new byte[array.length - 1]; + System.arraycopy(array, 1, trimmedArray, 0, array.length - 1); + return trimmedArray; + } + else { + return array; + } + } + + private static + void writeBigInteger(DnsOutput out, BigInteger val) { + byte[] b = trimByteArray(val.toByteArray()); + out.writeByteArray(b); + } + + private static + void writePaddedBigInteger(DnsOutput out, BigInteger val, int len) { + byte[] b = trimByteArray(val.toByteArray()); + + if (b.length > len) { + throw new IllegalArgumentException(); + } + + if (b.length < len) { + byte[] pad = new byte[len - b.length]; + out.writeByteArray(pad); + } + + out.writeByteArray(b); + } + + private static + void writePaddedBigIntegerLittleEndian(DnsOutput out, BigInteger val, int len) { + byte[] b = trimByteArray(val.toByteArray()); + + if (b.length > len) { + throw new IllegalArgumentException(); + } + + reverseByteArray(b); + out.writeByteArray(b); + + if (b.length < len) { + byte[] pad = new byte[len - b.length]; + out.writeByteArray(pad); + } + } + + + private static + class ECKeyInfo { + int length; + public BigInteger p, a, b, gx, gy, n; + EllipticCurve curve; + ECParameterSpec spec; + + ECKeyInfo(int length, String p_str, String a_str, String b_str, String gx_str, String gy_str, String n_str) { + this.length = length; + p = new BigInteger(p_str, 16); + a = new BigInteger(a_str, 16); + b = new BigInteger(b_str, 16); + gx = new BigInteger(gx_str, 16); + gy = new BigInteger(gy_str, 16); + n = new BigInteger(n_str, 16); + curve = new EllipticCurve(new ECFieldFp(p), a, b); + spec = new ECParameterSpec(curve, new ECPoint(gx, gy), n, 1); + } + } + + /** + * Converts a KEY/DNSKEY record into a PublicKey + */ + static + PublicKey toPublicKey(KEYBase r) throws DNSSECException { + int alg = r.getAlgorithm(); + try { + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + return toRSAPublicKey(r); + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + return toDSAPublicKey(r); + case Algorithm.ECC_GOST: + return toECGOSTPublicKey(r, GOST); + case Algorithm.ECDSAP256SHA256: + return toECDSAPublicKey(r, ECDSA_P256); + case Algorithm.ECDSAP384SHA384: + return toECDSAPublicKey(r, ECDSA_P384); + default: + throw new UnsupportedAlgorithmException(alg); + } + } catch (IOException e) { + throw new MalformedKeyException(r); + } catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } + } + + private static + PublicKey toRSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException { + DnsInput in = new DnsInput(r.getKey()); + int exponentLength = in.readU8(); + if (exponentLength == 0) { + exponentLength = in.readU16(); + } + BigInteger exponent = readBigInteger(in, exponentLength); + BigInteger modulus = readBigInteger(in); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(new RSAPublicKeySpec(modulus, exponent)); + } + + private static + BigInteger readBigInteger(DnsInput in, int len) throws IOException { + byte[] b = in.readByteArray(len); + return new BigInteger(1, b); + } + + private static + BigInteger readBigInteger(DnsInput in) { + byte[] b = in.readByteArray(); + return new BigInteger(1, b); + } + + private static + PublicKey toDSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException, MalformedKeyException { + DnsInput in = new DnsInput(r.getKey()); + + int t = in.readU8(); + if (t > 8) { + throw new MalformedKeyException(r); + } + + BigInteger q = readBigInteger(in, 20); + BigInteger p = readBigInteger(in, 64 + t * 8); + BigInteger g = readBigInteger(in, 64 + t * 8); + BigInteger y = readBigInteger(in, 64 + t * 8); + + KeyFactory factory = KeyFactory.getInstance("DSA"); + return factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); + } + + private static + PublicKey toECGOSTPublicKey(KEYBase r, ECKeyInfo keyinfo) throws IOException, GeneralSecurityException, MalformedKeyException { + DnsInput in = new DnsInput(r.getKey()); + + BigInteger x = readBigIntegerLittleEndian(in, keyinfo.length); + BigInteger y = readBigIntegerLittleEndian(in, keyinfo.length); + ECPoint q = new ECPoint(x, y); + + KeyFactory factory = KeyFactory.getInstance("ECGOST3410"); + return factory.generatePublic(new ECPublicKeySpec(q, keyinfo.spec)); + } + + private static + BigInteger readBigIntegerLittleEndian(DnsInput in, int len) throws IOException { + byte[] b = in.readByteArray(len); + reverseByteArray(b); + return new BigInteger(1, b); + } + + private static + void reverseByteArray(byte[] array) { + for (int i = 0; i < array.length / 2; i++) { + int j = array.length - i - 1; + byte tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + private static + PublicKey toECDSAPublicKey(KEYBase r, ECKeyInfo keyinfo) throws IOException, GeneralSecurityException, MalformedKeyException { + DnsInput in = new DnsInput(r.getKey()); + + // RFC 6605 DnsSection 4 + BigInteger x = readBigInteger(in, keyinfo.length); + BigInteger y = readBigInteger(in, keyinfo.length); + ECPoint q = new ECPoint(x, y); + + KeyFactory factory = KeyFactory.getInstance("EC"); + return factory.generatePublic(new ECPublicKeySpec(q, keyinfo.spec)); + } + + private static + byte[] fromRSAPublicKey(RSAPublicKey key) { + DnsOutput out = new DnsOutput(); + BigInteger exponent = key.getPublicExponent(); + BigInteger modulus = key.getModulus(); + int exponentLength = BigIntegerLength(exponent); + + if (exponentLength < 256) { + out.writeU8(exponentLength); + } + else { + out.writeU8(0); + out.writeU16(exponentLength); + } + writeBigInteger(out, exponent); + writeBigInteger(out, modulus); + + return out.toByteArray(); + } + + private static + byte[] fromDSAPublicKey(DSAPublicKey key) { + DnsOutput out = new DnsOutput(); + BigInteger q = key.getParams() + .getQ(); + BigInteger p = key.getParams() + .getP(); + BigInteger g = key.getParams() + .getG(); + BigInteger y = key.getY(); + int t = (p.toByteArray().length - 64) / 8; + + out.writeU8(t); + writeBigInteger(out, q); + writeBigInteger(out, p); + writePaddedBigInteger(out, g, 8 * t + 64); + writePaddedBigInteger(out, y, 8 * t + 64); + + return out.toByteArray(); + } + + private static + byte[] fromECGOSTPublicKey(ECPublicKey key, ECKeyInfo keyinfo) { + DnsOutput out = new DnsOutput(); + + BigInteger x = key.getW() + .getAffineX(); + BigInteger y = key.getW() + .getAffineY(); + + writePaddedBigIntegerLittleEndian(out, x, keyinfo.length); + writePaddedBigIntegerLittleEndian(out, y, keyinfo.length); + + return out.toByteArray(); + } + + private static + byte[] fromECDSAPublicKey(ECPublicKey key, ECKeyInfo keyinfo) { + DnsOutput out = new DnsOutput(); + + BigInteger x = key.getW() + .getAffineX(); + BigInteger y = key.getW() + .getAffineY(); + + writePaddedBigInteger(out, x, keyinfo.length); + writePaddedBigInteger(out, y, keyinfo.length); + + return out.toByteArray(); + } + + /** + * Builds a DNSKEY record from a PublicKey + */ + static + byte[] fromPublicKey(PublicKey key, int alg) throws DNSSECException { + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + if (!(key instanceof RSAPublicKey)) { + throw new IncompatibleKeyException(); + } + return fromRSAPublicKey((RSAPublicKey) key); + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + if (!(key instanceof DSAPublicKey)) { + throw new IncompatibleKeyException(); + } + return fromDSAPublicKey((DSAPublicKey) key); + case Algorithm.ECC_GOST: + if (!(key instanceof ECPublicKey)) { + throw new IncompatibleKeyException(); + } + return fromECGOSTPublicKey((ECPublicKey) key, GOST); + case Algorithm.ECDSAP256SHA256: + if (!(key instanceof ECPublicKey)) { + throw new IncompatibleKeyException(); + } + return fromECDSAPublicKey((ECPublicKey) key, ECDSA_P256); + case Algorithm.ECDSAP384SHA384: + if (!(key instanceof ECPublicKey)) { + throw new IncompatibleKeyException(); + } + return fromECDSAPublicKey((ECPublicKey) key, ECDSA_P384); + default: + throw new UnsupportedAlgorithmException(alg); + } + } + + /** + * Verify a DNSSEC signature. + * + * @param rrset The data to be verified. + * @param rrsig The RRSIG record containing the signature. + * @param key The DNSKEY record to verify the signature with. + * + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws KeyMismatchException The key and signature do not match + * @throws SignatureExpiredException The signature has expired + * @throws SignatureNotYetValidException The signature is not yet valid + * @throws SignatureVerificationException The signature does not verify. + * @throws DNSSECException Some other error occurred. + */ + public static + void verify(RRset rrset, RRSIGRecord rrsig, DNSKEYRecord key) throws DNSSECException { + if (!matches(rrsig, key)) { + throw new KeyMismatchException(key, rrsig); + } + + Date now = new Date(); + if (now.compareTo(rrsig.getExpire()) > 0) { + throw new SignatureExpiredException(rrsig.getExpire(), now); + } + if (now.compareTo(rrsig.getTimeSigned()) < 0) { + throw new SignatureNotYetValidException(rrsig.getTimeSigned(), now); + } + + verify(key.getPublicKey(), rrsig.getAlgorithm(), digestRRset(rrsig, rrset), rrsig.getSignature()); + } + + /** + * Creates a byte array containing the concatenation of the fields of the + * SIG record and the RRsets to be signed/verified. This does not perform + * a cryptographic digest. + * + * @param rrsig The RRSIG record used to sign/verify the rrset. + * @param rrset The data to be signed/verified. + * + * @return The data to be cryptographically signed or verified. + */ + public static + byte[] digestRRset(RRSIGRecord rrsig, RRset rrset) { + DnsOutput out = new DnsOutput(); + digestSIG(out, rrsig); + + int size = rrset.size(); + DnsRecord[] records = new DnsRecord[size]; + + Iterator it = rrset.rrs(); + Name name = rrset.getName(); + Name wild = null; + int sigLabels = rrsig.getLabels() + 1; // Add the root label back. + if (name.labels() > sigLabels) { + wild = name.wild(name.labels() - sigLabels); + } + while (it.hasNext()) { + records[--size] = (DnsRecord) it.next(); + } + Arrays.sort(records); + + DnsOutput header = new DnsOutput(); + if (wild != null) { + wild.toWireCanonical(header); + } + else { + name.toWireCanonical(header); + } + header.writeU16(rrset.getType()); + header.writeU16(rrset.getDClass()); + header.writeU32(rrsig.getOrigTTL()); + for (int i = 0; i < records.length; i++) { + out.writeByteArray(header.toByteArray()); + int lengthPosition = out.current(); + out.writeU16(0); + out.writeByteArray(records[i].rdataToWireCanonical()); + int rrlength = out.current() - lengthPosition - 2; + out.save(); + out.jump(lengthPosition); + out.writeU16(rrlength); + out.restore(); + } + return out.toByteArray(); + } + + private static + void verify(PublicKey key, int alg, byte[] data, byte[] signature) throws DNSSECException { + if (key instanceof DSAPublicKey) { + try { + signature = DSASignaturefromDNS(signature); + } catch (IOException e) { + throw new IllegalStateException(); + } + } + else if (key instanceof ECPublicKey) { + try { + switch (alg) { + case Algorithm.ECC_GOST: + signature = ECGOSTSignaturefromDNS(signature, GOST); + break; + case Algorithm.ECDSAP256SHA256: + signature = ECDSASignaturefromDNS(signature, ECDSA_P256); + break; + case Algorithm.ECDSAP384SHA384: + signature = ECDSASignaturefromDNS(signature, ECDSA_P384); + break; + default: + throw new UnsupportedAlgorithmException(alg); + } + } catch (IOException e) { + throw new IllegalStateException(); + } + } + + try { + Signature s = Signature.getInstance(algString(alg)); + s.initVerify(key); + s.update(data); + if (!s.verify(signature)) { + throw new SignatureVerificationException(); + } + } catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } + } + + /** + * Convert an algorithm number to the corresponding JCA string. + * + * @param alg The algorithm number. + * + * @throws UnsupportedAlgorithmException The algorithm is unknown. + */ + public static + String algString(int alg) throws UnsupportedAlgorithmException { + switch (alg) { + case Algorithm.RSAMD5: + return "MD5withRSA"; + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + return "SHA1withDSA"; + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + return "SHA1withRSA"; + case Algorithm.RSASHA256: + return "SHA256withRSA"; + case Algorithm.RSASHA512: + return "SHA512withRSA"; + case Algorithm.ECC_GOST: + return "GOST3411withECGOST3410"; + case Algorithm.ECDSAP256SHA256: + return "SHA256withECDSA"; + case Algorithm.ECDSAP384SHA384: + return "SHA384withECDSA"; + default: + throw new UnsupportedAlgorithmException(alg); + } + } + + private static + byte[] DSASignaturefromDNS(byte[] dns) throws DNSSECException, IOException { + if (dns.length != 1 + DSA_LEN * 2) { + throw new SignatureVerificationException(); + } + + DnsInput in = new DnsInput(dns); + DnsOutput out = new DnsOutput(); + + int t = in.readU8(); + + byte[] r = in.readByteArray(DSA_LEN); + int rlen = DSA_LEN; + if (r[0] < 0) { + rlen++; + } + + byte[] s = in.readByteArray(DSA_LEN); + int slen = DSA_LEN; + if (s[0] < 0) { + slen++; + } + + out.writeU8(ASN1_SEQ); + out.writeU8(rlen + slen + 4); + + out.writeU8(ASN1_INT); + out.writeU8(rlen); + if (rlen > DSA_LEN) { + out.writeU8(0); + } + out.writeByteArray(r); + + out.writeU8(ASN1_INT); + out.writeU8(slen); + if (slen > DSA_LEN) { + out.writeU8(0); + } + out.writeByteArray(s); + + return out.toByteArray(); + } + + private static + byte[] ECGOSTSignaturefromDNS(byte[] signature, ECKeyInfo keyinfo) throws DNSSECException, IOException { + if (signature.length != keyinfo.length * 2) { + throw new SignatureVerificationException(); + } + // Wire format is equal to the engine input + return signature; + } + + private static + byte[] ECDSASignaturefromDNS(byte[] signature, ECKeyInfo keyinfo) throws DNSSECException, IOException { + if (signature.length != keyinfo.length * 2) { + throw new SignatureVerificationException(); + } + + DnsInput in = new DnsInput(signature); + DnsOutput out = new DnsOutput(); + + byte[] r = in.readByteArray(keyinfo.length); + int rlen = keyinfo.length; + if (r[0] < 0) { + rlen++; + } + + byte[] s = in.readByteArray(keyinfo.length); + int slen = keyinfo.length; + if (s[0] < 0) { + slen++; + } + + out.writeU8(ASN1_SEQ); + out.writeU8(rlen + slen + 4); + + out.writeU8(ASN1_INT); + out.writeU8(rlen); + if (rlen > keyinfo.length) { + out.writeU8(0); + } + out.writeByteArray(r); + + out.writeU8(ASN1_INT); + out.writeU8(slen); + if (slen > keyinfo.length) { + out.writeU8(0); + } + out.writeByteArray(s); + + return out.toByteArray(); + } + + private static + boolean matches(SIGBase sig, KEYBase key) { + return (key.getAlgorithm() == sig.getAlgorithm() && key.getFootprint() == sig.getFootprint() && key.getName() + .equals(sig.getSigner())); + } + + /** + * Generate a DNSSEC signature. key and privateKey must refer to the + * same underlying cryptographic key. + * + * @param rrset The data to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param inception The time at which the signatures should become valid + * @param expiration The time at which the signatures should expire + * + * @return The generated signature + * + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws DNSSECException Some other error occurred. + */ + public static + RRSIGRecord sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, Date inception, Date expiration) throws DNSSECException { + return sign(rrset, key, privkey, inception, expiration, null); + } + + /** + * Generate a DNSSEC signature. key and privateKey must refer to the + * same underlying cryptographic key. + * + * @param rrset The data to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param inception The time at which the signatures should become valid + * @param expiration The time at which the signatures should expire + * @param provider The name of the JCA provider. If non-null, it will be + * passed to JCA getInstance() methods. + * + * @return The generated signature + * + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws DNSSECException Some other error occurred. + */ + public static + RRSIGRecord sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, Date inception, Date expiration, String provider) + throws DNSSECException { + int alg = key.getAlgorithm(); + checkAlgorithm(privkey, alg); + + RRSIGRecord rrsig = new RRSIGRecord(rrset.getName(), + rrset.getDClass(), + rrset.getTTL(), + rrset.getType(), + alg, + rrset.getTTL(), + expiration, + inception, + key.getFootprint(), + key.getName(), + null); + + rrsig.setSignature(sign(privkey, key.getPublicKey(), alg, digestRRset(rrsig, rrset), provider)); + return rrsig; + } + + private static + byte[] sign(PrivateKey privkey, PublicKey pubkey, int alg, byte[] data, String provider) throws DNSSECException { + byte[] signature; + try { + Signature s; + if (provider != null) { + s = Signature.getInstance(algString(alg), provider); + } + else { + s = Signature.getInstance(algString(alg)); + } + s.initSign(privkey); + s.update(data); + signature = s.sign(); + } catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } + + if (pubkey instanceof DSAPublicKey) { + try { + DSAPublicKey dsa = (DSAPublicKey) pubkey; + BigInteger P = dsa.getParams() + .getP(); + int t = (BigIntegerLength(P) - 64) / 8; + signature = DSASignaturetoDNS(signature, t); + } catch (IOException e) { + throw new IllegalStateException(); + } + } + else if (pubkey instanceof ECPublicKey) { + try { + switch (alg) { + case Algorithm.ECC_GOST: + // Wire format is equal to the engine output + break; + case Algorithm.ECDSAP256SHA256: + signature = ECDSASignaturetoDNS(signature, ECDSA_P256); + break; + case Algorithm.ECDSAP384SHA384: + signature = ECDSASignaturetoDNS(signature, ECDSA_P384); + break; + default: + throw new UnsupportedAlgorithmException(alg); + } + } catch (IOException e) { + throw new IllegalStateException(); + } + } + + return signature; + } + + private static + int BigIntegerLength(BigInteger i) { + return (i.bitLength() + 7) / 8; + } + + private static + byte[] DSASignaturetoDNS(byte[] signature, int t) throws IOException { + DnsInput in = new DnsInput(signature); + DnsOutput out = new DnsOutput(); + + out.writeU8(t); + + int tmp = in.readU8(); + if (tmp != ASN1_SEQ) { + throw new IOException(); + } + int seqlen = in.readU8(); + + tmp = in.readU8(); + if (tmp != ASN1_INT) { + throw new IOException(); + } + int rlen = in.readU8(); + if (rlen == DSA_LEN + 1) { + if (in.readU8() != 0) { + throw new IOException(); + } + } + else if (rlen != DSA_LEN) { + throw new IOException(); + } + byte[] bytes = in.readByteArray(DSA_LEN); + out.writeByteArray(bytes); + + tmp = in.readU8(); + if (tmp != ASN1_INT) { + throw new IOException(); + } + int slen = in.readU8(); + if (slen == DSA_LEN + 1) { + if (in.readU8() != 0) { + throw new IOException(); + } + } + else if (slen != DSA_LEN) { + throw new IOException(); + } + bytes = in.readByteArray(DSA_LEN); + out.writeByteArray(bytes); + + return out.toByteArray(); + } + + private static + byte[] ECDSASignaturetoDNS(byte[] signature, ECKeyInfo keyinfo) throws IOException { + DnsInput in = new DnsInput(signature); + DnsOutput out = new DnsOutput(); + + int tmp = in.readU8(); + if (tmp != ASN1_SEQ) { + throw new IOException(); + } + int seqlen = in.readU8(); + + tmp = in.readU8(); + if (tmp != ASN1_INT) { + throw new IOException(); + } + int rlen = in.readU8(); + if (rlen == keyinfo.length + 1) { + if (in.readU8() != 0) { + throw new IOException(); + } + } + else if (rlen != keyinfo.length) { + throw new IOException(); + } + byte[] bytes = in.readByteArray(keyinfo.length); + out.writeByteArray(bytes); + + tmp = in.readU8(); + if (tmp != ASN1_INT) { + throw new IOException(); + } + int slen = in.readU8(); + if (slen == keyinfo.length + 1) { + if (in.readU8() != 0) { + throw new IOException(); + } + } + else if (slen != keyinfo.length) { + throw new IOException(); + } + bytes = in.readByteArray(keyinfo.length); + out.writeByteArray(bytes); + + return out.toByteArray(); + } + + static + void checkAlgorithm(PrivateKey key, int alg) throws UnsupportedAlgorithmException { + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + if (!(key instanceof RSAPrivateKey)) { + throw new IncompatibleKeyException(); + } + break; + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + if (!(key instanceof DSAPrivateKey)) { + throw new IncompatibleKeyException(); + } + break; + case Algorithm.ECC_GOST: + case Algorithm.ECDSAP256SHA256: + case Algorithm.ECDSAP384SHA384: + if (!(key instanceof ECPrivateKey)) { + throw new IncompatibleKeyException(); + } + break; + default: + throw new UnsupportedAlgorithmException(alg); + } + } + + static + SIGRecord signMessage(DnsMessage dnsMessage, SIGRecord previous, KEYRecord key, PrivateKey privkey, Date inception, Date expiration) + throws DNSSECException { + int alg = key.getAlgorithm(); + checkAlgorithm(privkey, alg); + + SIGRecord sig = new SIGRecord(Name.root, DnsClass.ANY, 0, 0, alg, 0, expiration, inception, key.getFootprint(), key.getName(), null); + DnsOutput out = new DnsOutput(); + digestSIG(out, sig); + if (previous != null) { + out.writeByteArray(previous.getSignature()); + } + out.writeByteArray(dnsMessage.toWire()); + + sig.setSignature(sign(privkey, key.getPublicKey(), alg, out.toByteArray(), null)); + return sig; + } + + static + void verifyMessage(DnsMessage dnsMessage, byte[] bytes, SIGRecord sig, SIGRecord previous, KEYRecord key) throws DNSSECException { + if (dnsMessage.sig0start == 0) { + throw new NoSignatureException(); + } + + if (!matches(sig, key)) { + throw new KeyMismatchException(key, sig); + } + + Date now = new Date(); + + if (now.compareTo(sig.getExpire()) > 0) { + throw new SignatureExpiredException(sig.getExpire(), now); + } + if (now.compareTo(sig.getTimeSigned()) < 0) { + throw new SignatureNotYetValidException(sig.getTimeSigned(), now); + } + + DnsOutput out = new DnsOutput(); + digestSIG(out, sig); + if (previous != null) { + out.writeByteArray(previous.getSignature()); + } + + Header header = (Header) dnsMessage.getHeader() + .clone(); + header.decCount(DnsSection.ADDITIONAL); + out.writeByteArray(header.toWire()); + + out.writeByteArray(bytes, Header.LENGTH, dnsMessage.sig0start - Header.LENGTH); + + verify(key.getPublicKey(), sig.getAlgorithm(), out.toByteArray(), sig.getSignature()); + } + + /** + * Generate the digest value for a DS key + * + * @param key Which is covered by the DS record + * @param digestid The type of digest + * + * @return The digest value as an array of bytes + */ + static + byte[] generateDSDigest(DNSKEYRecord key, int digestid) { + MessageDigest digest; + try { + switch (digestid) { + case DSRecord.Digest.SHA1: + digest = MessageDigest.getInstance("sha-1"); + break; + case DSRecord.Digest.SHA256: + digest = MessageDigest.getInstance("sha-256"); + break; + case DSRecord.Digest.GOST3411: + digest = MessageDigest.getInstance("GOST3411"); + break; + case DSRecord.Digest.SHA384: + digest = MessageDigest.getInstance("sha-384"); + break; + default: + throw new IllegalArgumentException("unknown DS digest type " + digestid); + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("no message digest support"); + } + digest.update(key.getName() + .toWireCanonical()); + digest.update(key.rdataToWireCanonical()); + return digest.digest(); + } + +} diff --git a/src/dorkbox/network/dns/records/DSRecord.java b/src/dorkbox/network/dns/records/DSRecord.java new file mode 100644 index 00000000..598d5987 --- /dev/null +++ b/src/dorkbox/network/dns/records/DSRecord.java @@ -0,0 +1,170 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * DS - contains a Delegation Signer record, which acts as a + * placeholder for KEY records in the parent zone. + * + * @author David Blacka + * @author Brian Wellington + * @see DNSSEC + */ + +public +class DSRecord extends DnsRecord { + + public static final int SHA1_DIGEST_ID = Digest.SHA1; + public static final int SHA256_DIGEST_ID = Digest.SHA256; + public static final int GOST3411_DIGEST_ID = Digest.GOST3411; + public static final int SHA384_DIGEST_ID = Digest.SHA384; + private static final long serialVersionUID = -9001819329700081493L; + private int footprint; + private int alg; + private int digestid; + private byte[] digest; + + + public static + class Digest { + /** + * SHA-1 + */ + public static final int SHA1 = 1; + /** + * SHA-256 + */ + public static final int SHA256 = 2; + /** + * GOST R 34.11-94 + */ + public static final int GOST3411 = 3; + /** + * SHA-384 + */ + public static final int SHA384 = 4; + + private + Digest() {} + } + + DSRecord() {} + + @Override + DnsRecord getObject() { + return new DSRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + footprint = in.readU16(); + alg = in.readU8(); + digestid = in.readU8(); + digest = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(footprint); + out.writeU8(alg); + out.writeU8(digestid); + if (digest != null) { + out.writeByteArray(digest); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(footprint); + sb.append(" "); + sb.append(alg); + sb.append(" "); + sb.append(digestid); + if (digest != null) { + sb.append(" "); + sb.append(base16.toString(digest)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + footprint = st.getUInt16(); + alg = st.getUInt8(); + digestid = st.getUInt8(); + digest = st.getHex(); + } + + /** + * Creates a DS Record from the given data + * + * @param digestid The digest id code. + * @param key The key to digest + */ + public + DSRecord(Name name, int dclass, long ttl, int digestid, DNSKEYRecord key) { + this(name, dclass, ttl, key.getFootprint(), key.getAlgorithm(), digestid, DNSSEC.generateDSDigest(key, digestid)); + } + + /** + * Creates a DS Record from the given data + * + * @param footprint The original KEY record's footprint (keyid). + * @param alg The original key algorithm. + * @param digestid The digest id code. + * @param digest A hash of the original key. + */ + public + DSRecord(Name name, int dclass, long ttl, int footprint, int alg, int digestid, byte[] digest) { + super(name, DnsRecordType.DS, dclass, ttl); + this.footprint = checkU16("footprint", footprint); + this.alg = checkU8("alg", alg); + this.digestid = checkU8("digestid", digestid); + this.digest = digest; + } + + /** + * Returns the key's algorithm. + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the key's Digest ID. + */ + public + int getDigestID() { + return digestid; + } + + /** + * Returns the binary hash of the key. + */ + public + byte[] getDigest() { + return digest; + } + + /** + * Returns the key's footprint. + */ + public + int getFootprint() { + return footprint; + } + +} diff --git a/src/dorkbox/network/dns/records/DnsMessage.java b/src/dorkbox/network/dns/records/DnsMessage.java new file mode 100644 index 00000000..dd6ca727 --- /dev/null +++ b/src/dorkbox/network/dns/records/DnsMessage.java @@ -0,0 +1,1025 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsOpCode; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.constants.Flags; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.util.OS; +import io.netty.buffer.ByteBuf; +import io.netty.util.AbstractReferenceCounted; +import io.netty.util.ReferenceCounted; +import io.netty.util.ResourceLeakDetector; +import io.netty.util.ResourceLeakDetectorFactory; +import io.netty.util.ResourceLeakTracker; + +/** + * A DNS DnsMessage. A message is the basic unit of communication between + * the client and server of a DNS operation. A message consists of a Header + * and 4 message sections. + * + * @author Brian Wellington + * @see Header + * @see DnsSection + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public +class DnsMessage extends AbstractReferenceCounted implements Cloneable, ReferenceCounted { + + private static final ResourceLeakDetector leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(DnsMessage.class); + private final ResourceLeakTracker leak = leakDetector.track(this); + + /** + * The maximum length of a message in wire format. + */ + public static final int MAXLENGTH = 65535; + + private Header header; + + // To reduce the memory footprint of a message, + // each of the following fields is a single record or a list of records. + private Object questions; + private Object answers; + private Object authorities; + private Object additionals; + + private int size; + + + private TSIG tsigkey; + private TSIGRecord querytsig; + private int tsigerror; + + int tsigstart; + int tsigState; + int sig0start; + + /* The message was not signed */ + static final int TSIG_UNSIGNED = 0; + + /* The message was signed and verification succeeded */ + static final int TSIG_VERIFIED = 1; + + /* The message was an unsigned message in multiple-message response */ + static final int TSIG_INTERMEDIATE = 2; + + /* The message was signed and no verification was attempted. */ + static final int TSIG_SIGNED = 3; + + /* + * The message was signed and verification failed, or was not signed + * when it should have been. + */ + static final int TSIG_FAILED = 4; + + + private static DnsRecord[] emptyRecordArray = new DnsRecord[0]; + private static RRset[] emptyRRsetArray = new RRset[0]; + + /** + * Creates a new DnsMessage with the specified DnsMessage ID + */ + public + DnsMessage(int id) { + this(new Header(id)); + } + + private + DnsMessage(Header header) { + this.header = header; + } + + /** + * Creates a new DnsMessage with a random DnsMessage ID + */ + public + DnsMessage() { + this(new Header()); + } + + /** + * Creates a new DnsMessage with a random DnsMessage ID suitable for sending as a + * query. + * + * @param r A record containing the question + */ + public static + DnsMessage newQuery(DnsRecord r) { + DnsMessage m = new DnsMessage(); + m.header.setOpcode(DnsOpCode.QUERY); + m.header.setFlag(Flags.RD); + m.addRecord(r, DnsSection.QUESTION); + return m; + } + + /** + * Creates a new DnsMessage to contain a dynamic update. A random DnsMessage ID + * and the zone are filled in. + * + * @param zone The zone to be updated + */ + public static + DnsMessage newUpdate(Name zone) { + return new Update(zone); + } + + + + + + + + + /** + * Creates a new DnsMessage from its DNS wire format representation + * + * @param b A byte array containing the DNS DnsMessage. + */ + public + DnsMessage(byte[] b) throws IOException { + this(new DnsInput(b)); + } + + /** + * Creates a new DnsMessage from its DNS wire format representation + * + * @param in A DnsInput containing the DNS DnsMessage. + */ + public + DnsMessage(DnsInput in) throws IOException { + this(new Header(in)); + boolean isUpdate = (header.getOpcode() == DnsOpCode.UPDATE); + boolean truncated = header.getFlag(Flags.TC); + try { + for (int i = 0; i < DnsSection.TOTAL_SECTION_COUNT; i++) { + int count = header.getCount(i); + List records; + + if (count > 0) { + records = newRecordList(count); + setSection(i, records); + + + for (int j = 0; j < count; j++) { + int pos = in.readIndex(); + DnsRecord record = DnsRecord.fromWire(in, i, isUpdate); + + records.add(record); + + if (i == DnsSection.ADDITIONAL) { + if (record.getType() == DnsRecordType.TSIG) { + tsigstart = pos; + } + if (record.getType() == DnsRecordType.SIG) { + SIGRecord sig = (SIGRecord) record; + if (sig.getTypeCovered() == 0) { + sig0start = pos; + } + } + } + } + } + } + } catch (WireParseException e) { + if (!truncated) { + throw e; + } + } + size = in.readIndex(); + } + + /** + * Creates a new DnsMessage from its DNS wire format representation + * + * @param byteBuffer A ByteBuf containing the DNS DnsMessage. + */ + public + DnsMessage(ByteBuf byteBuffer) throws IOException { + this(new DnsInput(byteBuffer)); + } + + @SuppressWarnings("unchecked") + private static + T castRecord(Object record) { + return (T) record; + } + + private static + ArrayList newRecordList(int count) { + return new ArrayList(count); + } + + private static + ArrayList newRecordList() { + return new ArrayList(2); + } + + private + Object sectionAt(int section) { + switch (section) { + case DnsSection.QUESTION: + return questions; + case DnsSection.ANSWER: + return answers; + case DnsSection.AUTHORITY: + return authorities; + case DnsSection.ADDITIONAL: + return additionals; + } + + throw new IndexOutOfBoundsException(); // Should never reach here. + } + + private + void setSection(int section, Object value) { + switch (section) { + case DnsSection.QUESTION: + questions = value; + return; + case DnsSection.ANSWER: + answers = value; + return; + case DnsSection.AUTHORITY: + authorities = value; + return; + case DnsSection.ADDITIONAL: + additionals = value; + return; + } + + throw new IndexOutOfBoundsException(); // Should never reach here. + } + + + /** + * Retrieves the Header. + * + * @see Header + */ + public + Header getHeader() { + return header; + } + + /** + * Replaces the Header with a new one. + * + * @see Header + */ + public + void setHeader(Header h) { + header = h; + } + + /** + * Adds a record to a section of the DnsMessage, and adjusts the header. + * + * @see DnsRecord + * @see DnsSection + */ + public + void addRecord(DnsRecord record, int section) { + final Object records = sectionAt(section); + header.incCount(section); + + if (records == null) { + // it holds no records, so add a single record... + setSection(section, record); + return; + } + + if (records instanceof DnsRecord) { + // it holds a single record, so convert it to multiple records + final List recordList = newRecordList(); + recordList.add(castRecord(records)); + recordList.add(record); + setSection(section, recordList); + return; + } + + // holds a list of records + @SuppressWarnings("unchecked") + final List recordList = (List) records; + recordList.add(record); + } + + /** + * Removes a record from a section of the DnsMessage, and adjusts the header. + * + * @see DnsRecord + * @see DnsSection + */ + public + boolean removeRecord(DnsRecord record, int section) { + final Object records = sectionAt(section); + if (records == null) { + // can't remove a record if there are none + return false; + } + + if (records instanceof DnsRecord) { + setSection(section, null); + header.decCount(section); + return true; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + boolean remove = recordList.remove(record); + + if (remove) { + header.decCount(section); + return true; + } + + return false; + } + + /** + * Removes all records from a section of the DnsMessage, and adjusts the header. + * + * @see DnsRecord + * @see DnsSection + */ + public + void removeAllRecords(int section) { + setSection(section, null); + header.setCount(section, 0); + } + + /** + * Determines if the given record is already present in the given section. + * + * @see DnsRecord + * @see DnsSection + */ + public + boolean findRecord(DnsRecord record, int section) { + final Object records = sectionAt(section); + if (records == null) { + return false; + } + + if (records instanceof DnsRecord) { + return records.equals(record); + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return recordList.contains(record); + } + + /** + * Determines if the given record is already present in any section. + * + * @see DnsRecord + * @see DnsSection + */ + public + boolean findRecord(DnsRecord record) { + for (int i = DnsSection.ANSWER; i <= DnsSection.ADDITIONAL; i++) { + if (findRecord(record, i)) { + return true; + } + } + + return false; + } + + /** + * Determines if an RRset with the given name and type is already + * present in any section. + * + * @see RRset + * @see DnsSection + */ + public + boolean findRRset(Name name, int type) { + return (findRRset(name, type, DnsSection.ANSWER) || findRRset(name, type, DnsSection.AUTHORITY) || findRRset(name, + type, + DnsSection.ADDITIONAL)); + } + + /** + * Determines if an RRset with the given name and type is already + * present in the given section. + * + * @see RRset + * @see DnsSection + */ + public + boolean findRRset(Name name, int type, int section) { + final Object records = sectionAt(section); + if (records == null) { + return false; + } + + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + return record.getType() == type && name.equals(record.getName()); + } + + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + for (int i = 0; i < recordList.size(); i++) { + final DnsRecord record = recordList.get(i); + + if (record.getType() == type && name.equals(record.getName())) { + return true; + } + } + + return false; + } + + /** + * Returns the first record in the QUESTION section. + * + * @see DnsRecord + * @see DnsSection + */ + public + DnsRecord getQuestion() { + final Object records = sectionAt(DnsSection.QUESTION); + if (records == null) { + return null; + } + + if (records instanceof DnsRecord) { + return (DnsRecord) records; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return recordList.get(0); + } + + /** + * Returns the TSIG record from the ADDITIONAL section, if one is present. + * + * @see TSIGRecord + * @see TSIG + * @see DnsSection + */ + public + TSIGRecord getTSIG() { + final Object records = sectionAt(DnsSection.ADDITIONAL); + if (records == null) { + return null; + } + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + if (record.type != DnsRecordType.TSIG) { + return null; + } else { + return (TSIGRecord) record; + } + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + + DnsRecord record = recordList.get(recordList.size() - 1); + if (record.type != DnsRecordType.TSIG) { + return null; + } + else { + return (TSIGRecord) record; + } + } + + /** + * Returns an array containing all records in the given section grouped into + * RRsets. + * + * @see RRset + * @see DnsSection + */ + public + RRset[] getSectionRRsets(int section) { + final Object records = sectionAt(section); + if (records == null) { + return emptyRRsetArray; + } + + List sets = new ArrayList(header.getCount(section)); + Set hash = new HashSet(); + + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + + // only 1, so no need to make it complicated + return new RRset[] {new RRset(record)}; + } + + + // now there are multiple records + @SuppressWarnings("unchecked") + final List recordList = (List) records; + + for (int i = 0; i < recordList.size(); i++) { + final DnsRecord record = recordList.get(i); + + Name name = record.getName(); + boolean newset = true; + + if (hash.contains(name)) { + for (int j = sets.size() - 1; j >= 0; j--) { + RRset set = sets.get(j); + + if (set.getType() == record.getRRsetType() && + set.getDClass() == record.getDClass() && + set.getName().equals(name)) { + + set.addRR(record); + newset = false; + break; + } + } + } + + if (newset) { + RRset set = new RRset(record); + sets.add(set); + hash.add(name); + } + } + + return sets.toArray(new RRset[sets.size()]); + } + + /** + * Returns an array containing all records in the given section, or an + * empty array if the section is empty. + * + * @see DnsRecord + * @see DnsSection + */ + public + DnsRecord[] getSectionArray(int section) { + final Object records = sectionAt(section); + if (records == null) { + return emptyRecordArray; + } + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + + // only 1, so no need to make it complicated + return new DnsRecord[] {record}; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + return recordList.toArray(new DnsRecord[recordList.size()]); + } + + /** + * Returns an array containing the wire format representation of the DnsMessage. + */ + public + byte[] toWire() { + DnsOutput out = new DnsOutput(); + toWire(out); + size = out.current(); + return out.toByteArray(); + } + + public + void toWire(DnsOutput out) { + header.toWire(out); + Compression c = new Compression(); + for (int i = 0; i < DnsSection.TOTAL_SECTION_COUNT; i++) { + final Object records = sectionAt(i); + if (records == null) { + continue; + } + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + record.toWire(out, i, c); + continue; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + for (int j = 0; j < recordList.size(); j++) { + DnsRecord record = recordList.get(j); + record.toWire(out, i, c); + } + } + } + + /** + * Returns an array containing the wire format representation of the DnsMessage + * with the specified maximum length. This will generate a truncated + * message (with the TC bit) if the message doesn't fit, and will also + * sign the message with the TSIG key set by a call to setTSIG(). This + * method may return null if the message could not be rendered at all; this + * could happen if maxLength is smaller than a DNS header, for example. + * + * @param maxLength The maximum length of the message. + * + * @return The wire format of the message, or null if the message could not be + * rendered into the specified length. + * + * @see Flags + * @see TSIG + */ + public + byte[] toWire(int maxLength) { + DnsOutput out = new DnsOutput(); + // this will also prep the output stream. + boolean b = toWire(out, maxLength); + if (!b) { + System.err.println("ERROR CREATING MESSAGE FROM WIRE!"); + } + size = out.current(); + + // we output from the start. + out.getByteBuf().readerIndex(0); + return out.toByteArray(); + } + + /* Returns true if the message could be rendered. */ + private + boolean toWire(DnsOutput out, int maxLength) { + if (maxLength < Header.LENGTH) { + return false; + } + + Header newheader = null; + + int tempMaxLength = maxLength; + if (tsigkey != null) { + tempMaxLength -= tsigkey.recordLength(); + } + + OPTRecord opt = getOPT(); + byte[] optBytes = null; + if (opt != null) { + optBytes = opt.toWire(DnsSection.ADDITIONAL); + tempMaxLength -= optBytes.length; + } + + int startpos = out.current(); + header.toWire(out); + + Compression c = new Compression(); + int flags = header.getFlagsByte(); + int additionalCount = 0; + + for (int i = 0; i < DnsSection.TOTAL_SECTION_COUNT; i++) { + int skipped; + + final Object records = sectionAt(i); + if (records == null) { + continue; + } + + skipped = sectionToWire(out, i, c, tempMaxLength); + if (skipped != 0 && i != DnsSection.ADDITIONAL) { + flags = Header.setFlag(flags, Flags.TC, true); + out.writeU16At(header.getCount(i) - skipped, startpos + 4 + 2 * i); + for (int j = i + 1; j < DnsSection.ADDITIONAL; j++) { + out.writeU16At(0, startpos + 4 + 2 * j); + } + break; + } + if (i == DnsSection.ADDITIONAL) { + additionalCount = header.getCount(i) - skipped; + } + } + + if (optBytes != null) { + out.writeByteArray(optBytes); + additionalCount++; + } + + if (flags != header.getFlagsByte()) { + out.writeU16At(flags, startpos + 2); + } + + if (additionalCount != header.getCount(DnsSection.ADDITIONAL)) { + out.writeU16At(additionalCount, startpos + 10); + } + + if (tsigkey != null) { + TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(), tsigerror, querytsig); + + tsigrec.toWire(out, DnsSection.ADDITIONAL, c); + // write size/position info + out.writeU16At(additionalCount + 1, startpos + 10); + } + + return true; + } + + /** + * Returns the OPT record from the ADDITIONAL section, if one is present. + * + * @see OPTRecord + * @see DnsSection + */ + public + OPTRecord getOPT() { + DnsRecord[] additional = getSectionArray(DnsSection.ADDITIONAL); + for (int i = 0; i < additional.length; i++) { + if (additional[i] instanceof OPTRecord) { + return (OPTRecord) additional[i]; + } + } + return null; + } + + /* Returns the number of records not successfully rendered. */ + private + int sectionToWire(DnsOutput out, int section, Compression c, int maxLength) { + final Object records = sectionAt(section); + // will never be null, we check earlier + + int pos = out.current(); + int rendered = 0; + int skipped = 0; + DnsRecord lastRecord = null; + + + + if (records instanceof DnsRecord) { + DnsRecord record = (DnsRecord) records; + + if (section == DnsSection.ADDITIONAL && record.type == DnsRecordType.OPT) { + skipped++; + return skipped; + } + + record.toWire(out, section, c); + + if (out.current() > maxLength) { + out.jump(pos); + return 1 - rendered + skipped; + } + + return skipped; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + int n = recordList.size(); + + for (int i = 0; i < n; i++) { + DnsRecord record = recordList.get(i); + if (section == DnsSection.ADDITIONAL && record.type == DnsRecordType.OPT) { + skipped++; + continue; + } + + if (lastRecord != null && !sameSet(record, lastRecord)) { + pos = out.current(); + rendered = i; + } + + lastRecord = record; + record.toWire(out, section, c); + + if (out.current() > maxLength) { + out.jump(pos); + return n - rendered + skipped; + } + } + return skipped; + } + + private static + boolean sameSet(DnsRecord r1, DnsRecord r2) { + return (r1.getRRsetType() == r2.getRRsetType() && r1.getDClass() == r2.getDClass() && r1.getName() + .equals(r2.getName())); + } + + /** + * Sets the TSIG key and other necessary information to sign a message. + * + * @param key The TSIG key. + * @param error The value of the TSIG error field. + * @param querytsig If this is a response, the TSIG from the request. + */ + public + void setTSIG(TSIG key, int error, TSIGRecord querytsig) { + this.tsigkey = key; + this.tsigerror = error; + this.querytsig = querytsig; + } + + /** + * Creates a SHALLOW copy of this DnsMessage. This is done by the Resolver before adding + * TSIG and OPT records, for example. + * + * @see TSIGRecord + * @see OPTRecord + */ + @Override + public + Object clone() { + DnsMessage m = new DnsMessage(); + + + for (int i = 0; i < DnsSection.TOTAL_SECTION_COUNT; i++) { + final Object records = sectionAt(i); + if (records == null) { + continue; + } + + if (records instanceof DnsRecord) { + setSection(i, records); + continue; + } + + @SuppressWarnings("unchecked") + final List recordList = (List) records; + setSection(i, new ArrayList(recordList)); + } + + m.header = (Header) header.clone(); + m.size = size; + return m; + } + + /** + * Converts the DnsMessage to a String. + */ + public + String toString() { + String NL = OS.LINE_SEPARATOR; + + StringBuilder sb = new StringBuilder(NL); + OPTRecord opt = getOPT(); + + if (opt != null) { + sb.append(header.toStringWithRcode(getRcode())) + .append(NL); + } + else { + sb.append(header) + .append(NL); + } + + if (isSigned()) { + sb.append(";; TSIG "); + if (isVerified()) { + sb.append("ok"); + } + else { + sb.append("invalid"); + } + sb.append(NL); + } + + for (int i = 0; i < 4; i++) { + if (header.getOpcode() != DnsOpCode.UPDATE) { + sb.append(";; ") + .append(DnsSection.longString(i)) + .append(":") + .append(NL); + } + else { + sb.append(";; ") + .append(DnsSection.updString(i)) + .append(":") + .append(NL); + } + sb.append(sectionToString(i)) + .append(NL); + } + + sb.append(";; DnsMessage size: ") + .append(numBytes()) + .append(" bytes"); + return sb.toString(); + } + + /** + * Was this message signed by a TSIG? + * + * @see TSIG + */ + public + boolean isSigned() { + return (tsigState == TSIG_SIGNED || tsigState == TSIG_VERIFIED || tsigState == TSIG_FAILED); + } + + /** + * If this message was signed by a TSIG, was the TSIG verified? + * + * @see TSIG + */ + public + boolean isVerified() { + return (tsigState == TSIG_VERIFIED); + } + + /** + * Returns the message's rcode (error code). This incorporates the EDNS + * extended rcode. + */ + public + int getRcode() { + int rcode = header.getRcode(); + OPTRecord opt = getOPT(); + if (opt != null) { + rcode += (opt.getExtendedRcode() << 4); + } + return rcode; + } + + /** + * Returns the size of the message. Only valid if the message has been + * converted to or from wire format. + */ + public + int numBytes() { + return size; + } + + /** + * Converts the given section of the DnsMessage to a String. + * + * @see DnsSection + */ + public + String sectionToString(int i) { + if (i > 3) { + return null; + } + + StringBuilder sb = new StringBuilder(); + + DnsRecord[] records = getSectionArray(i); + for (int j = 0; j < records.length; j++) { + DnsRecord rec = records[j]; + if (i == DnsSection.QUESTION) { + sb.append(";;\t") + .append(rec.name); + sb.append(", type = ") + .append(DnsRecordType.string(rec.type)); + sb.append(", class = ") + .append(DnsClass.string(rec.dclass)); + } + else { + sb.append(rec); + } + + sb.append(OS.LINE_SEPARATOR); + } + return sb.toString(); + } + + /** + * Removes all the records in this DNS message. + */ + @SuppressWarnings("unchecked") + public + DnsMessage clear() { + for (int i = 0; i < DnsSection.TOTAL_SECTION_COUNT; i++) { + removeAllRecords(i); + } + return this; + } + + @Override + protected + void deallocate() { + clear(); + + final ResourceLeakTracker leak = this.leak; + if (leak != null) { + boolean closed = leak.close(this); + assert closed; + } + } + + @Override + public + DnsMessage touch(Object hint) { + if (leak != null) { + leak.record(hint); + } + return this; + } +} diff --git a/src/dorkbox/network/dns/records/DnsRecord.java b/src/dorkbox/network/dns/records/DnsRecord.java new file mode 100644 index 00000000..e09257b2 --- /dev/null +++ b/src/dorkbox/network/dns/records/DnsRecord.java @@ -0,0 +1,823 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Arrays; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.exceptions.RelativeNameException; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * A generic DNS resource record. The specific record types extend this class. + * A record contains a name, type, class, ttl, and rdata. + * + * @author Brian Wellington + */ + +public abstract +class DnsRecord implements Cloneable, Comparable, Serializable { + + private static final long serialVersionUID = 2694906050116005466L; + + protected Name name; + protected int type, dclass; + protected long ttl; + + private static final DecimalFormat byteFormat = new DecimalFormat(); + + static { + byteFormat.setMinimumIntegerDigits(3); + } + + protected + DnsRecord() {} + + DnsRecord(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + DnsRecordType.check(type); + DnsClass.check(dclass); + TTL.check(ttl); + this.name = name; + this.type = type; + this.dclass = dclass; + this.ttl = ttl; + } + + /** + * Creates a new record, with the given parameters. + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param data The complete rdata of the record, in uncompressed DNS wire + * format. + */ + public static + DnsRecord newRecord(Name name, int type, int dclass, long ttl, byte[] data) { + return newRecord(name, type, dclass, ttl, data.length, data); + } + + /** + * Creates a new record, with the given parameters. + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param length The length of the record's data. + * @param data The rdata of the record, in uncompressed DNS wire format. Only + * the first length bytes are used. + */ + public static + DnsRecord newRecord(Name name, int type, int dclass, long ttl, int length, byte[] data) { + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + DnsRecordType.check(type); + DnsClass.check(dclass); + TTL.check(ttl); + + DnsInput in; + if (data != null) { + in = new DnsInput(data); + } + else { + in = null; + } + try { + return newRecord(name, type, dclass, ttl, length, in); + } catch (IOException e) { + return null; + } + } + + private static + DnsRecord newRecord(Name name, int type, int dclass, long ttl, int length, DnsInput in) throws IOException { + DnsRecord rec; + rec = getEmptyRecord(name, type, dclass, ttl, in != null); + if (in != null) { + if (in.remaining() < length) { + throw new WireParseException("truncated record"); + } + in.setActive(length); + + rec.rrFromWire(in); + + int remaining = in.remaining(); + in.restoreActive(); + + if (remaining > 0) { + throw new WireParseException("invalid record length"); + } + } + return rec; + } + + private static + DnsRecord getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { + DnsRecord proto, rec; + + if (hasData) { + proto = DnsRecordType.getProto(type); + if (proto != null) { + rec = proto.getObject(); + } + else { + rec = new UNKRecord(); + } + } + else { + rec = new EmptyRecord(); + } + rec.name = name; + rec.type = type; + rec.dclass = dclass; + rec.ttl = ttl; + return rec; + } + + + + + + /** + * Creates an empty record of the correct type; must be overriden + */ + abstract + DnsRecord getObject(); + + /** + * Converts the type-specific RR to wire format - must be overriden + */ + abstract + void rrFromWire(DnsInput in) throws IOException; + + + + /** + * Creates a new empty record, with the given parameters. + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * + * @return An object of a subclass of Record + */ + public static + DnsRecord newRecord(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + DnsRecordType.check(type); + DnsClass.check(dclass); + TTL.check(ttl); + + return getEmptyRecord(name, type, dclass, ttl, false); + } + + /** + * Creates a new empty record, with the given parameters. This method is + * designed to create records that will be added to the QUERY section + * of a message. + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * + * @return An object of a subclass of Record + */ + public static + DnsRecord newRecord(Name name, int type, int dclass) { + return newRecord(name, type, dclass, 0); + } + + static + DnsRecord fromWire(DnsInput in, int section, boolean isUpdate) throws IOException { + int type, dclass; + long ttl; + int length; + Name name; + DnsRecord rec; + + name = new Name(in); + type = in.readU16(); + dclass = in.readU16(); + + if (section == DnsSection.QUESTION) { + return newRecord(name, type, dclass); + } + + ttl = in.readU32(); + length = in.readU16(); + if (length == 0 && isUpdate && (section == DnsSection.PREREQ || section == DnsSection.UPDATE)) { + return newRecord(name, type, dclass, ttl); + } + rec = newRecord(name, type, dclass, ttl, length, in); + return rec; + } + + static + DnsRecord fromWire(DnsInput in, int section) throws IOException { + return fromWire(in, section, false); + } + + /** + * Builds a Record from DNS uncompressed wire format. + */ + public static + DnsRecord fromWire(byte[] b, int section) throws IOException { + return fromWire(new DnsInput(b), section, false); + } + + + + + + + + /** + * Converts a Record into DNS uncompressed wire format. + */ + public + byte[] toWire(int section) { + DnsOutput out = new DnsOutput(); + toWire(out, section, null); + return out.toByteArray(); + } + + void toWire(DnsOutput out, int section, Compression c) { + name.toWire(out, c); + out.writeU16(type); + out.writeU16(dclass); + if (section == DnsSection.QUESTION) { + return; + } + out.writeU32(ttl); + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, c, false); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); + } + + /** + * Converts the type-specific RR to wire format - must be overriden + */ + abstract + void rrToWire(DnsOutput out, Compression c, boolean canonical); + + /** + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase). + */ + public + byte[] toWireCanonical() { + return toWireCanonical(false); + } + + /* + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase), optionally ignoring the TTL. + */ + private + byte[] toWireCanonical(boolean noTTL) { + DnsOutput out = new DnsOutput(); + toWireCanonical(out, noTTL); + return out.toByteArray(); + } + + private + void toWireCanonical(DnsOutput out, boolean noTTL) { + name.toWireCanonical(out); + out.writeU16(type); + out.writeU16(dclass); + if (noTTL) { + out.writeU32(0); + } + else { + out.writeU32(ttl); + } + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, null, true); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); + } + + /** + * Converts the rdata portion of a Record into a String representation + */ + public + void rdataToString(StringBuilder sb) { + rrToString(sb); + } + + /** + * Converts the type-specific RR to text format - must be overriden + */ + abstract + void rrToString(StringBuilder sb); + + /** + * Converts the text format of an RR to the internal format - must be overriden + */ + abstract + void rdataFromString(Tokenizer st, Name origin) throws IOException; + + /** + * Converts a String into a byte array. + */ + protected static + byte[] byteArrayFromString(String s) throws TextParseException { + byte[] array = s.getBytes(); + boolean escaped = false; + boolean hasEscapes = false; + + for (int i = 0; i < array.length; i++) { + if (array[i] == '\\') { + hasEscapes = true; + break; + } + } + if (!hasEscapes) { + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + return array; + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int digits = 0; + int intval = 0; + for (int i = 0; i < array.length; i++) { + byte b = array[i]; + if (escaped) { + if (b >= '0' && b <= '9' && digits < 3) { + digits++; + intval *= 10; + intval += (b - '0'); + if (intval > 255) { + throw new TextParseException("bad escape"); + } + if (digits < 3) { + continue; + } + b = (byte) intval; + } + else if (digits > 0 && digits < 3) { + throw new TextParseException("bad escape"); + } + os.write(b); + escaped = false; + } + else if (array[i] == '\\') { + escaped = true; + digits = 0; + intval = 0; + } + else { + os.write(array[i]); + } + } + if (digits > 0 && digits < 3) { + throw new TextParseException("bad escape"); + } + array = os.toByteArray(); + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + + return os.toByteArray(); + } + + /** + * Converts a byte array into a String. + */ + protected static + String byteArrayToString(byte[] array, boolean quote) { + StringBuilder sb = new StringBuilder(); + if (quote) { + sb.append('"'); + } + for (int i = 0; i < array.length; i++) { + int b = array[i] & 0xFF; + if (b < 0x20 || b >= 0x7f) { + sb.append('\\'); + sb.append(byteFormat.format(b)); + } + else if (b == '"' || b == '\\') { + sb.append('\\'); + sb.append((char) b); + } + else { + sb.append((char) b); + } + } + if (quote) { + sb.append('"'); + } + return sb.toString(); + } + + /** + * Converts a byte array into the unknown RR format. + */ + protected static + String unknownToString(byte[] data) { + StringBuilder sb = new StringBuilder(); + sb.append("\\# "); + sb.append(data.length); + sb.append(" "); + sb.append(base16.toString(data)); + return sb.toString(); + } + + /** + * Builds a new Record from its textual representation + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param st A tokenizer containing the textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * + * @return The new record + * + * @throws IOException The text format was invalid. + */ + public static + DnsRecord fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) throws IOException { + DnsRecord rec; + + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + DnsRecordType.check(type); + DnsClass.check(dclass); + TTL.check(ttl); + + Tokenizer.Token t = st.get(); + if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { + int length = st.getUInt16(); + byte[] data = st.getHex(); + if (data == null) { + data = new byte[0]; + } + if (length != data.length) { + throw st.exception("invalid unknown RR encoding: " + "length mismatch"); + } + DnsInput in = new DnsInput(data); + return newRecord(name, type, dclass, ttl, length, in); + } + st.unget(); + rec = getEmptyRecord(name, type, dclass, ttl, true); + rec.rdataFromString(st, origin); + t = st.get(); + if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { + throw st.exception("unexpected tokens at end of record"); + } + return rec; + } + + /** + * Builds a new Record from its textual representation + * + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param s The textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * + * @return The new record + * + * @throws IOException The text format was invalid. + */ + public static + DnsRecord fromString(Name name, int type, int dclass, long ttl, String s, Name origin) throws IOException { + return fromString(name, type, dclass, ttl, new Tokenizer(s), origin); + } + + /** + * Returns the record's name + * + * @see Name + */ + public + Name getName() { + return name; + } + + /** + * Returns the record's type + * + * @see DnsRecordType + */ + public + int getType() { + return type; + } + + /** + * Returns the record's class + */ + public + int getDClass() { + return dclass; + } + + /** + * Returns the record's TTL + */ + public + long getTTL() { + return ttl; + } + + /* Sets the TTL to the specified value. This is intentionally not public. EDIT: public now so we can change it if we want to */ + public + void setTTL(long ttl) { + this.ttl = ttl; + } + + /** + * Determines if two Records could be part of the same RRset. + * This compares the name, type, and class of the Records; the ttl and + * rdata are not compared. + */ + public + boolean sameRRset(DnsRecord rec) { + return (getRRsetType() == rec.getRRsetType() && dclass == rec.dclass && name.equals(rec.name)); + } + + /** + * Returns the type of RRset that this record would belong to. For all types + * except RRSIG, this is equivalent to getType(). + * + * @return The type of record, if not RRSIG. If the type is RRSIG, + * the type covered is returned. + * + * @see DnsRecordType + * @see RRset + * @see SIGRecord + */ + public + int getRRsetType() { + if (type == DnsRecordType.RRSIG) { + RRSIGRecord sig = (RRSIGRecord) this; + return sig.getTypeCovered(); + } + return type; + } + + /** + * Generates a hash code based on the Record's data. + */ + public + int hashCode() { + byte[] array = toWireCanonical(true); + int code = 0; + for (int i = 0; i < array.length; i++) { + code += ((code << 3) + (array[i] & 0xFF)); + } + return code; + } + + /** + * Determines if two Records are identical. This compares the name, type, + * class, and rdata (with names canonicalized). The TTLs are not compared. + * + * @param arg The record to compare to + * + * @return true if the records are equal, false otherwise. + */ + public + boolean equals(Object arg) { + if (arg == null || !(arg instanceof DnsRecord)) { + return false; + } + DnsRecord r = (DnsRecord) arg; + if (type != r.type || dclass != r.dclass || !name.equals(r.name)) { + return false; + } + byte[] array1 = rdataToWireCanonical(); + byte[] array2 = r.rdataToWireCanonical(); + return Arrays.equals(array1, array2); + } + + /** + * Converts the rdata in a Record into canonical DNS uncompressed wire format + * (all names are converted to lowercase). + */ + public + byte[] rdataToWireCanonical() { + DnsOutput out = new DnsOutput(); + rrToWire(out, null, true); + return out.toByteArray(); + } + + /** + * Converts a Record into a String representation + */ + @Override + public final + String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb); + return sb.toString(); + } + + /** + * Converts a Record into a String representation in a StringBuilder + */ + public + void toString(StringBuilder sb) { + sb.append(name); + if (sb.length() < 8) { + sb.append("\t"); + } + if (sb.length() < 16) { + sb.append("\t"); + } + sb.append("\t"); + if (Options.check("BINDTTL")) { + sb.append(TTL.format(ttl)); + } + else { + sb.append(ttl); + } + sb.append("\t"); + if (dclass != DnsClass.IN || !Options.check("noPrintIN")) { + sb.append(DnsClass.string(dclass)); + sb.append("\t"); + } + sb.append(DnsRecordType.string(type)); + + sb.append("\t"); + int length = sb.length(); + rrToString(sb); + + if (length == sb.length()) { + // delete the /t since we had no record data + sb.deleteCharAt(length-1); + } + } + + /** + * Creates a new record identical to the current record, but with a different + * name. This is most useful for replacing the name of a wildcard record. + */ + public + DnsRecord withName(Name name) { + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + DnsRecord rec = cloneRecord(); + rec.name = name; + return rec; + } + + DnsRecord cloneRecord() { + try { + return (DnsRecord) clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(); + } + } + + /** + * Creates a new record identical to the current record, but with a different + * class and ttl. This is most useful for dynamic update. + */ + DnsRecord withDClass(int dclass, long ttl) { + DnsRecord rec = cloneRecord(); + rec.dclass = dclass; + rec.ttl = ttl; + return rec; + } + + /** + * Compares this Record to another Object. + * + * @param o The Object to be compared. + * + * @return The value 0 if the argument is a record equivalent to this record; + * a value less than 0 if the argument is less than this record in the + * canonical ordering, and a value greater than 0 if the argument is greater + * than this record in the canonical ordering. The canonical ordering + * is defined to compare by name, class, type, and rdata. + * + * @throws ClassCastException if the argument is not a Record. + */ + @Override + public + int compareTo(Object o) { + DnsRecord arg = (DnsRecord) o; + + if (this == arg) { + return (0); + } + + int n = name.compareTo(arg.name); + if (n != 0) { + return (n); + } + n = dclass - arg.dclass; + if (n != 0) { + return (n); + } + n = type - arg.type; + if (n != 0) { + return (n); + } + byte[] rdata1 = rdataToWireCanonical(); + byte[] rdata2 = arg.rdataToWireCanonical(); + for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { + n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); + if (n != 0) { + return (n); + } + } + return (rdata1.length - rdata2.length); + } + + /** + * Returns the name for which additional data processing should be done + * for this record. This can be used both for building responses and + * parsing responses. + * + * @return The name to used for additional data processing, or null if this + * record type does not require additional data processing. + */ + public + Name getAdditionalName() { + return null; + } + + /* Checks that an int contains an unsigned 8 bit value */ + static + int checkU8(String field, int val) { + if (val < 0 || val > 0xFF) { + throw new IllegalArgumentException("\"" + field + "\" " + val + " must be an unsigned 8 " + "bit value"); + } + return val; + } + + /* Checks that an int contains an unsigned 16 bit value */ + static + int checkU16(String field, int val) { + if (val < 0 || val > 0xFFFF) { + throw new IllegalArgumentException("\"" + field + "\" " + val + " must be an unsigned 16 " + "bit value"); + } + return val; + } + + /* Checks that a long contains an unsigned 32 bit value */ + static + long checkU32(String field, long val) { + if (val < 0 || val > 0xFFFFFFFFL) { + throw new IllegalArgumentException("\"" + field + "\" " + val + " must be an unsigned 32 " + "bit value"); + } + return val; + } + + /* Checks that a name is absolute */ + static + Name checkName(String field, Name name) { + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + return name; + } + + static + byte[] checkByteArrayLength(String field, byte[] array, int maxLength) { + if (array.length > 0xFFFF) { + throw new IllegalArgumentException("\"" + field + "\" array " + "must have no more than " + maxLength + " elements"); + } + byte[] out = new byte[array.length]; + System.arraycopy(array, 0, out, 0, array.length); + return out; + } +} diff --git a/src/dorkbox/network/dns/records/DnsTypeProtoAssignment.java b/src/dorkbox/network/dns/records/DnsTypeProtoAssignment.java new file mode 100644 index 00000000..23658792 --- /dev/null +++ b/src/dorkbox/network/dns/records/DnsTypeProtoAssignment.java @@ -0,0 +1,78 @@ +package dorkbox.network.dns.records; + +import dorkbox.network.dns.constants.DnsRecordType; + +public +class DnsTypeProtoAssignment { + + // this is so we don't have to make each type constructor public + public static + void assign(final DnsRecordType.TypeMnemonic types) { + types.add(DnsRecordType.A, "A", new ARecord()); + types.add(DnsRecordType.NS, "NS", new NSRecord()); + types.add(DnsRecordType.MD, "MD", new MDRecord()); + types.add(DnsRecordType.MF, "MF", new MFRecord()); + types.add(DnsRecordType.CNAME, "CNAME", new CNAMERecord()); + types.add(DnsRecordType.SOA, "SOA", new SOARecord()); + types.add(DnsRecordType.MB, "MB", new MBRecord()); + types.add(DnsRecordType.MG, "MG", new MGRecord()); + types.add(DnsRecordType.MR, "MR", new MRRecord()); + types.add(DnsRecordType.NULL, "NULL", new NULLRecord()); + types.add(DnsRecordType.WKS, "WKS", new WKSRecord()); + types.add(DnsRecordType.PTR, "PTR", new PTRRecord()); + types.add(DnsRecordType.HINFO, "HINFO", new HINFORecord()); + types.add(DnsRecordType.MINFO, "MINFO", new MINFORecord()); + types.add(DnsRecordType.MX, "MX", new MXRecord()); + types.add(DnsRecordType.TXT, "TXT", new TXTRecord()); + types.add(DnsRecordType.RP, "RP", new RPRecord()); + types.add(DnsRecordType.AFSDB, "AFSDB", new AFSDBRecord()); + types.add(DnsRecordType.X25, "X25", new X25Record()); + types.add(DnsRecordType.ISDN, "ISDN", new ISDNRecord()); + types.add(DnsRecordType.RT, "RT", new RTRecord()); + types.add(DnsRecordType.NSAP, "NSAP", new NSAPRecord()); + types.add(DnsRecordType.NSAP_PTR, "NSAP-PTR", new NSAP_PTRRecord()); + types.add(DnsRecordType.SIG, "SIG", new SIGRecord()); + types.add(DnsRecordType.KEY, "KEY", new KEYRecord()); + types.add(DnsRecordType.PX, "PX", new PXRecord()); + types.add(DnsRecordType.GPOS, "GPOS", new GPOSRecord()); + types.add(DnsRecordType.AAAA, "AAAA", new AAAARecord()); + types.add(DnsRecordType.LOC, "LOC", new LOCRecord()); + types.add(DnsRecordType.NXT, "NXT", new NXTRecord()); + types.add(DnsRecordType.EID, "EID"); + types.add(DnsRecordType.NIMLOC, "NIMLOC"); + types.add(DnsRecordType.SRV, "SRV", new SRVRecord()); + types.add(DnsRecordType.ATMA, "ATMA"); + types.add(DnsRecordType.NAPTR, "NAPTR", new NAPTRRecord()); + types.add(DnsRecordType.KX, "KX", new KXRecord()); + types.add(DnsRecordType.CERT, "CERT", new CERTRecord()); + types.add(DnsRecordType.A6, "A6", new A6Record()); + types.add(DnsRecordType.DNAME, "DNAME", new DNAMERecord()); + types.add(DnsRecordType.OPT, "OPT", new OPTRecord()); + types.add(DnsRecordType.APL, "APL", new APLRecord()); + types.add(DnsRecordType.DS, "DS", new DSRecord()); + types.add(DnsRecordType.SSHFP, "SSHFP", new SSHFPRecord()); + types.add(DnsRecordType.IPSECKEY, "IPSECKEY", new IPSECKEYRecord()); + types.add(DnsRecordType.RRSIG, "RRSIG", new RRSIGRecord()); + types.add(DnsRecordType.NSEC, "NSEC", new NSECRecord()); + types.add(DnsRecordType.DNSKEY, "DNSKEY", new DNSKEYRecord()); + types.add(DnsRecordType.DHCID, "DHCID", new DHCIDRecord()); + types.add(DnsRecordType.NSEC3, "NSEC3", new NSEC3Record()); + types.add(DnsRecordType.NSEC3PARAM, "NSEC3PARAM", new NSEC3PARAMRecord()); + types.add(DnsRecordType.TLSA, "TLSA", new TLSARecord()); + types.add(DnsRecordType.SMIMEA, "SMIMEA", new SMIMEARecord()); + types.add(DnsRecordType.SMIMEA, "HIP"); + types.add(DnsRecordType.OPENPGPKEY, "OPENPGPKEY", new OPENPGPKEYRecord()); + types.add(DnsRecordType.SPF, "SPF", new SPFRecord()); + types.add(DnsRecordType.TKEY, "TKEY", new TKEYRecord()); + types.add(DnsRecordType.TSIG, "TSIG", new TSIGRecord()); + types.add(DnsRecordType.IXFR, "IXFR"); + types.add(DnsRecordType.AXFR, "AXFR"); + types.add(DnsRecordType.MAILB, "MAILB"); + types.add(DnsRecordType.MAILA, "MAILA"); + types.add(DnsRecordType.ANY, "ANY"); + types.add(DnsRecordType.URI, "URI", new URIRecord()); + types.add(DnsRecordType.CAA, "CAA", new CAARecord()); + types.add(DnsRecordType.TA, "TA"); + types.add(DnsRecordType.DLV, "DLV", new DLVRecord()); + } +} diff --git a/src/dorkbox/network/dns/records/EDNSOption.java b/src/dorkbox/network/dns/records/EDNSOption.java new file mode 100644 index 00000000..c9f1ff6a --- /dev/null +++ b/src/dorkbox/network/dns/records/EDNSOption.java @@ -0,0 +1,236 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.Arrays; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.exceptions.WireParseException; + +/** + * DNS extension options, as described in RFC 2671. The rdata of an OPT record + * is defined as a list of options; this represents a single option. + * + * @author Brian Wellington + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + */ +public abstract +class EDNSOption { + + private final int code; + + + public static + class Code { + /** + * Name Server Identifier, RFC 5001 + */ + public final static int NSID = 3; + /** + * Client Subnet, defined in draft-vandergaast-edns-client-subnet-02 + */ + public final static int CLIENT_SUBNET = 8; + private static Mnemonic codes = new Mnemonic("EDNS Option Codes", Mnemonic.CASE_UPPER); + + private + Code() {} + + static { + codes.setMaximum(0xFFFF); + codes.setPrefix("CODE"); + codes.setNumericAllowed(true); + + codes.add(NSID, "NSID"); + codes.add(CLIENT_SUBNET, "CLIENT_SUBNET"); + } + + /** + * Converts an EDNS Option Code into its textual representation + */ + public static + String string(int code) { + return codes.getText(code); + } + + /** + * Converts a textual representation of an EDNS Option Code into its + * numeric value. + * + * @param s The textual representation of the option code + * + * @return The option code, or -1 on error. + */ + public static + int value(String s) { + return codes.getValue(s); + } + } + + /** + * Creates an option with the given option code and data. + */ + public + EDNSOption(int code) { + this.code = DnsRecord.checkU16("code", code); + } + + /** + * Returns the EDNS Option's code. + * + * @return the option code + */ + public + int getCode() { + return code; + } + + /** + * Converts the wire format of an EDNS Option (including code and length) into + * the type-specific format. + * + * @return The option, in wire format. + */ + public static + EDNSOption fromWire(byte[] b) throws IOException { + return fromWire(new DnsInput(b)); + } + + /** + * Converts the wire format of an EDNS Option (including code and length) into + * the type-specific format. + * + * @param in The input stream. + */ + static + EDNSOption fromWire(DnsInput in) throws IOException { + int code, length; + + code = in.readU16(); + length = in.readU16(); + if (in.remaining() < length) { + throw new WireParseException("truncated option"); + } + in.setActive(length); + EDNSOption option; + switch (code) { + case Code.NSID: + option = new NSIDOption(); + break; + case Code.CLIENT_SUBNET: + option = new ClientSubnetOption(); + break; + default: + option = new GenericEDNSOption(code); + break; + } + option.optionFromWire(in); + in.restoreActive(); + + return option; + } + + /** + * Converts the wire format of an EDNS Option (the option data only) into the + * type-specific format. + * + * @param in The input Stream. + */ + abstract + void optionFromWire(DnsInput in) throws IOException; + + /** + * Converts an EDNS Option (including code and length) into wire format. + * + * @return The option, in wire format. + */ + public + byte[] toWire() throws IOException { + DnsOutput out = new DnsOutput(); + toWire(out); + return out.toByteArray(); + } + + /** + * Converts an EDNS Option (including code and length) into wire format. + * + * @param out The output stream. + */ + void toWire(DnsOutput out) { + out.writeU16(code); + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + optionToWire(out); + int length = out.current() - lengthPosition - 2; + out.writeU16At(length, lengthPosition); + } + + /** + * Converts an EDNS Option (the type-specific option data only) into wire format. + * + * @param out The output stream. + */ + abstract + void optionToWire(DnsOutput out); + + /** + * Generates a hash code based on the EDNS Option's data. + */ + public + int hashCode() { + byte[] array = getData(); + int hashval = 0; + for (int i = 0; i < array.length; i++) { + hashval += ((hashval << 3) + (array[i] & 0xFF)); + } + return hashval; + } + + /** + * Determines if two EDNS Options are identical. + * + * @param arg The option to compare to + * + * @return true if the options are equal, false otherwise. + */ + public + boolean equals(Object arg) { + if (arg == null || !(arg instanceof EDNSOption)) { + return false; + } + EDNSOption opt = (EDNSOption) arg; + if (code != opt.code) { + return false; + } + return Arrays.equals(getData(), opt.getData()); + } + + public + String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("{"); + sb.append(EDNSOption.Code.string(code)); + sb.append(": "); + sb.append(optionToString()); + sb.append("}"); + + return sb.toString(); + } + + abstract + String optionToString(); + + /** + * Returns the EDNS Option's data, as a byte array. + * + * @return the option data + */ + byte[] getData() { + DnsOutput out = new DnsOutput(); + optionToWire(out); + return out.toByteArray(); + } + +} diff --git a/src/dorkbox/network/dns/records/EmptyRecord.java b/src/dorkbox/network/dns/records/EmptyRecord.java new file mode 100644 index 00000000..bc741bce --- /dev/null +++ b/src/dorkbox/network/dns/records/EmptyRecord.java @@ -0,0 +1,47 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * A class implementing Records with no data; that is, records used in + * the question section of messages and meta-records in dynamic update. + * + * @author Brian Wellington + */ + +class EmptyRecord extends DnsRecord { + + private static final long serialVersionUID = 3601852050646429582L; + + EmptyRecord() {} + + @Override + DnsRecord getObject() { + return new EmptyRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + } + + @Override + void rrToString(StringBuilder sb) { + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + } + +} diff --git a/src/dorkbox/network/dns/records/ExtendedFlags.java b/src/dorkbox/network/dns/records/ExtendedFlags.java new file mode 100644 index 00000000..244d5575 --- /dev/null +++ b/src/dorkbox/network/dns/records/ExtendedFlags.java @@ -0,0 +1,51 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Mnemonic; + +/** + * Constants and functions relating to EDNS flags. + * + * @author Brian Wellington + */ + +public final +class ExtendedFlags { + + private static Mnemonic extflags = new Mnemonic("EDNS Flag", Mnemonic.CASE_LOWER); + + /** + * dnssec ok + */ + public static final int DO = 0x8000; + + static { + extflags.setMaximum(0xFFFF); + extflags.setPrefix("FLAG"); + extflags.setNumericAllowed(true); + + extflags.add(DO, "do"); + } + + private + ExtendedFlags() {} + + /** + * Converts a numeric extended flag into a String + */ + public static + String string(int i) { + return extflags.getText(i); + } + + /** + * Converts a textual representation of an extended flag into its numeric + * value + */ + public static + int value(String s) { + return extflags.getValue(s); + } + +} diff --git a/src/dorkbox/network/dns/records/GPOSRecord.java b/src/dorkbox/network/dns/records/GPOSRecord.java new file mode 100644 index 00000000..2f48c44a --- /dev/null +++ b/src/dorkbox/network/dns/records/GPOSRecord.java @@ -0,0 +1,191 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Geographical Location - describes the physical location of a host. + * + * @author Brian Wellington + */ + +public +class GPOSRecord extends DnsRecord { + + private static final long serialVersionUID = -6349714958085750705L; + + private byte[] latitude, longitude, altitude; + + GPOSRecord() {} + + @Override + DnsRecord getObject() { + return new GPOSRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + longitude = in.readCountedString(); + latitude = in.readCountedString(); + altitude = in.readCountedString(); + try { + validate(getLongitude(), getLatitude()); + } catch (IllegalArgumentException e) { + throw new WireParseException(e.getMessage()); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeCountedString(longitude); + out.writeCountedString(latitude); + out.writeCountedString(altitude); + } + + /** + * Convert to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(byteArrayToString(longitude, true)); + sb.append(" "); + sb.append(byteArrayToString(latitude, true)); + sb.append(" "); + sb.append(byteArrayToString(altitude, true)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + longitude = byteArrayFromString(st.getString()); + latitude = byteArrayFromString(st.getString()); + altitude = byteArrayFromString(st.getString()); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + try { + validate(getLongitude(), getLatitude()); + } catch (IllegalArgumentException e) { + throw new WireParseException(e.getMessage()); + } + } + + /** + * Creates an GPOS Record from the given data + * + * @param longitude The longitude component of the location. + * @param latitude The latitude component of the location. + * @param altitude The altitude component of the location (in meters above sea + * level). + */ + public + GPOSRecord(Name name, int dclass, long ttl, double longitude, double latitude, double altitude) { + super(name, DnsRecordType.GPOS, dclass, ttl); + validate(longitude, latitude); + this.longitude = Double.toString(longitude) + .getBytes(); + this.latitude = Double.toString(latitude) + .getBytes(); + this.altitude = Double.toString(altitude) + .getBytes(); + } + + private + void validate(double longitude, double latitude) throws IllegalArgumentException { + if (longitude < -90.0 || longitude > 90.0) { + throw new IllegalArgumentException("illegal longitude " + longitude); + } + if (latitude < -180.0 || latitude > 180.0) { + throw new IllegalArgumentException("illegal latitude " + latitude); + } + } + + /** + * Creates an GPOS Record from the given data + * + * @param longitude The longitude component of the location. + * @param latitude The latitude component of the location. + * @param altitude The altitude component of the location (in meters above sea + * level). + */ + public + GPOSRecord(Name name, int dclass, long ttl, String longitude, String latitude, String altitude) { + super(name, DnsRecordType.GPOS, dclass, ttl); + try { + this.longitude = byteArrayFromString(longitude); + this.latitude = byteArrayFromString(latitude); + validate(getLongitude(), getLatitude()); + this.altitude = byteArrayFromString(altitude); + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * Returns the longitude as a double + * + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ + public + double getLongitude() { + return Double.parseDouble(getLongitudeString()); + } + + /** + * Returns the longitude as a string + */ + public + String getLongitudeString() { + return byteArrayToString(longitude, false); + } + + /** + * Returns the latitude as a double + * + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ + public + double getLatitude() { + return Double.parseDouble(getLatitudeString()); + } + + /** + * Returns the latitude as a string + */ + public + String getLatitudeString() { + return byteArrayToString(latitude, false); + } + + /** + * Returns the altitude as a double + * + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ + public + double getAltitude() { + return Double.parseDouble(getAltitudeString()); + } + + /** + * Returns the altitude as a string + */ + public + String getAltitudeString() { + return byteArrayToString(altitude, false); + } + +} diff --git a/src/dorkbox/network/dns/records/GenericEDNSOption.java b/src/dorkbox/network/dns/records/GenericEDNSOption.java new file mode 100644 index 00000000..8ef64e39 --- /dev/null +++ b/src/dorkbox/network/dns/records/GenericEDNSOption.java @@ -0,0 +1,51 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.utils.base16; + +/** + * An EDNSOption with no internal structure. + * + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + * @author Brian Wellington + */ +public +class GenericEDNSOption extends EDNSOption { + + private byte[] data; + + GenericEDNSOption(int code) { + super(code); + } + + /** + * Construct a generic EDNS option. + * + * @param data The contents of the option. + */ + public + GenericEDNSOption(int code, byte[] data) { + super(code); + this.data = DnsRecord.checkByteArrayLength("option data", data, 0xFFFF); + } + + @Override + void optionFromWire(DnsInput in) throws IOException { + data = in.readByteArray(); + } + + @Override + void optionToWire(DnsOutput out) { + out.writeByteArray(data); + } + + @Override + String optionToString() { + return "<" + base16.toString(data) + ">"; + } + +} diff --git a/src/dorkbox/network/dns/records/HINFORecord.java b/src/dorkbox/network/dns/records/HINFORecord.java new file mode 100644 index 00000000..77dd4423 --- /dev/null +++ b/src/dorkbox/network/dns/records/HINFORecord.java @@ -0,0 +1,102 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Host Information - describes the CPU and OS of a host + * + * @author Brian Wellington + */ + +public +class HINFORecord extends DnsRecord { + + private static final long serialVersionUID = -4732870630947452112L; + + private byte[] cpu, os; + + HINFORecord() {} + + @Override + DnsRecord getObject() { + return new HINFORecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + cpu = in.readCountedString(); + os = in.readCountedString(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeCountedString(cpu); + out.writeCountedString(os); + } + + /** + * Converts to a string + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(byteArrayToString(cpu, true)); + sb.append(" "); + sb.append(byteArrayToString(os, true)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + cpu = byteArrayFromString(st.getString()); + os = byteArrayFromString(st.getString()); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + } + + /** + * Creates an HINFO Record from the given data + * + * @param cpu A string describing the host's CPU + * @param os A string describing the host's OS + * + * @throws IllegalArgumentException One of the strings has invalid escapes + */ + public + HINFORecord(Name name, int dclass, long ttl, String cpu, String os) { + super(name, DnsRecordType.HINFO, dclass, ttl); + try { + this.cpu = byteArrayFromString(cpu); + this.os = byteArrayFromString(os); + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * Returns the host's CPU + */ + public + String getCPU() { + return byteArrayToString(cpu, false); + } + + /** + * Returns the host's OS + */ + public + String getOS() { + return byteArrayToString(os, false); + } + +} diff --git a/src/dorkbox/network/dns/records/Header.java b/src/dorkbox/network/dns/records/Header.java new file mode 100644 index 00000000..5d8d2afe --- /dev/null +++ b/src/dorkbox/network/dns/records/Header.java @@ -0,0 +1,343 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.constants.DnsOpCode; +import dorkbox.network.dns.constants.DnsResponseCode; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.constants.Flags; +import dorkbox.util.FastThreadLocal; +import dorkbox.util.MersenneTwisterFast; +import dorkbox.util.OS; + +/** + * A DNS message header + * + * @author Brian Wellington + * @see DnsMessage + */ + +public +class Header implements Cloneable { + + private int id; + private int flags; + private int[] counts; + + private static final + FastThreadLocal random = new FastThreadLocal() { + @Override + public + MersenneTwisterFast initialValue() { + return new MersenneTwisterFast(); + } + }; + + + /** + * The length of a DNS Header in wire format. + */ + public static final int LENGTH = 12; + + /** + * Create a new empty header with a random message id + */ + public + Header() { + init(); + } + + private + void init() { + counts = new int[4]; + flags = 0; + id = -1; + } + + /** + * Creates a new Header from its DNS wire format representation + * + * @param b A byte array containing the DNS Header. + */ + public + Header(byte[] b) throws IOException { + this(new DnsInput(b)); + } + + /** + * Parses a Header from a stream containing DNS wire format. + */ + Header(DnsInput in) throws IOException { + this(in.readU16()); + flags = in.readU16(); + for (int i = 0; i < counts.length; i++) { + counts[i] = in.readU16(); + } + } + + /** + * Create a new empty header. + * + * @param id The message id + */ + public + Header(int id) { + init(); + setID(id); + } + + public + byte[] toWire() { + DnsOutput out = new DnsOutput(); + toWire(out); + return out.toByteArray(); + } + + void toWire(DnsOutput out) { + out.writeU16(getID()); + out.writeU16(flags); + for (int i = 0; i < counts.length; i++) { + out.writeU16(counts[i]); + } + } + + /** + * Retrieves the message ID + */ + public + int getID() { + if (id >= 0) { + return id; + } + synchronized (this) { + if (id < 0) { + id = random.get().nextInt(0xffff); + } + return id; + } + } + + /** + * Sets the message ID + */ + public + void setID(int id) { + if (id < 0 || id > 0xffff) { + throw new IllegalArgumentException("DNS message ID " + id + " is out of range"); + } + this.id = id; + } + + /** + * Sets a flag to the supplied value + * + * @see Flags + */ + public + void setFlag(int bit) { + checkFlag(bit); + flags = setFlag(flags, bit, true); + } + + static private + void checkFlag(int bit) { + if (!validFlag(bit)) { + throw new IllegalArgumentException("invalid flag bit " + bit); + } + } + + static private + boolean validFlag(int bit) { + return (bit >= 0 && bit <= 0xF && Flags.isFlag(bit)); + } + + static + int setFlag(int flags, int bit, boolean value) { + checkFlag(bit); + + // bits are indexed from left to right + if (value) { + return flags |= (1 << (15 - bit)); + } + else { + return flags &= ~(1 << (15 - bit)); + } + } + + /** + * Sets a flag to the supplied value + * + * @see Flags + */ + public + void unsetFlag(int bit) { + checkFlag(bit); + flags = setFlag(flags, bit, false); + } + + boolean[] getFlags() { + boolean[] array = new boolean[16]; + for (int i = 0; i < array.length; i++) { + if (validFlag(i)) { + array[i] = getFlag(i); + } + } + return array; + } + + /** + * Retrieves a flag + * + * @see Flags + */ + public + boolean getFlag(int bit) { + checkFlag(bit); + // bits are indexed from left to right + return (flags & (1 << (15 - bit))) != 0; + } + + void setCount(int field, int value) { + if (value < 0 || value > 0xFFFF) { + throw new IllegalArgumentException("DNS section count " + value + " is out of range"); + } + counts[field] = value; + } + + void incCount(int field) { + if (counts[field] == 0xFFFF) { + throw new IllegalStateException("DNS section count cannot " + "be incremented"); + } + counts[field]++; + } + + void decCount(int field) { + if (counts[field] == 0) { + throw new IllegalStateException("DNS section count cannot " + "be decremented"); + } + counts[field]--; + } + + int getFlagsByte() { + return flags; + } + + /* Creates a new Header identical to the current one */ + @Override + public + Object clone() { + Header h = new Header(); + h.id = id; + h.flags = flags; + System.arraycopy(counts, 0, h.counts, 0, counts.length); + return h; + } + + /** + * Converts the header into a String + */ + public + String toString() { + return toStringWithRcode(getRcode()); + } + + /** + * Retrieves the mesasge's rcode + * + * @see DnsResponseCode + */ + public + int getRcode() { + return flags & 0xF; + } + + /** + * Sets the message's rcode + * + * @see DnsResponseCode + */ + public + void setRcode(int value) { + if (value < 0 || value > 0xF) { + throw new IllegalArgumentException("DNS DnsResponseCode " + value + " is out of range"); + } + flags &= ~0xF; + flags |= value; + } + + String toStringWithRcode(int newrcode) { + StringBuilder sb = new StringBuilder(); + + sb.append(";; ->>HEADER<<- "); + sb.append("opcode: " + DnsOpCode.string(getOpcode())); + sb.append(", status: " + DnsResponseCode.string(newrcode)); + sb.append(", id: " + getID()); + sb.append(OS.LINE_SEPARATOR); + + sb.append(";; flags: ") + .append(printFlags()); + sb.append("; "); + for (int i = 0; i < 4; i++) { + sb.append(DnsSection.string(i)) + .append(": ") + .append(getCount(i)) + .append(" "); + } + return sb.toString(); + } + + /** + * Retrieves the mesasge's opcode + * + * @see DnsOpCode + */ + public + int getOpcode() { + return (flags >> 11) & 0xF; + } + + /** + * Sets the message's opcode + * + * @see DnsOpCode + */ + public + void setOpcode(int value) { + if (value < 0 || value > 0xF) { + throw new IllegalArgumentException("DNS DnsOpCode " + value + "is out of range"); + } + flags &= 0x87FF; + flags |= (value << 11); + } + + /** + * Retrieves the record count for the given section + * + * @see DnsSection + */ + public + int getCount(int field) { + return counts[field]; + } + + /** + * Converts the header's flags into a String + */ + public + String printFlags() { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < 16; i++) { + if (validFlag(i) && getFlag(i)) { + sb.append(Flags.string(i)); + sb.append(" "); + } + } + return sb.toString(); + } + +} diff --git a/src/dorkbox/network/dns/records/IPSECKEYRecord.java b/src/dorkbox/network/dns/records/IPSECKEYRecord.java new file mode 100644 index 00000000..028acdb4 --- /dev/null +++ b/src/dorkbox/network/dns/records/IPSECKEYRecord.java @@ -0,0 +1,253 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; + +/** + * IPsec Keying Material (RFC 4025) + * + * @author Brian Wellington + */ + +public +class IPSECKEYRecord extends DnsRecord { + + private static final long serialVersionUID = 3050449702765909687L; + private int precedence; + private int gatewayType; + private int algorithmType; + private Object gateway; + private byte[] key; + + + public static + class Algorithm { + public static final int DSA = 1; + public static final int RSA = 2; + private + Algorithm() {} + } + + + public static + class Gateway { + public static final int None = 0; + public static final int IPv4 = 1; + public static final int IPv6 = 2; + public static final int Name = 3; + private + Gateway() {} + } + + IPSECKEYRecord() {} + + @Override + DnsRecord getObject() { + return new IPSECKEYRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + precedence = in.readU8(); + gatewayType = in.readU8(); + algorithmType = in.readU8(); + switch (gatewayType) { + case Gateway.None: + gateway = null; + break; + case Gateway.IPv4: + gateway = InetAddress.getByAddress(in.readByteArray(4)); + break; + case Gateway.IPv6: + gateway = InetAddress.getByAddress(in.readByteArray(16)); + break; + case Gateway.Name: + gateway = new Name(in); + break; + default: + throw new WireParseException("invalid gateway type"); + } + if (in.remaining() > 0) { + key = in.readByteArray(); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(precedence); + out.writeU8(gatewayType); + out.writeU8(algorithmType); + switch (gatewayType) { + case Gateway.None: + break; + case Gateway.IPv4: + case Gateway.IPv6: + InetAddress gatewayAddr = (InetAddress) gateway; + out.writeByteArray(gatewayAddr.getAddress()); + break; + case Gateway.Name: + Name gatewayName = (Name) gateway; + gatewayName.toWire(out, null, canonical); + break; + } + if (key != null) { + out.writeByteArray(key); + } + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(precedence); + sb.append(" "); + sb.append(gatewayType); + sb.append(" "); + sb.append(algorithmType); + sb.append(" "); + + switch (gatewayType) { + case Gateway.None: + sb.append("."); + break; + case Gateway.IPv4: + case Gateway.IPv6: + InetAddress gatewayAddr = (InetAddress) gateway; + sb.append(gatewayAddr.getHostAddress()); + break; + case Gateway.Name: + sb.append(gateway); + break; + } + + if (key != null) { + sb.append(" "); + sb.append(Base64Fast.encode2(key)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + precedence = st.getUInt8(); + gatewayType = st.getUInt8(); + algorithmType = st.getUInt8(); + switch (gatewayType) { + case Gateway.None: + String s = st.getString(); + if (!s.equals(".")) { + throw new TextParseException("invalid gateway format"); + } + gateway = null; + break; + case Gateway.IPv4: + gateway = st.getAddress(Address.IPv4); + break; + case Gateway.IPv6: + gateway = st.getAddress(Address.IPv6); + break; + case Gateway.Name: + gateway = st.getName(origin); + break; + default: + throw new WireParseException("invalid gateway type"); + } + key = st.getBase64(false); + } + + /** + * Creates an IPSECKEY Record from the given data. + * + * @param precedence The record's precedence. + * @param gatewayType The record's gateway type. + * @param algorithmType The record's algorithm type. + * @param gateway The record's gateway. + * @param key The record's public key. + */ + public + IPSECKEYRecord(Name name, int dclass, long ttl, int precedence, int gatewayType, int algorithmType, Object gateway, byte[] key) { + super(name, DnsRecordType.IPSECKEY, dclass, ttl); + this.precedence = checkU8("precedence", precedence); + this.gatewayType = checkU8("gatewayType", gatewayType); + this.algorithmType = checkU8("algorithmType", algorithmType); + switch (gatewayType) { + case Gateway.None: + this.gateway = null; + break; + case Gateway.IPv4: + if (!(gateway instanceof InetAddress)) { + throw new IllegalArgumentException("\"gateway\" " + "must be an IPv4 " + "address"); + } + this.gateway = gateway; + break; + case Gateway.IPv6: + if (!(gateway instanceof Inet6Address)) { + throw new IllegalArgumentException("\"gateway\" " + "must be an IPv6 " + "address"); + } + this.gateway = gateway; + break; + case Gateway.Name: + if (!(gateway instanceof Name)) { + throw new IllegalArgumentException("\"gateway\" " + "must be a DNS " + "name"); + } + this.gateway = checkName("gateway", (Name) gateway); + break; + default: + throw new IllegalArgumentException("\"gatewayType\" " + "must be between 0 and 3"); + } + + this.key = key; + } + + /** + * Returns the record's precedence. + */ + public + int getPrecedence() { + return precedence; + } + + /** + * Returns the record's gateway type. + */ + public + int getGatewayType() { + return gatewayType; + } + + /** + * Returns the record's algorithm type. + */ + public + int getAlgorithmType() { + return algorithmType; + } + + /** + * Returns the record's gateway. + */ + public + Object getGateway() { + return gateway; + } + + /** + * Returns the record's public key + */ + public + byte[] getKey() { + return key; + } + +} diff --git a/src/dorkbox/network/dns/records/ISDNRecord.java b/src/dorkbox/network/dns/records/ISDNRecord.java new file mode 100644 index 00000000..9b03e7aa --- /dev/null +++ b/src/dorkbox/network/dns/records/ISDNRecord.java @@ -0,0 +1,118 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * ISDN - identifies the ISDN number and subaddress associated with a name. + * + * @author Brian Wellington + */ + +public +class ISDNRecord extends DnsRecord { + + private static final long serialVersionUID = -8730801385178968798L; + + private byte[] address; + private byte[] subAddress; + + ISDNRecord() {} + + @Override + DnsRecord getObject() { + return new ISDNRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + address = in.readCountedString(); + if (in.remaining() > 0) { + subAddress = in.readCountedString(); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeCountedString(address); + if (subAddress != null) { + out.writeCountedString(subAddress); + } + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(byteArrayToString(address, true)); + + if (subAddress != null) { + sb.append(" "); + sb.append(byteArrayToString(subAddress, true)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + address = byteArrayFromString(st.getString()); + Tokenizer.Token t = st.get(); + if (t.isString()) { + subAddress = byteArrayFromString(t.value); + } + else { + st.unget(); + } + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + } + + /** + * Creates an ISDN Record from the given data + * + * @param address The ISDN number associated with the domain. + * @param subAddress The subaddress, if any. + * + * @throws IllegalArgumentException One of the strings is invalid. + */ + public + ISDNRecord(Name name, int dclass, long ttl, String address, String subAddress) { + super(name, DnsRecordType.ISDN, dclass, ttl); + try { + this.address = byteArrayFromString(address); + if (subAddress != null) { + this.subAddress = byteArrayFromString(subAddress); + } + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * Returns the ISDN number associated with the domain. + */ + public + String getAddress() { + return byteArrayToString(address, false); + } + + /** + * Returns the ISDN subaddress, or null if there is none. + */ + public + String getSubAddress() { + if (subAddress == null) { + return null; + } + return byteArrayToString(subAddress, false); + } + +} diff --git a/src/dorkbox/network/dns/records/KEYBase.java b/src/dorkbox/network/dns/records/KEYBase.java new file mode 100644 index 00000000..cab88dc6 --- /dev/null +++ b/src/dorkbox/network/dns/records/KEYBase.java @@ -0,0 +1,175 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.PublicKey; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.utils.Options; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * The base class for KEY/DNSKEY records, which have identical formats + * + * @author Brian Wellington + */ + +abstract +class KEYBase extends DnsRecord { + + private static final long serialVersionUID = 3469321722693285454L; + + protected int flags, proto, alg; + protected byte[] key; + protected int footprint = -1; + protected PublicKey publicKey = null; + + protected + KEYBase() {} + + public + KEYBase(Name name, int type, int dclass, long ttl, int flags, int proto, int alg, byte[] key) { + super(name, type, dclass, ttl); + this.flags = checkU16("flags", flags); + this.proto = checkU8("proto", proto); + this.alg = checkU8("alg", alg); + this.key = key; + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + flags = in.readU16(); + proto = in.readU8(); + alg = in.readU8(); + if (in.remaining() > 0) { + key = in.readByteArray(); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(flags); + out.writeU8(proto); + out.writeU8(alg); + if (key != null) { + out.writeByteArray(key); + } + } + + /** + * Converts the DNSKEY/KEY Record to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(flags); + sb.append(" "); + sb.append(proto); + sb.append(" "); + sb.append(alg); + + if (key != null) { + if (Options.check("multiline")) { + sb.append(" (") + .append(OS.LINE_SEPARATOR); + + sb.append(Base64Fast.formatString(Base64Fast.encode2(key), 64, "\t", true)); + sb.append(" ; key_tag = "); + sb.append(getFootprint()); + } + else { + sb.append(" "); + sb.append(Base64Fast.encode2(key)); + } + } + } + + /** + * Returns the key's footprint (after computing it) + */ + public + int getFootprint() { + if (footprint >= 0) { + return footprint; + } + + int foot = 0; + + DnsOutput out = new DnsOutput(); + rrToWire(out, null, false); + byte[] rdata = out.toByteArray(); + + if (alg == DNSSEC.Algorithm.RSAMD5) { + int d1 = rdata[rdata.length - 3] & 0xFF; + int d2 = rdata[rdata.length - 2] & 0xFF; + foot = (d1 << 8) + d2; + } + else { + int i; + for (i = 0; i < rdata.length - 1; i += 2) { + int d1 = rdata[i] & 0xFF; + int d2 = rdata[i + 1] & 0xFF; + foot += ((d1 << 8) + d2); + } + if (i < rdata.length) { + int d1 = rdata[i] & 0xFF; + foot += (d1 << 8); + } + foot += ((foot >> 16) & 0xFFFF); + } + footprint = (foot & 0xFFFF); + return footprint; + } + + /** + * Returns the flags describing the key's properties + */ + public + int getFlags() { + return flags; + } + + /** + * Returns the protocol that the key was created for + */ + public + int getProtocol() { + return proto; + } + + /** + * Returns the key's algorithm + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the binary data representing the key + */ + public + byte[] getKey() { + return key; + } + + /** + * Returns a PublicKey corresponding to the data in this key. + * + * @throws DNSSEC.DNSSECException The key could not be converted. + */ + public + PublicKey getPublicKey() throws DNSSEC.DNSSECException { + if (publicKey != null) { + return publicKey; + } + + publicKey = DNSSEC.toPublicKey(this); + return publicKey; + } + +} diff --git a/src/dorkbox/network/dns/records/KEYRecord.java b/src/dorkbox/network/dns/records/KEYRecord.java new file mode 100644 index 00000000..a5ffb561 --- /dev/null +++ b/src/dorkbox/network/dns/records/KEYRecord.java @@ -0,0 +1,421 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.PublicKey; +import java.util.StringTokenizer; + +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Key - contains a cryptographic public key. The data can be converted + * to objects implementing java.security.interfaces.PublicKey + * + * @author Brian Wellington + * @see DNSSEC + */ + +public +class KEYRecord extends KEYBase { + + private static final long serialVersionUID = 6385613447571488906L; + /** + * This key cannot be used for confidentiality (encryption) + */ + public static final int FLAG_NOCONF = Flags.NOCONF; + /** + * This key cannot be used for authentication + */ + public static final int FLAG_NOAUTH = Flags.NOAUTH; + +/* flags */ + /** + * This key cannot be used for authentication or confidentiality + */ + public static final int FLAG_NOKEY = Flags.NOKEY; + /** + * A zone key + */ + public static final int OWNER_ZONE = Flags.ZONE; + /** + * A host/end entity key + */ + public static final int OWNER_HOST = Flags.HOST; + /** + * A user key + */ + public static final int OWNER_USER = Flags.USER; + /** + * Key was created for use with transaction level security + */ + public static final int PROTOCOL_TLS = Protocol.TLS; + /** + * Key was created for use with email + */ + public static final int PROTOCOL_EMAIL = Protocol.EMAIL; + +/* protocols */ + /** + * Key was created for use with DNSSEC + */ + public static final int PROTOCOL_DNSSEC = Protocol.DNSSEC; + /** + * Key was created for use with IPSEC + */ + public static final int PROTOCOL_IPSEC = Protocol.IPSEC; + /** + * Key was created for use with any protocol + */ + public static final int PROTOCOL_ANY = Protocol.ANY; + + + public static + class Protocol { + /** + * No defined protocol. + */ + public static final int NONE = 0; + /** + * Transaction Level Security + */ + public static final int TLS = 1; + /** + * Email + */ + public static final int EMAIL = 2; + /** + * DNSSEC + */ + public static final int DNSSEC = 3; + /** + * IPSEC Control + */ + public static final int IPSEC = 4; + /** + * Any protocol + */ + public static final int ANY = 255; + private static Mnemonic protocols = new Mnemonic("KEY protocol", Mnemonic.CASE_UPPER); + + /** + * KEY protocol identifiers. + */ + + private + Protocol() {} + + static { + protocols.setMaximum(0xFF); + protocols.setNumericAllowed(true); + + protocols.add(NONE, "NONE"); + protocols.add(TLS, "TLS"); + protocols.add(EMAIL, "EMAIL"); + protocols.add(DNSSEC, "DNSSEC"); + protocols.add(IPSEC, "IPSEC"); + protocols.add(ANY, "ANY"); + } + + /** + * Converts an KEY protocol value into its textual representation + */ + public static + String string(int type) { + return protocols.getText(type); + } + + /** + * Converts a textual representation of a KEY protocol into its + * numeric code. Integers in the range 0..255 are also accepted. + * + * @param s The textual representation of the protocol + * + * @return The protocol code, or -1 on error. + */ + public static + int value(String s) { + return protocols.getValue(s); + } + } + + + public static + class Flags { + /** + * KEY cannot be used for confidentiality + */ + public static final int NOCONF = 0x4000; + /** + * KEY cannot be used for authentication + */ + public static final int NOAUTH = 0x8000; + /** + * No key present + */ + public static final int NOKEY = 0xC000; + /** + * Bitmask of the use fields + */ + public static final int USE_MASK = 0xC000; + /** + * Flag 2 (unused) + */ + public static final int FLAG2 = 0x2000; + /** + * Flags extension + */ + public static final int EXTEND = 0x1000; + /** + * Flag 4 (unused) + */ + public static final int FLAG4 = 0x0800; + /** + * Flag 5 (unused) + */ + public static final int FLAG5 = 0x0400; + /** + * Key is owned by a user. + */ + public static final int USER = 0x0000; + /** + * Key is owned by a zone. + */ + public static final int ZONE = 0x0100; + /** + * Key is owned by a host. + */ + public static final int HOST = 0x0200; + /** + * Key owner type 3 (reserved). + */ + public static final int NTYP3 = 0x0300; + /** + * Key owner bitmask. + */ + public static final int OWNER_MASK = 0x0300; + /** + * Flag 8 (unused) + */ + public static final int FLAG8 = 0x0080; + /** + * Flag 9 (unused) + */ + public static final int FLAG9 = 0x0040; + /** + * Flag 10 (unused) + */ + public static final int FLAG10 = 0x0020; + /** + * Flag 11 (unused) + */ + public static final int FLAG11 = 0x0010; + /** + * Signatory value 0 + */ + public static final int SIG0 = 0; + /** + * Signatory value 1 + */ + public static final int SIG1 = 1; + /** + * Signatory value 2 + */ + public static final int SIG2 = 2; + /** + * Signatory value 3 + */ + public static final int SIG3 = 3; + /** + * Signatory value 4 + */ + public static final int SIG4 = 4; + /** + * Signatory value 5 + */ + public static final int SIG5 = 5; + /** + * Signatory value 6 + */ + public static final int SIG6 = 6; + /** + * Signatory value 7 + */ + public static final int SIG7 = 7; + /** + * Signatory value 8 + */ + public static final int SIG8 = 8; + /** + * Signatory value 9 + */ + public static final int SIG9 = 9; + /** + * Signatory value 10 + */ + public static final int SIG10 = 10; + /** + * Signatory value 11 + */ + public static final int SIG11 = 11; + /** + * Signatory value 12 + */ + public static final int SIG12 = 12; + /** + * Signatory value 13 + */ + public static final int SIG13 = 13; + /** + * Signatory value 14 + */ + public static final int SIG14 = 14; + /** + * Signatory value 15 + */ + public static final int SIG15 = 15; + private static Mnemonic flags = new Mnemonic("KEY flags", Mnemonic.CASE_UPPER); + + /** + * KEY flags identifiers. + */ + + private + Flags() {} + + static { + flags.setMaximum(0xFFFF); + flags.setNumericAllowed(false); + + flags.add(NOCONF, "NOCONF"); + flags.add(NOAUTH, "NOAUTH"); + flags.add(NOKEY, "NOKEY"); + flags.add(FLAG2, "FLAG2"); + flags.add(EXTEND, "EXTEND"); + flags.add(FLAG4, "FLAG4"); + flags.add(FLAG5, "FLAG5"); + flags.add(USER, "USER"); + flags.add(ZONE, "ZONE"); + flags.add(HOST, "HOST"); + flags.add(NTYP3, "NTYP3"); + flags.add(FLAG8, "FLAG8"); + flags.add(FLAG9, "FLAG9"); + flags.add(FLAG10, "FLAG10"); + flags.add(FLAG11, "FLAG11"); + flags.add(SIG0, "SIG0"); + flags.add(SIG1, "SIG1"); + flags.add(SIG2, "SIG2"); + flags.add(SIG3, "SIG3"); + flags.add(SIG4, "SIG4"); + flags.add(SIG5, "SIG5"); + flags.add(SIG6, "SIG6"); + flags.add(SIG7, "SIG7"); + flags.add(SIG8, "SIG8"); + flags.add(SIG9, "SIG9"); + flags.add(SIG10, "SIG10"); + flags.add(SIG11, "SIG11"); + flags.add(SIG12, "SIG12"); + flags.add(SIG13, "SIG13"); + flags.add(SIG14, "SIG14"); + flags.add(SIG15, "SIG15"); + } + + /** + * Converts a textual representation of KEY flags into its + * numeric code. Integers in the range 0..65535 are also accepted. + * + * @param s The textual representation of the protocol + * + * @return The protocol code, or -1 on error. + */ + public static + int value(String s) { + int value; + try { + value = Integer.parseInt(s); + if (value >= 0 && value <= 0xFFFF) { + return value; + } + return -1; + } catch (NumberFormatException e) { + } + StringTokenizer st = new StringTokenizer(s, "|"); + value = 0; + while (st.hasMoreTokens()) { + int val = flags.getValue(st.nextToken()); + if (val < 0) { + return -1; + } + value |= val; + } + return value; + } + } + + KEYRecord() {} + + @Override + DnsRecord getObject() { + return new KEYRecord(); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String flagString = st.getIdentifier(); + flags = Flags.value(flagString); + if (flags < 0) { + throw st.exception("Invalid flags: " + flagString); + } + String protoString = st.getIdentifier(); + proto = Protocol.value(protoString); + if (proto < 0) { + throw st.exception("Invalid protocol: " + protoString); + } + String algString = st.getIdentifier(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) { + throw st.exception("Invalid algorithm: " + algString); + } + /* If this is a null KEY, there's no key data */ + if ((flags & Flags.USE_MASK) == Flags.NOKEY) { + key = null; + } + else { + key = st.getBase64(); + } + } + + /** + * Creates a KEY Record from the given data + * + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key Binary data representing the key + */ + public + KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, byte[] key) { + super(name, DnsRecordType.KEY, dclass, ttl, flags, proto, alg, key); + } + + /** + * Creates a KEY Record from the given data + * + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key The key as a PublicKey + * + * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS + * format. + */ + public + KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, PublicKey key) throws DNSSEC.DNSSECException { + super(name, DnsRecordType.KEY, dclass, ttl, flags, proto, alg, DNSSEC.fromPublicKey(key, alg)); + publicKey = key; + } + +} diff --git a/src/dorkbox/network/dns/records/KXRecord.java b/src/dorkbox/network/dns/records/KXRecord.java new file mode 100644 index 00000000..abe7c05b --- /dev/null +++ b/src/dorkbox/network/dns/records/KXRecord.java @@ -0,0 +1,60 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Key Exchange - delegation of authority + * + * @author Brian Wellington + */ + +public +class KXRecord extends U16NameBase { + + private static final long serialVersionUID = 7448568832769757809L; + + KXRecord() {} + + @Override + DnsRecord getObject() { + return new KXRecord(); + } + + @Override + public + Name getAdditionalName() { + return getNameField(); + } + + /** + * Creates a KX Record from the given data + * + * @param preference The preference of this KX. Records with lower priority + * are preferred. + * @param target The host that authority is delegated to + */ + public + KXRecord(Name name, int dclass, long ttl, int preference, Name target) { + super(name, DnsRecordType.KX, dclass, ttl, preference, "preference", target, "target"); + } + + /** + * Returns the target of the KX record + */ + public + Name getTarget() { + return getNameField(); + } + + /** + * Returns the preference of this KX record + */ + public + int getPreference() { + return getU16Field(); + } + +} diff --git a/src/dorkbox/network/dns/records/LOCRecord.java b/src/dorkbox/network/dns/records/LOCRecord.java new file mode 100644 index 00000000..9f1e4002 --- /dev/null +++ b/src/dorkbox/network/dns/records/LOCRecord.java @@ -0,0 +1,349 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.NumberFormat; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Location - describes the physical location of hosts, networks, subnets. + * + * @author Brian Wellington + */ + +public +class LOCRecord extends DnsRecord { + + private static final long serialVersionUID = 9058224788126750409L; + + private static NumberFormat w2, w3; + + private long size, hPrecision, vPrecision; + private long latitude, longitude, altitude; + + static { + w2 = new DecimalFormat(); + w2.setMinimumIntegerDigits(2); + + w3 = new DecimalFormat(); + w3.setMinimumIntegerDigits(3); + } + + LOCRecord() {} + + @Override + DnsRecord getObject() { + return new LOCRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + int version; + + version = in.readU8(); + if (version != 0) { + throw new WireParseException("Invalid LOC version"); + } + + size = parseLOCformat(in.readU8()); + hPrecision = parseLOCformat(in.readU8()); + vPrecision = parseLOCformat(in.readU8()); + latitude = in.readU32(); + longitude = in.readU32(); + altitude = in.readU32(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(0); /* version */ + out.writeU8(toLOCformat(size)); + out.writeU8(toLOCformat(hPrecision)); + out.writeU8(toLOCformat(vPrecision)); + out.writeU32(latitude); + out.writeU32(longitude); + out.writeU32(altitude); + } + + /** + * Convert to a String + */ + @Override + void rrToString(StringBuilder sb) { + /* Latitude */ + sb.append(positionToString(latitude, 'N', 'S')); + sb.append(" "); + + /* Latitude */ + sb.append(positionToString(longitude, 'E', 'W')); + sb.append(" "); + + /* Altitude */ + renderFixedPoint(sb, w2, altitude - 10000000, 100); + sb.append("m "); + + /* Size */ + renderFixedPoint(sb, w2, size, 100); + sb.append("m "); + + /* Horizontal precision */ + renderFixedPoint(sb, w2, hPrecision, 100); + sb.append("m "); + + /* Vertical precision */ + renderFixedPoint(sb, w2, vPrecision, 100); + sb.append("m"); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + latitude = parsePosition(st, "latitude"); + longitude = parsePosition(st, "longitude"); + altitude = parseDouble(st, "altitude", true, -10000000, 4284967295L, 0) + 10000000; + size = parseDouble(st, "size", false, 0, 9000000000L, 100); + hPrecision = parseDouble(st, "horizontal precision", false, 0, 9000000000L, 1000000); + vPrecision = parseDouble(st, "vertical precision", false, 0, 9000000000L, 1000); + } + + private + long parsePosition(Tokenizer st, String type) throws IOException { + boolean isLatitude = type.equals("latitude"); + int deg = 0, min = 0; + double sec = 0; + long value; + String s; + + deg = st.getUInt16(); + if (deg > 180 || (deg > 90 && isLatitude)) { + throw st.exception("Invalid LOC " + type + " degrees"); + } + + s = st.getString(); + try { + min = Integer.parseInt(s); + if (min < 0 || min > 59) { + throw st.exception("Invalid LOC " + type + " minutes"); + } + s = st.getString(); + sec = parseFixedPoint(s); + if (sec < 0 || sec >= 60) { + throw st.exception("Invalid LOC " + type + " seconds"); + } + s = st.getString(); + } catch (NumberFormatException e) { + } + + if (s.length() != 1) { + throw st.exception("Invalid LOC " + type); + } + + value = (long) (1000 * (sec + 60L * (min + 60L * deg))); + + char c = Character.toUpperCase(s.charAt(0)); + if ((isLatitude && c == 'S') || (!isLatitude && c == 'W')) { + value = -value; + } + else if ((isLatitude && c != 'N') || (!isLatitude && c != 'E')) { + throw st.exception("Invalid LOC " + type); + } + + value += (1L << 31); + + return value; + } + + private + double parseFixedPoint(String s) { + if (s.matches("^-?\\d+$")) { + return Integer.parseInt(s); + } + else if (s.matches("^-?\\d+\\.\\d*$")) { + String[] parts = s.split("\\."); + double value = Integer.parseInt(parts[0]); + double fraction = Integer.parseInt(parts[1]); + if (value < 0) { + fraction *= -1; + } + int digits = parts[1].length(); + return value + (fraction / Math.pow(10, digits)); + } + else { + throw new NumberFormatException(); + } + } + + private + long parseDouble(Tokenizer st, String type, boolean required, long min, long max, long defaultValue) throws IOException { + Tokenizer.Token token = st.get(); + if (token.isEOL()) { + if (required) { + throw st.exception("Invalid LOC " + type); + } + st.unget(); + return defaultValue; + } + String s = token.value; + if (s.length() > 1 && s.charAt(s.length() - 1) == 'm') { + s = s.substring(0, s.length() - 1); + } + try { + long value = (long) (100 * parseFixedPoint(s)); + if (value < min || value > max) { + throw st.exception("Invalid LOC " + type); + } + return value; + } catch (NumberFormatException e) { + throw st.exception("Invalid LOC " + type); + } + } + + private + String positionToString(long value, char pos, char neg) { + StringBuilder sb = new StringBuilder(); + char direction; + + long temp = value - (1L << 31); + if (temp < 0) { + temp = -temp; + direction = neg; + } + else { + direction = pos; + } + + sb.append(temp / (3600 * 1000)); /* degrees */ + temp = temp % (3600 * 1000); + sb.append(" "); + + sb.append(temp / (60 * 1000)); /* minutes */ + temp = temp % (60 * 1000); + sb.append(" "); + + renderFixedPoint(sb, w3, temp, 1000); /* seconds */ + sb.append(" "); + + sb.append(direction); + + return sb.toString(); + } + + private + void renderFixedPoint(StringBuilder sb, NumberFormat formatter, long value, long divisor) { + sb.append(value / divisor); + value %= divisor; + + if (value != 0) { + sb.append("."); + sb.append(formatter.format(value)); + } + } + + private + int toLOCformat(long l) { + byte exp = 0; + while (l > 9) { + exp++; + l /= 10; + } + return (int) ((l << 4) + exp); + } + + private static + long parseLOCformat(int b) throws WireParseException { + long out = b >> 4; + int exp = b & 0xF; + if (out > 9 || exp > 9) { + throw new WireParseException("Invalid LOC Encoding"); + } + while (exp-- > 0) { + out *= 10; + } + return (out); + } + + /** + * Creates an LOC Record from the given data + * + * @param latitude The latitude of the center of the sphere + * @param longitude The longitude of the center of the sphere + * @param altitude The altitude of the center of the sphere, in m + * @param size The diameter of a sphere enclosing the described entity, in m. + * @param hPrecision The horizontal precision of the data, in m. + * @param vPrecision The vertical precision of the data, in m. + */ + public + LOCRecord(Name name, + int dclass, + long ttl, + double latitude, + double longitude, + double altitude, + double size, + double hPrecision, + double vPrecision) { + super(name, DnsRecordType.LOC, dclass, ttl); + this.latitude = (long) (latitude * 3600 * 1000 + (1L << 31)); + this.longitude = (long) (longitude * 3600 * 1000 + (1L << 31)); + this.altitude = (long) ((altitude + 100000) * 100); + this.size = (long) (size * 100); + this.hPrecision = (long) (hPrecision * 100); + this.vPrecision = (long) (vPrecision * 100); + } + + /** + * Returns the latitude + */ + public + double getLatitude() { + return ((double) (latitude - (1L << 31))) / (3600 * 1000); + } + + /** + * Returns the longitude + */ + public + double getLongitude() { + return ((double) (longitude - (1L << 31))) / (3600 * 1000); + } + + /** + * Returns the altitude + */ + public + double getAltitude() { + return ((double) (altitude - 10000000)) / 100; + } + + /** + * Returns the diameter of the enclosing sphere + */ + public + double getSize() { + return ((double) size) / 100; + } + + /** + * Returns the horizontal precision + */ + public + double getHPrecision() { + return ((double) hPrecision) / 100; + } + + /** + * Returns the horizontal precision + */ + public + double getVPrecision() { + return ((double) vPrecision) / 100; + } + +} diff --git a/src/dorkbox/network/dns/records/MBRecord.java b/src/dorkbox/network/dns/records/MBRecord.java new file mode 100644 index 00000000..dd7f19bf --- /dev/null +++ b/src/dorkbox/network/dns/records/MBRecord.java @@ -0,0 +1,50 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mailbox Record - specifies a host containing a mailbox. + * + * @author Brian Wellington + */ + +public +class MBRecord extends SingleNameBase { + + private static final long serialVersionUID = 532349543479150419L; + + MBRecord() {} + + @Override + DnsRecord getObject() { + return new MBRecord(); + } + + @Override + public + Name getAdditionalName() { + return getSingleName(); + } + + /** + * Creates a new MB Record with the given data + * + * @param mailbox The host containing the mailbox for the domain. + */ + public + MBRecord(Name name, int dclass, long ttl, Name mailbox) { + super(name, DnsRecordType.MB, dclass, ttl, mailbox, "mailbox"); + } + + /** + * Gets the mailbox for the domain + */ + public + Name getMailbox() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/MDRecord.java b/src/dorkbox/network/dns/records/MDRecord.java new file mode 100644 index 00000000..9a256e80 --- /dev/null +++ b/src/dorkbox/network/dns/records/MDRecord.java @@ -0,0 +1,51 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mail Destination Record - specifies a mail agent which delivers mail + * for a domain (obsolete) + * + * @author Brian Wellington + */ + +public +class MDRecord extends SingleNameBase { + + private static final long serialVersionUID = 5268878603762942202L; + + MDRecord() {} + + @Override + DnsRecord getObject() { + return new MDRecord(); + } + + @Override + public + Name getAdditionalName() { + return getSingleName(); + } + + /** + * Creates a new MD Record with the given data + * + * @param mailAgent The mail agent that delivers mail for the domain. + */ + public + MDRecord(Name name, int dclass, long ttl, Name mailAgent) { + super(name, DnsRecordType.MD, dclass, ttl, mailAgent, "mail agent"); + } + + /** + * Gets the mail agent for the domain + */ + public + Name getMailAgent() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/MFRecord.java b/src/dorkbox/network/dns/records/MFRecord.java new file mode 100644 index 00000000..54d98e2e --- /dev/null +++ b/src/dorkbox/network/dns/records/MFRecord.java @@ -0,0 +1,51 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mail Forwarder Record - specifies a mail agent which forwards mail + * for a domain (obsolete) + * + * @author Brian Wellington + */ + +public +class MFRecord extends SingleNameBase { + + private static final long serialVersionUID = -6670449036843028169L; + + MFRecord() {} + + @Override + DnsRecord getObject() { + return new MFRecord(); + } + + @Override + public + Name getAdditionalName() { + return getSingleName(); + } + + /** + * Creates a new MF Record with the given data + * + * @param mailAgent The mail agent that forwards mail for the domain. + */ + public + MFRecord(Name name, int dclass, long ttl, Name mailAgent) { + super(name, DnsRecordType.MF, dclass, ttl, mailAgent, "mail agent"); + } + + /** + * Gets the mail agent for the domain + */ + public + Name getMailAgent() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/MGRecord.java b/src/dorkbox/network/dns/records/MGRecord.java new file mode 100644 index 00000000..f2cc63db --- /dev/null +++ b/src/dorkbox/network/dns/records/MGRecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mail Group Record - specifies a mailbox which is a member of a mail group. + * + * @author Brian Wellington + */ + +public +class MGRecord extends SingleNameBase { + + private static final long serialVersionUID = -3980055550863644582L; + + MGRecord() {} + + @Override + DnsRecord getObject() { + return new MGRecord(); + } + + /** + * Creates a new MG Record with the given data + * + * @param mailbox The mailbox that is a member of the group specified by the + * domain. + */ + public + MGRecord(Name name, int dclass, long ttl, Name mailbox) { + super(name, DnsRecordType.MG, dclass, ttl, mailbox, "mailbox"); + } + + /** + * Gets the mailbox in the mail group specified by the domain + */ + public + Name getMailbox() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/MINFORecord.java b/src/dorkbox/network/dns/records/MINFORecord.java new file mode 100644 index 00000000..40e74ada --- /dev/null +++ b/src/dorkbox/network/dns/records/MINFORecord.java @@ -0,0 +1,98 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Mailbox information Record - lists the address responsible for a mailing + * list/mailbox and the address to receive error messages relating to the + * mailing list/mailbox. + * + * @author Brian Wellington + */ + +public +class MINFORecord extends DnsRecord { + + private static final long serialVersionUID = -3962147172340353796L; + + private Name responsibleAddress; + private Name errorAddress; + + MINFORecord() {} + + @Override + DnsRecord getObject() { + return new MINFORecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + responsibleAddress = new Name(in); + errorAddress = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + responsibleAddress.toWire(out, null, canonical); + errorAddress.toWire(out, null, canonical); + } + + /** + * Converts the MINFO Record to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(responsibleAddress); + sb.append(" "); + sb.append(errorAddress); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + responsibleAddress = st.getName(origin); + errorAddress = st.getName(origin); + } + + /** + * Creates an MINFO Record from the given data + * + * @param responsibleAddress The address responsible for the + * mailing list/mailbox. + * @param errorAddress The address to receive error messages relating to the + * mailing list/mailbox. + */ + public + MINFORecord(Name name, int dclass, long ttl, Name responsibleAddress, Name errorAddress) { + super(name, DnsRecordType.MINFO, dclass, ttl); + + this.responsibleAddress = checkName("responsibleAddress", responsibleAddress); + this.errorAddress = checkName("errorAddress", errorAddress); + } + + /** + * Gets the address responsible for the mailing list/mailbox. + */ + public + Name getResponsibleAddress() { + return responsibleAddress; + } + + /** + * Gets the address to receive error messages relating to the mailing + * list/mailbox. + */ + public + Name getErrorAddress() { + return errorAddress; + } + +} diff --git a/src/dorkbox/network/dns/records/MRRecord.java b/src/dorkbox/network/dns/records/MRRecord.java new file mode 100644 index 00000000..7216248d --- /dev/null +++ b/src/dorkbox/network/dns/records/MRRecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mailbox Rename Record - specifies a rename of a mailbox. + * + * @author Brian Wellington + */ + +public +class MRRecord extends SingleNameBase { + + private static final long serialVersionUID = -5617939094209927533L; + + MRRecord() {} + + @Override + DnsRecord getObject() { + return new MRRecord(); + } + + /** + * Creates a new MR Record with the given data + * + * @param newName The new name of the mailbox specified by the domain. + * domain. + */ + public + MRRecord(Name name, int dclass, long ttl, Name newName) { + super(name, DnsRecordType.MR, dclass, ttl, newName, "new name"); + } + + /** + * Gets the new name of the mailbox specified by the domain + */ + public + Name getNewName() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/MXRecord.java b/src/dorkbox/network/dns/records/MXRecord.java new file mode 100644 index 00000000..566fe8dc --- /dev/null +++ b/src/dorkbox/network/dns/records/MXRecord.java @@ -0,0 +1,68 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Mail Exchange - specifies where mail to a domain is sent + * + * @author Brian Wellington + */ + +public +class MXRecord extends U16NameBase { + + private static final long serialVersionUID = 2914841027584208546L; + + MXRecord() {} + + @Override + DnsRecord getObject() { + return new MXRecord(); + } + + @Override + public + Name getAdditionalName() { + return getNameField(); + } + + /** + * Creates an MX Record from the given data + * + * @param priority The priority of this MX. Records with lower priority + * are preferred. + * @param target The host that mail is sent to + */ + public + MXRecord(Name name, int dclass, long ttl, int priority, Name target) { + super(name, DnsRecordType.MX, dclass, ttl, priority, "priority", target, "target"); + } + + /** + * Returns the target of the MX record + */ + public + Name getTarget() { + return getNameField(); + } + + /** + * Returns the priority of this MX record + */ + public + int getPriority() { + return getU16Field(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(u16Field); + nameField.toWire(out, c, canonical); + } + +} diff --git a/src/dorkbox/network/dns/records/NAPTRRecord.java b/src/dorkbox/network/dns/records/NAPTRRecord.java new file mode 100644 index 00000000..788536e6 --- /dev/null +++ b/src/dorkbox/network/dns/records/NAPTRRecord.java @@ -0,0 +1,174 @@ +// Copyright (c) 2000-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Name Authority Pointer Record - specifies rewrite rule, that when applied + * to an existing string will produce a new domain. + * + * @author Chuck Santos + */ + +public +class NAPTRRecord extends DnsRecord { + + private static final long serialVersionUID = 5191232392044947002L; + + private int order, preference; + private byte[] flags, service, regexp; + private Name replacement; + + NAPTRRecord() {} + + @Override + DnsRecord getObject() { + return new NAPTRRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + order = in.readU16(); + preference = in.readU16(); + flags = in.readCountedString(); + service = in.readCountedString(); + regexp = in.readCountedString(); + replacement = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(order); + out.writeU16(preference); + out.writeCountedString(flags); + out.writeCountedString(service); + out.writeCountedString(regexp); + replacement.toWire(out, null, canonical); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(order); + sb.append(" "); + sb.append(preference); + sb.append(" "); + sb.append(byteArrayToString(flags, true)); + sb.append(" "); + sb.append(byteArrayToString(service, true)); + sb.append(" "); + sb.append(byteArrayToString(regexp, true)); + sb.append(" "); + sb.append(replacement); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + order = st.getUInt16(); + preference = st.getUInt16(); + try { + flags = byteArrayFromString(st.getString()); + service = byteArrayFromString(st.getString()); + regexp = byteArrayFromString(st.getString()); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + replacement = st.getName(origin); + } + + @Override + public + Name getAdditionalName() { + return replacement; + } + + /** + * Creates an NAPTR Record from the given data + * + * @param order The order of this NAPTR. Records with lower order are + * preferred. + * @param preference The preference, used to select between records at the + * same order. + * @param flags The control aspects of the NAPTRRecord. + * @param service The service or protocol available down the rewrite path. + * @param regexp The regular/substitution expression. + * @param replacement The domain-name to query for the next DNS resource + * record, depending on the value of the flags field. + * + * @throws IllegalArgumentException One of the strings has invalid escapes + */ + public + NAPTRRecord(Name name, int dclass, long ttl, int order, int preference, String flags, String service, String regexp, Name replacement) { + super(name, DnsRecordType.NAPTR, dclass, ttl); + this.order = checkU16("order", order); + this.preference = checkU16("preference", preference); + try { + this.flags = byteArrayFromString(flags); + this.service = byteArrayFromString(service); + this.regexp = byteArrayFromString(regexp); + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + this.replacement = checkName("replacement", replacement); + } + + /** + * Returns the order + */ + public + int getOrder() { + return order; + } + + /** + * Returns the preference + */ + public + int getPreference() { + return preference; + } + + /** + * Returns flags + */ + public + String getFlags() { + return byteArrayToString(flags, false); + } + + /** + * Returns service + */ + public + String getService() { + return byteArrayToString(service, false); + } + + /** + * Returns regexp + */ + public + String getRegexp() { + return byteArrayToString(regexp, false); + } + + /** + * Returns the replacement domain-name + */ + public + Name getReplacement() { + return replacement; + } + +} diff --git a/src/dorkbox/network/dns/records/NSAPRecord.java b/src/dorkbox/network/dns/records/NSAPRecord.java new file mode 100644 index 00000000..e0a91766 --- /dev/null +++ b/src/dorkbox/network/dns/records/NSAPRecord.java @@ -0,0 +1,120 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * NSAP Address Record. + * + * @author Brian Wellington + */ + +public +class NSAPRecord extends DnsRecord { + + private static final long serialVersionUID = -1037209403185658593L; + + private byte[] address; + + NSAPRecord() {} + + @Override + DnsRecord getObject() { + return new NSAPRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + address = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(address); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append("0x") + .append(base16.toString(address)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String addr = st.getString(); + this.address = checkAndConvertAddress(addr); + if (this.address == null) { + throw st.exception("invalid NSAP address " + addr); + } + } + + /** + * Creates an NSAP Record from the given data + * + * @param address The NSAP address. + * + * @throws IllegalArgumentException The address is not a valid NSAP address. + */ + public + NSAPRecord(Name name, int dclass, long ttl, String address) { + super(name, DnsRecordType.NSAP, dclass, ttl); + this.address = checkAndConvertAddress(address); + if (this.address == null) { + throw new IllegalArgumentException("invalid NSAP address " + address); + } + } + + private static + byte[] checkAndConvertAddress(String address) { + if (!address.substring(0, 2) + .equalsIgnoreCase("0x")) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + boolean partial = false; + int current = 0; + for (int i = 2; i < address.length(); i++) { + char c = address.charAt(i); + if (c == '.') { + continue; + } + int value = Character.digit(c, 16); + if (value == -1) { + return null; + } + if (partial) { + current += value; + bytes.write(current); + partial = false; + } + else { + current = value << 4; + partial = true; + } + + } + if (partial) { + return null; + } + return bytes.toByteArray(); + } + + /** + * Returns the NSAP address. + */ + public + String getAddress() { + return byteArrayToString(address, false); + } + +} diff --git a/src/dorkbox/network/dns/records/NSAP_PTRRecord.java b/src/dorkbox/network/dns/records/NSAP_PTRRecord.java new file mode 100644 index 00000000..9c84c312 --- /dev/null +++ b/src/dorkbox/network/dns/records/NSAP_PTRRecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * NSAP Pointer Record - maps a domain name representing an NSAP Address to + * a hostname. + * + * @author Brian Wellington + */ + +public +class NSAP_PTRRecord extends SingleNameBase { + + private static final long serialVersionUID = 2386284746382064904L; + + NSAP_PTRRecord() {} + + @Override + DnsRecord getObject() { + return new NSAP_PTRRecord(); + } + + /** + * Creates a new NSAP_PTR Record with the given data + * + * @param target The name of the host with this address + */ + public + NSAP_PTRRecord(Name name, int dclass, long ttl, Name target) { + super(name, DnsRecordType.NSAP_PTR, dclass, ttl, target, "target"); + } + + /** + * Gets the target of the NSAP_PTR Record + */ + public + Name getTarget() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/NSEC3PARAMRecord.java b/src/dorkbox/network/dns/records/NSEC3PARAMRecord.java new file mode 100644 index 00000000..d61f8eac --- /dev/null +++ b/src/dorkbox/network/dns/records/NSEC3PARAMRecord.java @@ -0,0 +1,188 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * Next SECure name 3 Parameters - this record contains the parameters (hash + * algorithm, salt, iterations) used for a valid, complete NSEC3 chain present + * in a zone. Zones signed using NSEC3 must include this record at the zone apex + * to inform authoritative servers that NSEC3 is being used with the given + * parameters. + * + * @author Brian Wellington + * @author David Blacka + */ + +public +class NSEC3PARAMRecord extends DnsRecord { + + private static final long serialVersionUID = -8689038598776316533L; + + private int hashAlg; + private int flags; + private int iterations; + private byte salt[]; + + NSEC3PARAMRecord() {} + + @Override + DnsRecord getObject() { + return new NSEC3PARAMRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + hashAlg = in.readU8(); + flags = in.readU8(); + iterations = in.readU16(); + + int salt_length = in.readU8(); + if (salt_length > 0) { + salt = in.readByteArray(salt_length); + } + else { + salt = null; + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(hashAlg); + out.writeU8(flags); + out.writeU16(iterations); + + if (salt != null) { + out.writeU8(salt.length); + out.writeByteArray(salt); + } + else { + out.writeU8(0); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(hashAlg); + sb.append(' '); + sb.append(flags); + sb.append(' '); + sb.append(iterations); + sb.append(' '); + + if (salt == null) { + sb.append('-'); + } + else { + sb.append(base16.toString(salt)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + hashAlg = st.getUInt8(); + flags = st.getUInt8(); + iterations = st.getUInt16(); + + String s = st.getString(); + if (s.equals("-")) { + salt = null; + } + else { + st.unget(); + salt = st.getHexString(); + if (salt.length > 255) { + throw st.exception("salt value too long"); + } + } + } + + /** + * Creates an NSEC3PARAM record from the given data. + * + * @param name The ownername of the NSEC3PARAM record (generally the zone name). + * @param dclass The class. + * @param ttl The TTL. + * @param hashAlg The hash algorithm. + * @param flags The value of the flags field. + * @param iterations The number of hash iterations. + * @param salt The salt to use (may be null). + */ + public + NSEC3PARAMRecord(Name name, int dclass, long ttl, int hashAlg, int flags, int iterations, byte[] salt) { + super(name, DnsRecordType.NSEC3PARAM, dclass, ttl); + this.hashAlg = checkU8("hashAlg", hashAlg); + this.flags = checkU8("flags", flags); + this.iterations = checkU16("iterations", iterations); + + if (salt != null) { + if (salt.length > 255) { + throw new IllegalArgumentException("Invalid salt " + "length"); + } + if (salt.length > 0) { + this.salt = new byte[salt.length]; + System.arraycopy(salt, 0, this.salt, 0, salt.length); + } + } + } + + /** + * Returns the hash algorithm + */ + public + int getHashAlgorithm() { + return hashAlg; + } + + /** + * Returns the flags + */ + public + int getFlags() { + return flags; + } + + /** + * Returns the number of iterations + */ + public + int getIterations() { + return iterations; + } + + /** + * Returns the salt + */ + public + byte[] getSalt() { + return salt; + } + + /** + * Hashes a name with the parameters of this NSEC3PARAM record. + * + * @param name The name to hash + * + * @return The hashed version of the name + * + * @throws NoSuchAlgorithmException The hash algorithm is unknown. + */ + public + byte[] hashName(Name name) throws NoSuchAlgorithmException { + return NSEC3Record.hashName(name, hashAlg, iterations, salt); + } + +} diff --git a/src/dorkbox/network/dns/records/NSEC3Record.java b/src/dorkbox/network/dns/records/NSEC3Record.java new file mode 100644 index 00000000..cb1d0748 --- /dev/null +++ b/src/dorkbox/network/dns/records/NSEC3Record.java @@ -0,0 +1,301 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; +import dorkbox.network.dns.utils.base32; + +/** + * Next SECure name 3 - this record contains the next hashed name in an + * ordered list of hashed names in the zone, and a set of types for which + * records exist for this name. The presence of this record in a response + * signifies a negative response from a DNSSEC-signed zone. + *

+ * This replaces the NSEC and NXT records, when used. + * + * @author Brian Wellington + * @author David Blacka + */ + +public +class NSEC3Record extends DnsRecord { + + public static final int SHA1_DIGEST_ID = Digest.SHA1; + private static final long serialVersionUID = -7123504635968932855L; + private int hashAlg; + private int flags; + private int iterations; + private byte[] salt; + private byte[] next; + private TypeBitmap types; + private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, false); + + + public static + class Flags { + /** + * Unsigned delegation are not included in the NSEC3 chain. + */ + public static final int OPT_OUT = 0x01; + + /** + * NSEC3 flags identifiers. + */ + + private + Flags() {} + } + + + public static + class Digest { + /** + * SHA-1 + */ + public static final int SHA1 = 1; + + private + Digest() {} + } + + NSEC3Record() {} + + @Override + DnsRecord getObject() { + return new NSEC3Record(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + hashAlg = in.readU8(); + flags = in.readU8(); + iterations = in.readU16(); + + int salt_length = in.readU8(); + if (salt_length > 0) { + salt = in.readByteArray(salt_length); + } + else { + salt = null; + } + + int next_length = in.readU8(); + next = in.readByteArray(next_length); + types = new TypeBitmap(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(hashAlg); + out.writeU8(flags); + out.writeU16(iterations); + + if (salt != null) { + out.writeU8(salt.length); + out.writeByteArray(salt); + } + else { + out.writeU8(0); + } + + out.writeU8(next.length); + out.writeByteArray(next); + types.toWire(out); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(hashAlg); + sb.append(' '); + sb.append(flags); + sb.append(' '); + sb.append(iterations); + sb.append(' '); + + if (salt == null) { + sb.append('-'); + } + else { + sb.append(base16.toString(salt)); + } + + sb.append(' '); + sb.append(b32.toString(next)); + + if (!types.empty()) { + sb.append(' '); + sb.append(types.toString()); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + hashAlg = st.getUInt8(); + flags = st.getUInt8(); + iterations = st.getUInt16(); + + String s = st.getString(); + if (s.equals("-")) { + salt = null; + } + else { + st.unget(); + salt = st.getHexString(); + if (salt.length > 255) { + throw st.exception("salt value too long"); + } + } + + next = st.getBase32String(b32); + types = new TypeBitmap(st); + } + + /** + * Creates an NSEC3 record from the given data. + * + * @param name The ownername of the NSEC3 record (base32'd hash plus zonename). + * @param dclass The class. + * @param ttl The TTL. + * @param hashAlg The hash algorithm. + * @param flags The value of the flags field. + * @param iterations The number of hash iterations. + * @param salt The salt to use (may be null). + * @param next The next hash (may not be null). + * @param types The types present at the original ownername. + */ + public + NSEC3Record(Name name, int dclass, long ttl, int hashAlg, int flags, int iterations, byte[] salt, byte[] next, int[] types) { + super(name, DnsRecordType.NSEC3, dclass, ttl); + this.hashAlg = checkU8("hashAlg", hashAlg); + this.flags = checkU8("flags", flags); + this.iterations = checkU16("iterations", iterations); + + if (salt != null) { + if (salt.length > 255) { + throw new IllegalArgumentException("Invalid salt"); + } + if (salt.length > 0) { + this.salt = new byte[salt.length]; + System.arraycopy(salt, 0, this.salt, 0, salt.length); + } + } + + if (next.length > 255) { + throw new IllegalArgumentException("Invalid next hash"); + } + this.next = new byte[next.length]; + System.arraycopy(next, 0, this.next, 0, next.length); + this.types = new TypeBitmap(types); + } + + /** + * Returns the hash algorithm + */ + public + int getHashAlgorithm() { + return hashAlg; + } + + /** + * Returns the flags + */ + public + int getFlags() { + return flags; + } + + /** + * Returns the number of iterations + */ + public + int getIterations() { + return iterations; + } + + /** + * Returns the salt + */ + public + byte[] getSalt() { + return salt; + } + + /** + * Returns the next hash + */ + public + byte[] getNext() { + return next; + } + + /** + * Returns the set of types defined for this name + */ + public + int[] getTypes() { + return types.toArray(); + } + + /** + * Returns whether a specific type is in the set of types. + */ + public + boolean hasType(int type) { + return types.contains(type); + } + + /** + * Hashes a name with the parameters of this NSEC3 record. + * + * @param name The name to hash + * + * @return The hashed version of the name + * + * @throws NoSuchAlgorithmException The hash algorithm is unknown. + */ + public + byte[] hashName(Name name) throws NoSuchAlgorithmException { + return hashName(name, hashAlg, iterations, salt); + } + + static + byte[] hashName(Name name, int hashAlg, int iterations, byte[] salt) throws NoSuchAlgorithmException { + MessageDigest digest; + switch (hashAlg) { + case Digest.SHA1: + digest = MessageDigest.getInstance("sha-1"); + break; + default: + throw new NoSuchAlgorithmException("Unknown NSEC3 algorithm" + "identifier: " + hashAlg); + } + byte[] hash = null; + for (int i = 0; i <= iterations; i++) { + digest.reset(); + if (i == 0) { + digest.update(name.toWireCanonical()); + } + else { + digest.update(hash); + } + if (salt != null) { + digest.update(salt); + } + hash = digest.digest(); + } + return hash; + } + +} diff --git a/src/dorkbox/network/dns/records/NSECRecord.java b/src/dorkbox/network/dns/records/NSECRecord.java new file mode 100644 index 00000000..ddd666bf --- /dev/null +++ b/src/dorkbox/network/dns/records/NSECRecord.java @@ -0,0 +1,113 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Next SECure name - this record contains the following name in an + * ordered list of names in the zone, and a set of types for which + * records exist for this name. The presence of this record in a response + * signifies a negative response from a DNSSEC-signed zone. + *

+ * This replaces the NXT record. + * + * @author Brian Wellington + * @author David Blacka + */ + +public +class NSECRecord extends DnsRecord { + + private static final long serialVersionUID = -5165065768816265385L; + + private Name next; + private TypeBitmap types; + + NSECRecord() {} + + @Override + DnsRecord getObject() { + return new NSECRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + next = new Name(in); + types = new TypeBitmap(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + // Note: The next name is not lowercased. + next.toWire(out, null, false); + types.toWire(out); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(next); + + if (!types.empty()) { + sb.append(' '); + sb.append(types.toString()); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + next = st.getName(origin); + types = new TypeBitmap(st); + } + + /** + * Creates an NSEC Record from the given data. + * + * @param next The following name in an ordered list of the zone + * @param types An array containing the types present. + */ + public + NSECRecord(Name name, int dclass, long ttl, Name next, int[] types) { + super(name, DnsRecordType.NSEC, dclass, ttl); + this.next = checkName("next", next); + for (int i = 0; i < types.length; i++) { + DnsRecordType.check(types[i]); + } + this.types = new TypeBitmap(types); + } + + /** + * Returns the next name + */ + public + Name getNext() { + return next; + } + + /** + * Returns the set of types defined for this name + */ + public + int[] getTypes() { + return types.toArray(); + } + + /** + * Returns whether a specific type is in the set of types. + */ + public + boolean hasType(int type) { + return types.contains(type); + } + +} diff --git a/src/dorkbox/network/dns/records/NSIDOption.java b/src/dorkbox/network/dns/records/NSIDOption.java new file mode 100644 index 00000000..4304c8ba --- /dev/null +++ b/src/dorkbox/network/dns/records/NSIDOption.java @@ -0,0 +1,30 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +/** + * The Name Server Identifier Option, define in RFC 5001. + * + * @author Brian Wellington + * @see OPTRecord + */ +public +class NSIDOption extends GenericEDNSOption { + + private static final long serialVersionUID = 74739759292589056L; + + NSIDOption() { + super(EDNSOption.Code.NSID); + } + + /** + * Construct an NSID option. + * + * @param data The contents of the option. + */ + public + NSIDOption(byte[] data) { + super(EDNSOption.Code.NSID, data); + } + +} diff --git a/src/dorkbox/network/dns/records/NSRecord.java b/src/dorkbox/network/dns/records/NSRecord.java new file mode 100644 index 00000000..ef522158 --- /dev/null +++ b/src/dorkbox/network/dns/records/NSRecord.java @@ -0,0 +1,50 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Name Server Record - contains the name server serving the named zone + * + * @author Brian Wellington + */ + +public +class NSRecord extends SingleCompressedNameBase { + + private static final long serialVersionUID = 487170758138268838L; + + NSRecord() {} + + @Override + DnsRecord getObject() { + return new NSRecord(); + } + + @Override + public + Name getAdditionalName() { + return getSingleName(); + } + + /** + * Creates a new NS Record with the given data + * + * @param target The name server for the given domain + */ + public + NSRecord(Name name, int dclass, long ttl, Name target) { + super(name, DnsRecordType.NS, dclass, ttl, target, "target"); + } + + /** + * Gets the target of the NS Record + */ + public + Name getTarget() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/NULLRecord.java b/src/dorkbox/network/dns/records/NULLRecord.java new file mode 100644 index 00000000..cdb48cca --- /dev/null +++ b/src/dorkbox/network/dns/records/NULLRecord.java @@ -0,0 +1,78 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * The NULL Record. This has no defined purpose, but can be used to + * hold arbitrary data. + * + * @author Brian Wellington + */ + +public +class NULLRecord extends DnsRecord { + + private static final long serialVersionUID = -5796493183235216538L; + + private byte[] data; + + NULLRecord() {} + + @Override + DnsRecord getObject() { + return new NULLRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + data = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(unknownToString(data)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no defined text format for NULL records"); + } + + /** + * Creates a NULL record from the given data. + * + * @param data The contents of the record. + */ + public + NULLRecord(Name name, int dclass, long ttl, byte[] data) { + super(name, DnsRecordType.NULL, dclass, ttl); + + if (data.length > 0xFFFF) { + throw new IllegalArgumentException("data must be <65536 bytes"); + } + this.data = data; + } + + /** + * Returns the contents of this record. + */ + public + byte[] getData() { + return data; + } + +} diff --git a/src/dorkbox/network/dns/records/NXTRecord.java b/src/dorkbox/network/dns/records/NXTRecord.java new file mode 100644 index 00000000..1b385fed --- /dev/null +++ b/src/dorkbox/network/dns/records/NXTRecord.java @@ -0,0 +1,129 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.BitSet; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Next name - this record contains the following name in an ordered list + * of names in the zone, and a set of types for which records exist for + * this name. The presence of this record in a response signifies a + * failed query for data in a DNSSEC-signed zone. + * + * @author Brian Wellington + */ + +public +class NXTRecord extends DnsRecord { + + private static final long serialVersionUID = -8851454400765507520L; + + private Name next; + private BitSet bitmap; + + NXTRecord() {} + + @Override + DnsRecord getObject() { + return new NXTRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + next = new Name(in); + bitmap = new BitSet(); + int bitmapLength = in.remaining(); + for (int i = 0; i < bitmapLength; i++) { + int t = in.readU8(); + for (int j = 0; j < 8; j++) { + if ((t & (1 << (7 - j))) != 0) { + bitmap.set(i * 8 + j); + } + } + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + next.toWire(out, null, canonical); + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + out.writeU8(t); + t = 0; + } + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(next); + int length = bitmap.length(); + for (short i = 0; i < length; i++) { + if (bitmap.get(i)) { + sb.append(" "); + sb.append(DnsRecordType.string(i)); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + next = st.getName(origin); + bitmap = new BitSet(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) { + break; + } + int typecode = DnsRecordType.value(t.value, true); + if (typecode <= 0 || typecode > 128) { + throw st.exception("Invalid type: " + t.value); + } + bitmap.set(typecode); + } + st.unget(); + } + + /** + * Creates an NXT Record from the given data + * + * @param next The following name in an ordered list of the zone + * @param bitmap The set of type for which records exist at this name + */ + public + NXTRecord(Name name, int dclass, long ttl, Name next, BitSet bitmap) { + super(name, DnsRecordType.NXT, dclass, ttl); + this.next = checkName("next", next); + this.bitmap = bitmap; + } + + /** + * Returns the next name + */ + public + Name getNext() { + return next; + } + + /** + * Returns the set of types defined for this name + */ + public + BitSet getBitmap() { + return bitmap; + } + +} diff --git a/src/dorkbox/network/dns/records/OPENPGPKEYRecord.java b/src/dorkbox/network/dns/records/OPENPGPKEYRecord.java new file mode 100644 index 00000000..221d3e90 --- /dev/null +++ b/src/dorkbox/network/dns/records/OPENPGPKEYRecord.java @@ -0,0 +1,88 @@ +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * OPENPGPKEY Record - Stores an OpenPGP certificate associated with a name. + * RFC 7929. + * + * @author Brian Wellington + * @author Valentin Hauner + */ +public +class OPENPGPKEYRecord extends DnsRecord { + + private static final long serialVersionUID = -1277262990243423062L; + + private byte[] cert; + + OPENPGPKEYRecord() {} + + @Override + DnsRecord getObject() { + return new OPENPGPKEYRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + cert = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(cert); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + if (cert != null) { + if (Options.check("multiline")) { + sb.append("(") + .append(OS.LINE_SEPARATOR); + sb.append(Base64Fast.formatString(Base64Fast.encode2(cert), 64, "\t", true)); + } + else { + sb.append("\t"); + sb.append(Base64Fast.encode2(cert)); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + cert = st.getBase64(); + } + + /** + * Creates an OPENPGPKEY Record from the given data + * + * @param cert Binary data representing the certificate + */ + public + OPENPGPKEYRecord(Name name, int dclass, long ttl, byte[] cert) { + super(name, DnsRecordType.OPENPGPKEY, dclass, ttl); + this.cert = cert; + } + + /** + * Returns the binary representation of the certificate + */ + public + byte[] getCert() { + return cert; + } + +} diff --git a/src/dorkbox/network/dns/records/OPTRecord.java b/src/dorkbox/network/dns/records/OPTRecord.java new file mode 100644 index 00000000..c5bd60be --- /dev/null +++ b/src/dorkbox/network/dns/records/OPTRecord.java @@ -0,0 +1,232 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsResponseCode; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Options - describes Extended DNS (EDNS) properties of a DnsMessage. + * No specific options are defined other than those specified in the + * header. An OPT should be generated by Resolver. + *

+ * EDNS is a method to extend the DNS protocol while providing backwards + * compatibility and not significantly changing the protocol. This + * implementation of EDNS is mostly complete at level 0. + * + * @author Brian Wellington + * @see DnsMessage + */ + +public +class OPTRecord extends DnsRecord { + + private static final long serialVersionUID = -6254521894809367938L; + + private List options; + + OPTRecord() {} + + @Override + DnsRecord getObject() { + return new OPTRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + if (in.remaining() > 0) { + options = new ArrayList(); + } + while (in.remaining() > 0) { + EDNSOption option = EDNSOption.fromWire(in); + options.add(option); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + if (options == null) { + return; + } + Iterator it = options.iterator(); + while (it.hasNext()) { + EDNSOption option = (EDNSOption) it.next(); + option.toWire(out); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + if (options != null) { + sb.append(options); + sb.append(" "); + } + + sb.append(" ; payload "); + sb.append(getPayloadSize()); + sb.append(", xrcode "); + sb.append(getExtendedRcode()); + sb.append(", version "); + sb.append(getVersion()); + sb.append(", flags "); + sb.append(getFlags()); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for OPT"); + } + + /** + * Determines if two OPTRecords are identical. This compares the name, type, + * class, and rdata (with names canonicalized). Additionally, because TTLs + * are relevant for OPT records, the TTLs are compared. + * + * @param arg The record to compare to + * + * @return true if the records are equal, false otherwise. + */ + public + boolean equals(final Object arg) { + return super.equals(arg) && ttl == ((OPTRecord) arg).ttl; + } + + /** + * Returns the maximum allowed payload size. + */ + public + int getPayloadSize() { + return dclass; + } + + /** + * Returns the extended DnsResponseCode + * + * @see DnsResponseCode + */ + public + int getExtendedRcode() { + return (int) (ttl >>> 24); + } + + /** + * Returns the highest supported EDNS version + */ + public + int getVersion() { + return (int) ((ttl >>> 16) & 0xFF); + } + + /** + * Returns the EDNS flags + */ + public + int getFlags() { + return (int) (ttl & 0xFFFF); + } + + /** + * Creates an OPT Record with no data. This is normally called by + * SimpleResolver, but can also be called by a server. + * + * @param payloadSize The size of a packet that can be reassembled on the + * sending host. + * @param xrcode The value of the extended rcode field. This is the upper + * 16 bits of the full rcode. + * @param flags Additional message flags. + * @param version The EDNS version that this DNS implementation supports. + * This should be 0 for dnsjava. + * + * @see ExtendedFlags + */ + public + OPTRecord(int payloadSize, int xrcode, int version, int flags) { + this(payloadSize, xrcode, version, flags, null); + } + + /** + * Creates an OPT Record. This is normally called by SimpleResolver, but can + * also be called by a server. + * + * @param payloadSize The size of a packet that can be reassembled on the + * sending host. + * @param xrcode The value of the extended rcode field. This is the upper + * 16 bits of the full rcode. + * @param flags Additional message flags. + * @param version The EDNS version that this DNS implementation supports. + * This should be 0 for dnsjava. + * @param options The list of options that comprise the data field. There + * are currently no defined options. + * + * @see ExtendedFlags + */ + public + OPTRecord(int payloadSize, int xrcode, int version, int flags, List options) { + super(Name.root, DnsRecordType.OPT, payloadSize, 0); + checkU16("payloadSize", payloadSize); + checkU8("xrcode", xrcode); + checkU8("version", version); + checkU16("flags", flags); + ttl = ((long) xrcode << 24) + ((long) version << 16) + flags; + if (options != null) { + this.options = new ArrayList(options); + } + } + + /** + * Creates an OPT Record with no data. This is normally called by + * SimpleResolver, but can also be called by a server. + */ + public + OPTRecord(int payloadSize, int xrcode, int version) { + this(payloadSize, xrcode, version, 0, null); + } + + /** + * Gets all options in the OPTRecord. This returns a list of EDNSOptions. + */ + public + List getOptions() { + if (options == null) { + return Collections.EMPTY_LIST; + } + return Collections.unmodifiableList(options); + } + + /** + * Gets all options in the OPTRecord with a specific code. This returns a list + * of EDNSOptions. + */ + public + List getOptions(int code) { + if (options == null) { + return Collections.EMPTY_LIST; + } + List list = Collections.EMPTY_LIST; + for (Iterator it = options.iterator(); it.hasNext(); ) { + EDNSOption opt = (EDNSOption) it.next(); + if (opt.getCode() == code) { + if (list == Collections.EMPTY_LIST) { + list = new ArrayList(); + } + list.add(opt); + } + } + return list; + } + +} diff --git a/src/dorkbox/network/dns/records/PTRRecord.java b/src/dorkbox/network/dns/records/PTRRecord.java new file mode 100644 index 00000000..ebb2575e --- /dev/null +++ b/src/dorkbox/network/dns/records/PTRRecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Pointer Record - maps a domain name representing an Internet Address to + * a hostname. + * + * @author Brian Wellington + */ + +public +class PTRRecord extends SingleCompressedNameBase { + + private static final long serialVersionUID = -8321636610425434192L; + + PTRRecord() {} + + @Override + DnsRecord getObject() { + return new PTRRecord(); + } + + /** + * Creates a new PTR Record with the given data + * + * @param target The name of the machine with this address + */ + public + PTRRecord(Name name, int dclass, long ttl, Name target) { + super(name, DnsRecordType.PTR, dclass, ttl, target, "target"); + } + + /** + * Gets the target of the PTR Record + */ + public + Name getTarget() { + return getSingleName(); + } + +} diff --git a/src/dorkbox/network/dns/records/PXRecord.java b/src/dorkbox/network/dns/records/PXRecord.java new file mode 100644 index 00000000..94d7b293 --- /dev/null +++ b/src/dorkbox/network/dns/records/PXRecord.java @@ -0,0 +1,109 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * X.400 mail mapping record. + * + * @author Brian Wellington + */ + +public +class PXRecord extends DnsRecord { + + private static final long serialVersionUID = 1811540008806660667L; + + private int preference; + private Name map822; + private Name mapX400; + + PXRecord() {} + + @Override + DnsRecord getObject() { + return new PXRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + preference = in.readU16(); + map822 = new Name(in); + mapX400 = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(preference); + map822.toWire(out, null, canonical); + mapX400.toWire(out, null, canonical); + } + + /** + * Converts the PX Record to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(preference); + sb.append(" "); + sb.append(map822); + sb.append(" "); + sb.append(mapX400); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + preference = st.getUInt16(); + map822 = st.getName(origin); + mapX400 = st.getName(origin); + } + + /** + * Creates an PX Record from the given data + * + * @param preference The preference of this mail address. + * @param map822 The RFC 822 component of the mail address. + * @param mapX400 The X.400 component of the mail address. + */ + public + PXRecord(Name name, int dclass, long ttl, int preference, Name map822, Name mapX400) { + super(name, DnsRecordType.PX, dclass, ttl); + + this.preference = checkU16("preference", preference); + this.map822 = checkName("map822", map822); + this.mapX400 = checkName("mapX400", mapX400); + } + + /** + * Gets the preference of the route. + */ + public + int getPreference() { + return preference; + } + + /** + * Gets the RFC 822 component of the mail address. + */ + public + Name getMap822() { + return map822; + } + + /** + * Gets the X.400 component of the mail address. + */ + public + Name getMapX400() { + return mapX400; + } + +} diff --git a/src/dorkbox/network/dns/records/RPRecord.java b/src/dorkbox/network/dns/records/RPRecord.java new file mode 100644 index 00000000..47c4173a --- /dev/null +++ b/src/dorkbox/network/dns/records/RPRecord.java @@ -0,0 +1,95 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Responsible Person Record - lists the mail address of a responsible person + * and a domain where TXT records are available. + * + * @author Tom Scola (tscola@research.att.com) + * @author Brian Wellington + */ + +public +class RPRecord extends DnsRecord { + + private static final long serialVersionUID = 8124584364211337460L; + + private Name mailbox; + private Name textDomain; + + RPRecord() {} + + @Override + DnsRecord getObject() { + return new RPRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + mailbox = new Name(in); + textDomain = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + mailbox.toWire(out, null, canonical); + textDomain.toWire(out, null, canonical); + } + + /** + * Converts the RP Record to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(mailbox); + sb.append(" "); + sb.append(textDomain); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + mailbox = st.getName(origin); + textDomain = st.getName(origin); + } + + /** + * Creates an RP Record from the given data + * + * @param mailbox The responsible person + * @param textDomain The address where TXT records can be found + */ + public + RPRecord(Name name, int dclass, long ttl, Name mailbox, Name textDomain) { + super(name, DnsRecordType.RP, dclass, ttl); + + this.mailbox = checkName("mailbox", mailbox); + this.textDomain = checkName("textDomain", textDomain); + } + + /** + * Gets the mailbox address of the RP Record + */ + public + Name getMailbox() { + return mailbox; + } + + /** + * Gets the text domain info of the RP Record + */ + public + Name getTextDomain() { + return textDomain; + } + +} diff --git a/src/dorkbox/network/dns/records/RRSIGRecord.java b/src/dorkbox/network/dns/records/RRSIGRecord.java new file mode 100644 index 00000000..efa02216 --- /dev/null +++ b/src/dorkbox/network/dns/records/RRSIGRecord.java @@ -0,0 +1,61 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.util.Date; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Recource Record Signature - An RRSIG provides the digital signature of an + * RRset, so that the data can be authenticated by a DNSSEC-capable resolver. + * The signature is generated by a key contained in a DNSKEY Record. + * + * @author Brian Wellington + * @see RRset + * @see DNSSEC + * @see KEYRecord + */ + +public +class RRSIGRecord extends SIGBase { + + private static final long serialVersionUID = -2609150673537226317L; + + RRSIGRecord() {} + + @Override + DnsRecord getObject() { + return new RRSIGRecord(); + } + + /** + * Creates an RRSIG Record from the given data + * + * @param covered The RRset type covered by this signature + * @param alg The cryptographic algorithm of the key that generated the + * signature + * @param origttl The original TTL of the RRset + * @param expire The time at which the signature expires + * @param timeSigned The time at which this signature was generated + * @param footprint The footprint/key id of the signing key. + * @param signer The owner of the signing key + * @param signature Binary data representing the signature + */ + public + RRSIGRecord(Name name, + int dclass, + long ttl, + int covered, + int alg, + long origttl, + Date expire, + Date timeSigned, + int footprint, + Name signer, + byte[] signature) { + super(name, DnsRecordType.RRSIG, dclass, ttl, covered, alg, origttl, expire, timeSigned, footprint, signer, signature); + } + +} diff --git a/src/dorkbox/network/dns/records/RRset.java b/src/dorkbox/network/dns/records/RRset.java new file mode 100644 index 00000000..fb3229ab --- /dev/null +++ b/src/dorkbox/network/dns/records/RRset.java @@ -0,0 +1,308 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * A set of Records with the same name, type, and class. Also included + * are all RRSIG records signing the data records. + * + * @author Brian Wellington + * @see DnsRecord + * @see RRSIGRecord + */ + +public +class RRset implements Serializable { + + private static final long serialVersionUID = -3270249290171239695L; + + /* + * rrs contains both normal and RRSIG records, with the RRSIG records + * at the end. + */ + private List rrs; + private short nsigs; + private short position; + + /** + * Creates an RRset and sets its contents to the specified record + */ + public + RRset(DnsRecord record) { + this(); + safeAddRR(record); + } + + /** + * Creates an empty RRset + */ + public + RRset() { + rrs = new ArrayList(1); + nsigs = 0; + position = 0; + } + + private + void safeAddRR(DnsRecord r) { + if (!(r instanceof RRSIGRecord)) { + if (nsigs == 0) { + rrs.add(r); + } + else { + rrs.add(rrs.size() - nsigs, r); + } + } + else { + rrs.add(r); + nsigs++; + } + } + + /** + * Creates an RRset with the contents of an existing RRset + */ + public + RRset(RRset rrset) { + synchronized (rrset) { + rrs = (List) ((ArrayList) rrset.rrs).clone(); + nsigs = rrset.nsigs; + position = rrset.position; + } + } + + /** + * Adds a Record to an RRset + */ + public synchronized + void addRR(DnsRecord r) { + if (rrs.size() == 0) { + safeAddRR(r); + return; + } + DnsRecord first = first(); + if (!r.sameRRset(first)) { + throw new IllegalArgumentException("record does not match " + "rrset"); + } + + if (r.getTTL() != first.getTTL()) { + if (r.getTTL() > first.getTTL()) { + r = r.cloneRecord(); + r.setTTL(first.getTTL()); + } + else { + for (int i = 0; i < rrs.size(); i++) { + DnsRecord tmp = (DnsRecord) rrs.get(i); + tmp = tmp.cloneRecord(); + tmp.setTTL(r.getTTL()); + rrs.set(i, tmp); + } + } + } + + if (!rrs.contains(r)) { + safeAddRR(r); + } + } + + /** + * Returns the first record + * + * @throws IllegalStateException if the rrset is empty + */ + public synchronized + DnsRecord first() { + if (rrs.size() == 0) { + throw new IllegalStateException("rrset is empty"); + } + return (DnsRecord) rrs.get(0); + } + + /** + * Deletes a Record from an RRset + */ + public synchronized + void deleteRR(DnsRecord r) { + if (rrs.remove(r) && (r instanceof RRSIGRecord)) { + nsigs--; + } + } + + /** + * Deletes all Records from an RRset + */ + public synchronized + void clear() { + rrs.clear(); + position = 0; + nsigs = 0; + } + + /** + * Returns an Iterator listing all (data) records. + * + * @param cycle If true, cycle through the records so that each Iterator will + * start with a different record. + */ + public synchronized + Iterator rrs(boolean cycle) { + return iterator(true, cycle); + } + + private synchronized + Iterator iterator(boolean data, boolean cycle) { + int size, start, total; + + total = rrs.size(); + + if (data) { + size = total - nsigs; + } + else { + size = nsigs; + } + if (size == 0) { + return Collections.EMPTY_LIST.iterator(); + } + + if (data) { + if (!cycle) { + start = 0; + } + else { + if (position >= size) { + position = 0; + } + start = position++; + } + } + else { + start = total - nsigs; + } + + List list = new ArrayList(size); + if (data) { + list.addAll(rrs.subList(start, size)); + if (start != 0) { + list.addAll(rrs.subList(0, start)); + } + } + else { + list.addAll(rrs.subList(start, total)); + } + + return list.iterator(); + } + + /** + * Returns an Iterator listing all (data) records. This cycles through + * the records, so each Iterator will start with a different record. + */ + public synchronized + Iterator rrs() { + return iterator(true, true); + } + + /** + * Returns an Iterator listing all signature records + */ + public synchronized + Iterator sigs() { + return iterator(false, false); + } + + /** + * Returns the number of (data) records + */ + public synchronized + int size() { + return rrs.size() - nsigs; + } + + /** + * Converts the RRset to a String + */ + public + String toString() { + if (rrs.size() == 0) { + return ("{empty}"); + } + StringBuilder sb = new StringBuilder(); + sb.append("{ "); + sb.append(getName() + " "); + sb.append(getTTL() + " "); + sb.append(DnsClass.string(getDClass()) + " "); + sb.append(DnsRecordType.string(getType()) + " "); + sb.append(iteratorToString(iterator(true, false))); + if (nsigs > 0) { + sb.append(" sigs: "); + sb.append(iteratorToString(iterator(false, false))); + } + sb.append(" }"); + return sb.toString(); + } + + /** + * Returns the name of the records + * + * @see Name + */ + public + Name getName() { + return first().getName(); + } + + /** + * Returns the type of the records + * + * @see DnsRecordType + */ + public + int getType() { + return first().getRRsetType(); + } + + /** + * Returns the class of the records + * + * @see DnsClass + */ + public + int getDClass() { + return first().getDClass(); + } + + /** + * Returns the ttl of the records + */ + public synchronized + long getTTL() { + return first().getTTL(); + } + + private + String iteratorToString(Iterator it) { + StringBuilder sb = new StringBuilder(); + while (it.hasNext()) { + DnsRecord rr = (DnsRecord) it.next(); + sb.append("["); + rr.rdataToString(sb); + sb.append("]"); + if (it.hasNext()) { + sb.append(" "); + } + } + return sb.toString(); + } + +} diff --git a/src/dorkbox/network/dns/records/RTRecord.java b/src/dorkbox/network/dns/records/RTRecord.java new file mode 100644 index 00000000..96d2d77e --- /dev/null +++ b/src/dorkbox/network/dns/records/RTRecord.java @@ -0,0 +1,54 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Route Through Record - lists a route preference and intermediate host. + * + * @author Brian Wellington + */ + +public +class RTRecord extends U16NameBase { + + private static final long serialVersionUID = -3206215651648278098L; + + RTRecord() {} + + @Override + DnsRecord getObject() { + return new RTRecord(); + } + + /** + * Creates an RT Record from the given data + * + * @param preference The preference of the route. Smaller numbers indicate + * more preferred routes. + * @param intermediateHost The domain name of the host to use as a router. + */ + public + RTRecord(Name name, int dclass, long ttl, int preference, Name intermediateHost) { + super(name, DnsRecordType.RT, dclass, ttl, preference, "preference", intermediateHost, "intermediateHost"); + } + + /** + * Gets the preference of the route. + */ + public + int getPreference() { + return getU16Field(); + } + + /** + * Gets the host to use as a router. + */ + public + Name getIntermediateHost() { + return getNameField(); + } + +} diff --git a/src/dorkbox/network/dns/records/SIG0.java b/src/dorkbox/network/dns/records/SIG0.java new file mode 100644 index 00000000..c55cab6f --- /dev/null +++ b/src/dorkbox/network/dns/records/SIG0.java @@ -0,0 +1,84 @@ +// Copyright (c) 2001-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.security.PrivateKey; +import java.util.Date; + +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.utils.Options; + +/** + * Creates SIG(0) transaction signatures. + * + * @author Pasi Eronen + * @author Brian Wellington + */ + +public +class SIG0 { + + /** + * The default validity period for outgoing SIG(0) signed messages. + * Can be overriden by the sig0validity option. + */ + private static final short VALIDITY = 300; + + private + SIG0() { } + + /** + * Sign a dnsMessage with SIG(0). The DNS key and private key must refer to the + * same underlying cryptographic key. + * + * @param dnsMessage The dnsMessage to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param previous If this dnsMessage is a response, the SIG(0) from the query + */ + public static + void signMessage(DnsMessage dnsMessage, KEYRecord key, PrivateKey privkey, SIGRecord previous) throws DNSSEC.DNSSECException { + + int validity = Options.intValue("sig0validity"); + if (validity < 0) { + validity = VALIDITY; + } + + long now = System.currentTimeMillis(); + Date timeSigned = new Date(now); + Date timeExpires = new Date(now + validity * 1000); + + SIGRecord sig = DNSSEC.signMessage(dnsMessage, previous, key, privkey, timeSigned, timeExpires); + + dnsMessage.addRecord(sig, DnsSection.ADDITIONAL); + } + + /** + * Verify a dnsMessage using SIG(0). + * + * @param dnsMessage The dnsMessage to be signed + * @param b An array containing the dnsMessage in unparsed form. This is + * necessary since SIG(0) signs the dnsMessage in wire format, and we can't + * recreate the exact wire format (with the same name compression). + * @param key The KEY record to verify the signature with. + * @param previous If this dnsMessage is a response, the SIG(0) from the query + */ + public static + void verifyMessage(DnsMessage dnsMessage, byte[] b, KEYRecord key, SIGRecord previous) throws DNSSEC.DNSSECException { + SIGRecord sig = null; + DnsRecord[] additional = dnsMessage.getSectionArray(DnsSection.ADDITIONAL); + for (int i = 0; i < additional.length; i++) { + if (additional[i].getType() != DnsRecordType.SIG) { + continue; + } + if (((SIGRecord) additional[i]).getTypeCovered() != 0) { + continue; + } + sig = (SIGRecord) additional[i]; + break; + } + DNSSEC.verifyMessage(dnsMessage, b, sig, previous, key); + } + +} diff --git a/src/dorkbox/network/dns/records/SIGBase.java b/src/dorkbox/network/dns/records/SIGBase.java new file mode 100644 index 00000000..f72016bf --- /dev/null +++ b/src/dorkbox/network/dns/records/SIGBase.java @@ -0,0 +1,229 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.Date; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.FormattedTime; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * The base class for SIG/RRSIG records, which have identical formats + * + * @author Brian Wellington + */ + +abstract +class SIGBase extends DnsRecord { + + private static final long serialVersionUID = -3738444391533812369L; + + protected int covered; + protected int alg, labels; + protected long origttl; + protected Date expire, timeSigned; + protected int footprint; + protected Name signer; + protected byte[] signature; + + protected + SIGBase() {} + + public + SIGBase(Name name, + int type, + int dclass, + long ttl, + int covered, + int alg, + long origttl, + Date expire, + Date timeSigned, + int footprint, + Name signer, + byte[] signature) { + super(name, type, dclass, ttl); + DnsRecordType.check(covered); + TTL.check(origttl); + this.covered = covered; + this.alg = checkU8("alg", alg); + this.labels = name.labels() - 1; + if (name.isWild()) { + this.labels--; + } + this.origttl = origttl; + this.expire = expire; + this.timeSigned = timeSigned; + this.footprint = checkU16("footprint", footprint); + this.signer = checkName("signer", signer); + this.signature = signature; + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + covered = in.readU16(); + alg = in.readU8(); + labels = in.readU8(); + origttl = in.readU32(); + expire = new Date(1000 * in.readU32()); + timeSigned = new Date(1000 * in.readU32()); + footprint = in.readU16(); + signer = new Name(in); + signature = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(covered); + out.writeU8(alg); + out.writeU8(labels); + out.writeU32(origttl); + out.writeU32(expire.getTime() / 1000); + out.writeU32(timeSigned.getTime() / 1000); + out.writeU16(footprint); + signer.toWire(out, null, canonical); + out.writeByteArray(signature); + } + + /** + * Converts the RRSIG/SIG Record to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(DnsRecordType.string(covered)); + sb.append(" "); + sb.append(alg); + sb.append(" "); + sb.append(labels); + sb.append(" "); + sb.append(origttl); + sb.append(" "); + if (Options.check("multiline")) { + sb.append("(\n\t"); + } + sb.append(FormattedTime.format(expire)); + sb.append(" "); + sb.append(FormattedTime.format(timeSigned)); + sb.append(" "); + sb.append(footprint); + sb.append(" "); + sb.append(signer); + if (Options.check("multiline")) { + sb.append(OS.LINE_SEPARATOR); + sb.append(Base64Fast.formatString(Base64Fast.encode2(signature), 64, "\t", true)); + } + else { + sb.append(" "); + sb.append(Base64Fast.encode2(signature)); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String typeString = st.getString(); + covered = DnsRecordType.value(typeString); + if (covered < 0) { + throw st.exception("Invalid type: " + typeString); + } + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) { + throw st.exception("Invalid algorithm: " + algString); + } + labels = st.getUInt8(); + origttl = st.getTTL(); + expire = FormattedTime.parse(st.getString()); + timeSigned = FormattedTime.parse(st.getString()); + footprint = st.getUInt16(); + signer = st.getName(origin); + signature = st.getBase64(); + } + + /** + * Returns the RRset type covered by this signature + */ + public + int getTypeCovered() { + return covered; + } + + /** + * Returns the cryptographic algorithm of the key that generated the signature + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the number of labels in the signed domain name. This may be + * different than the record's domain name if the record is a wildcard + * record. + */ + public + int getLabels() { + return labels; + } + + /** + * Returns the original TTL of the RRset + */ + public + long getOrigTTL() { + return origttl; + } + + /** + * Returns the time at which the signature expires + */ + public + Date getExpire() { + return expire; + } + + /** + * Returns the time at which this signature was generated + */ + public + Date getTimeSigned() { + return timeSigned; + } + + /** + * Returns The footprint/key id of the signing key. + */ + public + int getFootprint() { + return footprint; + } + + /** + * Returns the owner of the signing key + */ + public + Name getSigner() { + return signer; + } + + /** + * Returns the binary data representing the signature + */ + public + byte[] getSignature() { + return signature; + } + + void setSignature(byte[] signature) { + this.signature = signature; + } + +} diff --git a/src/dorkbox/network/dns/records/SIGRecord.java b/src/dorkbox/network/dns/records/SIGRecord.java new file mode 100644 index 00000000..c0984d06 --- /dev/null +++ b/src/dorkbox/network/dns/records/SIGRecord.java @@ -0,0 +1,61 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.util.Date; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Signature - A SIG provides the digital signature of an RRset, so that + * the data can be authenticated by a DNSSEC-capable resolver. The + * signature is usually generated by a key contained in a KEYRecord + * + * @author Brian Wellington + * @see RRset + * @see DNSSEC + * @see KEYRecord + */ + +public +class SIGRecord extends SIGBase { + + private static final long serialVersionUID = 4963556060953589058L; + + SIGRecord() {} + + @Override + DnsRecord getObject() { + return new SIGRecord(); + } + + /** + * Creates an SIG Record from the given data + * + * @param covered The RRset type covered by this signature + * @param alg The cryptographic algorithm of the key that generated the + * signature + * @param origttl The original TTL of the RRset + * @param expire The time at which the signature expires + * @param timeSigned The time at which this signature was generated + * @param footprint The footprint/key id of the signing key. + * @param signer The owner of the signing key + * @param signature Binary data representing the signature + */ + public + SIGRecord(Name name, + int dclass, + long ttl, + int covered, + int alg, + long origttl, + Date expire, + Date timeSigned, + int footprint, + Name signer, + byte[] signature) { + super(name, DnsRecordType.SIG, dclass, ttl, covered, alg, origttl, expire, timeSigned, footprint, signer, signature); + } + +} diff --git a/src/dorkbox/network/dns/records/SMIMEARecord.java b/src/dorkbox/network/dns/records/SMIMEARecord.java new file mode 100644 index 00000000..39a505ff --- /dev/null +++ b/src/dorkbox/network/dns/records/SMIMEARecord.java @@ -0,0 +1,178 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * S/MIME cert association, draft-ietf-dane-smime. + * + * @author Brian Wellington + */ + +public +class SMIMEARecord extends DnsRecord { + + private static final long serialVersionUID = 1640247915216425235L; + +// Note; these are copied from the TLSA type. + private int certificateUsage; + private int selector; + private int matchingType; + private byte[] certificateAssociationData; + + + public static + class CertificateUsage { + public static final int CA_CONSTRAINT = 0; + public static final int SERVICE_CERTIFICATE_CONSTRAINT = 1; + public static final int TRUST_ANCHOR_ASSERTION = 2; + public static final int DOMAIN_ISSUED_CERTIFICATE = 3; + private + CertificateUsage() {} + } + + + public static + class Selector { + /** + * Full certificate; the Certificate binary structure defined in + * [RFC5280] + */ + public static final int FULL_CERTIFICATE = 0; + /** + * SubjectPublicKeyInfo; DER-encoded binary structure defined in + * [RFC5280] + */ + public static final int SUBJECT_PUBLIC_KEY_INFO = 1; + + private + Selector() {} + } + + + public static + class MatchingType { + /** + * Exact match on selected content + */ + public static final int EXACT = 0; + /** + * SHA-256 hash of selected content [RFC6234] + */ + public static final int SHA256 = 1; + /** + * SHA-512 hash of selected content [RFC6234] + */ + public static final int SHA512 = 2; + + private + MatchingType() {} + } + + SMIMEARecord() {} + + @Override + DnsRecord getObject() { + return new SMIMEARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + certificateUsage = in.readU8(); + selector = in.readU8(); + matchingType = in.readU8(); + certificateAssociationData = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(certificateUsage); + out.writeU8(selector); + out.writeU8(matchingType); + out.writeByteArray(certificateAssociationData); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(certificateUsage); + sb.append(" "); + sb.append(selector); + sb.append(" "); + sb.append(matchingType); + sb.append(" "); + sb.append(base16.toString(certificateAssociationData)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + certificateUsage = st.getUInt8(); + selector = st.getUInt8(); + matchingType = st.getUInt8(); + certificateAssociationData = st.getHex(); + } + + /** + * Creates an SMIMEA Record from the given data + * + * @param certificateUsage The provided association that will be used to + * match the certificate presented in the S/MIME handshake. + * @param selector The part of the S/MIME certificate presented by the server + * that will be matched against the association data. + * @param matchingType How the certificate association is presented. + * @param certificateAssociationData The "certificate association data" to be + * matched. + */ + public + SMIMEARecord(Name name, int dclass, long ttl, int certificateUsage, int selector, int matchingType, byte[] certificateAssociationData) { + super(name, DnsRecordType.SMIMEA, dclass, ttl); + this.certificateUsage = checkU8("certificateUsage", certificateUsage); + this.selector = checkU8("selector", selector); + this.matchingType = checkU8("matchingType", matchingType); + this.certificateAssociationData = checkByteArrayLength("certificateAssociationData", certificateAssociationData, 0xFFFF); + } + + /** + * Returns the certificate usage of the SMIMEA record + */ + public + int getCertificateUsage() { + return certificateUsage; + } + + /** + * Returns the selector of the SMIMEA record + */ + public + int getSelector() { + return selector; + } + + /** + * Returns the matching type of the SMIMEA record + */ + public + int getMatchingType() { + return matchingType; + } + + /** + * Returns the certificate associate data of this SMIMEA record + */ + public final + byte[] getCertificateAssociationData() { + return certificateAssociationData; + } + +} diff --git a/src/dorkbox/network/dns/records/SOARecord.java b/src/dorkbox/network/dns/records/SOARecord.java new file mode 100644 index 00000000..0bff3b60 --- /dev/null +++ b/src/dorkbox/network/dns/records/SOARecord.java @@ -0,0 +1,186 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Start of Authority - describes properties of a zone. + * + * @author Brian Wellington + */ + +public +class SOARecord extends DnsRecord { + + private static final long serialVersionUID = 1049740098229303931L; + + private Name host, admin; + private long serial, refresh, retry, expire, minimum; + + SOARecord() {} + + @Override + DnsRecord getObject() { + return new SOARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + host = new Name(in); + admin = new Name(in); + serial = in.readU32(); + refresh = in.readU32(); + retry = in.readU32(); + expire = in.readU32(); + minimum = in.readU32(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + host.toWire(out, c, canonical); + admin.toWire(out, c, canonical); + out.writeU32(serial); + out.writeU32(refresh); + out.writeU32(retry); + out.writeU32(expire); + out.writeU32(minimum); + } + + /** + * Convert to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(host); + sb.append(" "); + sb.append(admin); + + if (Options.check("multiline")) { + sb.append(" (\n\t\t\t\t\t"); + sb.append(serial); + sb.append("\t; serial\n\t\t\t\t\t"); + sb.append(refresh); + sb.append("\t; refresh\n\t\t\t\t\t"); + sb.append(retry); + sb.append("\t; retry\n\t\t\t\t\t"); + sb.append(expire); + sb.append("\t; expire\n\t\t\t\t\t"); + sb.append(minimum); + sb.append(" )\t; minimum"); + } + else { + sb.append(" "); + sb.append(serial); + sb.append(" "); + sb.append(refresh); + sb.append(" "); + sb.append(retry); + sb.append(" "); + sb.append(expire); + sb.append(" "); + sb.append(minimum); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + host = st.getName(origin); + admin = st.getName(origin); + serial = st.getUInt32(); + refresh = st.getTTLLike(); + retry = st.getTTLLike(); + expire = st.getTTLLike(); + minimum = st.getTTLLike(); + } + + /** + * Creates an SOA Record from the given data + * + * @param host The primary name server for the zone + * @param admin The zone administrator's address + * @param serial The zone's serial number + * @param refresh The amount of time until a secondary checks for a new serial + * number + * @param retry The amount of time between a secondary's checks for a new + * serial number + * @param expire The amount of time until a secondary expires a zone + * @param minimum The minimum TTL for records in the zone + */ + public + SOARecord(Name name, int dclass, long ttl, Name host, Name admin, long serial, long refresh, long retry, long expire, long minimum) { + super(name, DnsRecordType.SOA, dclass, ttl); + this.host = checkName("host", host); + this.admin = checkName("admin", admin); + this.serial = checkU32("serial", serial); + this.refresh = checkU32("refresh", refresh); + this.retry = checkU32("retry", retry); + this.expire = checkU32("expire", expire); + this.minimum = checkU32("minimum", minimum); + } + + /** + * Returns the primary name server + */ + public + Name getHost() { + return host; + } + + /** + * Returns the zone administrator's address + */ + public + Name getAdmin() { + return admin; + } + + /** + * Returns the zone's serial number + */ + public + long getSerial() { + return serial; + } + + /** + * Returns the zone refresh interval + */ + public + long getRefresh() { + return refresh; + } + + /** + * Returns the zone retry interval + */ + public + long getRetry() { + return retry; + } + + /** + * Returns the time until a secondary expires a zone + */ + public + long getExpire() { + return expire; + } + + /** + * Returns the minimum TTL for records in the zone + */ + public + long getMinimum() { + return minimum; + } + +} diff --git a/src/dorkbox/network/dns/records/SPFRecord.java b/src/dorkbox/network/dns/records/SPFRecord.java new file mode 100644 index 00000000..b82cd664 --- /dev/null +++ b/src/dorkbox/network/dns/records/SPFRecord.java @@ -0,0 +1,52 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.util.List; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Sender Policy Framework (RFC 4408, experimental) + * + * @author Brian Wellington + */ + +public +class SPFRecord extends TXTBase { + + private static final long serialVersionUID = -2100754352801658722L; + + SPFRecord() {} + + @Override + DnsRecord getObject() { + return new SPFRecord(); + } + + /** + * Creates a SPF Record from the given data + * + * @param strings The text strings + * + * @throws IllegalArgumentException One of the strings has invalid escapes + */ + public + SPFRecord(Name name, int dclass, long ttl, List strings) { + super(name, DnsRecordType.SPF, dclass, ttl, strings); + } + + /** + * Creates a SPF Record from the given data + * + * @param string One text string + * + * @throws IllegalArgumentException The string has invalid escapes + */ + public + SPFRecord(Name name, int dclass, long ttl, String string) { + super(name, DnsRecordType.SPF, dclass, ttl, string); + } + +} diff --git a/src/dorkbox/network/dns/records/SRVRecord.java b/src/dorkbox/network/dns/records/SRVRecord.java new file mode 100644 index 00000000..e2eac878 --- /dev/null +++ b/src/dorkbox/network/dns/records/SRVRecord.java @@ -0,0 +1,130 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Server Selection Record - finds hosts running services in a domain. An + * SRV record will normally be named _<service>._<protocol>.domain + * - examples would be _sips._tcp.example.org (for the secure SIP protocol) and + * _http._tcp.example.com (if HTTP used SRV records) + * + * @author Brian Wellington + */ + +public +class SRVRecord extends DnsRecord { + + private static final long serialVersionUID = -3886460132387522052L; + + private int priority, weight, port; + private Name target; + + SRVRecord() {} + + @Override + DnsRecord getObject() { + return new SRVRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + priority = in.readU16(); + weight = in.readU16(); + port = in.readU16(); + target = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(priority); + out.writeU16(weight); + out.writeU16(port); + target.toWire(out, null, canonical); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(priority + " "); + sb.append(weight + " "); + sb.append(port + " "); + sb.append(target); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + priority = st.getUInt16(); + weight = st.getUInt16(); + port = st.getUInt16(); + target = st.getName(origin); + } + + @Override + public + Name getAdditionalName() { + return target; + } + + /** + * Creates an SRV Record from the given data + * + * @param priority The priority of this SRV. Records with lower priority + * are preferred. + * @param weight The weight, used to select between records at the same + * priority. + * @param port The TCP/UDP port that the service uses + * @param target The host running the service + */ + public + SRVRecord(Name name, int dclass, long ttl, int priority, int weight, int port, Name target) { + super(name, DnsRecordType.SRV, dclass, ttl); + this.priority = checkU16("priority", priority); + this.weight = checkU16("weight", weight); + this.port = checkU16("port", port); + this.target = checkName("target", target); + } + + /** + * Returns the priority + */ + public + int getPriority() { + return priority; + } + + /** + * Returns the weight + */ + public + int getWeight() { + return weight; + } + + /** + * Returns the port that the service runs on + */ + public + int getPort() { + return port; + } + + /** + * Returns the host running that the service + */ + public + Name getTarget() { + return target; + } + +} diff --git a/src/dorkbox/network/dns/records/SSHFPRecord.java b/src/dorkbox/network/dns/records/SSHFPRecord.java new file mode 100644 index 00000000..2986d1be --- /dev/null +++ b/src/dorkbox/network/dns/records/SSHFPRecord.java @@ -0,0 +1,123 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * SSH Fingerprint - stores the fingerprint of an SSH host key. + * + * @author Brian Wellington + */ + +public +class SSHFPRecord extends DnsRecord { + + private static final long serialVersionUID = -8104701402654687025L; + private int alg; + private int digestType; + private byte[] fingerprint; + + + public static + class Algorithm { + public static final int RSA = 1; + public static final int DSS = 2; + private + Algorithm() {} + } + + + public static + class Digest { + public static final int SHA1 = 1; + + private + Digest() {} + } + + SSHFPRecord() {} + + @Override + DnsRecord getObject() { + return new SSHFPRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + alg = in.readU8(); + digestType = in.readU8(); + fingerprint = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(alg); + out.writeU8(digestType); + out.writeByteArray(fingerprint); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(alg); + sb.append(" "); + sb.append(digestType); + sb.append(" "); + sb.append(base16.toString(fingerprint)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + alg = st.getUInt8(); + digestType = st.getUInt8(); + fingerprint = st.getHex(true); + } + + /** + * Creates an SSHFP Record from the given data. + * + * @param alg The public key's algorithm. + * @param digestType The public key's digest type. + * @param fingerprint The public key's fingerprint. + */ + public + SSHFPRecord(Name name, int dclass, long ttl, int alg, int digestType, byte[] fingerprint) { + super(name, DnsRecordType.SSHFP, dclass, ttl); + this.alg = checkU8("alg", alg); + this.digestType = checkU8("digestType", digestType); + this.fingerprint = fingerprint; + } + + /** + * Returns the public key's algorithm. + */ + public + int getAlgorithm() { + return alg; + } + + /** + * Returns the public key's digest type. + */ + public + int getDigestType() { + return digestType; + } + + /** + * Returns the fingerprint + */ + public + byte[] getFingerPrint() { + return fingerprint; + } + +} diff --git a/src/dorkbox/network/dns/records/SingleCompressedNameBase.java b/src/dorkbox/network/dns/records/SingleCompressedNameBase.java new file mode 100644 index 00000000..8eef22d3 --- /dev/null +++ b/src/dorkbox/network/dns/records/SingleCompressedNameBase.java @@ -0,0 +1,34 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; + +/** + * Implements common functionality for the many record types whose format + * is a single compressed name. + * + * @author Brian Wellington + */ + +abstract +class SingleCompressedNameBase extends SingleNameBase { + + private static final long serialVersionUID = -236435396815460677L; + + protected + SingleCompressedNameBase() {} + + protected + SingleCompressedNameBase(Name name, int type, int dclass, long ttl, Name singleName, String description) { + super(name, type, dclass, ttl, singleName, description); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + singleName.toWire(out, c, canonical); + } + +} diff --git a/src/dorkbox/network/dns/records/SingleNameBase.java b/src/dorkbox/network/dns/records/SingleNameBase.java new file mode 100644 index 00000000..9ddaf47d --- /dev/null +++ b/src/dorkbox/network/dns/records/SingleNameBase.java @@ -0,0 +1,66 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Implements common functionality for the many record types whose format + * is a single name. + * + * @author Brian Wellington + */ + +abstract +class SingleNameBase extends DnsRecord { + + private static final long serialVersionUID = -18595042501413L; + + protected Name singleName; + + protected + SingleNameBase() {} + + protected + SingleNameBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); + } + + protected + SingleNameBase(Name name, int type, int dclass, long ttl, Name singleName, String description) { + super(name, type, dclass, ttl); + this.singleName = checkName(description, singleName); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + singleName = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + singleName.toWire(out, null, canonical); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(singleName.toString()); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + singleName = st.getName(origin); + } + + protected + Name getSingleName() { + return singleName; + } + +} diff --git a/src/dorkbox/network/dns/records/TKEYRecord.java b/src/dorkbox/network/dns/records/TKEYRecord.java new file mode 100644 index 00000000..bd8910bf --- /dev/null +++ b/src/dorkbox/network/dns/records/TKEYRecord.java @@ -0,0 +1,285 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.Date; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsResponseCode; +import dorkbox.network.dns.utils.FormattedTime; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * Transaction Key - used to compute and/or securely transport a shared + * secret to be used with TSIG. + * + * @author Brian Wellington + * @see TSIG + */ + +public +class TKEYRecord extends DnsRecord { + + private static final long serialVersionUID = 8828458121926391756L; + + private Name alg; + private Date timeInception; + private Date timeExpire; + private int mode, error; + private byte[] key; + private byte[] other; + + /** + * The key is assigned by the server (unimplemented) + */ + public static final int SERVERASSIGNED = 1; + + /** + * The key is computed using a Diffie-Hellman key exchange + */ + public static final int DIFFIEHELLMAN = 2; + + /** + * The key is computed using GSS_API (unimplemented) + */ + public static final int GSSAPI = 3; + + /** + * The key is assigned by the resolver (unimplemented) + */ + public static final int RESOLVERASSIGNED = 4; + + /** + * The key should be deleted + */ + public static final int DELETE = 5; + + TKEYRecord() {} + + @Override + DnsRecord getObject() { + return new TKEYRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + alg = new Name(in); + timeInception = new Date(1000 * in.readU32()); + timeExpire = new Date(1000 * in.readU32()); + mode = in.readU16(); + error = in.readU16(); + + int keylen = in.readU16(); + if (keylen > 0) { + key = in.readByteArray(keylen); + } + else { + key = null; + } + + int otherlen = in.readU16(); + if (otherlen > 0) { + other = in.readByteArray(otherlen); + } + else { + other = null; + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + alg.toWire(out, null, canonical); + + out.writeU32(timeInception.getTime() / 1000); + out.writeU32(timeExpire.getTime() / 1000); + + out.writeU16(mode); + out.writeU16(error); + + if (key != null) { + out.writeU16(key.length); + out.writeByteArray(key); + } + else { + out.writeU16(0); + } + + if (other != null) { + out.writeU16(other.length); + out.writeByteArray(other); + } + else { + out.writeU16(0); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(alg); + sb.append(" "); + if (Options.check("multiline")) { + sb.append("(") + .append(OS.LINE_SEPARATOR) + .append("\t"); + } + sb.append(FormattedTime.format(timeInception)); + sb.append(" "); + sb.append(FormattedTime.format(timeExpire)); + sb.append(" "); + sb.append(modeString()); + sb.append(" "); + sb.append(DnsResponseCode.TSIGstring(error)); + if (Options.check("multiline")) { + sb.append(OS.LINE_SEPARATOR); + if (key != null) { + sb.append(Base64Fast.formatString(Base64Fast.encode2(key), 64, "\t", true)); + sb.append(OS.LINE_SEPARATOR); + } + if (other != null) { + sb.append(Base64Fast.formatString(Base64Fast.encode2(other), 64, "\t", true)); + } + sb.append(" )"); + } + else { + sb.append(" "); + if (key != null) { + sb.append("\t"); + sb.append(Base64Fast.encode2(key)); + + sb.append(" "); + } + if (other != null) { + sb.append("\t"); + sb.append(Base64Fast.encode2(other)); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for TKEY"); + } + + protected + String modeString() { + switch (mode) { + case SERVERASSIGNED: + return "SERVERASSIGNED"; + case DIFFIEHELLMAN: + return "DIFFIEHELLMAN"; + case GSSAPI: + return "GSSAPI"; + case RESOLVERASSIGNED: + return "RESOLVERASSIGNED"; + case DELETE: + return "DELETE"; + default: + return Integer.toString(mode); + } + } + + /** + * Creates a TKEY Record from the given data. + * + * @param alg The shared key's algorithm + * @param timeInception The beginning of the validity period of the shared + * secret or keying material + * @param timeExpire The end of the validity period of the shared + * secret or keying material + * @param mode The mode of key agreement + * @param error The extended error field. Should be 0 in queries + * @param key The shared secret + * @param other The other data field. Currently unused + * responses. + */ + public + TKEYRecord(Name name, + int dclass, + long ttl, + Name alg, + Date timeInception, + Date timeExpire, + int mode, + int error, + byte[] key, + byte other[]) { + super(name, DnsRecordType.TKEY, dclass, ttl); + this.alg = checkName("alg", alg); + this.timeInception = timeInception; + this.timeExpire = timeExpire; + this.mode = checkU16("mode", mode); + this.error = checkU16("error", error); + this.key = key; + this.other = other; + } + + /** + * Returns the shared key's algorithm + */ + public + Name getAlgorithm() { + return alg; + } + + /** + * Returns the beginning of the validity period of the shared secret or + * keying material + */ + public + Date getTimeInception() { + return timeInception; + } + + /** + * Returns the end of the validity period of the shared secret or + * keying material + */ + public + Date getTimeExpire() { + return timeExpire; + } + + /** + * Returns the key agreement mode + */ + public + int getMode() { + return mode; + } + + /** + * Returns the extended error + */ + public + int getError() { + return error; + } + + /** + * Returns the shared secret or keying material + */ + public + byte[] getKey() { + return key; + } + + /** + * Returns the other data + */ + public + byte[] getOther() { + return other; + } + +} diff --git a/src/dorkbox/network/dns/records/TLSARecord.java b/src/dorkbox/network/dns/records/TLSARecord.java new file mode 100644 index 00000000..3ec1b089 --- /dev/null +++ b/src/dorkbox/network/dns/records/TLSARecord.java @@ -0,0 +1,176 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.network.dns.utils.base16; + +/** + * Transport Layer Security Authentication + * + * @author Brian Wellington + */ + +public +class TLSARecord extends DnsRecord { + + private static final long serialVersionUID = 356494267028580169L; + private int certificateUsage; + private int selector; + private int matchingType; + private byte[] certificateAssociationData; + + + public static + class CertificateUsage { + public static final int CA_CONSTRAINT = 0; + public static final int SERVICE_CERTIFICATE_CONSTRAINT = 1; + public static final int TRUST_ANCHOR_ASSERTION = 2; + public static final int DOMAIN_ISSUED_CERTIFICATE = 3; + private + CertificateUsage() {} + } + + + public static + class Selector { + /** + * Full certificate; the Certificate binary structure defined in + * [RFC5280] + */ + public static final int FULL_CERTIFICATE = 0; + /** + * SubjectPublicKeyInfo; DER-encoded binary structure defined in + * [RFC5280] + */ + public static final int SUBJECT_PUBLIC_KEY_INFO = 1; + + private + Selector() {} + } + + + public static + class MatchingType { + /** + * Exact match on selected content + */ + public static final int EXACT = 0; + /** + * SHA-256 hash of selected content [RFC6234] + */ + public static final int SHA256 = 1; + /** + * SHA-512 hash of selected content [RFC6234] + */ + public static final int SHA512 = 2; + + private + MatchingType() {} + } + + TLSARecord() {} + + @Override + DnsRecord getObject() { + return new TLSARecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + certificateUsage = in.readU8(); + selector = in.readU8(); + matchingType = in.readU8(); + certificateAssociationData = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU8(certificateUsage); + out.writeU8(selector); + out.writeU8(matchingType); + out.writeByteArray(certificateAssociationData); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(certificateUsage); + sb.append(" "); + sb.append(selector); + sb.append(" "); + sb.append(matchingType); + sb.append(" "); + sb.append(base16.toString(certificateAssociationData)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + certificateUsage = st.getUInt8(); + selector = st.getUInt8(); + matchingType = st.getUInt8(); + certificateAssociationData = st.getHex(); + } + + /** + * Creates an TLSA Record from the given data + * + * @param certificateUsage The provided association that will be used to + * match the certificate presented in the TLS handshake. + * @param selector The part of the TLS certificate presented by the server + * that will be matched against the association data. + * @param matchingType How the certificate association is presented. + * @param certificateAssociationData The "certificate association data" to be + * matched. + */ + public + TLSARecord(Name name, int dclass, long ttl, int certificateUsage, int selector, int matchingType, byte[] certificateAssociationData) { + super(name, DnsRecordType.TLSA, dclass, ttl); + this.certificateUsage = checkU8("certificateUsage", certificateUsage); + this.selector = checkU8("selector", selector); + this.matchingType = checkU8("matchingType", matchingType); + this.certificateAssociationData = checkByteArrayLength("certificateAssociationData", certificateAssociationData, 0xFFFF); + } + + /** + * Returns the certificate usage of the TLSA record + */ + public + int getCertificateUsage() { + return certificateUsage; + } + + /** + * Returns the selector of the TLSA record + */ + public + int getSelector() { + return selector; + } + + /** + * Returns the matching type of the TLSA record + */ + public + int getMatchingType() { + return matchingType; + } + + /** + * Returns the certificate associate data of this TLSA record + */ + public final + byte[] getCertificateAssociationData() { + return certificateAssociationData; + } + +} diff --git a/src/dorkbox/network/dns/records/TSIG.java b/src/dorkbox/network/dns/records/TSIG.java new file mode 100644 index 00000000..1b0994b8 --- /dev/null +++ b/src/dorkbox/network/dns/records/TSIG.java @@ -0,0 +1,780 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsResponseCode; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Options; +import dorkbox.util.Base64Fast; + +/** + * Transaction signature handling. This class generates and verifies + * TSIG records on messages, which provide transaction security. + * + * @author Brian Wellington + * @see TSIGRecord + */ + +public +class TSIG { + + /** + * The domain name representing the HMAC-MD5 algorithm. + */ + public static final Name HMAC_MD5 = Name.fromConstantString("HMAC-MD5.SIG-ALG.REG.INT."); + + /** + * The domain name representing the HMAC-MD5 algorithm (deprecated). + */ + public static final Name HMAC = HMAC_MD5; + + /** + * The domain name representing the HMAC-SHA1 algorithm. + */ + public static final Name HMAC_SHA1 = Name.fromConstantString("hmac-sha1."); + + /** + * The domain name representing the HMAC-SHA224 algorithm. + * Note that SHA224 is not supported by Java out-of-the-box, this requires use + * of a third party provider like BouncyCastle.org. + */ + public static final Name HMAC_SHA224 = Name.fromConstantString("hmac-sha224."); + + /** + * The domain name representing the HMAC-SHA256 algorithm. + */ + public static final Name HMAC_SHA256 = Name.fromConstantString("hmac-sha256."); + + /** + * The domain name representing the HMAC-SHA384 algorithm. + */ + public static final Name HMAC_SHA384 = Name.fromConstantString("hmac-sha384."); + + /** + * The domain name representing the HMAC-SHA512 algorithm. + */ + public static final Name HMAC_SHA512 = Name.fromConstantString("hmac-sha512."); + + private static Map algMap; + /** + * The default fudge value for outgoing packets. Can be overriden by the + * tsigfudge option. + */ + public static final short FUDGE = 300; + private Name name, alg; + private Mac hmac; + + static { + Map out = new HashMap(); + out.put(HMAC_MD5, "HmacMD5"); + out.put(HMAC_SHA1, "HmacSHA1"); + out.put(HMAC_SHA224, "HmacSHA224"); + out.put(HMAC_SHA256, "HmacSHA256"); + out.put(HMAC_SHA384, "HmacSHA384"); + out.put(HMAC_SHA512, "HmacSHA512"); + algMap = Collections.unmodifiableMap(out); + } + + /** + * Verifies the data (computes the secure hash and compares it to the input) + * + * @param mac The HMAC generator + * @param signature The signature to compare against + * + * @return true if the signature matches, false otherwise + */ + private static + boolean verify(Mac mac, byte[] signature) { + return verify(mac, signature, false); + } + + /** + * Verifies the data (computes the secure hash and compares it to the input) + * + * @param mac The HMAC generator + * @param signature The signature to compare against + * @param truncation_ok If true, the signature may be truncated; only the + * number of bytes in the provided signature are compared. + * + * @return true if the signature matches, false otherwise + */ + private static + boolean verify(Mac mac, byte[] signature, boolean truncation_ok) { + byte[] expected = mac.doFinal(); + if (truncation_ok && signature.length < expected.length) { + byte[] truncated = new byte[signature.length]; + System.arraycopy(expected, 0, truncated, 0, truncated.length); + expected = truncated; + } + return Arrays.equals(signature, expected); + } + + /** + * Creates a new TSIG key, which can be used to sign or verify a message. + * + * @param algorithm The algorithm of the shared key. + * @param name The name of the shared key. + * @param key The shared key. + */ + public + TSIG(Name algorithm, Name name, SecretKey key) { + this.name = name; + this.alg = algorithm; + String macAlgorithm = nameToAlgorithm(algorithm); + init_hmac(macAlgorithm, key); + } + + public static + String nameToAlgorithm(Name name) { + String alg = (String) algMap.get(name); + if (alg != null) { + return alg; + } + throw new IllegalArgumentException("Unknown algorithm"); + } + + private + void init_hmac(String macAlgorithm, SecretKey key) { + try { + hmac = Mac.getInstance(macAlgorithm); + hmac.init(key); + } catch (GeneralSecurityException ex) { + throw new IllegalArgumentException("Caught security " + "exception setting up " + "HMAC."); + } + } + + /** + * Creates a new TSIG key from a pre-initialized Mac instance. + * This assumes that init() has already been called on the mac + * to set up the key. + * + * @param mac The JCE HMAC object + * @param name The name of the key + */ + public + TSIG(Mac mac, Name name) { + this.name = name; + this.hmac = mac; + this.alg = algorithmToName(mac.getAlgorithm()); + } + + public static + Name algorithmToName(String alg) { + Iterator it = algMap.entrySet() + .iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + if (alg.equalsIgnoreCase((String) entry.getValue())) { + return (Name) entry.getKey(); + } + } + throw new IllegalArgumentException("Unknown algorithm"); + } + + /** + * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to + * sign or verify a message. + * + * @param name The name of the shared key. + * @param key The shared key's data. + */ + public + TSIG(Name name, byte[] key) { + this(HMAC_MD5, name, key); + } + + /** + * Creates a new TSIG key, which can be used to sign or verify a message. + * + * @param algorithm The algorithm of the shared key. + * @param name The name of the shared key. + * @param keyBytes The shared key's data. + */ + public + TSIG(Name algorithm, Name name, byte[] keyBytes) { + this.name = name; + this.alg = algorithm; + String macAlgorithm = nameToAlgorithm(algorithm); + SecretKey key = new SecretKeySpec(keyBytes, macAlgorithm); + init_hmac(macAlgorithm, key); + } + + /** + * Creates a new TSIG object, which can be used to sign or verify a message. + * + * @param name The name of the shared key. + * @param algorithm The algorithm of the shared key. The legal values are + * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and + * "hmac-sha512". + * @param key The shared key's data represented as a base64 encoded string. + * + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ + public + TSIG(String algorithm, String name, String key) { + this(algorithmToName(algorithm), name, key); + } + + /** + * Creates a new TSIG object, which can be used to sign or verify a message. + * + * @param name The name of the shared key. + * @param key The shared key's data represented as a base64 encoded string. + * + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ + public + TSIG(Name algorithm, String name, String key) { + byte[] keyBytes; + try { + keyBytes = Base64Fast.decode2(key); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid TSIG key string"); + } + + if (keyBytes == null) { + throw new IllegalArgumentException("Invalid TSIG key string"); + } + + try { + this.name = Name.fromString(name, Name.root); + } catch (TextParseException e) { + throw new IllegalArgumentException("Invalid TSIG key name"); + } + this.alg = algorithm; + String macAlgorithm = nameToAlgorithm(this.alg); + init_hmac(macAlgorithm, new SecretKeySpec(keyBytes, macAlgorithm)); + } + + /** + * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to + * sign or verify a message. + * + * @param name The name of the shared key + * @param key The shared key's data, represented as a base64 encoded string. + * + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ + public + TSIG(String name, String key) { + this(HMAC_MD5, name, key); + } + + /** + * Creates a new TSIG object, which can be used to sign or verify a message. + * + * @param str The TSIG key, in the form name:secret, name/secret, + * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must + * be "hmac-md5", "hmac-sha1", or "hmac-sha256". + * + * @throws IllegalArgumentException The string does not contain both a name + * and secret. + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ + static public + TSIG fromString(String str) { + String[] parts = str.split("[:/]", 3); + if (parts.length < 2) { + throw new IllegalArgumentException("Invalid TSIG key " + "specification"); + } + if (parts.length == 3) { + try { + return new TSIG(parts[0], parts[1], parts[2]); + } catch (IllegalArgumentException e) { + parts = str.split("[:/]", 2); + } + } + return new TSIG(HMAC_MD5, parts[0], parts[1]); + } + + /** + * Generates a TSIG record for a message and adds it to the message + * + * @param m The message + * @param old If this message is a response, the TSIG from the request + */ + public + void applyStream(DnsMessage m, TSIGRecord old, boolean first) { + if (first) { + apply(m, old); + return; + } + Date timeSigned = new Date(); + int fudge; + hmac.reset(); + + fudge = Options.intValue("tsigfudge"); + if (fudge < 0 || fudge > 0x7FFF) { + fudge = FUDGE; + } + + DnsOutput out = new DnsOutput(); + out.writeU16(old.getSignature().length); + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + + /* Digest the message */ + hmac.update(m.toWire()); + + out = new DnsOutput(); + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + hmac.update(out.toByteArray()); + + byte[] signature = hmac.doFinal(); + byte[] other = null; + + DnsRecord r = new TSIGRecord(name, + DnsClass.ANY, + 0, + alg, + timeSigned, + fudge, + signature, + m.getHeader() + .getID(), + DnsResponseCode.NOERROR, + other); + m.addRecord(r, DnsSection.ADDITIONAL); + m.tsigState = DnsMessage.TSIG_SIGNED; + } + + /** + * Generates a TSIG record for a message and adds it to the message + * + * @param m The message + * @param old If this message is a response, the TSIG from the request + */ + public + void apply(DnsMessage m, TSIGRecord old) { + apply(m, DnsResponseCode.NOERROR, old); + } + + /** + * Generates a TSIG record with a specific error for a message and adds it + * to the message. + * + * @param m The message + * @param error The error + * @param old If this message is a response, the TSIG from the request + */ + public + void apply(DnsMessage m, int error, TSIGRecord old) { + DnsRecord r = generate(m, m.toWire(), error, old); + m.addRecord(r, DnsSection.ADDITIONAL); + m.tsigState = DnsMessage.TSIG_SIGNED; + } + + /** + * Generates a TSIG record with a specific error for a message that has + * been rendered. + * + * @param m The message + * @param b The rendered message + * @param error The error + * @param old If this message is a response, the TSIG from the request + * + * @return The TSIG record to be added to the message + */ + public + TSIGRecord generate(DnsMessage m, byte[] b, int error, TSIGRecord old) { + Date timeSigned; + if (error != DnsResponseCode.BADTIME) { + timeSigned = new Date(); + } + else { + timeSigned = old.getTimeSigned(); + } + int fudge; + boolean signing = false; + if (error == DnsResponseCode.NOERROR || error == DnsResponseCode.BADTIME) { + signing = true; + hmac.reset(); + } + + fudge = Options.intValue("tsigfudge"); + if (fudge < 0 || fudge > 0x7FFF) { + fudge = FUDGE; + } + + if (old != null) { + DnsOutput out = new DnsOutput(); + out.writeU16(old.getSignature().length); + if (signing) { + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + } + } + + /* Digest the message */ + if (signing) { + hmac.update(b); + } + + DnsOutput out = new DnsOutput(); + name.toWireCanonical(out); + out.writeU16(DnsClass.ANY); /* class */ + out.writeU32(0); /* ttl */ + alg.toWireCanonical(out); + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + out.writeU16(error); + out.writeU16(0); /* No other data */ + + if (signing) { + hmac.update(out.toByteArray()); + } + + byte[] signature; + if (signing) { + signature = hmac.doFinal(); + } + else { + signature = new byte[0]; + } + + byte[] other = null; + if (error == DnsResponseCode.BADTIME) { + out = new DnsOutput(); + time = new Date().getTime() / 1000; + timeHigh = (int) (time >> 32); + timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + other = out.toByteArray(); + } + + return (new TSIGRecord(name, + DnsClass.ANY, + 0, + alg, + timeSigned, + fudge, + signature, + m.getHeader() + .getID(), + error, + other)); + } + + /** + * Verifies a TSIG record on an incoming message. Since this is only called + * in the context where a TSIG is expected to be present, it is an error + * if one is not present. After calling this routine, DnsMessage.isVerified() may + * be called on this message. + * + * @param m The message + * @param b The message in unparsed form. This is necessary since TSIG + * signs the message in wire format, and we can't recreate the exact wire + * format (with the same name compression). + * @param old If this message is a response, the TSIG from the request + * + * @return The result of the verification (as an DnsResponseCode) + * + * @see DnsResponseCode + */ + public + int verify(DnsMessage m, byte[] b, TSIGRecord old) { + return verify(m, b, b.length, old); + } + + /** + * Verifies a TSIG record on an incoming message. Since this is only called + * in the context where a TSIG is expected to be present, it is an error + * if one is not present. After calling this routine, DnsMessage.isVerified() may + * be called on this message. + * + * @param m The message + * @param b An array containing the message in unparsed form. This is + * necessary since TSIG signs the message in wire format, and we can't + * recreate the exact wire format (with the same name compression). + * @param length The length of the message in the array. + * @param old If this message is a response, the TSIG from the request + * + * @return The result of the verification (as an DnsResponseCode) + * + * @see DnsResponseCode + */ + public + byte verify(DnsMessage m, byte[] b, int length, TSIGRecord old) { + m.tsigState = DnsMessage.TSIG_FAILED; + TSIGRecord tsig = m.getTSIG(); + hmac.reset(); + if (tsig == null) { + return DnsResponseCode.FORMERR; + } + + if (!tsig.getName() + .equals(name) || !tsig.getAlgorithm() + .equals(alg)) { + if (Options.check("verbose")) { + System.err.println("BADKEY failure"); + } + return DnsResponseCode.BADKEY; + } + long now = System.currentTimeMillis(); + long then = tsig.getTimeSigned() + .getTime(); + long fudge = tsig.getFudge(); + if (Math.abs(now - then) > fudge * 1000) { + if (Options.check("verbose")) { + System.err.println("BADTIME failure"); + } + return DnsResponseCode.BADTIME; + } + + if (old != null && tsig.getError() != DnsResponseCode.BADKEY && tsig.getError() != DnsResponseCode.BADSIG) { + DnsOutput out = new DnsOutput(); + out.writeU16(old.getSignature().length); + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + } + m.getHeader() + .decCount(DnsSection.ADDITIONAL); + byte[] header = m.getHeader() + .toWire(); + m.getHeader() + .incCount(DnsSection.ADDITIONAL); + hmac.update(header); + + int len = m.tsigstart - header.length; + hmac.update(b, header.length, len); + + DnsOutput out = new DnsOutput(); + tsig.getName() + .toWireCanonical(out); + out.writeU16(tsig.dclass); + out.writeU32(tsig.ttl); + tsig.getAlgorithm() + .toWireCanonical(out); + long time = tsig.getTimeSigned() + .getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(tsig.getFudge()); + out.writeU16(tsig.getError()); + if (tsig.getOther() != null) { + out.writeU16(tsig.getOther().length); + out.writeByteArray(tsig.getOther()); + } + else { + out.writeU16(0); + } + + hmac.update(out.toByteArray()); + + byte[] signature = tsig.getSignature(); + int digestLength = hmac.getMacLength(); + int minDigestLength; + if (hmac.getAlgorithm() + .toLowerCase() + .contains("md5")) { + minDigestLength = 10; + } + else { + minDigestLength = digestLength / 2; + } + + if (signature.length > digestLength) { + if (Options.check("verbose")) { + System.err.println("BADSIG: signature too long"); + } + return DnsResponseCode.BADSIG; + } + else if (signature.length < minDigestLength) { + if (Options.check("verbose")) { + System.err.println("BADSIG: signature too short"); + } + return DnsResponseCode.BADSIG; + } + else if (!verify(hmac, signature, true)) { + if (Options.check("verbose")) { + System.err.println("BADSIG: signature verification"); + } + return DnsResponseCode.BADSIG; + } + + m.tsigState = DnsMessage.TSIG_VERIFIED; + return DnsResponseCode.NOERROR; + } + + /** + * Returns the maximum length of a TSIG record generated by this key. + * + * @see TSIGRecord + */ + public + int recordLength() { + return (name.length() + 10 + alg.length() + 8 + // time signed, fudge + 18 + // 2 byte MAC length, 16 byte MAC + 4 + // original id, error + 8); // 2 byte error length, 6 byte max error field. + } + + public static + class StreamVerifier { + /** + * A helper class for verifying multiple message responses. + */ + + private TSIG key; + private Mac verifier; + private int nresponses; + private int lastsigned; + private TSIGRecord lastTSIG; + + /** + * Creates an object to verify a multiple message response + */ + public + StreamVerifier(TSIG tsig, TSIGRecord old) { + key = tsig; + verifier = tsig.hmac; + nresponses = 0; + lastTSIG = old; + } + + /** + * Verifies a TSIG record on an incoming message that is part of a + * multiple message response. + * TSIG records must be present on the first and last messages, and + * at least every 100 records in between. + * After calling this routine, DnsMessage.isVerified() may be called on + * this message. + * + * @param m The message + * @param b The message in unparsed form + * + * @return The result of the verification (as an DnsResponseCode) + * + * @see DnsResponseCode + */ + public + int verify(DnsMessage m, byte[] b) { + TSIGRecord tsig = m.getTSIG(); + + nresponses++; + + if (nresponses == 1) { + int result = key.verify(m, b, lastTSIG); + if (result == DnsResponseCode.NOERROR) { + byte[] signature = tsig.getSignature(); + DnsOutput out = new DnsOutput(); + out.writeU16(signature.length); + verifier.update(out.toByteArray()); + verifier.update(signature); + } + lastTSIG = tsig; + return result; + } + + if (tsig != null) { + m.getHeader() + .decCount(DnsSection.ADDITIONAL); + } + byte[] header = m.getHeader() + .toWire(); + if (tsig != null) { + m.getHeader() + .incCount(DnsSection.ADDITIONAL); + } + verifier.update(header); + + int len; + if (tsig == null) { + len = b.length - header.length; + } + else { + len = m.tsigstart - header.length; + } + verifier.update(b, header.length, len); + + if (tsig != null) { + lastsigned = nresponses; + lastTSIG = tsig; + } + else { + boolean required = (nresponses - lastsigned >= 100); + if (required) { + m.tsigState = DnsMessage.TSIG_FAILED; + return DnsResponseCode.FORMERR; + } + else { + m.tsigState = DnsMessage.TSIG_INTERMEDIATE; + return DnsResponseCode.NOERROR; + } + } + + if (!tsig.getName() + .equals(key.name) || !tsig.getAlgorithm() + .equals(key.alg)) { + if (Options.check("verbose")) { + System.err.println("BADKEY failure"); + } + m.tsigState = DnsMessage.TSIG_FAILED; + return DnsResponseCode.BADKEY; + } + + DnsOutput out = new DnsOutput(); + long time = tsig.getTimeSigned() + .getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(tsig.getFudge()); + verifier.update(out.toByteArray()); + + if (TSIG.verify(verifier, tsig.getSignature()) == false) { + if (Options.check("verbose")) { + System.err.println("BADSIG failure"); + } + m.tsigState = DnsMessage.TSIG_FAILED; + return DnsResponseCode.BADSIG; + } + + verifier.reset(); + out = new DnsOutput(); + out.writeU16(tsig.getSignature().length); + verifier.update(out.toByteArray()); + verifier.update(tsig.getSignature()); + + m.tsigState = DnsMessage.TSIG_VERIFIED; + return DnsResponseCode.NOERROR; + } + } + +} diff --git a/src/dorkbox/network/dns/records/TSIGRecord.java b/src/dorkbox/network/dns/records/TSIGRecord.java new file mode 100644 index 00000000..ea5f02d0 --- /dev/null +++ b/src/dorkbox/network/dns/records/TSIGRecord.java @@ -0,0 +1,264 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.Date; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsResponseCode; +import dorkbox.network.dns.utils.Options; +import dorkbox.network.dns.utils.Tokenizer; +import dorkbox.util.Base64Fast; +import dorkbox.util.OS; + +/** + * Transaction Signature - this record is automatically generated by the + * resolver. TSIG records provide transaction security between the + * sender and receiver of a message, using a shared key. + * + * @author Brian Wellington + * @see TSIGm + */ + +public +class TSIGRecord extends DnsRecord { + + private static final long serialVersionUID = -88820909016649306L; + + private Name alg; + private Date timeSigned; + private int fudge; + private byte[] signature; + private int originalID; + private int error; + private byte[] other; + + TSIGRecord() {} + + @Override + DnsRecord getObject() { + return new TSIGRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + alg = new Name(in); + + long timeHigh = in.readU16(); + long timeLow = in.readU32(); + long time = (timeHigh << 32) + timeLow; + timeSigned = new Date(time * 1000); + fudge = in.readU16(); + + int sigLen = in.readU16(); + signature = in.readByteArray(sigLen); + + originalID = in.readU16(); + error = in.readU16(); + + int otherLen = in.readU16(); + if (otherLen > 0) { + other = in.readByteArray(otherLen); + } + else { + other = null; + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + alg.toWire(out, null, canonical); + + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + out.writeU16(signature.length); + out.writeByteArray(signature); + + out.writeU16(originalID); + out.writeU16(error); + + if (other != null) { + out.writeU16(other.length); + out.writeByteArray(other); + } + else { + out.writeU16(0); + } + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(alg); + sb.append(" "); + if (Options.check("multiline")) { + sb.append("(") + .append(OS.LINE_SEPARATOR) + .append("\t"); + } + + sb.append(timeSigned.getTime() / 1000); + sb.append(" "); + sb.append(fudge); + sb.append(" "); + sb.append(signature.length); + if (Options.check("multiline")) { + sb.append(OS.LINE_SEPARATOR); + sb.append(Base64Fast.formatString(Base64Fast.encode2(signature), 64, "\t", true)); + } + else { + sb.append(" "); + sb.append(Base64Fast.encode2(signature)); + } + sb.append(" "); + sb.append(DnsResponseCode.TSIGstring(error)); + sb.append(" "); + if (other == null) { + sb.append(0); + } + else { + sb.append(other.length); + if (Options.check("multiline")) { + sb.append(OS.LINE_SEPARATOR) + .append(OS.LINE_SEPARATOR) + .append(OS.LINE_SEPARATOR) + .append("\t"); + } + else { + sb.append(" "); + } + if (error == DnsResponseCode.BADTIME) { + if (other.length != 6) { + sb.append(""); + } + else { + long time = ((long) (other[0] & 0xFF) << 40) + ((long) (other[1] & 0xFF) << 32) + ((other[2] & 0xFF) << 24) + + ((other[3] & 0xFF) << 16) + ((other[4] & 0xFF) << 8) + ((other[5] & 0xFF)); + sb.append(""); + } + } + else { + sb.append("<"); + sb.append(Base64Fast.encode2(other)); + sb.append(">"); + } + } + if (Options.check("multiline")) { + sb.append(" )"); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for TSIG"); + } + + /** + * Creates a TSIG Record from the given data. This is normally called by + * the TSIG class + * + * @param alg The shared key's algorithm + * @param timeSigned The time that this record was generated + * @param fudge The fudge factor for time - if the time that the message is + * received is not in the range [now - fudge, now + fudge], the signature + * fails + * @param signature The signature + * @param originalID The message ID at the time of its generation + * @param error The extended error field. Should be 0 in queries. + * @param other The other data field. Currently used only in BADTIME + * responses. + * + * @see TSIG + */ + public + TSIGRecord(Name name, + int dclass, + long ttl, + Name alg, + Date timeSigned, + int fudge, + byte[] signature, + int originalID, + int error, + byte other[]) { + super(name, DnsRecordType.TSIG, dclass, ttl); + this.alg = checkName("alg", alg); + this.timeSigned = timeSigned; + this.fudge = checkU16("fudge", fudge); + this.signature = signature; + this.originalID = checkU16("originalID", originalID); + this.error = checkU16("error", error); + this.other = other; + } + + /** + * Returns the shared key's algorithm + */ + public + Name getAlgorithm() { + return alg; + } + + /** + * Returns the time that this record was generated + */ + public + Date getTimeSigned() { + return timeSigned; + } + + /** + * Returns the time fudge factor + */ + public + int getFudge() { + return fudge; + } + + /** + * Returns the signature + */ + public + byte[] getSignature() { + return signature; + } + + /** + * Returns the original message ID + */ + public + int getOriginalID() { + return originalID; + } + + /** + * Returns the extended error + */ + public + int getError() { + return error; + } + + /** + * Returns the other data + */ + public + byte[] getOther() { + return other; + } + +} diff --git a/src/dorkbox/network/dns/records/TTL.java b/src/dorkbox/network/dns/records/TTL.java new file mode 100644 index 00000000..1deb2c2b --- /dev/null +++ b/src/dorkbox/network/dns/records/TTL.java @@ -0,0 +1,146 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import dorkbox.network.dns.exceptions.InvalidTTLException; + +/** + * Routines for parsing BIND-style TTL values. These values consist of + * numbers followed by 1 letter units of time (W - week, D - day, H - hour, + * M - minute, S - second). + * + * @author Brian Wellington + */ + +public final +class TTL { + + public static final long MAX_VALUE = 0x7FFFFFFFL; + + private + TTL() {} + + /** + * Parses a TTL, which can either be expressed as a number or a BIND-style + * string with numbers and units. + * + * @param s The string representing the TTL + * + * @return The TTL as a number of seconds + * + * @throws NumberFormatException The string was not in a valid TTL format. + */ + public static + long parseTTL(String s) { + return parse(s, true); + } + + /** + * Parses a TTL-like value, which can either be expressed as a number or a + * BIND-style string with numbers and units. + * + * @param s The string representing the numeric value. + * @param clamp Whether to clamp values in the range [MAX_VALUE + 1, 2^32 -1] + * to MAX_VALUE. This should be donw for TTLs, but not other values which + * can be expressed in this format. + * + * @return The value as a number of seconds + * + * @throws NumberFormatException The string was not in a valid TTL format. + */ + public static + long parse(String s, boolean clamp) { + if (s == null || s.length() == 0 || !Character.isDigit(s.charAt(0))) { + throw new NumberFormatException(); + } + long value = 0; + long ttl = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + long oldvalue = value; + if (Character.isDigit(c)) { + value = (value * 10) + Character.getNumericValue(c); + if (value < oldvalue) { + throw new NumberFormatException(); + } + } + else { + switch (Character.toUpperCase(c)) { + case 'W': + value *= 7; + case 'D': + value *= 24; + case 'H': + value *= 60; + case 'M': + value *= 60; + case 'S': + break; + default: + throw new NumberFormatException(); + } + ttl += value; + value = 0; + if (ttl > 0xFFFFFFFFL) { + throw new NumberFormatException(); + } + } + } + if (ttl == 0) { + ttl = value; + } + + if (ttl > 0xFFFFFFFFL) { + throw new NumberFormatException(); + } + else if (ttl > MAX_VALUE && clamp) { + ttl = MAX_VALUE; + } + return ttl; + } + + public static + String format(long ttl) { + TTL.check(ttl); + StringBuilder sb = new StringBuilder(); + long secs, mins, hours, days, weeks; + secs = ttl % 60; + ttl /= 60; + mins = ttl % 60; + ttl /= 60; + hours = ttl % 24; + ttl /= 24; + days = ttl % 7; + ttl /= 7; + weeks = ttl; + if (weeks > 0) { + sb.append(weeks) + .append("W"); + } + if (days > 0) { + sb.append(days) + .append("D"); + } + if (hours > 0) { + sb.append(hours) + .append("H"); + } + if (mins > 0) { + sb.append(mins) + .append("M"); + } + if (secs > 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) { + sb.append(secs) + .append("S"); + } + return sb.toString(); + } + + static + void check(long i) { + if (i < 0 || i > MAX_VALUE) { + throw new InvalidTTLException(i); + } + } + +} diff --git a/src/dorkbox/network/dns/records/TXTBase.java b/src/dorkbox/network/dns/records/TXTBase.java new file mode 100644 index 00000000..d09f4783 --- /dev/null +++ b/src/dorkbox/network/dns/records/TXTBase.java @@ -0,0 +1,138 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Implements common functionality for the many record types whose format + * is a list of strings. + * + * @author Brian Wellington + */ + +abstract +class TXTBase extends DnsRecord { + + private static final long serialVersionUID = -4319510507246305931L; + + protected List strings; + + protected + TXTBase() {} + + protected + TXTBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); + } + + protected + TXTBase(Name name, int type, int dclass, long ttl, String string) { + this(name, type, dclass, ttl, Collections.singletonList(string)); + } + + protected + TXTBase(Name name, int type, int dclass, long ttl, List strings) { + super(name, type, dclass, ttl); + if (strings == null) { + throw new IllegalArgumentException("strings must not be null"); + } + this.strings = new ArrayList(strings.size()); + Iterator it = strings.iterator(); + try { + while (it.hasNext()) { + String s = (String) it.next(); + this.strings.add(byteArrayFromString(s)); + } + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + strings = new ArrayList(2); + while (in.remaining() > 0) { + byte[] b = in.readCountedString(); + strings.add(b); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + Iterator it = strings.iterator(); + while (it.hasNext()) { + byte[] b = (byte[]) it.next(); + out.writeCountedString(b); + } + } + + /** + * converts to a String + */ + @Override + void rrToString(StringBuilder sb) { + Iterator it = strings.iterator(); + while (it.hasNext()) { + byte[] array = (byte[]) it.next(); + sb.append(byteArrayToString(array, true)); + if (it.hasNext()) { + sb.append(" "); + } + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + strings = new ArrayList(2); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) { + break; + } + try { + strings.add(byteArrayFromString(t.value)); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + + } + st.unget(); + } + + /** + * Returns the text strings + * + * @return A list of Strings corresponding to the text strings. + */ + public + List getStrings() { + List list = new ArrayList(strings.size()); + for (int i = 0; i < strings.size(); i++) { + list.add(byteArrayToString((byte[]) strings.get(i), false)); + } + return list; + } + + /** + * Returns the text strings + * + * @return A list of byte arrays corresponding to the text strings. + */ + public + List getStringsAsByteArrays() { + return strings; + } + +} diff --git a/src/dorkbox/network/dns/records/TXTRecord.java b/src/dorkbox/network/dns/records/TXTRecord.java new file mode 100644 index 00000000..ad92aeea --- /dev/null +++ b/src/dorkbox/network/dns/records/TXTRecord.java @@ -0,0 +1,52 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.util.List; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; + +/** + * Text - stores text strings + * + * @author Brian Wellington + */ + +public +class TXTRecord extends TXTBase { + + private static final long serialVersionUID = -5780785764284221342L; + + TXTRecord() {} + + @Override + DnsRecord getObject() { + return new TXTRecord(); + } + + /** + * Creates a TXT Record from the given data + * + * @param strings The text strings + * + * @throws IllegalArgumentException One of the strings has invalid escapes + */ + public + TXTRecord(Name name, int dclass, long ttl, List strings) { + super(name, DnsRecordType.TXT, dclass, ttl, strings); + } + + /** + * Creates a TXT Record from the given data + * + * @param string One text string + * + * @throws IllegalArgumentException The string has invalid escapes + */ + public + TXTRecord(Name name, int dclass, long ttl, String string) { + super(name, DnsRecordType.TXT, dclass, ttl, string); + } + +} diff --git a/src/dorkbox/network/dns/records/TypeBitmap.java b/src/dorkbox/network/dns/records/TypeBitmap.java new file mode 100644 index 00000000..abe41e78 --- /dev/null +++ b/src/dorkbox/network/dns/records/TypeBitmap.java @@ -0,0 +1,166 @@ +// Copyright (c) 2004-2009 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +/** + * Routines for deal with the lists of types found in NSEC/NSEC3 records. + * + * @author Brian Wellington + */ + +import java.io.IOException; +import java.io.Serializable; +import java.util.Iterator; +import java.util.TreeSet; + +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.WireParseException; +import dorkbox.network.dns.utils.Tokenizer; + +final +class TypeBitmap implements Serializable { + + private static final long serialVersionUID = -125354057735389003L; + + private TreeSet types; + + public + TypeBitmap(int[] array) { + this(); + for (int i = 0; i < array.length; i++) { + DnsRecordType.check(array[i]); + types.add(new Integer(array[i])); + } + } + + private + TypeBitmap() { + types = new TreeSet(); + } + + public + TypeBitmap(DnsInput in) throws WireParseException { + this(); + int lastbase = -1; + while (in.remaining() > 0) { + if (in.remaining() < 2) { + throw new WireParseException("invalid bitmap descriptor"); + } + int mapbase = in.readU8(); + if (mapbase < lastbase) { + throw new WireParseException("invalid ordering"); + } + int maplength = in.readU8(); + if (maplength > in.remaining()) { + throw new WireParseException("invalid bitmap"); + } + for (int i = 0; i < maplength; i++) { + int current = in.readU8(); + if (current == 0) { + continue; + } + for (int j = 0; j < 8; j++) { + if ((current & (1 << (7 - j))) == 0) { + continue; + } + int typecode = mapbase * 256 + +i * 8 + j; + types.add(Mnemonic.toInteger(typecode)); + } + } + } + } + + public + TypeBitmap(Tokenizer st) throws IOException { + this(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) { + break; + } + int typecode = DnsRecordType.value(t.value); + if (typecode < 0) { + throw st.exception("Invalid type: " + t.value); + } + types.add(Mnemonic.toInteger(typecode)); + } + st.unget(); + } + + public + int[] toArray() { + int[] array = new int[types.size()]; + int n = 0; + for (Iterator it = types.iterator(); it.hasNext(); ) { + array[n++] = ((Integer) it.next()).intValue(); + } + return array; + } + + public + String toString() { + StringBuilder sb = new StringBuilder(); + for (Iterator it = types.iterator(); it.hasNext(); ) { + int t = ((Integer) it.next()).intValue(); + sb.append(DnsRecordType.string(t)); + if (it.hasNext()) { + sb.append(' '); + } + } + return sb.toString(); + } + + public + void toWire(DnsOutput out) { + if (types.size() == 0) { + return; + } + + int mapbase = -1; + TreeSet map = new TreeSet(); + + for (Iterator it = types.iterator(); it.hasNext(); ) { + int t = ((Integer) it.next()).intValue(); + int base = t >> 8; + if (base != mapbase) { + if (map.size() > 0) { + mapToWire(out, map, mapbase); + map.clear(); + } + mapbase = base; + } + map.add(new Integer(t)); + } + mapToWire(out, map, mapbase); + } + + private static + void mapToWire(DnsOutput out, TreeSet map, int mapbase) { + int arraymax = (((Integer) map.last()).intValue()) & 0xFF; + int arraylength = (arraymax / 8) + 1; + int[] array = new int[arraylength]; + out.writeU8(mapbase); + out.writeU8(arraylength); + for (Iterator it = map.iterator(); it.hasNext(); ) { + int typecode = ((Integer) it.next()).intValue(); + array[(typecode & 0xFF) / 8] |= (1 << (7 - typecode % 8)); + } + for (int j = 0; j < arraylength; j++) { + out.writeU8(array[j]); + } + } + + public + boolean empty() { + return types.isEmpty(); + } + + public + boolean contains(int typecode) { + return types.contains(Mnemonic.toInteger(typecode)); + } + +} diff --git a/src/dorkbox/network/dns/records/U16NameBase.java b/src/dorkbox/network/dns/records/U16NameBase.java new file mode 100644 index 00000000..1e080b4e --- /dev/null +++ b/src/dorkbox/network/dns/records/U16NameBase.java @@ -0,0 +1,78 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Implements common functionality for the many record types whose format + * is an unsigned 16 bit integer followed by a name. + * + * @author Brian Wellington + */ + +abstract +class U16NameBase extends DnsRecord { + + private static final long serialVersionUID = -8315884183112502995L; + + protected int u16Field; + protected Name nameField; + + protected + U16NameBase() {} + + protected + U16NameBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); + } + + protected + U16NameBase(Name name, int type, int dclass, long ttl, int u16Field, String u16Description, Name nameField, String nameDescription) { + super(name, type, dclass, ttl); + this.u16Field = checkU16(u16Description, u16Field); + this.nameField = checkName(nameDescription, nameField); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + u16Field = in.readU16(); + nameField = new Name(in); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(u16Field); + nameField.toWire(out, null, canonical); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(u16Field); + sb.append(" "); + sb.append(nameField); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + u16Field = st.getUInt16(); + nameField = st.getName(origin); + } + + protected + int getU16Field() { + return u16Field; + } + + protected + Name getNameField() { + return nameField; + } + +} diff --git a/src/dorkbox/network/dns/records/UNKRecord.java b/src/dorkbox/network/dns/records/UNKRecord.java new file mode 100644 index 00000000..a9d2a7ac --- /dev/null +++ b/src/dorkbox/network/dns/records/UNKRecord.java @@ -0,0 +1,65 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * A class implementing Records of unknown and/or unimplemented types. This + * class can only be initialized using static Record initializers. + * + * @author Brian Wellington + */ + +public +class UNKRecord extends DnsRecord { + + private static final long serialVersionUID = -4193583311594626915L; + + private byte[] data; + + UNKRecord() {} + + @Override + DnsRecord getObject() { + return new UNKRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + data = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); + } + + /** + * Converts this Record to the String "unknown format" + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(unknownToString(data)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("invalid unknown RR encoding"); + } + + /** + * Returns the contents of this record. + */ + public + byte[] getData() { + return data; + } + +} diff --git a/src/dorkbox/network/dns/records/URIRecord.java b/src/dorkbox/network/dns/records/URIRecord.java new file mode 100644 index 00000000..27d47820 --- /dev/null +++ b/src/dorkbox/network/dns/records/URIRecord.java @@ -0,0 +1,120 @@ +// Implemented by Anthony Kirby (anthony@anthony.org) +// based on SRVRecord.java Copyright (c) 1999-2004 Brian Wellington + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Uniform Resource Identifier (URI) DNS Resource Record + * + * @author Anthony Kirby + * @see http://tools.ietf.org/html/draft-faltstrom-uri + */ + +public +class URIRecord extends DnsRecord { + + private static final long serialVersionUID = 7955422413971804232L; + + private int priority, weight; + private byte[] target; + + URIRecord() { + target = new byte[] {}; + } + + @Override + DnsRecord getObject() { + return new URIRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + priority = in.readU16(); + weight = in.readU16(); + target = in.readByteArray(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeU16(priority); + out.writeU16(weight); + out.writeByteArray(target); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(priority + " "); + sb.append(weight + " "); + sb.append(byteArrayToString(target, true)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + priority = st.getUInt16(); + weight = st.getUInt16(); + try { + target = byteArrayFromString(st.getString()); + } catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + } + + /** + * Creates a URI Record from the given data + * + * @param priority The priority of this URI. Records with lower priority + * are preferred. + * @param weight The weight, used to select between records at the same + * priority. + * @param target The host/port running the service + */ + public + URIRecord(Name name, int dclass, long ttl, int priority, int weight, String target) { + super(name, DnsRecordType.URI, dclass, ttl); + this.priority = checkU16("priority", priority); + this.weight = checkU16("weight", weight); + try { + this.target = byteArrayFromString(target); + } catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** + * Returns the priority + */ + public + int getPriority() { + return priority; + } + + /** + * Returns the weight + */ + public + int getWeight() { + return weight; + } + + /** + * Returns the target URI + */ + public + String getTarget() { + return byteArrayToString(target, false); + } + +} diff --git a/src/dorkbox/network/dns/records/Update.java b/src/dorkbox/network/dns/records/Update.java new file mode 100644 index 00000000..6da3d06c --- /dev/null +++ b/src/dorkbox/network/dns/records/Update.java @@ -0,0 +1,322 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.util.Iterator; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsClass; +import dorkbox.network.dns.constants.DnsOpCode; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.constants.DnsSection; +import dorkbox.network.dns.exceptions.RelativeNameException; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * A helper class for constructing dynamic DNS (DDNS) update messages. + * + * @author Brian Wellington + */ + +public +class Update extends DnsMessage { + + private Name origin; + private int dclass; + + /** + * Creates an update message. The class is assumed to be IN. + * + * @param zone The name of the zone being updated. + */ + public + Update(Name zone) { + this(zone, DnsClass.IN); + } + + /** + * Creates an update message. + * + * @param zone The name of the zone being updated. + * @param dclass The class of the zone being updated. + */ + public + Update(Name zone, int dclass) { + super(); + if (!zone.isAbsolute()) { + throw new RelativeNameException(zone); + } + DnsClass.check(dclass); + getHeader().setOpcode(DnsOpCode.UPDATE); + DnsRecord soa = DnsRecord.newRecord(zone, DnsRecordType.SOA, DnsClass.IN); + addRecord(soa, DnsSection.QUESTION); + this.origin = zone; + this.dclass = dclass; + } + + /** + * Inserts a prerequisite that the specified name exists; that is, there + * exist records with the given name in the zone. + */ + public + void present(Name name) { + newPrereq(DnsRecord.newRecord(name, DnsRecordType.ANY, DnsClass.ANY, 0)); + } + + private + void newPrereq(DnsRecord rec) { + addRecord(rec, DnsSection.PREREQ); + } + + /** + * Inserts a prerequisite that the specified rrset exists; that is, there + * exist records with the given name and type in the zone. + */ + public + void present(Name name, int type) { + newPrereq(DnsRecord.newRecord(name, type, DnsClass.ANY, 0)); + } + + /** + * Parses a record from the string, and inserts a prerequisite that the + * record exists. Due to the way value-dependent prequisites work, the + * condition that must be met is that the set of all records with the same + * and type in the update message must be identical to the set of all records + * with that name and type on the server. + * + * @throws IOException The record could not be parsed. + */ + public + void present(Name name, int type, String record) throws IOException { + newPrereq(DnsRecord.fromString(name, type, dclass, 0, record, origin)); + } + + /** + * Parses a record from the tokenizer, and inserts a prerequisite that the + * record exists. Due to the way value-dependent prequisites work, the + * condition that must be met is that the set of all records with the same + * and type in the update message must be identical to the set of all records + * with that name and type on the server. + * + * @throws IOException The record could not be parsed. + */ + public + void present(Name name, int type, Tokenizer tokenizer) throws IOException { + newPrereq(DnsRecord.fromString(name, type, dclass, 0, tokenizer, origin)); + } + + /** + * Inserts a prerequisite that the specified record exists. Due to the way + * value-dependent prequisites work, the condition that must be met is that + * the set of all records with the same and type in the update message must + * be identical to the set of all records with that name and type on the server. + */ + public + void present(DnsRecord record) { + newPrereq(record); + } + + /** + * Inserts a prerequisite that the specified name does not exist; that is, + * there are no records with the given name in the zone. + */ + public + void absent(Name name) { + newPrereq(DnsRecord.newRecord(name, DnsRecordType.ANY, DnsClass.NONE, 0)); + } + + /** + * Inserts a prerequisite that the specified rrset does not exist; that is, + * there are no records with the given name and type in the zone. + */ + public + void absent(Name name, int type) { + newPrereq(DnsRecord.newRecord(name, type, DnsClass.NONE, 0)); + } + + /** + * Indicates that the records should be inserted into the zone. + */ + public + void add(DnsRecord[] records) { + for (int i = 0; i < records.length; i++) { + add(records[i]); + } + } + + /** + * Indicates that the record should be inserted into the zone. + */ + public + void add(DnsRecord record) { + newUpdate(record); + } + + private + void newUpdate(DnsRecord rec) { + addRecord(rec, DnsSection.UPDATE); + } + + /** + * Indicates that all of the records in the rrset should be inserted into the + * zone. + */ + public + void add(RRset rrset) { + for (Iterator it = rrset.rrs(); it.hasNext(); ) { + add((DnsRecord) it.next()); + } + } + + /** + * Indicates that all records with the given name should be deleted from + * the zone. + */ + public + void delete(Name name) { + newUpdate(DnsRecord.newRecord(name, DnsRecordType.ANY, DnsClass.ANY, 0)); + } + + /** + * Parses a record from the string, and indicates that the record + * should be deleted from the zone. + * + * @throws IOException The record could not be parsed. + */ + public + void delete(Name name, int type, String record) throws IOException { + newUpdate(DnsRecord.fromString(name, type, DnsClass.NONE, 0, record, origin)); + } + + /** + * Parses a record from the tokenizer, and indicates that the record + * should be deleted from the zone. + * + * @throws IOException The record could not be parsed. + */ + public + void delete(Name name, int type, Tokenizer tokenizer) throws IOException { + newUpdate(DnsRecord.fromString(name, type, DnsClass.NONE, 0, tokenizer, origin)); + } + + /** + * Indicates that the records should be deleted from the zone. + */ + public + void delete(DnsRecord[] records) { + for (int i = 0; i < records.length; i++) { + delete(records[i]); + } + } + + /** + * Indicates that the specified record should be deleted from the zone. + */ + public + void delete(DnsRecord record) { + newUpdate(record.withDClass(DnsClass.NONE, 0)); + } + + /** + * Indicates that all of the records in the rrset should be deleted from the + * zone. + */ + public + void delete(RRset rrset) { + for (Iterator it = rrset.rrs(); it.hasNext(); ) { + delete((DnsRecord) it.next()); + } + } + + /** + * Parses a record from the string, and indicates that the record + * should be inserted into the zone replacing any other records with the + * same name and type. + * + * @throws IOException The record could not be parsed. + */ + public + void replace(Name name, int type, long ttl, String record) throws IOException { + delete(name, type); + add(name, type, ttl, record); + } + + /** + * Parses a record from the string, and indicates that the record + * should be inserted into the zone. + * + * @throws IOException The record could not be parsed. + */ + public + void add(Name name, int type, long ttl, String record) throws IOException { + newUpdate(DnsRecord.fromString(name, type, dclass, ttl, record, origin)); + } + + /** + * Indicates that all records with the given name and type should be deleted + * from the zone. + */ + public + void delete(Name name, int type) { + newUpdate(DnsRecord.newRecord(name, type, DnsClass.ANY, 0)); + } + + /** + * Parses a record from the tokenizer, and indicates that the record + * should be inserted into the zone replacing any other records with the + * same name and type. + * + * @throws IOException The record could not be parsed. + */ + public + void replace(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException { + delete(name, type); + add(name, type, ttl, tokenizer); + } + + /** + * Parses a record from the tokenizer, and indicates that the record + * should be inserted into the zone. + * + * @throws IOException The record could not be parsed. + */ + public + void add(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException { + newUpdate(DnsRecord.fromString(name, type, dclass, ttl, tokenizer, origin)); + } + + /** + * Indicates that the records should be inserted into the zone replacing any + * other records with the same name and type as each one. + */ + public + void replace(DnsRecord[] records) { + for (int i = 0; i < records.length; i++) { + replace(records[i]); + } + } + + /** + * Indicates that the record should be inserted into the zone replacing any + * other records with the same name and type. + */ + public + void replace(DnsRecord record) { + delete(record.getName(), record.getType()); + add(record); + } + + /** + * Indicates that all of the records in the rrset should be inserted into the + * zone replacing any other records with the same name and type. + */ + public + void replace(RRset rrset) { + delete(rrset.getName(), rrset.getType()); + for (Iterator it = rrset.rrs(); it.hasNext(); ) { + add((DnsRecord) it.next()); + } + } + +} diff --git a/src/dorkbox/network/dns/records/WKSRecord.java b/src/dorkbox/network/dns/records/WKSRecord.java new file mode 100644 index 00000000..3e6a862c --- /dev/null +++ b/src/dorkbox/network/dns/records/WKSRecord.java @@ -0,0 +1,861 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Mnemonic; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Address; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * Well Known Services - Lists services offered by this host. + * + * @author Brian Wellington + */ + +public +class WKSRecord extends DnsRecord { + + private static final long serialVersionUID = -9104259763909119805L; + private byte[] address; + private int protocol; + private int[] services; + + + public static + class Protocol { + /** + * Internet Control DnsMessage + */ + public static final int ICMP = 1; + /** + * Internet Group Management + */ + public static final int IGMP = 2; + /** + * Gateway-to-Gateway + */ + public static final int GGP = 3; + /** + * Stream + */ + public static final int ST = 5; + /** + * Transmission Control + */ + public static final int TCP = 6; + /** + * UCL + */ + public static final int UCL = 7; + /** + * Exterior Gateway Protocol + */ + public static final int EGP = 8; + /** + * any private interior gateway + */ + public static final int IGP = 9; + /** + * BBN RCC Monitoring + */ + public static final int BBN_RCC_MON = 10; + /** + * Network Voice Protocol + */ + public static final int NVP_II = 11; + /** + * PUP + */ + public static final int PUP = 12; + /** + * ARGUS + */ + public static final int ARGUS = 13; + /** + * EMCON + */ + public static final int EMCON = 14; + /** + * Cross Net Debugger + */ + public static final int XNET = 15; + /** + * Chaos + */ + public static final int CHAOS = 16; + /** + * User Datagram + */ + public static final int UDP = 17; + /** + * Multiplexing + */ + public static final int MUX = 18; + /** + * DCN Measurement Subsystems + */ + public static final int DCN_MEAS = 19; + /** + * Host Monitoring + */ + public static final int HMP = 20; + /** + * Packet Radio Measurement + */ + public static final int PRM = 21; + /** + * XEROX NS IDP + */ + public static final int XNS_IDP = 22; + /** + * Trunk-1 + */ + public static final int TRUNK_1 = 23; + /** + * Trunk-2 + */ + public static final int TRUNK_2 = 24; + /** + * Leaf-1 + */ + public static final int LEAF_1 = 25; + /** + * Leaf-2 + */ + public static final int LEAF_2 = 26; + /** + * Reliable Data Protocol + */ + public static final int RDP = 27; + /** + * Internet Reliable Transaction + */ + public static final int IRTP = 28; + /** + * ISO Transport Protocol Class 4 + */ + public static final int ISO_TP4 = 29; + /** + * Bulk Data Transfer Protocol + */ + public static final int NETBLT = 30; + /** + * MFE Network Services Protocol + */ + public static final int MFE_NSP = 31; + /** + * MERIT Internodal Protocol + */ + public static final int MERIT_INP = 32; + /** + * Sequential Exchange Protocol + */ + public static final int SEP = 33; + /** + * CFTP + */ + public static final int CFTP = 62; + /** + * SATNET and Backroom EXPAK + */ + public static final int SAT_EXPAK = 64; + /** + * MIT Subnet Support + */ + public static final int MIT_SUBNET = 65; + /** + * MIT Remote Virtual Disk Protocol + */ + public static final int RVD = 66; + /** + * Internet Pluribus Packet Core + */ + public static final int IPPC = 67; + /** + * SATNET Monitoring + */ + public static final int SAT_MON = 69; + /** + * Internet Packet Core Utility + */ + public static final int IPCV = 71; + /** + * Backroom SATNET Monitoring + */ + public static final int BR_SAT_MON = 76; + /** + * WIDEBAND Monitoring + */ + public static final int WB_MON = 78; + /** + * WIDEBAND EXPAK + */ + public static final int WB_EXPAK = 79; + private static Mnemonic protocols = new Mnemonic("IP protocol", Mnemonic.CASE_LOWER); + + /** + * IP protocol identifiers. This is basically copied out of RFC 1010. + */ + + private + Protocol() {} + + static { + protocols.setMaximum(0xFF); + protocols.setNumericAllowed(true); + + protocols.add(ICMP, "icmp"); + protocols.add(IGMP, "igmp"); + protocols.add(GGP, "ggp"); + protocols.add(ST, "st"); + protocols.add(TCP, "tcp"); + protocols.add(UCL, "ucl"); + protocols.add(EGP, "egp"); + protocols.add(IGP, "igp"); + protocols.add(BBN_RCC_MON, "bbn-rcc-mon"); + protocols.add(NVP_II, "nvp-ii"); + protocols.add(PUP, "pup"); + protocols.add(ARGUS, "argus"); + protocols.add(EMCON, "emcon"); + protocols.add(XNET, "xnet"); + protocols.add(CHAOS, "chaos"); + protocols.add(UDP, "udp"); + protocols.add(MUX, "mux"); + protocols.add(DCN_MEAS, "dcn-meas"); + protocols.add(HMP, "hmp"); + protocols.add(PRM, "prm"); + protocols.add(XNS_IDP, "xns-idp"); + protocols.add(TRUNK_1, "trunk-1"); + protocols.add(TRUNK_2, "trunk-2"); + protocols.add(LEAF_1, "leaf-1"); + protocols.add(LEAF_2, "leaf-2"); + protocols.add(RDP, "rdp"); + protocols.add(IRTP, "irtp"); + protocols.add(ISO_TP4, "iso-tp4"); + protocols.add(NETBLT, "netblt"); + protocols.add(MFE_NSP, "mfe-nsp"); + protocols.add(MERIT_INP, "merit-inp"); + protocols.add(SEP, "sep"); + protocols.add(CFTP, "cftp"); + protocols.add(SAT_EXPAK, "sat-expak"); + protocols.add(MIT_SUBNET, "mit-subnet"); + protocols.add(RVD, "rvd"); + protocols.add(IPPC, "ippc"); + protocols.add(SAT_MON, "sat-mon"); + protocols.add(IPCV, "ipcv"); + protocols.add(BR_SAT_MON, "br-sat-mon"); + protocols.add(WB_MON, "wb-mon"); + protocols.add(WB_EXPAK, "wb-expak"); + } + + /** + * Converts an IP protocol value into its textual representation + */ + public static + String string(int type) { + return protocols.getText(type); + } + + /** + * Converts a textual representation of an IP protocol into its + * numeric code. Integers in the range 0..255 are also accepted. + * + * @param s The textual representation of the protocol + * + * @return The protocol code, or -1 on error. + */ + public static + int value(String s) { + return protocols.getValue(s); + } + } + + + public static + class Service { + /** + * Remote Job Entry + */ + public static final int RJE = 5; + /** + * Echo + */ + public static final int ECHO = 7; + /** + * Discard + */ + public static final int DISCARD = 9; + /** + * Active Users + */ + public static final int USERS = 11; + /** + * Daytime + */ + public static final int DAYTIME = 13; + /** + * Quote of the Day + */ + public static final int QUOTE = 17; + /** + * Character Generator + */ + public static final int CHARGEN = 19; + /** + * File Transfer [Default Data] + */ + public static final int FTP_DATA = 20; + /** + * File Transfer [Control] + */ + public static final int FTP = 21; + /** + * Telnet + */ + public static final int TELNET = 23; + /** + * Simple Mail Transfer + */ + public static final int SMTP = 25; + /** + * NSW User System FE + */ + public static final int NSW_FE = 27; + /** + * MSG ICP + */ + public static final int MSG_ICP = 29; + /** + * MSG Authentication + */ + public static final int MSG_AUTH = 31; + /** + * Display Support Protocol + */ + public static final int DSP = 33; + /** + * Time + */ + public static final int TIME = 37; + /** + * Resource Location Protocol + */ + public static final int RLP = 39; + /** + * Graphics + */ + public static final int GRAPHICS = 41; + /** + * Host Name Server + */ + public static final int NAMESERVER = 42; + /** + * Who Is + */ + public static final int NICNAME = 43; + /** + * MPM FLAGS Protocol + */ + public static final int MPM_FLAGS = 44; + /** + * DnsMessage Processing Module [recv] + */ + public static final int MPM = 45; + /** + * MPM [default send] + */ + public static final int MPM_SND = 46; + /** + * NI FTP + */ + public static final int NI_FTP = 47; + /** + * Login Host Protocol + */ + public static final int LOGIN = 49; + /** + * IMP Logical Address Maintenance + */ + public static final int LA_MAINT = 51; + /** + * Domain Name Server + */ + public static final int DOMAIN = 53; + /** + * ISI Graphics Language + */ + public static final int ISI_GL = 55; + /** + * NI MAIL + */ + public static final int NI_MAIL = 61; + /** + * VIA Systems - FTP + */ + public static final int VIA_FTP = 63; + /** + * TACACS-Database Service + */ + public static final int TACACS_DS = 65; + /** + * Bootstrap Protocol Server + */ + public static final int BOOTPS = 67; + /** + * Bootstrap Protocol Client + */ + public static final int BOOTPC = 68; + /** + * Trivial File Transfer + */ + public static final int TFTP = 69; + /** + * Remote Job Service + */ + public static final int NETRJS_1 = 71; + /** + * Remote Job Service + */ + public static final int NETRJS_2 = 72; + /** + * Remote Job Service + */ + public static final int NETRJS_3 = 73; + /** + * Remote Job Service + */ + public static final int NETRJS_4 = 74; + /** + * Finger + */ + public static final int FINGER = 79; + /** + * HOSTS2 Name Server + */ + public static final int HOSTS2_NS = 81; + /** + * SU/MIT Telnet Gateway + */ + public static final int SU_MIT_TG = 89; + /** + * MIT Dover Spooler + */ + public static final int MIT_DOV = 91; + /** + * Device Control Protocol + */ + public static final int DCP = 93; + /** + * SUPDUP + */ + public static final int SUPDUP = 95; + /** + * Swift Remote Virtual File Protocol + */ + public static final int SWIFT_RVF = 97; + /** + * TAC News + */ + public static final int TACNEWS = 98; + /** + * Metagram Relay + */ + public static final int METAGRAM = 99; + /** + * NIC Host Name Server + */ + public static final int HOSTNAME = 101; + /** + * ISO-TSAP + */ + public static final int ISO_TSAP = 102; + /** + * X400 + */ + public static final int X400 = 103; + /** + * X400-SND + */ + public static final int X400_SND = 104; + /** + * Mailbox Name Nameserver + */ + public static final int CSNET_NS = 105; + /** + * Remote Telnet Service + */ + public static final int RTELNET = 107; + /** + * Post Office Protocol - Version 2 + */ + public static final int POP_2 = 109; + /** + * SUN Remote Procedure Call + */ + public static final int SUNRPC = 111; + /** + * Authentication Service + */ + public static final int AUTH = 113; + /** + * Simple File Transfer Protocol + */ + public static final int SFTP = 115; + /** + * UUCP Path Service + */ + public static final int UUCP_PATH = 117; + /** + * Network News Transfer Protocol + */ + public static final int NNTP = 119; + /** + * HYDRA Expedited Remote Procedure + */ + public static final int ERPC = 121; + /** + * Network Time Protocol + */ + public static final int NTP = 123; + /** + * Locus PC-Interface Net Map Server + */ + public static final int LOCUS_MAP = 125; + /** + * Locus PC-Interface Conn Server + */ + public static final int LOCUS_CON = 127; + /** + * Password Generator Protocol + */ + public static final int PWDGEN = 129; + /** + * CISCO FNATIVE + */ + public static final int CISCO_FNA = 130; + /** + * CISCO TNATIVE + */ + public static final int CISCO_TNA = 131; + /** + * CISCO SYSMAINT + */ + public static final int CISCO_SYS = 132; + /** + * Statistics Service + */ + public static final int STATSRV = 133; + /** + * INGRES-NET Service + */ + public static final int INGRES_NET = 134; + /** + * Location Service + */ + public static final int LOC_SRV = 135; + /** + * PROFILE Naming System + */ + public static final int PROFILE = 136; + /** + * NETBIOS Name Service + */ + public static final int NETBIOS_NS = 137; + /** + * NETBIOS Datagram Service + */ + public static final int NETBIOS_DGM = 138; + /** + * NETBIOS Session Service + */ + public static final int NETBIOS_SSN = 139; + /** + * EMFIS Data Service + */ + public static final int EMFIS_DATA = 140; + /** + * EMFIS Control Service + */ + public static final int EMFIS_CNTL = 141; + /** + * Britton-Lee IDM + */ + public static final int BL_IDM = 142; + /** + * Survey Measurement + */ + public static final int SUR_MEAS = 243; + /** + * LINK + */ + public static final int LINK = 245; + private static Mnemonic services = new Mnemonic("TCP/UDP service", Mnemonic.CASE_LOWER); + + /** + * TCP/UDP services. This is basically copied out of RFC 1010, + * with MIT-ML-DEV removed, as it is not unique, and the description + * of SWIFT-RVF fixed. + */ + + private + Service() {} + + static { + services.setMaximum(0xFFFF); + services.setNumericAllowed(true); + + services.add(RJE, "rje"); + services.add(ECHO, "echo"); + services.add(DISCARD, "discard"); + services.add(USERS, "users"); + services.add(DAYTIME, "daytime"); + services.add(QUOTE, "quote"); + services.add(CHARGEN, "chargen"); + services.add(FTP_DATA, "ftp-data"); + services.add(FTP, "ftp"); + services.add(TELNET, "telnet"); + services.add(SMTP, "smtp"); + services.add(NSW_FE, "nsw-fe"); + services.add(MSG_ICP, "msg-icp"); + services.add(MSG_AUTH, "msg-auth"); + services.add(DSP, "dsp"); + services.add(TIME, "time"); + services.add(RLP, "rlp"); + services.add(GRAPHICS, "graphics"); + services.add(NAMESERVER, "nameserver"); + services.add(NICNAME, "nicname"); + services.add(MPM_FLAGS, "mpm-flags"); + services.add(MPM, "mpm"); + services.add(MPM_SND, "mpm-snd"); + services.add(NI_FTP, "ni-ftp"); + services.add(LOGIN, "login"); + services.add(LA_MAINT, "la-maint"); + services.add(DOMAIN, "domain"); + services.add(ISI_GL, "isi-gl"); + services.add(NI_MAIL, "ni-mail"); + services.add(VIA_FTP, "via-ftp"); + services.add(TACACS_DS, "tacacs-ds"); + services.add(BOOTPS, "bootps"); + services.add(BOOTPC, "bootpc"); + services.add(TFTP, "tftp"); + services.add(NETRJS_1, "netrjs-1"); + services.add(NETRJS_2, "netrjs-2"); + services.add(NETRJS_3, "netrjs-3"); + services.add(NETRJS_4, "netrjs-4"); + services.add(FINGER, "finger"); + services.add(HOSTS2_NS, "hosts2-ns"); + services.add(SU_MIT_TG, "su-mit-tg"); + services.add(MIT_DOV, "mit-dov"); + services.add(DCP, "dcp"); + services.add(SUPDUP, "supdup"); + services.add(SWIFT_RVF, "swift-rvf"); + services.add(TACNEWS, "tacnews"); + services.add(METAGRAM, "metagram"); + services.add(HOSTNAME, "hostname"); + services.add(ISO_TSAP, "iso-tsap"); + services.add(X400, "x400"); + services.add(X400_SND, "x400-snd"); + services.add(CSNET_NS, "csnet-ns"); + services.add(RTELNET, "rtelnet"); + services.add(POP_2, "pop-2"); + services.add(SUNRPC, "sunrpc"); + services.add(AUTH, "auth"); + services.add(SFTP, "sftp"); + services.add(UUCP_PATH, "uucp-path"); + services.add(NNTP, "nntp"); + services.add(ERPC, "erpc"); + services.add(NTP, "ntp"); + services.add(LOCUS_MAP, "locus-map"); + services.add(LOCUS_CON, "locus-con"); + services.add(PWDGEN, "pwdgen"); + services.add(CISCO_FNA, "cisco-fna"); + services.add(CISCO_TNA, "cisco-tna"); + services.add(CISCO_SYS, "cisco-sys"); + services.add(STATSRV, "statsrv"); + services.add(INGRES_NET, "ingres-net"); + services.add(LOC_SRV, "loc-srv"); + services.add(PROFILE, "profile"); + services.add(NETBIOS_NS, "netbios-ns"); + services.add(NETBIOS_DGM, "netbios-dgm"); + services.add(NETBIOS_SSN, "netbios-ssn"); + services.add(EMFIS_DATA, "emfis-data"); + services.add(EMFIS_CNTL, "emfis-cntl"); + services.add(BL_IDM, "bl-idm"); + services.add(SUR_MEAS, "sur-meas"); + services.add(LINK, "link"); + } + + /** + * Converts a TCP/UDP service port number into its textual + * representation. + */ + public static + String string(int type) { + return services.getText(type); + } + + /** + * Converts a textual representation of a TCP/UDP service into its + * port number. Integers in the range 0..65535 are also accepted. + * + * @param s The textual representation of the service. + * + * @return The port number, or -1 on error. + */ + public static + int value(String s) { + return services.getValue(s); + } + } + + WKSRecord() {} + + @Override + DnsRecord getObject() { + return new WKSRecord(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + address = in.readByteArray(4); + protocol = in.readU8(); + byte[] array = in.readByteArray(); + List list = new ArrayList(); + for (int i = 0; i < array.length; i++) { + for (int j = 0; j < 8; j++) { + int octet = array[i] & 0xFF; + if ((octet & (1 << (7 - j))) != 0) { + list.add(new Integer(i * 8 + j)); + } + } + } + services = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + services[i] = ((Integer) list.get(i)).intValue(); + } + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeByteArray(address); + out.writeU8(protocol); + int highestPort = services[services.length - 1]; + byte[] array = new byte[highestPort / 8 + 1]; + for (int i = 0; i < services.length; i++) { + int port = services[i]; + array[port / 8] |= (1 << (7 - port % 8)); + } + out.writeByteArray(array); + } + + /** + * Converts rdata to a String + */ + @Override + void rrToString(StringBuilder sb) { + sb.append(Address.toDottedQuad(address)); + sb.append(" "); + sb.append(protocol); + for (int i = 0; i < services.length; i++) { + sb.append(" ") + .append(services[i]); + } + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String s = st.getString(); + address = Address.toByteArray(s, Address.IPv4); + if (address == null) { + throw st.exception("invalid address"); + } + + s = st.getString(); + protocol = Protocol.value(s); + if (protocol < 0) { + throw st.exception("Invalid IP protocol: " + s); + } + + List list = new ArrayList(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) { + break; + } + int service = Service.value(t.value); + if (service < 0) { + throw st.exception("Invalid TCP/UDP service: " + t.value); + } + list.add(new Integer(service)); + } + st.unget(); + services = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + services[i] = ((Integer) list.get(i)).intValue(); + } + } + + /** + * Creates a WKS Record from the given data + * + * @param address The IP address + * @param protocol The IP protocol number + * @param services An array of supported services, represented by port number. + */ + public + WKSRecord(Name name, int dclass, long ttl, InetAddress address, int protocol, int[] services) { + super(name, DnsRecordType.WKS, dclass, ttl); + if (Address.familyOf(address) != Address.IPv4) { + throw new IllegalArgumentException("invalid IPv4 address"); + } + this.address = address.getAddress(); + this.protocol = checkU8("protocol", protocol); + for (int i = 0; i < services.length; i++) { + checkU16("service", services[i]); + } + this.services = new int[services.length]; + System.arraycopy(services, 0, this.services, 0, services.length); + Arrays.sort(this.services); + } + + /** + * Returns the IP address. + */ + public + InetAddress getAddress() { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * Returns the IP protocol. + */ + public + int getProtocol() { + return protocol; + } + + /** + * Returns the services provided by the host on the specified address. + */ + public + int[] getServices() { + return services; + } + +} diff --git a/src/dorkbox/network/dns/records/X25Record.java b/src/dorkbox/network/dns/records/X25Record.java new file mode 100644 index 00000000..b5e2d55b --- /dev/null +++ b/src/dorkbox/network/dns/records/X25Record.java @@ -0,0 +1,96 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.records; + +import java.io.IOException; + +import dorkbox.network.dns.Compression; +import dorkbox.network.dns.DnsInput; +import dorkbox.network.dns.DnsOutput; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.utils.Tokenizer; + +/** + * X25 - identifies the PSDN (Public Switched Data Network) address in the + * X.121 numbering plan associated with a name. + * + * @author Brian Wellington + */ + +public +class X25Record extends DnsRecord { + + private static final long serialVersionUID = 4267576252335579764L; + + private byte[] address; + + X25Record() {} + + @Override + DnsRecord getObject() { + return new X25Record(); + } + + @Override + void rrFromWire(DnsInput in) throws IOException { + address = in.readCountedString(); + } + + @Override + void rrToWire(DnsOutput out, Compression c, boolean canonical) { + out.writeCountedString(address); + } + + @Override + void rrToString(StringBuilder sb) { + sb.append(byteArrayToString(address, true)); + } + + @Override + void rdataFromString(Tokenizer st, Name origin) throws IOException { + String addr = st.getString(); + this.address = checkAndConvertAddress(addr); + if (this.address == null) { + throw st.exception("invalid PSDN address " + addr); + } + } + + /** + * Creates an X25 Record from the given data + * + * @param address The X.25 PSDN address. + * + * @throws IllegalArgumentException The address is not a valid PSDN address. + */ + public + X25Record(Name name, int dclass, long ttl, String address) { + super(name, DnsRecordType.X25, dclass, ttl); + this.address = checkAndConvertAddress(address); + if (this.address == null) { + throw new IllegalArgumentException("invalid PSDN address " + address); + } + } + + private static + byte[] checkAndConvertAddress(String address) { + int length = address.length(); + byte[] out = new byte[length]; + for (int i = 0; i < length; i++) { + char c = address.charAt(i); + if (!Character.isDigit(c)) { + return null; + } + out[i] = (byte) c; + } + return out; + } + + /** + * Returns the X.25 PSDN address. + */ + public + String getAddress() { + return byteArrayToString(address, false); + } +} diff --git a/src/dorkbox/network/dns/utils/Address.java b/src/dorkbox/network/dns/utils/Address.java new file mode 100644 index 00000000..8bdb26aa --- /dev/null +++ b/src/dorkbox/network/dns/utils/Address.java @@ -0,0 +1,501 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import dorkbox.network.DnsClient; +import dorkbox.network.dns.Name; +import dorkbox.network.dns.constants.DnsRecordType; +import dorkbox.network.dns.records.DnsRecord; +import dorkbox.network.dns.records.PTRRecord; +import io.netty.resolver.ResolvedAddressTypes; + +/** + * Routines dealing with IP addresses. Includes functions similar to + * those in the java.net.InetAddress class. + * + * @author Brian Wellington + */ + +public final +class Address { + + public static final int IPv4 = 1; + public static final int IPv6 = 2; + + private + Address() {} + + private static + byte[] parseV4(String s) { + int numDigits; + int currentOctet; + byte[] values = new byte[4]; + int currentValue; + int length = s.length(); + + currentOctet = 0; + currentValue = 0; + numDigits = 0; + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c >= '0' && c <= '9') { + /* Can't have more than 3 digits per octet. */ + if (numDigits == 3) { + return null; + } + /* Octets shouldn't start with 0, unless they are 0. */ + if (numDigits > 0 && currentValue == 0) { + return null; + } + numDigits++; + currentValue *= 10; + currentValue += (c - '0'); + /* 255 is the maximum value for an octet. */ + if (currentValue > 255) { + return null; + } + } + else if (c == '.') { + /* Can't have more than 3 dots. */ + if (currentOctet == 3) { + return null; + } + /* Two consecutive dots are bad. */ + if (numDigits == 0) { + return null; + } + values[currentOctet++] = (byte) currentValue; + currentValue = 0; + numDigits = 0; + } + else { + return null; + } + } + /* Must have 4 octets. */ + if (currentOctet != 3) { + return null; + } + /* The fourth octet can't be empty. */ + if (numDigits == 0) { + return null; + } + values[currentOctet] = (byte) currentValue; + return values; + } + + private static + byte[] parseV6(String s) { + int range = -1; + byte[] data = new byte[16]; + + String[] tokens = s.split(":", -1); + + int first = 0; + int last = tokens.length - 1; + + if (tokens[0].length() == 0) { + // If the first two tokens are empty, it means the string + // started with ::, which is fine. If only the first is + // empty, the string started with :, which is bad. + if (last - first > 0 && tokens[1].length() == 0) { + first++; + } + else { + return null; + } + } + + if (tokens[last].length() == 0) { + // If the last two tokens are empty, it means the string + // ended with ::, which is fine. If only the last is + // empty, the string ended with :, which is bad. + if (last - first > 0 && tokens[last - 1].length() == 0) { + last--; + } + else { + return null; + } + } + + if (last - first + 1 > 8) { + return null; + } + + int i, j; + for (i = first, j = 0; i <= last; i++) { + if (tokens[i].length() == 0) { + if (range >= 0) { + return null; + } + range = j; + continue; + } + + if (tokens[i].indexOf('.') >= 0) { + // An IPv4 address must be the last component + if (i < last) { + return null; + } + // There can't have been more than 6 components. + if (i > 6) { + return null; + } + byte[] v4addr = Address.toByteArray(tokens[i], IPv4); + if (v4addr == null) { + return null; + } + for (int k = 0; k < 4; k++) { + data[j++] = v4addr[k]; + } + break; + } + + try { + for (int k = 0; k < tokens[i].length(); k++) { + char c = tokens[i].charAt(k); + if (Character.digit(c, 16) < 0) { + return null; + } + } + int x = Integer.parseInt(tokens[i], 16); + if (x > 0xFFFF || x < 0) { + return null; + } + data[j++] = (byte) (x >>> 8); + data[j++] = (byte) (x & 0xFF); + } catch (NumberFormatException e) { + return null; + } + } + + if (j < 16 && range < 0) { + return null; + } + + if (range >= 0) { + int empty = 16 - j; + System.arraycopy(data, range, data, range + empty, j - range); + for (i = range; i < range + empty; i++) { + data[i] = 0; + } + } + + return data; + } + + /** + * Convert a string containing an IP address to an array of 4 or 16 integers. + * + * @param s The address, in text format. + * @param family The address family. + * + * @return The address + */ + public static + int[] toArray(String s, int family) { + byte[] byteArray = toByteArray(s, family); + if (byteArray == null) { + return null; + } + int[] intArray = new int[byteArray.length]; + for (int i = 0; i < byteArray.length; i++) { + intArray[i] = byteArray[i] & 0xFF; + } + return intArray; + } + + /** + * Convert a string containing an IPv4 address to an array of 4 integers. + * + * @param s The address, in text format. + * + * @return The address + */ + public static + int[] toArray(String s) { + return toArray(s, IPv4); + } + + /** + * Convert a string containing an IP address to an array of 4 or 16 bytes. + * + * @param s The address, in text format. + * @param family The address family. + * + * @return The address + */ + public static + byte[] toByteArray(String s, int family) { + if (family == IPv4) { + return parseV4(s); + } + else if (family == IPv6) { + return parseV6(s); + } + else { + throw new IllegalArgumentException("unknown address family"); + } + } + + /** + * Determines if a string contains a valid IP address. + * + * @param s The string + * + * @return Whether the string contains a valid IP address + */ + public static + boolean isDottedQuad(String s) { + byte[] address = Address.toByteArray(s, IPv4); + return (address != null); + } + + /** + * Converts a byte array containing an IPv4 address into a dotted quad string. + * + * @param addr The array + * + * @return The string representation + */ + public static + String toDottedQuad(byte[] addr) { + return ((addr[0] & 0xFF) + "." + (addr[1] & 0xFF) + "." + (addr[2] & 0xFF) + "." + (addr[3] & 0xFF)); + } + + /** + * Converts an int array containing an IPv4 address into a dotted quad string. + * + * @param addr The array + * + * @return The string representation + */ + public static + String toDottedQuad(int[] addr) { + return (addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3]); + } + + private static + List lookupHostName(String name) throws UnknownHostException { + DnsClient client = new DnsClient(); + List resolved = client.resolve(name); + client.stop(); + return resolved; + } + + /** + * Determines the IP address of a host + * + * @param name The hostname to look up + * + * @return The first matching IP address + * + * @throws UnknownHostException The hostname does not have any addresses + */ + public static + InetAddress getByName(String name) throws UnknownHostException { + try { + return getByAddress(name); + } catch (UnknownHostException e) { + List records = lookupHostName(name); + if (records == null) { + return null; + } + + return records.get(0); + } + } + + /** + * Determines all IP address of a host + * + * @param name The hostname to look up + * + * @return All matching IP addresses + * + * @throws UnknownHostException The hostname does not have any addresses + */ + public static + InetAddress[] getAllByName(String name) throws UnknownHostException { + try { + InetAddress addr = getByAddress(name); + return new InetAddress[] {addr}; + } catch (UnknownHostException e) { + List combined = new ArrayList(); + DnsClient client = new DnsClient(); + // ipv4 + client.resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY); + List resolved = client.resolve(name); + combined.addAll(resolved); + client.stop(); + + + client = new DnsClient(); + client.resolvedAddressTypes(ResolvedAddressTypes.IPV6_ONLY); + // ipv6 + List resolved2 = client.resolve(name); + combined.addAll(resolved2); + client.stop(); + + + return combined.toArray(new InetAddress[0]); + } + } + + /** + * Converts an address from its string representation to an IP address. + * The address can be either IPv4 or IPv6. + * + * @param addr The address, in string form + * + * @return The IP addresses + * + * @throws UnknownHostException The address is not a valid IP address. + */ + public static + InetAddress getByAddress(String addr) throws UnknownHostException { + byte[] bytes; + bytes = toByteArray(addr, IPv4); + if (bytes != null) { + return InetAddress.getByAddress(addr, bytes); + } + bytes = toByteArray(addr, IPv6); + if (bytes != null) { + return InetAddress.getByAddress(addr, bytes); + } + throw new UnknownHostException("Invalid address: " + addr); + } + + /** + * Converts an address from its string representation to an IP address in + * a particular family. + * + * @param addr The address, in string form + * @param family The address family, either IPv4 or IPv6. + * + * @return The IP addresses + * + * @throws UnknownHostException The address is not a valid IP address in + * the specified address family. + */ + public static + InetAddress getByAddress(String addr, int family) throws UnknownHostException { + if (family != IPv4 && family != IPv6) { + throw new IllegalArgumentException("unknown address family"); + } + byte[] bytes; + bytes = toByteArray(addr, family); + if (bytes != null) { + return InetAddress.getByAddress(addr, bytes); + } + throw new UnknownHostException("Invalid address: " + addr); + } + + /** + * Determines the hostname for an address + * + * @param addr The address to look up + * + * @return The associated host name + * + * @throws UnknownHostException There is no hostname for the address + */ + public static + String getHostName(InetAddress addr) throws UnknownHostException { + Name name = ReverseMap.fromAddress(addr); + + DnsClient client = new DnsClient(); + client.resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY); + DnsRecord[] records = new DnsRecord[0]; + try { + records = client.resolve(name.toString(true), DnsRecordType.PTR); + } catch (Throwable ignored) { + } + client.stop(); + + if (records == null) { + throw new UnknownHostException("unknown address"); + } + + PTRRecord ptr = (PTRRecord) records[0]; + return ptr.getTarget() + .toString(); + } + + /** + * Truncates an address to the specified number of bits. For example, + * truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0. + * + * @param address The source address + * @param maskLength The number of bits to truncate the address to. + */ + public static + InetAddress truncate(InetAddress address, int maskLength) { + int family = familyOf(address); + int maxMaskLength = addressLength(family) * 8; + if (maskLength < 0 || maskLength > maxMaskLength) { + throw new IllegalArgumentException("invalid mask length"); + } + if (maskLength == maxMaskLength) { + return address; + } + byte[] bytes = address.getAddress(); + for (int i = maskLength / 8 + 1; i < bytes.length; i++) { + bytes[i] = 0; + } + int maskBits = maskLength % 8; + int bitmask = 0; + for (int i = 0; i < maskBits; i++) { + bitmask |= (1 << (7 - i)); + } + bytes[maskLength / 8] &= bitmask; + try { + return InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("invalid address"); + } + } + + /** + * Returns the family of an InetAddress. + * + * @param address The supplied address. + * + * @return The family, either IPv4 or IPv6. + */ + public static + int familyOf(InetAddress address) { + if (address instanceof Inet4Address) { + return IPv4; + } + if (address instanceof Inet6Address) { + return IPv6; + } + throw new IllegalArgumentException("unknown address family"); + } + + /** + * Returns the length of an address in a particular family. + * + * @param family The address family, either IPv4 or IPv6. + * + * @return The length of addresses in that family. + */ + public static + int addressLength(int family) { + if (family == IPv4) { + return 4; + } + if (family == IPv6) { + return 16; + } + throw new IllegalArgumentException("unknown address family"); + } +} diff --git a/src/dorkbox/network/dns/utils/FormattedTime.java b/src/dorkbox/network/dns/utils/FormattedTime.java new file mode 100644 index 00000000..50043e81 --- /dev/null +++ b/src/dorkbox/network/dns/utils/FormattedTime.java @@ -0,0 +1,90 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +/** + * Routines for converting time values to and from YYYYMMDDHHMMSS format. + * + * @author Brian Wellington + */ + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import dorkbox.network.dns.exceptions.TextParseException; + +public final +class FormattedTime { + + private static NumberFormat w2, w4; + + static { + w2 = new DecimalFormat(); + w2.setMinimumIntegerDigits(2); + + w4 = new DecimalFormat(); + w4.setMinimumIntegerDigits(4); + w4.setGroupingUsed(false); + } + + private + FormattedTime() {} + + /** + * Converts a Date into a formatted string. + * + * @param date The Date to convert. + * + * @return The formatted string. + */ + public static + String format(Date date) { + Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + StringBuilder sb = new StringBuilder(); + + c.setTime(date); + sb.append(w4.format(c.get(Calendar.YEAR))); + sb.append(w2.format(c.get(Calendar.MONTH) + 1)); + sb.append(w2.format(c.get(Calendar.DAY_OF_MONTH))); + sb.append(w2.format(c.get(Calendar.HOUR_OF_DAY))); + sb.append(w2.format(c.get(Calendar.MINUTE))); + sb.append(w2.format(c.get(Calendar.SECOND))); + return sb.toString(); + } + + /** + * Parses a formatted time string into a Date. + * + * @param s The string, in the form YYYYMMDDHHMMSS. + * + * @return The Date object. + * + * @throws TextParseException The string was invalid. + */ + public static + Date parse(String s) throws TextParseException { + if (s.length() != 14) { + throw new TextParseException("Invalid time encoding: " + s); + } + + Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + c.clear(); + try { + int year = Integer.parseInt(s.substring(0, 4)); + int month = Integer.parseInt(s.substring(4, 6)) - 1; + int date = Integer.parseInt(s.substring(6, 8)); + int hour = Integer.parseInt(s.substring(8, 10)); + int minute = Integer.parseInt(s.substring(10, 12)); + int second = Integer.parseInt(s.substring(12, 14)); + c.set(year, month, date, hour, minute, second); + } catch (NumberFormatException e) { + throw new TextParseException("Invalid time encoding: " + s); + } + return c.getTime(); + } + +} diff --git a/src/dorkbox/network/dns/utils/Options.java b/src/dorkbox/network/dns/utils/Options.java new file mode 100644 index 00000000..99e77c32 --- /dev/null +++ b/src/dorkbox/network/dns/utils/Options.java @@ -0,0 +1,143 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * Boolean options:
+ * bindttl - Print TTLs in BIND format
+ * multiline - Print records in multiline format
+ * noprintin - Don't print the class of a record if it's IN
+ * verbose - Turn on general debugging statements
+ * verbosemsg - Print all messages sent or received by SimpleResolver
+ * verbosecompression - Print messages related to name compression
+ * verbosesec - Print messages related to signature verification
+ * verbosecache - Print messages related to cache lookups
+ *
+ * Valued options:
+ * tsigfudge=n - Sets the default TSIG fudge value (in seconds)
+ * sig0validity=n - Sets the default SIG(0) validity period (in seconds)
+ * + * @author Brian Wellington + */ + +public final +class Options { + + private static Map table; + + static { + try { + refresh(); + } catch (SecurityException e) { + } + } + + private + Options() {} + + public static + void refresh() { + String s = System.getProperty("dnsjava.options"); + if (s != null) { + StringTokenizer st = new StringTokenizer(s, ","); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + int index = token.indexOf('='); + if (index == -1) { + set(token); + } + else { + String option = token.substring(0, index); + String value = token.substring(index + 1); + set(option, value); + } + } + } + } + + /** + * Sets an option to "true" + */ + public static + void set(String option) { + if (table == null) { + table = new HashMap(); + } + table.put(option.toLowerCase(), "true"); + } + + /** + * Sets an option to the the supplied value + */ + public static + void set(String option, String value) { + if (table == null) { + table = new HashMap(); + } + table.put(option.toLowerCase(), value.toLowerCase()); + } + + /** + * Clears all defined options + */ + public static + void clear() { + table = null; + } + + /** + * Removes an option + */ + public static + void unset(String option) { + if (table == null) { + return; + } + table.remove(option.toLowerCase()); + } + + /** + * Checks if an option is defined + */ + public static + boolean check(String option) { + if (table == null) { + return false; + } + return (table.get(option.toLowerCase()) != null); + } + + /** + * Returns the value of an option as an integer, or -1 if not defined. + */ + public static + int intValue(String option) { + String s = value(option); + if (s != null) { + try { + int val = Integer.parseInt(s); + if (val > 0) { + return (val); + } + } catch (NumberFormatException e) { + } + } + return (-1); + } + + /** + * Returns the value of an option + */ + public static + String value(String option) { + if (table == null) { + return null; + } + return ((String) table.get(option.toLowerCase())); + } + +} diff --git a/src/dorkbox/network/dns/utils/ReverseMap.java b/src/dorkbox/network/dns/utils/ReverseMap.java new file mode 100644 index 00000000..9c26a2b3 --- /dev/null +++ b/src/dorkbox/network/dns/utils/ReverseMap.java @@ -0,0 +1,153 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.exceptions.TextParseException; + +/** + * A set functions designed to deal with DNS names used in reverse mappings. + * For the IPv4 address a.b.c.d, the reverse map name is d.c.b.a.in-addr.arpa. + * For an IPv6 address, the reverse map name is ...ip6.arpa. + * + * @author Brian Wellington + */ + +public final +class ReverseMap { + + private static Name inaddr4 = Name.fromConstantString("in-addr.arpa."); + private static Name inaddr6 = Name.fromConstantString("ip6.arpa."); + + /* Otherwise the class could be instantiated */ + private + ReverseMap() {} + + /** + * Creates a reverse map name corresponding to an address contained in + * an array of 4 integers between 0 and 255 (for an IPv4 address) or 16 + * integers between 0 and 255 (for an IPv6 address). + * + * @param addr The address from which to build a name. + * + * @return The name corresponding to the address in the reverse map. + */ + public static + Name fromAddress(int[] addr) { + byte[] bytes = new byte[addr.length]; + for (int i = 0; i < addr.length; i++) { + if (addr[i] < 0 || addr[i] > 0xFF) { + throw new IllegalArgumentException("array must " + "contain values " + "between 0 and 255"); + } + bytes[i] = (byte) addr[i]; + } + return fromAddress(bytes); + } + + /** + * Creates a reverse map name corresponding to an address contained in + * an array of 4 bytes (for an IPv4 address) or 16 bytes (for an IPv6 address). + * + * @param addr The address from which to build a name. + * + * @return The name corresponding to the address in the reverse map. + */ + public static + Name fromAddress(byte[] addr) { + if (addr.length != 4 && addr.length != 16) { + throw new IllegalArgumentException("array must contain " + "4 or 16 elements"); + } + + StringBuilder sb = new StringBuilder(); + if (addr.length == 4) { + for (int i = addr.length - 1; i >= 0; i--) { + sb.append(addr[i] & 0xFF); + if (i > 0) { + sb.append("."); + } + } + } + else { + int[] nibbles = new int[2]; + for (int i = addr.length - 1; i >= 0; i--) { + nibbles[0] = (addr[i] & 0xFF) >> 4; + nibbles[1] = (addr[i] & 0xFF) & 0xF; + for (int j = nibbles.length - 1; j >= 0; j--) { + sb.append(Integer.toHexString(nibbles[j])); + if (i > 0 || j > 0) { + sb.append("."); + } + } + } + } + + try { + if (addr.length == 4) { + return Name.fromString(sb.toString(), inaddr4); + } + else { + return Name.fromString(sb.toString(), inaddr6); + } + } catch (TextParseException e) { + throw new IllegalStateException("name cannot be invalid"); + } + } + + /** + * Creates a reverse map name corresponding to an address contained in + * an InetAddress. + * + * @param addr The address from which to build a name. + * + * @return The name corresponding to the address in the reverse map. + */ + public static + Name fromAddress(InetAddress addr) { + return fromAddress(addr.getAddress()); + } + + /** + * Creates a reverse map name corresponding to an address contained in + * a String. + * + * @param addr The address from which to build a name. + * + * @return The name corresponding to the address in the reverse map. + * + * @throws UnknownHostException The string does not contain a valid address. + */ + public static + Name fromAddress(String addr, int family) throws UnknownHostException { + byte[] array = Address.toByteArray(addr, family); + if (array == null) { + throw new UnknownHostException("Invalid IP address"); + } + return fromAddress(array); + } + + /** + * Creates a reverse map name corresponding to an address contained in + * a String. + * + * @param addr The address from which to build a name. + * + * @return The name corresponding to the address in the reverse map. + * + * @throws UnknownHostException The string does not contain a valid address. + */ + public static + Name fromAddress(String addr) throws UnknownHostException { + byte[] array = Address.toByteArray(addr, Address.IPv4); + if (array == null) { + array = Address.toByteArray(addr, Address.IPv6); + } + if (array == null) { + throw new UnknownHostException("Invalid IP address"); + } + return fromAddress(array); + } + +} diff --git a/src/dorkbox/network/dns/utils/Tokenizer.java b/src/dorkbox/network/dns/utils/Tokenizer.java new file mode 100644 index 00000000..34dbcac4 --- /dev/null +++ b/src/dorkbox/network/dns/utils/Tokenizer.java @@ -0,0 +1,888 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) +// +// Copyright (C) 2003-2004 Nominum, Inc. +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +package dorkbox.network.dns.utils; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +import dorkbox.network.dns.Name; +import dorkbox.network.dns.exceptions.RelativeNameException; +import dorkbox.network.dns.exceptions.TextParseException; +import dorkbox.network.dns.records.TTL; +import dorkbox.util.Base64Fast; + +/** + * Tokenizer is used to parse DNS records and zones from text format, + * + * @author Brian Wellington + * @author Bob Halley + */ + +public +class Tokenizer { + private static final char[] VALID = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] INTERNAL = new int[256]; + + static { + Arrays.fill(INTERNAL, -1); + for (int i = 0, iS = VALID.length; i < iS; i++) { + INTERNAL[VALID[i]] = 1; + } + INTERNAL['='] = 1; + } + + + private static String delim = " \t\n;()\""; + private static String quotes = "\""; + + /** + * End of file + */ + public static final int EOF = 0; + + /** + * End of line + */ + public static final int EOL = 1; + + /** + * Whitespace; only returned when wantWhitespace is set + */ + public static final int WHITESPACE = 2; + + /** + * An identifier (unquoted string) + */ + public static final int IDENTIFIER = 3; + + /** + * A quoted string + */ + public static final int QUOTED_STRING = 4; + + /** + * A comment; only returned when wantComment is set + */ + public static final int COMMENT = 5; + + private PushbackInputStream is; + private boolean ungottenToken; + private int multiline; + private boolean quoting; + private String delimiters; + private Token current; + private StringBuilder sb; + private boolean wantClose; + + private String filename; + private int line; + + + public static + class Token { + /** + * The type of token. + */ + public int type; + + /** + * The value of the token, or null for tokens without values. + */ + public String value; + + private + Token() { + type = -1; + value = null; + } + + private + Token set(int type, StringBuilder value) { + if (type < 0) { + throw new IllegalArgumentException(); + } + this.type = type; + this.value = value == null ? null : value.toString(); + return this; + } + + /** + * Converts the token to a string containing a representation useful + * for debugging. + */ + public + String toString() { + switch (type) { + case EOF: + return ""; + case EOL: + return ""; + case WHITESPACE: + return ""; + case IDENTIFIER: + return ""; + case QUOTED_STRING: + return ""; + case COMMENT: + return ""; + default: + return ""; + } + } + + /** + * Indicates whether this token contains a string. + */ + public + boolean isString() { + return (type == IDENTIFIER || type == QUOTED_STRING); + } + + /** + * Indicates whether this token contains an EOL or EOF. + */ + public + boolean isEOL() { + return (type == EOL || type == EOF); + } + } + + + public static + class TokenizerException extends TextParseException { + String message; + + public + TokenizerException(String filename, int line, String message) { + super(filename + ":" + line + ": " + message); + this.message = message; + } + + public + String getBaseMessage() { + return message; + } + } + + /** + * Creates a Tokenizer from a string. + * + * @param s The String to tokenize. + */ + public + Tokenizer(String s) { + this(new ByteArrayInputStream(s.getBytes())); + } + + /** + * Creates a Tokenizer from an arbitrary input stream. + * + * @param is The InputStream to tokenize. + */ + public + Tokenizer(InputStream is) { + if (!(is instanceof BufferedInputStream)) { + is = new BufferedInputStream(is); + } + this.is = new PushbackInputStream(is, 2); + ungottenToken = false; + multiline = 0; + quoting = false; + delimiters = delim; + current = new Token(); + sb = new StringBuilder(); + filename = ""; + line = 1; + } + + /** + * Creates a Tokenizer from a file. + * + * @param f The File to tokenize. + */ + public + Tokenizer(File f) throws FileNotFoundException { + this(new FileInputStream(f)); + wantClose = true; + filename = f.getName(); + } + + /** + * Gets the next token from a tokenizer and converts it to a string. + * + * @return The next token in the stream, as a string. + * + * @throws TextParseException The input was invalid or not a string. + * @throws IOException An I/O error occurred. + */ + public + String getString() throws IOException { + Token next = get(); + if (!next.isString()) { + throw exception("expected a string"); + } + return next.value; + } + + /** + * Gets the next token from a tokenizer, ignoring whitespace and comments. + * + * @return The next token in the stream. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + Token get() throws IOException { + return get(false, false); + } + + /** + * Gets the next token from a tokenizer. + * + * @param wantWhitespace If true, leading whitespace will be returned as a + * token. + * @param wantComment If true, comments are returned as tokens. + * + * @return The next token in the stream. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + Token get(boolean wantWhitespace, boolean wantComment) throws IOException { + int type; + int c; + + if (ungottenToken) { + ungottenToken = false; + if (current.type == WHITESPACE) { + if (wantWhitespace) { + return current; + } + } + else if (current.type == COMMENT) { + if (wantComment) { + return current; + } + } + else { + if (current.type == EOL) { + line++; + } + return current; + } + } + int skipped = skipWhitespace(); + if (skipped > 0 && wantWhitespace) { + return current.set(WHITESPACE, null); + } + type = IDENTIFIER; + sb.setLength(0); + while (true) { + c = getChar(); + if (c == -1 || delimiters.indexOf(c) != -1) { + if (c == -1) { + if (quoting) { + throw exception("EOF in " + "quoted string"); + } + else if (sb.length() == 0) { + return current.set(EOF, null); + } + else { + return current.set(type, sb); + } + } + if (sb.length() == 0 && type != QUOTED_STRING) { + if (c == '(') { + multiline++; + skipWhitespace(); + continue; + } + else if (c == ')') { + if (multiline <= 0) { + throw exception("invalid " + "close " + "parenthesis"); + } + multiline--; + skipWhitespace(); + continue; + } + else if (c == '"') { + if (!quoting) { + quoting = true; + delimiters = quotes; + type = QUOTED_STRING; + } + else { + quoting = false; + delimiters = delim; + skipWhitespace(); + } + continue; + } + else if (c == '\n') { + return current.set(EOL, null); + } + else if (c == ';') { + while (true) { + c = getChar(); + if (c == '\n' || c == -1) { + break; + } + sb.append((char) c); + } + if (wantComment) { + ungetChar(c); + return current.set(COMMENT, sb); + } + else if (c == -1 && type != QUOTED_STRING) { + checkUnbalancedParens(); + return current.set(EOF, null); + } + else if (multiline > 0) { + skipWhitespace(); + sb.setLength(0); + continue; + } + else { + return current.set(EOL, null); + } + } + else { + throw new IllegalStateException(); + } + } + else { + ungetChar(c); + } + break; + } + else if (c == '\\') { + c = getChar(); + if (c == -1) { + throw exception("unterminated escape sequence"); + } + sb.append('\\'); + } + else if (quoting && c == '\n') { + throw exception("newline in quoted string"); + } + sb.append((char) c); + } + if (sb.length() == 0 && type != QUOTED_STRING) { + checkUnbalancedParens(); + return current.set(EOF, null); + } + return current.set(type, sb); + } + + private + int skipWhitespace() throws IOException { + int skipped = 0; + while (true) { + int c = getChar(); + if (c != ' ' && c != '\t') { + if (!(c == '\n' && multiline > 0)) { + ungetChar(c); + return skipped; + } + } + skipped++; + } + } + + private + int getChar() throws IOException { + int c = is.read(); + if (c == '\r') { + int next = is.read(); + if (next != '\n') { + is.unread(next); + } + c = '\n'; + } + if (c == '\n') { + line++; + } + return c; + } + + private + void ungetChar(int c) throws IOException { + if (c == -1) { + return; + } + is.unread(c); + if (c == '\n') { + line--; + } + } + + private + void checkUnbalancedParens() throws TextParseException { + if (multiline > 0) { + throw exception("unbalanced parentheses"); + } + } + + /** + * Creates an exception which includes the current state in the error message + * + * @param s The error message to include. + * + * @return The exception to be thrown + */ + public + TextParseException exception(String s) { + return new TokenizerException(filename, line, s); + } + + /** + * Gets the next token from a tokenizer, ensures it is an unquoted string, + * and converts it to a string. + * + * @return The next token in the stream, as a string. + * + * @throws TextParseException The input was invalid or not an unquoted string. + * @throws IOException An I/O error occurred. + */ + public + String getIdentifier() throws IOException { + return _getIdentifier("an identifier"); + } + + private + String _getIdentifier(String expected) throws IOException { + Token next = get(); + if (next.type != IDENTIFIER) { + throw exception("expected " + expected); + } + return next.value; + } + + /** + * Gets the next token from a tokenizer and converts it to an unsigned 32 bit + * integer. + * + * @return The next token in the stream, as an unsigned 32 bit integer. + * + * @throws TextParseException The input was invalid or not an unsigned 32 + * bit integer. + * @throws IOException An I/O error occurred. + */ + public + long getUInt32() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFFFFFFFL) { + throw exception("expected an 32 bit unsigned integer"); + } + return l; + } + + /** + * Gets the next token from a tokenizer and converts it to a long. + * + * @return The next token in the stream, as a long. + * + * @throws TextParseException The input was invalid or not a long. + * @throws IOException An I/O error occurred. + */ + public + long getLong() throws IOException { + String next = _getIdentifier("an integer"); + if (!Character.isDigit(next.charAt(0))) { + throw exception("expected an integer"); + } + try { + return Long.parseLong(next); + } catch (NumberFormatException e) { + throw exception("expected an integer"); + } + } + + /** + * Gets the next token from a tokenizer and converts it to an unsigned 16 bit + * integer. + * + * @return The next token in the stream, as an unsigned 16 bit integer. + * + * @throws TextParseException The input was invalid or not an unsigned 16 + * bit integer. + * @throws IOException An I/O error occurred. + */ + public + int getUInt16() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFFFL) { + throw exception("expected an 16 bit unsigned integer"); + } + return (int) l; + } + + /** + * Gets the next token from a tokenizer and converts it to an unsigned 8 bit + * integer. + * + * @return The next token in the stream, as an unsigned 8 bit integer. + * + * @throws TextParseException The input was invalid or not an unsigned 8 + * bit integer. + * @throws IOException An I/O error occurred. + */ + public + int getUInt8() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFL) { + throw exception("expected an 8 bit unsigned integer"); + } + return (int) l; + } + + /** + * Gets the next token from a tokenizer and parses it as a TTL. + * + * @return The next token in the stream, as an unsigned 32 bit integer. + * + * @throws TextParseException The input was not valid. + * @throws IOException An I/O error occurred. + * @see TTL + */ + public + long getTTL() throws IOException { + String next = _getIdentifier("a TTL value"); + try { + return TTL.parseTTL(next); + } catch (NumberFormatException e) { + throw exception("expected a TTL value"); + } + } + + /** + * Gets the next token from a tokenizer and parses it as if it were a TTL. + * + * @return The next token in the stream, as an unsigned 32 bit integer. + * + * @throws TextParseException The input was not valid. + * @throws IOException An I/O error occurred. + * @see TTL + */ + public + long getTTLLike() throws IOException { + String next = _getIdentifier("a TTL-like value"); + try { + return TTL.parse(next, false); + } catch (NumberFormatException e) { + throw exception("expected a TTL-like value"); + } + } + + /** + * Gets the next token from a tokenizer and converts it to a name. + * + * @param origin The origin to append to relative names. + * + * @return The next token in the stream, as a name. + * + * @throws TextParseException The input was invalid or not a valid name. + * @throws IOException An I/O error occurred. + * @throws RelativeNameException The parsed name was relative, even with the + * origin. + * @see Name + */ + public + Name getName(Name origin) throws IOException { + String next = _getIdentifier("a name"); + try { + Name name = Name.fromString(next, origin); + if (!name.isAbsolute()) { + throw new RelativeNameException(name); + } + return name; + } catch (TextParseException e) { + throw exception(e.getMessage()); + } + } + + /** + * Gets the next token from a tokenizer and converts it to a byte array + * containing an IP address. + * + * @param family The address family. + * + * @return The next token in the stream, as an byte array representing an IP + * address. + * + * @throws TextParseException The input was invalid or not a valid address. + * @throws IOException An I/O error occurred. + * @see Address + */ + public + byte[] getAddressBytes(int family) throws IOException { + String next = _getIdentifier("an address"); + byte[] bytes = Address.toByteArray(next, family); + if (bytes == null) { + throw exception("Invalid address: " + next); + } + return bytes; + } + + /** + * Gets the next token from a tokenizer and converts it to an IP Address. + * + * @param family The address family. + * + * @return The next token in the stream, as an InetAddress + * + * @throws TextParseException The input was invalid or not a valid address. + * @throws IOException An I/O error occurred. + * @see Address + */ + public + InetAddress getAddress(int family) throws IOException { + String next = _getIdentifier("an address"); + try { + return Address.getByAddress(next, family); + } catch (UnknownHostException e) { + throw exception(e.getMessage()); + } + } + + /** + * Gets the next token from a tokenizer, which must be an EOL or EOF. + * + * @throws TextParseException The input was invalid or not an EOL or EOF token. + * @throws IOException An I/O error occurred. + */ + public + void getEOL() throws IOException { + Token next = get(); + if (next.type != EOL && next.type != EOF) { + throw exception("expected EOL or EOF"); + } + } + + /** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the base64 encoded data to a byte array. + * + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getBase64() throws IOException { + return getBase64(false); + } + + /** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the base64 encoded data to a byte array. + * + * @param required If true, an exception will be thrown if no strings remain; + * otherwise null be be returned. + * + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getBase64(boolean required) throws IOException { + String s = remainingStrings(); + if (s == null) { + if (required) { + throw exception("expected base64 encoded string"); + } + else { + return null; + } + } + + // have to validate the base-64 encoded strings first. + char[] chars = s.toCharArray(); + for (char aChar : chars) { + if (aChar > 256 || INTERNAL[aChar] != 1) { + // not valid + throw new TextParseException("Invalid base64 character!"); + } + } + + byte[] array = Base64Fast.decode2(s); + if (array == null) { + throw exception("invalid base64 encoding"); + } + + return array; + } + + /** + * Returns a concatenation of the remaining strings from a Tokenizer. + */ + private + String remainingStrings() throws IOException { + StringBuilder buffer = null; + while (true) { + Tokenizer.Token t = get(); + if (!t.isString()) { + break; + } + if (buffer == null) { + buffer = new StringBuilder(); + } + buffer.append(t.value); + } + unget(); + if (buffer == null) { + return null; + } + return buffer.toString(); + } + + /** + * Returns a token to the stream, so that it will be returned by the next call + * to get(). + * + * @throws IllegalStateException There are already ungotten tokens. + */ + public + void unget() { + if (ungottenToken) { + throw new IllegalStateException("Cannot unget multiple tokens"); + } + if (current.type == EOL) { + line--; + } + ungottenToken = true; + } + + /** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the hex encoded data to a byte array. + * + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getHex() throws IOException { + return getHex(false); + } + + /** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the hex encoded data to a byte array. + * + * @param required If true, an exception will be thrown if no strings remain; + * otherwise null be be returned. + * + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getHex(boolean required) throws IOException { + String s = remainingStrings(); + if (s == null) { + if (required) { + throw exception("expected hex encoded string"); + } + else { + return null; + } + } + byte[] array = base16.fromString(s); + if (array == null) { + throw exception("invalid hex encoding"); + } + return array; + } + + /** + * Gets the next token from a tokenizer and decodes it as hex. + * + * @return The byte array containing the decoded string. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getHexString() throws IOException { + String next = _getIdentifier("a hex string"); + byte[] array = base16.fromString(next); + if (array == null) { + throw exception("invalid hex encoding"); + } + return array; + } + + /** + * Gets the next token from a tokenizer and decodes it as base32. + * + * @param b32 The base32 context to decode with. + * + * @return The byte array containing the decoded string. + * + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ + public + byte[] getBase32String(base32 b32) throws IOException { + String next = _getIdentifier("a base32 string"); + byte[] array = b32.fromString(next); + if (array == null) { + throw exception("invalid base32 encoding"); + } + return array; + } + + /** + * Closes any files opened by this tokenizer. + */ + public + void close() { + if (wantClose) { + try { + is.close(); + } catch (IOException e) { + } + } + } + + @Override + protected + void finalize() { + close(); + } + +} diff --git a/src/dorkbox/network/dns/utils/base16.java b/src/dorkbox/network/dns/utils/base16.java new file mode 100644 index 00000000..b79fa80f --- /dev/null +++ b/src/dorkbox/network/dns/utils/base16.java @@ -0,0 +1,80 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Routines for converting between Strings of hex-encoded data and arrays of + * binary data. This is not actually used by DNS. + * + * @author Brian Wellington + */ + +public +class base16 { + + private static final String Base16 = "0123456789ABCDEF"; + + private + base16() {} + + /** + * Convert binary data to a hex-encoded String + * + * @param b An array containing binary data + * + * @return A String containing the encoded data + */ + public static + String toString(byte[] b) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < b.length; i++) { + short value = (short) (b[i] & 0xFF); + byte high = (byte) (value >> 4); + byte low = (byte) (value & 0xF); + os.write(Base16.charAt(high)); + os.write(Base16.charAt(low)); + } + return new String(os.toByteArray()); + } + + /** + * Convert a hex-encoded String to binary data + * + * @param str A String containing the encoded data + * + * @return An array containing the binary data, or null if the string is invalid + */ + public static + byte[] fromString(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) { + if (!Character.isWhitespace((char) raw[i])) { + bs.write(raw[i]); + } + } + byte[] in = bs.toByteArray(); + if (in.length % 2 != 0) { + return null; + } + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < in.length; i += 2) { + byte high = (byte) Base16.indexOf(Character.toUpperCase((char) in[i])); + byte low = (byte) Base16.indexOf(Character.toUpperCase((char) in[i + 1])); + try { + ds.writeByte((high << 4) + low); + } catch (IOException e) { + } + } + return bs.toByteArray(); + } + +} diff --git a/src/dorkbox/network/dns/utils/base32.java b/src/dorkbox/network/dns/utils/base32.java new file mode 100644 index 00000000..47d7c64c --- /dev/null +++ b/src/dorkbox/network/dns/utils/base32.java @@ -0,0 +1,229 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package dorkbox.network.dns.utils; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Routines for converting between Strings of base32-encoded data and arrays + * of binary data. This currently supports the base32 and base32hex alphabets + * specified in RFC 4648, sections 6 and 7. + * + * @author Brian Wellington + */ + +public +class base32 { + + private String alphabet; + + + private boolean padding, lowercase; + + + public static + class Alphabet { + public static final String BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + public static final String BASE32HEX = "0123456789ABCDEFGHIJKLMNOPQRSTUV="; + private + Alphabet() {} + } + + /** + * Creates an object that can be used to do base32 conversions. + * + * @param alphabet Which alphabet should be used + * @param padding Whether padding should be used + * @param lowercase Whether lowercase characters should be used. + * default parameters (The standard base32 alphabet, no padding, uppercase) + */ + public + base32(String alphabet, boolean padding, boolean lowercase) { + this.alphabet = alphabet; + this.padding = padding; + this.lowercase = lowercase; + } + + /** + * Convert binary data to a base32-encoded String + * + * @param b An array containing binary data + * + * @return A String containing the encoded data + */ + public + String toString(byte[] b) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < (b.length + 4) / 5; i++) { + short s[] = new short[5]; + int t[] = new int[8]; + + int blocklen = 5; + for (int j = 0; j < 5; j++) { + if ((i * 5 + j) < b.length) { + s[j] = (short) (b[i * 5 + j] & 0xFF); + } + else { + s[j] = 0; + blocklen--; + } + } + int padlen = blockLenToPadding(blocklen); + + // convert the 5 byte block into 8 characters (values 0-31). + + // upper 5 bits from first byte + t[0] = (byte) ((s[0] >> 3) & 0x1F); + // lower 3 bits from 1st byte, upper 2 bits from 2nd. + t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03)); + // bits 5-1 from 2nd. + t[2] = (byte) ((s[1] >> 1) & 0x1F); + // lower 1 bit from 2nd, upper 4 from 3rd + t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F)); + // lower 4 from 3rd, upper 1 from 4th. + t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01)); + // bits 6-2 from 4th + t[5] = (byte) ((s[3] >> 2) & 0x1F); + // lower 2 from 4th, upper 3 from 5th; + t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07)); + // lower 5 from 5th; + t[7] = (byte) (s[4] & 0x1F); + + // write out the actual characters. + for (int j = 0; j < t.length - padlen; j++) { + char c = alphabet.charAt(t[j]); + if (lowercase) { + c = Character.toLowerCase(c); + } + os.write(c); + } + + // write out the padding (if any) + if (padding) { + for (int j = t.length - padlen; j < t.length; j++) { + os.write('='); + } + } + } + + return new String(os.toByteArray()); + } + + static private + int blockLenToPadding(int blocklen) { + switch (blocklen) { + case 1: + return 6; + case 2: + return 4; + case 3: + return 3; + case 4: + return 1; + case 5: + return 0; + default: + return -1; + } + } + + /** + * Convert a base32-encoded String to binary data + * + * @param str A String containing the encoded data + * + * @return An array containing the binary data, or null if the string is invalid + */ + public + byte[] fromString(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) { + char c = (char) raw[i]; + if (!Character.isWhitespace(c)) { + c = Character.toUpperCase(c); + bs.write((byte) c); + } + } + + if (padding) { + if (bs.size() % 8 != 0) { + return null; + } + } + else { + while (bs.size() % 8 != 0) { + bs.write('='); + } + } + + byte[] in = bs.toByteArray(); + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < in.length / 8; i++) { + short[] s = new short[8]; + int[] t = new int[5]; + + int padlen = 8; + for (int j = 0; j < 8; j++) { + char c = (char) in[i * 8 + j]; + if (c == '=') { + break; + } + s[j] = (short) alphabet.indexOf(in[i * 8 + j]); + if (s[j] < 0) { + return null; + } + padlen--; + } + int blocklen = paddingToBlockLen(padlen); + if (blocklen < 0) { + return null; + } + + // all 5 bits of 1st, high 3 (of 5) of 2nd + t[0] = (s[0] << 3) | s[1] >> 2; + // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th + t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4); + // lower 4 of 4th, high 4 of 5th + t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F); + // lower 1 of 5th, all 5 of 6th, high 2 of 7th + t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3); + // lower 3 of 7th, all of 8th + t[4] = ((s[6] & 0x07) << 5) | s[7]; + + try { + for (int j = 0; j < blocklen; j++) { + ds.writeByte((byte) (t[j] & 0xFF)); + } + } catch (IOException e) { + } + } + + return bs.toByteArray(); + } + + static private + int paddingToBlockLen(int padlen) { + switch (padlen) { + case 6: + return 1; + case 4: + return 2; + case 3: + return 3; + case 1: + return 4; + case 0: + return 5; + default: + return -1; + } + } + +} diff --git a/src/dorkbox/network/dns/decoder/DomainDecoder.java b/src/io/nettyxbill/resolver/dns/NoopDnsQueryLifecycleObserverFactory.java similarity index 51% rename from src/dorkbox/network/dns/decoder/DomainDecoder.java rename to src/io/nettyxbill/resolver/dns/NoopDnsQueryLifecycleObserverFactory.java index 7fe701ad..d8de8399 100644 --- a/src/dorkbox/network/dns/decoder/DomainDecoder.java +++ b/src/io/nettyxbill/resolver/dns/NoopDnsQueryLifecycleObserverFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * Copyright 2017 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance @@ -13,21 +13,23 @@ * License for the specific language governing permissions and limitations * under the License. */ -package dorkbox.network.dns.decoder; +package io.nettyxbill.resolver.dns; -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.resolver.dns.DnsNameResolverAccess; +import dorkbox.network.dns.records.DnsMessage; +import io.netty.util.internal.UnstableApi; -/** - * Decodes domain names. IE: NS (name * server) and CNAME (canonical name) - */ -public -class DomainDecoder implements RecordDecoder { +@UnstableApi +public final +class NoopDnsQueryLifecycleObserverFactory implements DnsQueryLifecycleObserverFactory { + public static final NoopDnsQueryLifecycleObserverFactory INSTANCE = new NoopDnsQueryLifecycleObserverFactory(); + + private + NoopDnsQueryLifecycleObserverFactory() { + } @Override public - String decode(final DnsRecord record, final ByteBuf response) { - return DnsNameResolverAccess.decodeDomainName(response); + DnsQueryLifecycleObserver newDnsQueryLifecycleObserver(DnsMessage question) { + return NoopDnsQueryLifecycleObserver.INSTANCE; } }