Compare commits

...

40 Commits

Author SHA1 Message Date
Robinson 1c3a54ae22
Merge remote-tracking branch 'origin/master' 2023-10-11 12:17:11 +02:00
Robinson 4b212e2e08
Updated build deps 2023-10-09 12:32:05 +02:00
Robinson 86fb06e8e7
Updated build deps 2023-10-02 23:59:23 +02:00
Robinson 1e3db0d1ad
Version 2.16 2023-10-02 23:49:13 +02:00
Robinson c9755c6c82
NamedThreadFactory is now internal 2023-10-02 16:10:59 +02:00
Robinson 217c3e89ef
version 2.15 2023-09-07 18:21:28 +02:00
Robinson bb2aeec6dc
updated deps 2023-09-07 18:20:43 +02:00
Robinson 16c034e698
updated license 2023-08-20 13:34:10 +02:00
Robinson c75bf05ebc
version 2.14 2023-08-20 13:18:37 +02:00
Robinson 7dabfc9896
updated deps 2023-08-20 13:17:56 +02:00
Robinson f0dae840c0
update gradle 2023-08-19 22:13:16 +02:00
Robinson 36eef77621
version 2.13 2023-08-19 22:12:54 +02:00
Robinson d6bf061351
updated OS library 2023-08-19 22:12:16 +02:00
Robinson 60297f4ce3
version 2.12 2023-08-13 12:47:33 -05:00
Robinson fc7b3761e8
removed debug syserr 2023-08-13 12:46:36 -05:00
Robinson 33cee07d60
Better handling of thread detection during shutdown 2023-08-10 16:48:21 -06:00
Robinson 93aabd075d
version 2.11 2023-08-06 01:08:57 -06:00
Robinson da7e32ee2a
Updated deps 2023-08-06 01:07:45 -06:00
Robinson ac52c28367
Added MPLv2 license 2023-08-06 01:06:35 -06:00
Robinson 6fbd4c9fb1
Hardcoded project name 2023-08-06 01:05:38 -06:00
Robinson 66a8a5c031
Removed utils dependency, updated collections.
Updated build deps
updated version
2023-08-04 17:00:38 -06:00
Robinson d4bbf71d1f
updated version 2023-05-01 14:30:42 +02:00
Robinson 895dd2b7fd
Lifecycle factories are now kotlin 2023-05-01 14:30:13 +02:00
Robinson 42f4b81d5b
Rename .java to .kt 2023-05-01 14:30:13 +02:00
Robinson 25fb0ff1fb
updated version 2023-02-22 18:27:27 +01:00
Robinson 4ce8bbcec0
More clearly define public key verification data structures. 2023-02-22 18:25:59 +01:00
Robinson 20ef556c41
getHostName returns null INSTEAD of throwing exceptions 2023-02-22 18:21:57 +01:00
Robinson f616ec60b0
An empty record cannot be null and not-null at the same time. This fixes the unit test (since an empty record is valid) 2023-02-22 18:20:13 +01:00
Robinson ad18ba1734
Formatting 2023-02-22 18:16:23 +01:00
Robinson b9fa8312f0
updated logger 2023-02-22 12:04:39 +01:00
Robinson 561f0ab5bc
updated dependencies 2023-02-22 12:00:49 +01:00
Robinson 09d63394a2
Updated comments, made logger internal. 2023-02-22 11:57:25 +01:00
Robinson 0259b17bf4
updated license dates 2023-01-12 11:21:15 +01:00
Robinson f7496cbdd7
Updated dependencies 2022-12-29 23:20:31 +01:00
Robinson 0bb31461af
updated utils 2022-12-15 23:56:29 +01:00
Robinson a79113db2c
Updated gradle, updated build deps. 2022-11-21 13:51:53 +01:00
Robinson 54646207bf
Updated deps 2022-11-11 22:58:58 +01:00
Robinson 9f2642db56
Updated license 2022-07-27 00:20:58 +02:00
Robinson 5b18e16d89
Removed coroutines from parts of the networking message processing. 2022-07-27 00:20:34 +02:00
Robinson 8d73a3018a
Updated library 2022-07-26 23:29:07 +02:00
200 changed files with 26861 additions and 846 deletions

547
LICENSE
View File

@ -1,7 +1,7 @@
- NetworkDNS -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/NetworkDNS
Copyright 2022
Copyright 2023
Dorkbox LLC
High-performance and event-driven/reactive DNS stack for Java 8+
@ -15,16 +15,10 @@
- Netty - An event-driven asynchronous network application framework
[The Apache Software License, Version 2.0]
https://netty.io
Copyright 2022
Copyright 2023
The Netty Project
Contributors. See source NOTICE
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2022
QOS.ch
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
@ -33,45 +27,149 @@
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
https://www.slf4j.org
Copyright 2023
QOS.ch
- Collections - Collection types and utilities to enhance the default collections.
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
Copyright 2023
Dorkbox LLC
Extra license information
- Bias, BinarySearch -
[MIT License]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/timboudreau/util
Copyright 2013
Tim Boudreau
- ConcurrentEntry -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
Copyright 2016
bennidi
dorkbox
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
- Predicate -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
xoppa
- Select, QuickSelect -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
Jon Renner
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- NetworkUtils - Utilities for managing network configurations, IP/MAC address conversion, and ping (via OS native commands)
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/NetworkUtils
Copyright 2022
Copyright 2023
Dorkbox LLC
Extra license information
- Netty -
[The Apache Software License, Version 2.0]
https://netty.io/
https://netty.io
Copyright 2014
The Netty Project
This product contains a modified portion of Netty Network Utils
- Apache Harmony -
[The Apache Software License, Version 2.0]
http://archive.apache.org/dist/harmony/
https://archive.apache.org/dist/harmony/
Copyright 2010
The Apache Software Foundation
This product contains a modified portion of 'Apache Harmony', an open source Java SE
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
- Mozilla Public Suffix List -
[Mozilla Public License 2.0]
https://publicsuffix.org/list/public_suffix_list.dat
Copyright 2010
The Apache Software Foundation
- Apache HTTP Utils -
[The Apache Software License, Version 2.0]
https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/psl/
Copyright 2010
The Apache Software Foundation
This product contains a modified portion of 'PublicSuffixDomainFilter.java'
- UrlRewriteFilter - UrlRewriteFilter is a Java Web Filter for any J2EE compliant web application server
[BSD 3-Clause License]
https://github.com/paultuckey/urlrewritefilter
Copyright 2022
QOS.ch
Paul Tuckey
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2023
JetBrains s.r.o.
- JNA - Simplified native library access for Java.
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2022
Copyright 2023
Timothy Wall
- JNA-Platform - Mappings for a number of commonly used platform functions
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2022
Copyright 2023
Timothy Wall
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
https://www.slf4j.org
Copyright 2023
QOS.ch
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
@ -83,7 +181,7 @@
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Executor
Copyright 2022
Copyright 2023
Dorkbox LLC
Extra license information
@ -110,25 +208,25 @@
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2022
Copyright 2023
JetBrains s.r.o.
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2022
https://www.slf4j.org
Copyright 2023
QOS.ch
- Logback - Logback is a logging framework for Java applications
[The Apache Software License, Version 2.0]
http://logback.qos.ch
Copyright 2022
https://logback.qos.ch
Copyright 2023
QOS.ch
- SSHJ - SSHv2 library for Java
[The Apache Software License, Version 2.0]
https://github.com/hierynomus/sshj
Copyright 2022
Copyright 2023
Jeroen van Erp
SSHJ Contributors
@ -145,13 +243,13 @@
- JZlib -
[The Apache Software License, Version 2.0]
http://www.jcraft.com/jzlib
https://github.com/ymnk/jzlib
Atsuhiko Yamanaka
JCraft, Inc.
- Bouncy Castle Crypto -
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
https://www.bouncycastle.org
The Legion of the Bouncy Castle Inc
- ed25519-java -
@ -192,7 +290,7 @@
- OS - Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/OS
Copyright 2022
Copyright 2023
Dorkbox LLC
Extra license information
@ -219,401 +317,6 @@
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Utilities - Utilities for use within Java projects
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2022
Dorkbox LLC
Extra license information
- MersenneTwisterFast -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2003
Sean Luke
Michael Lecuyer (portions Copyright 1993
- FileUtil (code from FilenameUtils.java for normalize + dependencies) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
http://commons.apache.org/proper/commons-io/
Copyright 2013
The Apache Software Foundation
Kevin A. Burton
Scott Sanders
Daniel Rall
Christoph.Reck
Peter Donald
Jeff Turner
Matthew Hawthorne
Martin Cooper
Jeremias Maerki
Stephen Colebourne
- FastThreadLocal -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java
https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java
Copyright 2014
Lightweight Java Game Library Project
Riven
- Base64Fast -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
http://migbase64.sourceforge.net/
Copyright 2004
Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)
- BCrypt -
[BSD 2-Clause "Simplified" or "FreeBSD" license]
https://git.dorkbox.com/dorkbox/Utilities
http://www.mindrot.org/projects/jBCrypt
Copyright 2006
Damien Miller (djm@mindrot.org)
GWT modified version
- Modified hex conversion utility methods -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://netty.io
Copyright 2014
The Netty Project
- Retrofit - A type-safe HTTP client for Android and Java
[The Apache Software License, Version 2.0]
https://github.com/square/retrofit
Copyright 2020
Square, Inc
- Resource Listing - Listing the contents of a resource directory
[The Apache Software License, Version 2.0]
http://www.uofr.net/~greg/java/get-resource-listing.html
Copyright 2017
Greg Briggs
- CommonUtils - Common utility extension functions for kotlin
[The Apache Software License, Version 2.0]
https://www.pronghorn.tech
Copyright 2017
Pronghorn Technology LLC
Dorkbox LLC
- UrlRewriteFilter - UrlRewriteFilter is a Java Web Filter for any J2EE compliant web application server
[BSD 3-Clause License]
https://github.com/paultuckey/urlrewritefilter
Copyright 2022
Paul Tuckey
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2022
JetBrains s.r.o.
- Java Uuid Generator - A set of Java classes for working with UUIDs
[The Apache Software License, Version 2.0]
https://github.com/cowtowncoder/java-uuid-generator
Copyright 2022
Tatu Saloranta (tatu.saloranta@iki.fi)
Contributors. See source release-notes/CREDITS
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2022
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2022
QOS.ch
- XZ for Java - Complete implementation of XZ data compression in pure Java
[Public Domain, per Creative Commons CC0]
https://tukaani.org/xz/java.html
Copyright 2022
Lasse Collin
Igor Pavlov
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- JNA - Simplified native library access for Java.
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2022
Timothy Wall
- JNA-Platform - Mappings for a number of commonly used platform functions
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2022
Timothy Wall
- Netty - An event-driven asynchronous network application framework
[The Apache Software License, Version 2.0]
https://netty.io
Copyright 2022
The Netty Project
Contributors. See source NOTICE
- Bouncy Castle Crypto - Lightweight cryptography API and JCE Extension
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
Copyright 2022
The Legion of the Bouncy Castle Inc
- Lightweight Java Game Library - Java library that enables cross-platform access to popular native APIs
[BSD 3-Clause License]
https://github.com/LWJGL/lwjgl3
Copyright 2022
Lightweight Java Game Library
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
[The Apache Software License, Version 2.0]
https://github.com/jhalterman/typetools
Copyright 2022
Jonathan Halterman and friends
- Collections - Niche collections to augment what is already available.
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
Copyright 2022
Dorkbox LLC
Extra license information
- AhoCorasickDoubleArrayTrie - Niche collections to augment what is already available.
[The Apache Software License, Version 2.0]
https://github.com/hankcs/AhoCorasickDoubleArrayTrie
Copyright 2018
hankcs <me@hankcs.com>
- Bias, BinarySearch -
[MIT License]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/timboudreau/util
Copyright 2013
Tim Boudreau
- ConcurrentEntry -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
Copyright 2016
bennidi
dorkbox
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
- Predicate -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
xoppa
- Select, QuickSelect -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
Jon Renner
- TimSort, ComparableTimSort -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Collections
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2008
The Android Open Source Project
- ConcurrentWeakIdentityHashMap - Concurrent WeakIdentity HashMap
[The Apache Software License, Version 2.0]
https://github.com/spring-projects/spring-loaded/blob/master/springloaded/src/main/java/org/springsource/loaded/support/ConcurrentWeakIdentityHashMap.java
Copyright 2016
zhanhb
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 8+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Executor
Copyright 2022
Dorkbox LLC
Extra license information
- ZT Process Executor -
[The Apache Software License, Version 2.0]
https://github.com/zeroturnaround/zt-exec
Copyright 2014
ZeroTurnaround LLC
- Apache Commons Exec -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-exec/
Copyright 2014
The Apache Software Foundation
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2022
JetBrains s.r.o.
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2022
QOS.ch
- Logback - Logback is a logging framework for Java applications
[The Apache Software License, Version 2.0]
http://logback.qos.ch
Copyright 2022
QOS.ch
- SSHJ - SSHv2 library for Java
[The Apache Software License, Version 2.0]
https://github.com/hierynomus/sshj
Copyright 2022
Jeroen van Erp
SSHJ Contributors
Extra license information
- Apache MINA -
[The Apache Software License, Version 2.0]
https://mina.apache.org/sshd-project/
The Apache Software Foundation
- Apache Commons-Net -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-net/
The Apache Software Foundation
- JZlib -
[The Apache Software License, Version 2.0]
http://www.jcraft.com/jzlib
Atsuhiko Yamanaka
JCraft, Inc.
- Bouncy Castle Crypto -
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
The Legion of the Bouncy Castle Inc
- ed25519-java -
[Public Domain, per Creative Commons CC0]
https://github.com/str4d/ed25519-java
https://github.com/str4d
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- OS - Information about the system, Java runtime, OS, Window Manager, and Desktop Environment.
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/OS
Copyright 2022
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates

373
LICENSE.MPLv2 Normal file
View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -17,7 +17,7 @@ Maven Info
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>NetworkDNS</artifactId>
<version>2.7</version>
<version>2.16</version>
</dependency>
</dependencies>
```
@ -27,7 +27,7 @@ Gradle Info
```
dependencies {
...
implementation("com.dorkbox:NetworkDNS:2.7")
implementation("com.dorkbox:NetworkDNS:2.16")
}
```

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License.
*/
import java.time.Instant
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
///////////////////////////////
////// PUBLISH TO SONATYPE / MAVEN CENTRAL
@ -25,19 +25,19 @@ import java.time.Instant
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
plugins {
id("com.dorkbox.GradleUtils") version "2.17"
id("com.dorkbox.Licensing") version "2.12"
id("com.dorkbox.VersionUpdate") version "2.5"
id("com.dorkbox.GradlePublish") version "1.12"
id("com.dorkbox.GradleUtils") version "3.18"
id("com.dorkbox.Licensing") version "2.28"
id("com.dorkbox.VersionUpdate") version "2.8"
id("com.dorkbox.GradlePublish") version "1.20"
kotlin("jvm") version "1.6.10"
kotlin("jvm") version "1.9.0"
}
object Extras {
// set for the project
const val description = "High-performance and event-driven/reactive DNS stack for Java 8+"
const val group = "com.dorkbox"
const val version = "2.7"
const val version = "2.16"
// set as project.ext
const val name = "NetworkDNS"
@ -45,8 +45,6 @@ object Extras {
const val vendor = "Dorkbox LLC"
const val vendorUrl = "https://dorkbox.com"
const val url = "https://git.dorkbox.com/dorkbox/NetworkDNS"
val buildDate = Instant.now().toString()
}
///////////////////////////////
@ -81,28 +79,28 @@ tasks.jar.get().apply {
attributes["Specification-Vendor"] = Extras.vendor
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
attributes["Implementation-Version"] = Extras.buildDate
attributes["Implementation-Version"] = GradleUtils.now()
attributes["Implementation-Vendor"] = Extras.vendor
}
}
dependencies {
api("com.dorkbox:NetworkUtils:2.15")
api("com.dorkbox:OS:1.0")
api("com.dorkbox:Utilities:1.25")
api("com.dorkbox:Collections:2.6")
api("com.dorkbox:NetworkUtils:2.23")
api("com.dorkbox:OS:1.8")
api("com.dorkbox:Updates:1.1")
val nettyVer = "4.1.77.Final"
val nettyVer = "4.1.99.Final"
api("io.netty:netty-buffer:$nettyVer")
api("io.netty:netty-transport:$nettyVer")
api("io.netty:netty-transport-native-epoll:$nettyVer")
api("io.netty:netty-transport-classes-kqueue:$nettyVer")
api("io.netty:netty-codec:$nettyVer")
api("org.slf4j:slf4j-api:1.8.0-beta4")
implementation("org.slf4j:slf4j-api:2.0.9")
testImplementation("junit:junit:4.13.2")
testImplementation("ch.qos.logback:logback-classic:1.3.0-alpha4")
testImplementation("ch.qos.logback:logback-classic:1.4.5")
}
publishToSonatype {
@ -128,3 +126,14 @@ publishToSonatype {
email = "email@dorkbox.com"
}
}
repositories {
mavenCentral()
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

6
gradlew vendored
View File

@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

14
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1,5 +1,5 @@
/*
* Copyright 2018 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,3 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
rootProject.name = "NetworkDNS"

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.chain;
public interface Chain<CTX, R extends ChainResult> {
R execute(CTX context);
}

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.chain;
public interface ChainResult {
boolean hasNext();
}

View File

@ -0,0 +1,29 @@
package org.handwerkszeug.chain.impl;
import java.util.ArrayList;
import java.util.List;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.ChainResult;
public class DefaultChainExecutor<CTX, R extends ChainResult> implements
Chain<CTX, R> {
protected List<Chain<CTX, R>> chains = new ArrayList<Chain<CTX, R>>();
@Override
public R execute(CTX context) {
R r = null;
for (Chain<CTX, R> c : this.chains) {
r = c.execute(context);
if (r.hasNext() == false) {
break;
}
}
return r;
}
public void add(Chain<CTX, R> c) {
this.chains.add(c);
}
}

View File

@ -0,0 +1,26 @@
package org.handwerkszeug.chain.impl;
import org.handwerkszeug.chain.ChainResult;
public class SimpleChainResult implements ChainResult {
public static final ChainResult Continue = new SimpleChainResult(true);
public static final ChainResult Terminate = new SimpleChainResult();
protected boolean hasNext;
public SimpleChainResult() {
this(false);
}
public SimpleChainResult(boolean hasNext) {
this.hasNext = hasNext;
}
@Override
public boolean hasNext() {
return this.hasNext;
}
}

View File

@ -0,0 +1,13 @@
package org.handwerkszeug.dns;
public class Constants {
public static final String BASE_NAME = "org.handwerkszeug.dns";
public static final String SYSTEM_PROPERTY_NAMESERVERS = BASE_NAME
+ ".nameservers";
public static final String SYSTEM_PROPERTY_ACTIVE_NAMESERVER_CONTAINER = BASE_NAME
+ ".container";
public static final int DEFAULT_PORT = 53;
}

View File

@ -0,0 +1,56 @@
package org.handwerkszeug.dns;
import org.handwerkszeug.util.EnumUtil;
import org.handwerkszeug.util.VariableEnum;
/**
* RFC1035 3.2.4. CLASS values
*
* @author taichi
*/
public enum DNSClass implements VariableEnum {
/**
* the Internet
*/
IN(1),
/**
* the CSNET class (Obsolete - used only for examples in some obsolete RFCs)
*/
CS(2),
/**
* the CHAOS class
*/
CH(3),
/**
* Hesiod [Dyer 87]
*/
HS(4),
/**
* any class
*/
ANY(255);
private int value;
@Override
public int value() {
return this.value;
}
private DNSClass(int i) {
this.value = i;
}
public static DNSClass valueOf(int value) {
return EnumUtil.find(DNSClass.values(), value);
}
public static DNSClass find(String value) {
return EnumUtil.find(DNSClass.values(), value, null);
}
}

View File

@ -0,0 +1,185 @@
package org.handwerkszeug.dns;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.handwerkszeug.dns.record.AbstractRecord;
import io.netty.buffer.ByteBuf;
/**
* RFC1035 4. MESSAGES
*
* <pre>
* +---------------------+
* | Header |
* +---------------------+
* | Question | the question for the name server
* +---------------------+
* | Answer | RRs answering the question
* +---------------------+
* | Authority | RRs pointing toward an authority
* +---------------------+
* | Additional | RRs holding additional information
* +---------------------+
* </pre>
*
* @author taichi
*/
public class DNSMessage {
protected Header header;
protected List<ResourceRecord> question;
protected List<ResourceRecord> answer;
protected List<ResourceRecord> authority;
protected List<ResourceRecord> additional;
protected int messageSize;
public DNSMessage(Header header) {
this.header(header);
this.question = new ArrayList<ResourceRecord>();
this.answer = new ArrayList<ResourceRecord>();
this.authority = new ArrayList<ResourceRecord>();
this.additional = new ArrayList<ResourceRecord>();
}
public DNSMessage() {
this(new Header());
}
public DNSMessage(DNSMessage from) {
this(new Header(from.header()));
this.question().addAll(from.question());
this.answer().addAll(from.answer());
this.authority().addAll(from.authority());
this.additional().addAll(from.additional());
this.messageSize(from.messageSize());
}
public DNSMessage(ByteBuf buffer) {
this.header = new Header(buffer);
if (this.header.rcode().equals(RCode.FormErr)) {
this.question = Collections.emptyList();
this.answer = Collections.emptyList();
this.authority = Collections.emptyList();
this.additional = Collections.emptyList();
} else {
this.parse(buffer);
}
}
protected void parse(ByteBuf buffer) {
Header header = this.header();
int q = header.qdcount();
if (q < 1) {
this.question = Collections.emptyList();
}
else {
this.question(new ArrayList<ResourceRecord>(q));
for (int i = 0; i < q; i++) {
this.question()
.add(AbstractRecord.parseSection(buffer));
}
}
this.answer(parse(buffer, header.ancount()));
this.authority(parse(buffer, header.nscount()));
this.additional(parse(buffer, header.arcount()));
this.messageSize(buffer.readerIndex());
}
protected static
List<ResourceRecord> parse(ByteBuf buffer, int size) {
if (size < 1) {
return Collections.emptyList();
}
List<ResourceRecord> result = new ArrayList<ResourceRecord>(size);
for (int i = 0; i < size; i++) {
ResourceRecord rr = AbstractRecord.parseSection(buffer);
rr.parse(buffer);
result.add(rr);
}
return result;
}
public void write(ByteBuf buffer) {
header().qdcount(this.question().size());
header().ancount(this.answer().size());
header().nscount(this.authority().size());
header().arcount(this.additional().size());
header().write(buffer);
NameCompressor nc = new SimpleNameCompressor();
for (ResourceRecord rr : this.question()) {
AbstractRecord.writeSection(buffer, nc, rr);
}
write(buffer, nc, answer());
write(buffer, nc, authority());
write(buffer, nc, additional());
}
protected void write(ByteBuf buffer, NameCompressor compressor,
List<ResourceRecord> list) {
for (ResourceRecord rr : list) {
AbstractRecord.writeSection(buffer, compressor, rr);
rr.write(buffer, compressor);
}
}
public Header header() {
return this.header;
}
public void header(Header header) {
this.header = header;
}
/**
* 4.1.2. Question section format
*/
public List<ResourceRecord> question() {
return this.question;
}
public void question(List<ResourceRecord> list) {
this.question = list;
}
public List<ResourceRecord> answer() {
return this.answer;
}
public void answer(List<ResourceRecord> list) {
this.answer = list;
}
public List<ResourceRecord> authority() {
return this.authority;
}
public void authority(List<ResourceRecord> list) {
this.authority = list;
}
public List<ResourceRecord> additional() {
return this.additional;
}
public void additional(List<ResourceRecord> list) {
this.additional = list;
}
public int messageSize() {
return this.messageSize;
}
public void messageSize(int size) {
this.messageSize = size;
}
}

View File

@ -0,0 +1,362 @@
package org.handwerkszeug.dns;
import java.security.SecureRandom;
import java.text.MessageFormat;
import org.handwerkszeug.dns.nls.Messages;
import io.netty.buffer.ByteBuf;
/**
* RFC1035 4.1.1. Header section format
*
* <pre>
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ID |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |QR| DnsOpCode |AA|TC|RD|RA| Z | RCODE |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | QDCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ANCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | NSCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ARCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* </pre>
*/
public class Header {
static final int MIN_USHORT = 0;
static final int MAX_USHORT = 0xFFFF;
static final int FLAGS_QR = 15;
static final int FLAGS_Opcode = 11;
static final int FLAGS_AA = 10;
static final int FLAGS_TC = 9;
static final int FLAGS_RD = 8;
static final int FLAGS_RA = 7;
static final int FLAGS_Z = 4;
static final int FLAGS_RCODE = 0;
static final int[] FLAGS_ALL = { FLAGS_QR, FLAGS_AA, FLAGS_TC, FLAGS_RD, FLAGS_RA };
static final String[] FLAGS_TXT = { "qr", "aa", "tc", "rd", "ra" };
static final SecureRandom RANDOM;
static {
RANDOM = new SecureRandom();
byte[] seed = RANDOM.generateSeed(20); // TODO more ? from config?
RANDOM.setSeed(seed);
}
protected int id;
// include there flags |QR| DnsOpCode |AA|TC|RD|RA| Z | RCODE|
protected int flags;
protected int qdcount;
protected int ancount;
protected int nscount;
protected int arcount;
public Header() {
this(RANDOM.nextInt(MAX_USHORT));
}
protected Header(int id) {
id(id);
}
public Header(ByteBuf in) {
this(in.readUnsignedShort());
flags(in.readUnsignedShort());
qdcount(in.readUnsignedShort());
ancount(in.readUnsignedShort());
nscount(in.readUnsignedShort());
arcount(in.readUnsignedShort());
}
public Header(Header from) {
this();
this.flags(from.flags());
this.qdcount(from.qdcount());
this.ancount(from.ancount());
this.nscount(from.nscount());
this.arcount(from.arcount());
}
public void write(ByteBuf out) {
out.writeShort(id());
out.writeShort(flags());
out.writeShort(qdcount());
out.writeShort(ancount());
out.writeShort(nscount());
out.writeShort(arcount());
}
/**
* A 16 bit identifier assigned by the program that generates any kind of
* query. This identifier is copied the corresponding reply and can be used
* by the requester to match up replies to outstanding queries.
*
*/
public int id() {
return this.id;
}
public void id(int i) {
this.id = verify16bitValue("ID", i);
}
private static int verify16bitValue(String column, int i) {
if ((i < MIN_USHORT) || (MAX_USHORT < i)) {
throw new IllegalArgumentException(String.format(
Messages.Not16bitValue, column, i));
}
return i;
}
public int flags() {
return this.flags;
}
protected void flags(int flags) {
this.flags = verify16bitValue("Flags", flags);
}
protected int flag(int shift, int mask) {
return (this.flags >> shift) & mask;
}
/**
* A one bit field that specifies whether this message is a query (0), or a
* response (1).
*/
public boolean qr() {
return flag(FLAGS_QR, 0x1) != 0;
}
public void qr(boolean is) {
flip(FLAGS_QR, is);
}
/**
* @see OpCode
*/
public OpCode opcode() {
int code = flag(FLAGS_Opcode, 0xF);
return OpCode.valueOf(code); // TODO cache?
}
public void opcode(OpCode op) {
// clear current opcode
this.flags &= 0x87FF; // 1000 0111 1111 1111
// set opcode
this.flags |= op.value() << FLAGS_Opcode;
}
/**
* Authoritative Answer - this bit is valid in responses, and specifies that
* the responding name server is an authority for the domain name in
* question section.
*/
public boolean aa() {
return flag(FLAGS_AA, 0x1) != 0;
}
public void aa(boolean is) {
flip(FLAGS_AA, is);
}
private void flip(int index, boolean is) {
int i = 1 << index; // TODO move to caller ?
if (is) {
this.flags |= i;
} else {
this.flags &= i ^ 0xFFFF;
}
}
/**
* TrunCation - specifies that this message was truncated due to length
* greater than that permitted on the transmission channel.
*/
public boolean tc() {
return flag(FLAGS_TC, 0x1) != 0;
}
public void tc(boolean is) {
flip(FLAGS_TC, is);
}
/**
* Recursion Desired - this bit may be set in a query and is copied into the
* response. If RD is set, it directs the name server to pursue the query
* recursively. Recursive query support is optional.
*/
public boolean rd() {
return flag(FLAGS_RD, 0x1) != 0;
}
public void rd(boolean is) {
flip(FLAGS_RD, is);
}
/**
* Recursion Available - this be is set or cleared in a response, and
* denotes whether recursive query support is available in the name server.
*/
public boolean ra() {
return flag(FLAGS_RA, 0x1) != 0;
}
public void ra(boolean is) {
flip(FLAGS_RA, is);
}
/**
* Reserved for future use. Must be zero in all queries and responses.
*/
public int z() {
return flag(FLAGS_Z, 0x7);
}
/**
* @see RCode
*/
public RCode rcode() {
int code = flag(FLAGS_RCODE, 0xF);
return RCode.valueOf(code); // TODO cache ?
}
public void rcode(RCode rc) {
// clear current response code
this.flags &= 0xFFF0; // 1111 1111 1111 0000
// set response code
this.flags |= rc.value();
}
/**
* an unsigned 16 bit integer specifying the number of entries in the
* question section.
*/
public int qdcount() {
return this.qdcount;
}
public void qdcount(int value) {
this.qdcount = verify16bitValue("qdcount", value);
}
/**
* an unsigned 16 bit integer specifying the number of resource records in
* the answer section.
*/
public int ancount() {
return this.ancount;
}
public void ancount(int value) {
this.ancount = verify16bitValue("ancount", value);
}
/**
* an unsigned 16 bit integer specifying the number of name server resource
* records in the authority records section.
*/
public int nscount() {
return this.nscount;
}
public void nscount(int value) {
this.nscount = verify16bitValue("nscount", value);
}
/**
* an unsigned 16 bit integer specifying the number of resource records in
* the additional records section.
*/
public int arcount() {
return this.arcount;
}
public void arcount(int value) {
this.arcount = verify16bitValue("arcount", value);
}
@Override
public String toString() {
MessageFormat form = new MessageFormat(";; ->>HEADER<<- "
+ "opcode: {0}, rcode: {1}, id: {2,number,#}\n"
+ ";; flags: {3}; QUERY: {4,number,#}, "
+ "ANSWER: {5,number,#}, " + "AUTHORITY: {6,number,#}, "
+ "ADDITIONAL: {7,number,#}");
Object[] args = { opcode().name(), rcode().name(), id(),
toFlagsString(), qdcount(), ancount(), nscount(), arcount() };
return form.format(args);
}
protected String toFlagsString() {
StringBuilder stb = new StringBuilder();
for (int i = 0, length = FLAGS_ALL.length; i < length; i++) {
if (flag(FLAGS_ALL[i], 0x1) != 0) {
stb.append(FLAGS_TXT[i]);
stb.append(" ");
}
}
return stb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.id;
result = prime * result + this.flags;
result = prime * result + this.qdcount;
result = prime * result + this.ancount;
result = prime * result + this.nscount;
result = prime * result + this.arcount;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof Header) {
Header other = (Header) obj;
return equals(other);
}
return false;
}
public boolean equals(Header other) {
if (other == null) {
return false;
}
if (this.id != other.id) {
return false;
}
if (this.flags != other.flags) {
return false;
}
if (this.qdcount != other.qdcount) {
return false;
}
if (this.ancount != other.ancount) {
return false;
}
if (this.nscount != other.nscount) {
return false;
}
if (this.arcount != other.arcount) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,46 @@
package org.handwerkszeug.dns;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public
class Markers {
/**
* maker prefixes.
*/
public static final String PREFIX_PKG = "org.handwerkszeug.dns";
public static final Marker MARKER_ROOT = MarkerFactory.getMarker(PREFIX_PKG);
/**
* using for design decision. like plug-in or add-ins.
*/
public static final Marker DESIGN = MarkerFactory.getMarker(PREFIX_PKG + ".design");
/**
* using for boundaries. like I/O or another library.
*/
public static final Marker BOUNDARY = MarkerFactory.getMarker(PREFIX_PKG + ".boundary");
/**
* using for object lifecycle.
*/
public static final Marker LIFECYCLE = MarkerFactory.getMarker(PREFIX_PKG + ".lifecycle");
/**
* using for implementation details. primary purpose is debugging.
*/
public static final Marker DETAIL = MarkerFactory.getMarker(PREFIX_PKG + ".detail");
/**
* using for profiling.
*/
public static final Marker PROFILE = MarkerFactory.getMarker(PREFIX_PKG + ".profile");
static {
Marker[] markers = {DESIGN, BOUNDARY, LIFECYCLE, DETAIL, PROFILE};
for (Marker m : markers) {
MARKER_ROOT.add(m);
}
}
}

View File

@ -0,0 +1,414 @@
package org.handwerkszeug.dns;
import static org.handwerkszeug.util.Validation.notNull;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.handwerkszeug.dns.nls.Messages;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class Name implements Comparable<Name> {
public static final Name NULL_NAME;
public static final Name WILDCARD;
static final byte[] NULL_ARRAY = new byte[0];
static final byte[] WILDCARD_ARRAY = new byte[] { '*' };
static {
NULL_NAME = create(NULL_ARRAY);
WILDCARD = create(WILDCARD_ARRAY);
}
static Name create(byte[] array) {
List<byte[]> l = new ArrayList<byte[]>(1);
l.add(array);
return new Name(Collections.unmodifiableList(l));
}
/**
* 4.1.4. DnsMessage compression
*/
public static final int MASK_POINTER = 0xC0; // 1100 0000
/**
* 2.3.4. Size limits
*/
public static final int MAX_LABEL_SIZE = 63; // 0011 1111
/**
* 2.3.4. Size limits
*/
public static final int MAX_NAME_SIZE = 255;
protected final List<byte[]> name;
public Name(ByteBuf buffer) {
this.name = this.parse(buffer);
}
public Name(String name) {
this.name = this.parse(name);
}
protected Name(List<byte[]> rawdata) {
this.name = rawdata;
}
protected List<byte[]> parse(ByteBuf buffer) {
List<byte[]> list = new ArrayList<byte[]>();
boolean jumped = false;
int namesize = 0;
for (int length = buffer.readUnsignedByte(); -1 < length; length = buffer
.readUnsignedByte()) {
if (length == 0) {
list.add(NULL_ARRAY);
break;
} else if ((length & MASK_POINTER) != 0) {
int p = ((length ^ MASK_POINTER) << 8)
+ buffer.readUnsignedByte();
if (jumped == false) {
buffer.markReaderIndex();
jumped = true;
}
buffer.readerIndex(p);
} else if (length <= MAX_LABEL_SIZE) {
namesize += length;
if (MAX_NAME_SIZE < namesize) {
throw new IllegalArgumentException(String.format(
Messages.NamesMustBe255orLess, namesize));
}
byte[] ary = new byte[length];
buffer.readBytes(ary);
list.add(ary);
} else {
throw new IllegalStateException(String.format(
Messages.InvalidCompressionMask, length));
}
}
if (jumped) {
buffer.resetReaderIndex();
}
return Collections.unmodifiableList(list);
}
/**
* 5.1. Format
*
* <pre>
* \X where X is any character other than a digit (0-9), is
* used to quote that character so that its special meaning
* does not apply. For example, "\." can be used to place
* a dot character in a label.
*
* \DDD where each D is a digit is the octet corresponding to
* the decimal number described by DDD. The resulting
* octet is assumed to be text and is not checked for
* special meaning.
* </pre>
*
* @param namedata
* @return
*/
protected List<byte[]> parse(String namedata) {
if (".".equals(namedata)) {
return NULL_NAME.name;
}
// TODO IDN support from RFC3490 RFC3491 RFC3492 RFC3454
List<byte[]> result = new ArrayList<byte[]>();
byte[] bytes = namedata.getBytes();
int namesize = 0;
ByteBuf buffer = Unpooled.buffer(MAX_LABEL_SIZE);
int current = 0;
int length = bytes.length;
boolean escape = false;
int digits = 0;
int value = 0;
for (; current < length; current++) {
byte b = bytes[current];
if (escape) {
if ((('0' <= b) && (b <= '9')) && (digits++ < 3)) {
value *= 10;
value += (b - '0');
if (255 < value) {
throw new IllegalArgumentException(String.format(
Messages.EscapedDecimalIsInvalid, value));
}
if (2 < digits) {
appendByte(namedata, buffer, (byte) value);
escape = false;
}
} else if (0 < digits) {
throw new IllegalArgumentException(
String.format(
Messages.MixtureOfEscapedDigitAndNonDigit,
namedata));
} else {
appendByte(namedata, buffer, b);
escape = false;
}
} else if (b == '\\') {
escape = true;
digits = 0;
value = 0;
} else if (b == '.') {
namesize = namesize + addBytes(result, buffer) + 1;
} else {
appendByte(namedata, buffer, b);
}
}
if (escape) {
throw new IllegalArgumentException(String.format(
Messages.InvalidEscapeSequence, namedata));
}
if (buffer.isReadable()) {
// relative domain name
namesize = namesize + addBytes(result, buffer);
} else {
// absolute domain name
result.add(NULL_ARRAY);
}
buffer.release();
namesize += 1;
if (MAX_NAME_SIZE < namesize) {
throw new IllegalArgumentException(String.format(
Messages.NamesMustBe255orLess, namesize));
}
return result;
}
protected void appendByte(String namedata, ByteBuf buffer, byte b) {
if (buffer.isWritable()) {
buffer.writeByte(b);
} else {
throw new IllegalArgumentException(String.format(Messages.LabelsMustBe63orLess, namedata));
}
}
protected int addBytes(List<byte[]> result, ByteBuf buffer) {
int size = buffer.readableBytes();
if (size < 1) {
throw new IllegalArgumentException(Messages.NullLabelIsNotValid);
}
byte[] newone = new byte[size];
buffer.readBytes(newone);
buffer.clear();
result.add(newone);
return size;
}
public void write(ByteBuf buffer, NameCompressor compressor) {
// TODO DNAME and other non compress RR
// TODO need writing cache?
if (writePointer(buffer, compressor, this) == false) {
compressor.put(this, buffer.writerIndex());
for (int i = 0, size = this.name.size(); i < size; i++) {
byte[] current = this.name.get(i);
int cl = current.length;
buffer.writeByte(cl);
if (0 < cl) {
buffer.writeBytes(current);
if (i + 1 < size) {
Name n = new Name(this.name.subList(i + 1, size));
if (writePointer(buffer, compressor, n)) {
break;
} else {
compressor.put(n, buffer.writerIndex());
}
}
}
}
}
}
protected boolean writePointer(ByteBuf buffer,
NameCompressor compressor, Name n) {
int position = compressor.get(n);
if (-1 < position) {
int pointer = (MASK_POINTER << 8) | position;
buffer.writeShort(pointer);
return true;
}
return false;
}
public Name toParent() {
int size = this.name.size();
if (1 < size) {
List<byte[]> newone = this.name.subList(1, size);
return new Name(newone);
}
return NULL_NAME;
}
public Name toWildcard() {
int size = this.name.size();
if (1 < size) {
List<byte[]> newone = new ArrayList<byte[]>(size);
newone.add(WILDCARD_ARRAY);
newone.addAll(this.name.subList(1, size));
return new Name(Collections.unmodifiableList(newone));
}
return WILDCARD;
}
public boolean contains(Name other) {
notNull(other, "other");
int mySize = this.name.size();
int otherSize = other.name.size();
if (mySize < otherSize) {
return false;
}
int diff = mySize - otherSize;
for (int i = 0; i < otherSize; i++) {
byte[] me = this.name.get(i + diff);
byte[] yu = other.name.get(i);
if (Arrays.equals(me, yu) == false) {
return false;
}
}
return true;
}
public Name replace(Name from, Name to) {
notNull(from, "from");
notNull(to, "to");
if (contains(from)) {
int diff = this.name.size() - from.name.size();
int newsize = diff + to.name.size();
List<byte[]> newone = new ArrayList<byte[]>(newsize);
newone.addAll(this.name.subList(0, diff));
newone.addAll(to.name);
int size = 0;
for (byte[] ary : newone) {
size += ary.length;
}
if (size < MAX_NAME_SIZE) {
return new Name(Collections.unmodifiableList(newone));
}
}
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
for (byte[] b : this.name) {
result = prime * result + Arrays.hashCode(b);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Name) {
return equals(Name.class.cast(obj));
}
return false;
}
public boolean equals(Name other) {
if (other == null) {
return false;
}
int mySize = this.name.size();
int yrSize = other.name.size();
if (mySize != yrSize) {
return false;
}
for (int i = 0; i < mySize; i++) {
byte[] me = this.name.get(i);
byte[] yu = other.name.get(i);
if (Arrays.equals(me, yu) == false) {
return false;
}
}
return true;
}
@Override
public int compareTo(Name o) {
// TODO use more effective algorithm for red black tree.
int mySize = this.name.size();
int yrSize = o.name.size();
if (mySize != yrSize) {
return mySize - yrSize;
}
int minSize = Math.min(mySize, yrSize);
for (int i = minSize - 1; (-1 < i); i--) {
byte[] mine = this.name.get(i);
byte[] other = o.name.get(i);
if (Arrays.equals(mine, other) == false) {
int size = Math.min(mine.length, other.length);
for (int ii = size - 1; -1 < ii; i--) {
byte mb = mine[ii];
byte yb = other[ii];
if (mb != yb) {
if (mb < yb) {
return -1;
} else {
return 1;
}
}
}
}
}
return 0;
}
@Override
public String toString() {
StringBuilder stb = new StringBuilder();
DecimalFormat fmt = new DecimalFormat();
fmt.setMinimumIntegerDigits(3);
for (Iterator<byte[]> cursor = this.name.iterator(); cursor.hasNext();) {
byte[] ary = cursor.next();
for (int i = 0, l = ary.length; i < l; i++) {
int b = ary[i] & 0xFF;
if ((b < 0x21) || (0x7F < b)) {
stb.append('\\');
stb.append(fmt.format(b));
} else {
switch (b) {
case '"':
case '(':
case ')':
case '.':
case ';':
case '\\':
case '@':
case '$':
stb.append('\\');
default:
stb.append((char) b);
break;
}
}
}
if (cursor.hasNext()) {
stb.append('.');
}
}
return stb.toString();
}
}

View File

@ -0,0 +1,25 @@
package org.handwerkszeug.dns;
/**
* 3.3. Standard RRs
* <p>
* The following RR definitions are expected to occur, at least potentially, in
* all classes. In particular, NS, SOA, CNAME, and PTR will be used in all
* classes, and have the same format in all classes. Because their RDATA format
* is known, all domain names in the RDATA section of these RRs may be
* compressed.
* </p>
*
* 4.1.4. DnsMessage compression
*
* @author taichi
*
*/
public interface NameCompressor {
void put(Name name, int offset);
int get(Name name);
}

View File

@ -0,0 +1,13 @@
package org.handwerkszeug.dns;
import java.util.List;
import werkzeugkasten.common.util.Disposable;
import werkzeugkasten.common.util.Initializable;
public interface NameServerContainer extends Initializable, Disposable {
String name();
List<String> nameservers();
}

View File

@ -0,0 +1,47 @@
package org.handwerkszeug.dns;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import werkzeugkasten.common.util.Disposable;
import werkzeugkasten.common.util.Initializable;
public class NameServerContainerProvider implements Initializable, Disposable {
public static final String DEFAULT_NAME = "default";
protected Map<String, NameServerContainer> containers = new HashMap<String, NameServerContainer>();
@Override
public void initialize() {
initialize(Thread.currentThread().getContextClassLoader());
}
public void initialize(ClassLoader classLoader) {
ServiceLoader<NameServerContainer> loader = ServiceLoader.load(
NameServerContainer.class, classLoader);
for (NameServerContainer nc : loader) {
this.containers.put(nc.name(), nc);
}
}
@Override
public void dispose() {
this.containers.clear();
}
public NameServerContainer getContainer() {
String name = System
.getProperty(Constants.SYSTEM_PROPERTY_ACTIVE_NAMESERVER_CONTAINER);
return getContainer(name);
}
public NameServerContainer getContainer(String name) {
NameServerContainer result = this.containers.get(name);
if (result == null) {
result = this.containers.get(DEFAULT_NAME);
}
return result;
}
}

View File

@ -0,0 +1,20 @@
package org.handwerkszeug.dns;
public class NullNameCompressor implements NameCompressor {
public static final NullNameCompressor INSTANCE = new NullNameCompressor();
private NullNameCompressor() {
}
@Override
public void put(Name name, int offset) {
}
@Override
public int get(Name name) {
return -1;
}
}

View File

@ -0,0 +1,45 @@
package org.handwerkszeug.dns;
import org.handwerkszeug.util.EnumUtil;
import org.handwerkszeug.util.VariableEnum;
/**
* A four bit field that specifies kind of query in this message. This value is
* set by the originator of a query and copied into the response. The values
* are:
*
* @author taichi
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
* System (DNS) Parameters</a>
*/
public enum OpCode implements VariableEnum {
/**
* a standard query
*/
QUERY(0),
/**
* an inverse query
*/
IQUERY(1),
/**
* a server status request
*/
STATUS(2);
private int code;
private OpCode(int i) {
this.code = i;
}
@Override
public int value() {
return this.code;
}
public static OpCode valueOf(int code) {
return EnumUtil.find(OpCode.values(), code);
}
}

View File

@ -0,0 +1,92 @@
package org.handwerkszeug.dns;
import org.handwerkszeug.util.EnumUtil;
import org.handwerkszeug.util.VariableEnum;
/**
* Response code - this 4 bit field is set as part of responses. The values have
* the following interpretation:
*
* @author taichi
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
* System (DNS) Parameters</a>
*/
public enum RCode implements VariableEnum {
/**
* No error condition
*/
NoError(0),
/**
* Format error - The name server was unable to interpret the query.
*/
FormErr(1),
/**
* Server failure - The name server was unable to process this query due to
* a problem with the name server.
*/
ServFail(2),
/**
* Name Error - Meaningful only for responses from an authoritative name
* server, this code signifies that the domain name referenced in the query
* does not exist.
*/
NXDomain(3),
/**
* Not Implemented - The name server does not support the requested kind of
* query.
*/
NotImp(4),
/**
* Refused - The name server refuses to perform the specified operation for
* policy reasons. For example, a name server may not wish to provide the
* information to the particular requester, or a name server may not wish to
* perform a particular operation (e.g., zone transfer) for particular data.
*/
Refused(5),
/**
* Name Exists when it should not
*/
YXDomain(6),
/**
* RR Set Exists when it should not
*/
YXRRSet(7),
/**
* RR Set that should exist does not
*/
NXRRSet(8),
/**
* Server Not Authoritative for zone
*/
NotAuth(9),
/**
* Name not contained in zone
*/
NotZone(10);
private int code;
@Override
public int value() {
return this.code;
}
private RCode(int i) {
this.code = i;
}
public static RCode valueOf(int code) {
return EnumUtil.find(RCode.values(), code);
}
}

View File

@ -0,0 +1,292 @@
package org.handwerkszeug.dns;
import org.handwerkszeug.dns.nls.Messages;
import org.handwerkszeug.dns.record.AAAARecord;
import org.handwerkszeug.dns.record.ARecord;
import org.handwerkszeug.dns.record.HINFORecord;
import org.handwerkszeug.dns.record.MINFORecord;
import org.handwerkszeug.dns.record.MXRecord;
import org.handwerkszeug.dns.record.NULLRecord;
import org.handwerkszeug.dns.record.SOARecord;
import org.handwerkszeug.dns.record.SingleNameRecord;
import org.handwerkszeug.dns.record.TXTRecord;
import org.handwerkszeug.dns.record.WKSRecord;
import org.handwerkszeug.util.EnumUtil;
import org.handwerkszeug.util.VariableEnum;
/**
* 3.2.2. TYPE values
*
* @author taichi
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
* System (DNS) Parameters</a>
*/
public enum RRType implements VariableEnum {
/**
* a host address
*/
A(1) {
@Override
public ResourceRecord newRecord() {
return new ARecord();
}
},
/**
* an authoritative name server
*/
NS(2) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* a mail destination (Obsolete - use MX)
*/
MD(3) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* a mail forwarder (Obsolete - use MX)
*/
MF(4) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* the canonical name for an alias
*/
CNAME(5) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* marks the start of a zone of authority
*/
SOA(6) {
@Override
public ResourceRecord newRecord() {
return new SOARecord();
}
},
/**
* a mailbox domain name (EXPERIMENTAL)
*/
MB(7) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* a mail group member (EXPERIMENTAL)
*/
MG(8) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* a mail rename domain name (EXPERIMENTAL)
*/
MR(9) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* a null RR (EXPERIMENTAL)
*/
NULL(10) {
@Override
public ResourceRecord newRecord() {
return new NULLRecord();
}
},
/**
* a well known service description
*/
WKS(11) {
@Override
public ResourceRecord newRecord() {
return new WKSRecord();
}
},
/**
* a domain name pointer
*/
PTR(12) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
/**
* host information
*/
HINFO(13) {
@Override
public ResourceRecord newRecord() {
return new HINFORecord();
}
},
/**
* mailbox or mail list information
*/
MINFO(14) {
@Override
public ResourceRecord newRecord() {
return new MINFORecord();
}
},
/**
* mail exchange
*/
MX(15) {
@Override
public ResourceRecord newRecord() {
return new MXRecord();
}
},
/**
* text strings
*/
TXT(16) {
@Override
public ResourceRecord newRecord() {
return new TXTRecord();
}
},
/**
* IP6 Address
*/
AAAA(28) {
@Override
public ResourceRecord newRecord() {
return new AAAARecord();
}
},
/**
* Naming Authority Pointer
*
* @see http://tools.ietf.org/html/rfc3403#section-4
*/
NAPTR(35) {
@Override
public ResourceRecord newRecord() {
// TODO not implemented...
throw new UnsupportedOperationException();
}
},
/**
* Non-Terminal DNS Name Redirection
*
* @see http://www.ietf.org/rfc/rfc2672.txt
*/
DNAME(39) {
@Override
public ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
// RFC6891 6.1.1 OPT values
/**
* An OPT pseudo-RR (sometimes called a meta-RR) MAY be added to the additional data section of a request.
*
* @see http://www.ietf.org/rfc/rfc6891.txt
*/
OPT(41) {
@Override
public
ResourceRecord newRecord() {
return new SingleNameRecord(this);
}
},
// RFC1035 3.2.3. QTYPE values
/**
* A request for a transfer of an entire zone
*/
AXFR(252) {
@Override
public ResourceRecord newRecord() {
throw new UnsupportedOperationException(String.format(
Messages.NoResourceRecord, AXFR.name()));
}
},
/**
* A request for mailbox-related records (MB, MG or MR)
*/
MAILB(253) {
@Override
public ResourceRecord newRecord() {
throw new UnsupportedOperationException(String.format(
Messages.NoResourceRecord, MAILB.name()));
}
},
/**
* A request for mail agent RRs (Obsolete - see MX)
*/
MAILA(254) {
@Override
public ResourceRecord newRecord() {
throw new UnsupportedOperationException(String.format(
Messages.NoResourceRecord, MAILA.name()));
}
},
/**
* A request for all records
*/
ANY(255) {
@Override
public ResourceRecord newRecord() {
throw new UnsupportedOperationException(String.format(
Messages.NoResourceRecord, ANY.name()));
}
},
UNKNOWN(-1) {
@Override
public ResourceRecord newRecord() {
return new NULLRecord();
}
};
private int code;
private RRType(int i) {
this.code = i;
}
public abstract ResourceRecord newRecord();
@Override
public int value() {
return this.code;
}
public static RRType valueOf(int code) {
return EnumUtil.find(RRType.values(), code, UNKNOWN);
}
public static RRType find(String value) {
return EnumUtil.find(RRType.values(), value, null);
}
}

View File

@ -0,0 +1,10 @@
package org.handwerkszeug.dns;
public interface ResolveContext {
DNSMessage request();
DNSMessage response();
Response resolve(Name qname, RRType qtype);
}

View File

@ -0,0 +1,12 @@
package org.handwerkszeug.dns;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.impl.SimpleChainResult;
/**
*
* @author taichi
*/
public interface Resolver extends Chain<ResolveContext, SimpleChainResult> {
}

View File

@ -0,0 +1,88 @@
package org.handwerkszeug.dns;
import java.util.List;
import io.netty.buffer.ByteBuf;
/**
* 4.1.3. Resource record format<br/>
* The answer, authority, and additional sections all share the same format: a
* variable number of resource records, where the number of records is specified
* in the corresponding count field in the header. Each resource record has the
* following format:
*
* <pre>
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | |
* / /
* / NAME /
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | TYPE |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | CLASS |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | TTL |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | RDLENGTH |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
* / RDATA /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* </pre>
*
* @author taichi
* @see <a href="http://www.iana.org/assignments/dns-parameters">Domain Name
* System (DNS) Parameters</a>
*/
public interface ResourceRecord {
/**
* two octets containing one of the RR type codes. This field specifies the
* meaning of the data in the RDATA field.
*/
RRType type();
/**
* a domain name to which this resource record pertains.
*/
Name name();
void name(Name name);
/**
* two octets which specify the class of the data in the RDATA field.
*/
DNSClass dnsClass();
void dnsClass(DNSClass dnsClass);
/**
* a 32 bit unsigned integer that specifies the time interval (in seconds)
* that the resource record may be cached before it should be discarded.
* Zero values are interpreted to mean that the RR can only be used for the
* transaction in progress, and should not be cached.
*/
long ttl();
void ttl(long ttl);
/**
* an unsigned 16 bit integer that specifies the length in octets of the
* RDATA field.
*/
int rdlength();
void rdlength(int rdlength);
void setRDATA(List<String> list);
void parse(ByteBuf buffer);
void write(ByteBuf buffer, NameCompressor compressor);
ResourceRecord toQnameRecord(Name qname);
}

View File

@ -0,0 +1,11 @@
package org.handwerkszeug.dns;
/**
* @author taichi
*/
public interface Response {
RCode rcode();
void postProcess(ResolveContext context);
}

View File

@ -0,0 +1,21 @@
package org.handwerkszeug.dns;
import java.util.HashMap;
import java.util.Map;
public class SimpleNameCompressor implements NameCompressor {
protected Map<Name, Integer> map = new HashMap<Name, Integer>();
public void put(Name name, int offset) {
this.map.put(name, offset);
}
public int get(Name name) {
Integer i = this.map.get(name);
if (i == null) {
return -1;
}
return i.intValue();
}
}

View File

@ -0,0 +1,11 @@
package org.handwerkszeug.dns;
public interface Zone {
Name name();
DNSClass dnsClass();
ZoneType type();
Response find(Name qname, RRType qtype);
}

View File

@ -0,0 +1,7 @@
package org.handwerkszeug.dns;
public enum ZoneType {
master, slave, stub, forward, rootHint;
}

View File

@ -0,0 +1,285 @@
package org.handwerkszeug.dns.aaaa;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.handwerkszeug.dns.Constants;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.Header;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameServerContainer;
import org.handwerkszeug.dns.NameServerContainerProvider;
import org.handwerkszeug.dns.OpCode;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.client.WKPortNumbers;
import org.handwerkszeug.dns.client.WKProtocols;
import org.handwerkszeug.dns.nls.Messages;
import org.handwerkszeug.dns.record.WKSRecord;
import org.handwerkszeug.util.EnumUtil;
import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
import werkzeugkasten.common.util.StringUtil;
public class DNSClient extends SimpleChannelHandler {
protected static final String LINE_SEP = System.getProperty("line.separator");
protected NameServerContainer container;
protected WKProtocols wkProtocols = new WKProtocols();
protected WKPortNumbers wkPortNumbers = new WKPortNumbers();
protected List<String> names = new ArrayList<String>();
protected DNSClass dnsclass = DNSClass.IN;
protected RRType type = RRType.A;
protected OpCode opCode = OpCode.QUERY;
protected InetSocketAddress serverAddress;
protected int serverPort = Constants.DEFAULT_PORT; // default DNS port.
protected DNSMessage request;
protected long time;
public static void main(String[] args) throws Exception {
DNSClient client = new DNSClient();
client.initialize();
client.process(args);
}
protected void initialize() {
NameServerContainerProvider provider = new NameServerContainerProvider();
provider.initialize();
this.container = provider.getContainer();
this.container.initialize();
this.wkProtocols.load();
this.wkPortNumbers.load();
}
protected void process(String[] args) throws Exception {
parseArgs(args);
setUpRequest();
sendRequest();
}
protected void parseArgs(String[] args) throws Exception {
InetAddress address = null;
for (int i = 0, length = args.length; i < length; i++) {
String current = args[i];
if (current.startsWith("@")) {
String s = current.substring(1);
address = InetAddress.getByName(s);
} else if (current.startsWith("-") && (1 < current.length())) {
switch (current.charAt(1)) {
case 'x': {
this.opCode = OpCode.IQUERY;
break;
}
case 'p': {
String next = args[++i];
if (Pattern.matches("\\p{Digit}+", next)) {
int num = Integer.parseInt(next);
if ((0 < num) && (num < 0xFFFF)) {
this.serverPort = num;
} else {
System.err.printf(Messages.InvalidPortNumber, num);
}
}
break;
}
}
} else {
String s = current.toUpperCase();
DNSClass dc = EnumUtil.find(DNSClass.values(), s, null);
if (dc != null) {
this.dnsclass = dc;
continue;
}
RRType t = EnumUtil.find(RRType.values(), s, null);
if (t != null) {
this.type = t;
continue;
}
this.names.add(current);
}
}
if (address == null) {
address = findDNSServer();
}
this.serverAddress = new InetSocketAddress(address, this.serverPort);
}
protected InetAddress findDNSServer() throws Exception {
List<String> list = this.container.nameservers();
if (0 < list.size()) {
String host = list.get(0).toString();
return InetAddress.getByName(host);
}
return InetAddress.getLocalHost();
}
protected void setUpRequest() {
this.request = new DNSMessage(new Header());
this.request.header().opcode(this.opCode);
this.request.header().rd(true);
for (String s : this.names) {
String name = s;
if ((OpCode.IQUERY.equals(this.opCode) == false)
&& (s.endsWith(".") == false)) {
name += ".";
}
ResourceRecord rr = this.type.newRecord();
rr.name(new Name(name));
this.request.question().add(rr);
}
}
protected void sendRequest() {
// use UDP/IP
ChannelFactory factory = new NioDatagramChannelFactory(Executors.newSingleThreadExecutor());
try {
ConnectionlessBootstrap bootstrap = new ConnectionlessBootstrap(factory);
bootstrap.getPipeline().addLast("handler", DNSClient.this);
bootstrap.setOption("broadcast", "false");
// bootstrap.setOption("sendBufferSize", 512);
// bootstrap.setOption("receiveBufferSize", 512);
ChannelFuture future = bootstrap.connect(this.serverAddress);
future.awaitUninterruptibly(30, TimeUnit.SECONDS);
if (future.isSuccess() == false) {
future.getCause().printStackTrace();
}
future.getChannel().getCloseFuture().awaitUninterruptibly();
} finally {
factory.releaseExternalResources();
}
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
System.out.println("DNSClient#channelConnected");
System.out.printf("Local %s | Remote %s\n", e.getChannel()
.getLocalAddress(), e.getChannel().getRemoteAddress());
ChannelBuffer buffer = ChannelBuffers.buffer(512);
this.request.write(buffer);
this.time = System.currentTimeMillis();
e.getChannel().write(buffer);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
System.out.println("DNSClient#messageReceived");
System.out.println(e.getChannel().getLocalAddress() + " | "
+ e.getChannel().getRemoteAddress());
this.time = System.currentTimeMillis() - this.time;
ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
DNSMessage msg = new DNSMessage(buffer);
printResult(this.time, msg);
e.getChannel().close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
e.getChannel().close();
}
protected String formatMessage(long time, DNSMessage msg) {
StringBuilder stb = new StringBuilder();
stb.append(msg.header());
stb.append(LINE_SEP);
stb.append(LINE_SEP);
stb.append(";; QUESTION SECTION:");
stb.append(LINE_SEP);
for (ResourceRecord rr : msg.question()) {
stb.append(';');
int i = stb.length();
stb.append(rr.name().toString());
StringUtil.padRight(stb, ' ', i + 30);
stb.append(' ');
stb.append(rr.dnsClass().name());
stb.append(' ');
stb.append(rr.type().name());
stb.append(LINE_SEP);
}
stb.append(LINE_SEP);
append(stb, ";; ANSWER SECTION:", msg.answer());
stb.append(LINE_SEP);
append(stb, ";; AUTHORITY SECTION:", msg.authority());
stb.append(LINE_SEP);
append(stb, ";; ADDITIONAL SECTION:", msg.additional());
stb.append(LINE_SEP);
stb.append(";; Query time: ");
stb.append(time);
stb.append(" msec");
stb.append(LINE_SEP);
stb.append(";; Server: ");
stb.append(this.serverAddress);
stb.append(LINE_SEP);
stb.append(";; When: ");
stb.append(DateFormat.getDateTimeInstance().format(new Date()));
stb.append(LINE_SEP);
stb.append(";; Message size: ");
stb.append(msg.messageSize());
stb.append(" bytes");
stb.append(LINE_SEP);
return stb.toString();
}
protected void append(StringBuilder stb, String name, List<ResourceRecord> list) {
stb.append(name);
stb.append(LINE_SEP);
if (list != null) {
for (ResourceRecord rr : list) {
stb.append(rr.toString());
if (rr.type().equals(RRType.WKS)) {
append(stb, (WKSRecord) rr);
}
stb.append(LINE_SEP);
}
}
}
protected void append(StringBuilder stb, WKSRecord record) {
stb.append(' ');
stb.append(this.wkProtocols.find(record.protocol()));
stb.append(' ');
this.wkPortNumbers.appendServices(record, stb);
}
protected void printResult(long time, DNSMessage msg) {
System.out.println(formatMessage(time, msg));
}
protected void printHelp() {
// TODO print help message.
}
}

View File

@ -0,0 +1,133 @@
package org.handwerkszeug.dns.aaaa;
import java.io.File;
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.conf.ServerConfiguration;
import org.handwerkszeug.dns.conf.ServerConfigurationImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelFactory;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import werkzeugkasten.common.util.Disposable;
import werkzeugkasten.common.util.Initializable;
public class DNSServer implements Initializable, Disposable {
protected static Logger LOG = LoggerFactory.getLogger(DNSServer.class);
protected ServerConfiguration config;
protected ChannelFactory serverChannelFactory;
protected ChannelFactory clientChannelFactory;
protected ConnectionlessBootstrap bootstrap;
protected ChannelGroup group;
public static void main(String[] args) throws Exception {
ServerConfiguration conf = parseArgs(args);
final DNSServer server = new DNSServer(conf);
server.initialize();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
server.dispose();
}
});
server.process();
}
public static ServerConfiguration parseArgs(String[] args) throws Exception {
ServerConfigurationImpl config = new ServerConfigurationImpl();
URL from = null;
if ((args != null) && (0 < args.length)) {
from = readFrom(args[0]);
}
if (from == null) {
from = readFrom("named.yml");
}
if (from == null) {
LOG.info("read named.default.yml from ClassLoader");
ClassLoader cl = Thread.currentThread().getContextClassLoader();
from = cl.getResource("named.default.yml");
}
if (from == null) {
throw new IllegalStateException("configuration file is not found.");
}
config.load(from);
return config;
}
public static URL readFrom(String path) throws MalformedURLException {
File f = new File(path);
LOG.info("read from {}", f.getAbsolutePath());
if (f.exists()) {
if (f.canRead()) {
return f.toURI().toURL();
} else {
LOG.info("{} cannot read", f.getAbsolutePath());
}
} else {
LOG.info("{} is not exists", f.getAbsolutePath());
}
return null;
}
public DNSServer(ServerConfiguration config) {
this.config = config;
}
@Override
public void initialize() {
LOG.debug(Markers.LIFECYCLE, "initialize server");
ExecutorService executor = Executors.newFixedThreadPool(this.config.getThreadPoolSize());
// TODO need TCP?
this.clientChannelFactory = new NioDatagramChannelFactory(executor);
// TODO TCP and/or UDP
this.serverChannelFactory = new NioDatagramChannelFactory(executor);
ChannelPipelineFactory pipelineFactory = new DNSServerPipelineFactory(this.config, this.clientChannelFactory);
this.bootstrap = new ConnectionlessBootstrap(this.serverChannelFactory);
this.bootstrap.setPipelineFactory(pipelineFactory);
this.group = new DefaultChannelGroup();
}
public void process() {
for (SocketAddress sa : this.config.getBindingHosts()) {
LOG.info(Markers.BOUNDARY, "binding {}", sa);
this.group.add(this.bootstrap.bind(sa));
}
}
@Override
public void dispose() {
try {
this.group.close().awaitUninterruptibly();
} finally {
dispose(this.clientChannelFactory);
dispose(this.serverChannelFactory);
}
}
protected void dispose(ExternalResourceReleasable releasable) {
try {
releasable.releaseExternalResources();
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
}

View File

@ -0,0 +1,29 @@
package org.handwerkszeug.dns.aaaa;
import org.handwerkszeug.dns.conf.ServerConfiguration;
import org.handwerkszeug.dns.server.DNSMessageDecoder;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
public class DNSServerPipelineFactory implements ChannelPipelineFactory {
protected ServerConfiguration config;
protected ChannelFactory clientChannelFactory;
protected DNSMessageDecoder decoder = new DNSMessageDecoder();
public DNSServerPipelineFactory(ServerConfiguration config, ChannelFactory clientChannelFactory) {
this.config = config;
this.clientChannelFactory = clientChannelFactory;
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline result = Channels.pipeline();
result.addLast("decoder", this.decoder);
result.addLast("fowarder", new ForwardingHandler(this.config, this.clientChannelFactory));
return result;
}
}

View File

@ -0,0 +1,153 @@
package org.handwerkszeug.dns.aaaa;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.conf.ServerConfiguration;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ForwardingHandler extends SimpleChannelUpstreamHandler {
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 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<SocketAddress> newlist = new ArrayList<SocketAddress>(this.config.getForwarders());
sendRequest(e, original, cb, newlist);
}
protected void sendRequest(final MessageEvent e, final DNSMessage original,
final ClientBootstrap bootstrap,
final List<SocketAddress> 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(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
LOG.error("ForwardingHandler#exceptionCaught");
Throwable t = e.getCause();
LOG.error(t.getMessage(), t);
}
protected class ClientHanler extends SimpleChannelUpstreamHandler {
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(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
LOG.error("ClientHanler#exceptionCaught");
Throwable t = e.getCause();
LOG.error(t.getMessage(), t);
e.getFuture().setFailure(t);
e.getChannel().close();
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
package org.handwerkszeug.dns.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.handwerkszeug.dns.record.WKSRecord;
import org.handwerkszeug.util.ClassUtil;
import werkzeugkasten.common.util.Streams;
import werkzeugkasten.common.util.StringUtil;
/**
* <a href="http://www.iana.org/assignments/port-numbers">PORT NUMBERS</a>
*
* @author taichi
*/
public class WKPortNumbers {
public static final String UNKNOWN_PORT = "unknown";
public static final String PATH = ClassUtil.toPackagePath(WKPortNumbers.class) + "/PortNumbers.txt";
protected static final Set<String> skipWords = new HashSet<String>();
static {
skipWords.add("Reserved");
skipWords.add("Unassigned");
skipWords.add("Discard");
}
protected Map<Integer, String> ports = new HashMap<Integer, String>();
protected Map<String, Integer> keywords = new HashMap<String, Integer>();
public WKPortNumbers() {}
public void load() {
load(PATH);
}
public void load(String path) {
try {
InputStream fin = getClass().getResource("/" + path).openStream();
load(fin);
} catch (IOException e) {
throw new IllegalStateException("Resource '" + path + "' was not found");
}
}
public void load(final InputStream in) {
new Streams.using<BufferedReader, Exception>() {
@Override
public BufferedReader open() throws Exception {
return new BufferedReader(new InputStreamReader(in));
}
@Override
public void handle(BufferedReader stream) throws Exception {
WKPortNumbers.this.parse(stream);
}
@Override
public void happen(Exception exception) {
throw new IllegalStateException(exception);
}
};
}
protected void parse(BufferedReader br) throws IOException {
while (br.ready()) {
parse(br.readLine());
}
}
protected void parse(String line) {
if (line.startsWith("#")) {
return;
}
String[] ary = line.split("\\p{Space}+");
if ((ary.length < 3) || skipWords.contains(ary[2])) {
return;
}
int index = ary[1].indexOf('/');
String port = ary[1].substring(0, index);
add(Integer.valueOf(port), ary[0]);
}
public void add(Integer port, String keyword) {
this.ports.put(port, keyword);
this.keywords.put(keyword, port);
}
public String find(Integer port) {
if (port == null) {
return UNKNOWN_PORT;
}
String keyword = this.ports.get(port);
if (StringUtil.isEmpty(keyword)) {
return UNKNOWN_PORT;
}
return keyword;
}
public Integer find(String keyword) {
if (StringUtil.isEmpty(keyword)) {
return null;
}
return this.keywords.get(keyword.toLowerCase());
}
static final Pattern isDigit = Pattern.compile("\\d+");
public void setServices(WKSRecord record, String[] services) {
List<Integer> list = new ArrayList<Integer>();
for (String s : services) {
if (isDigit.matcher(s).matches()) {
list.add(Integer.valueOf(s));
} else {
Integer i = find(s);
if (i != null) {
list.add(i);
}
}
}
int[] ary = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
ary[i] = list.get(i);
}
WKPortNumbers.setServices(record, ary);
}
/**
* 3.4.2. WKS RDATA format
*
* @param record
* @param services
*/
public static void setServices(WKSRecord record, int[] services) {
Arrays.sort(services);
int last = services[services.length - 1];
byte[] bitmap = new byte[last / 8 + 1];
for (int i : services) {
bitmap[i / 8] |= (1 << (7 - i % 8));
}
record.bitmap(bitmap);
}
protected List<Integer> getServices(WKSRecord record) {
byte[] bitmap = record.bitmap();
List<Integer> result = new ArrayList<Integer>();
for (int i = 0, length = bitmap.length; i < length; i++) {
int octets = bitmap[i] & 0xFF;
for (int j = 0; j < 8; j++) {
if ((octets & (1 << (7 - j))) != 0) {
result.add(Integer.valueOf(i * 8 + j));
}
}
}
return result;
}
public void appendServices(WKSRecord record, StringBuilder stb) {
for (Integer i : getServices(record)) {
stb.append(find(i));
stb.append(' ');
}
}
}

View File

@ -0,0 +1,139 @@
package org.handwerkszeug.dns.client;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.handwerkszeug.util.ClassUtil;
import werkzeugkasten.common.util.Streams;
import werkzeugkasten.common.util.StringUtil;
import werkzeugkasten.common.util.XMLEventParser;
import werkzeugkasten.common.util.XMLEventParser.DefaultHandler;
/**
*
* <a href="http://www.iana.org/assignments/protocol-numbers/">Protocol
* Numbers</a>
*
* @author taichi
*
*/
public class WKProtocols {
public static final String UNKNOWN_PROTOCOL = "UNKNOWN";
public static final String PATH = ClassUtil.toPackagePath(WKProtocols.class) + "/ProtocolNumbers.xml";
protected Map<Short, String> protocols = new HashMap<Short, String>();
public WKProtocols() {
}
public void load() {
load(PATH);
}
public void load(String path) {
try {
InputStream fin = getClass().getResource("/" + path).openStream();
load(fin);
} catch (IOException e) {
throw new IllegalStateException("Resource '" + path + "' was not found");
}
}
public void load(final InputStream in) {
new Streams.using<BufferedInputStream, Exception>() {
@Override
public BufferedInputStream open() throws Exception {
return new BufferedInputStream(in);
}
@Override
public void handle(BufferedInputStream stream) throws Exception {
XMLEventParser parser = new XMLEventParser(stream);
parser.add(new DefaultHandler("registry"));
parser.add(new RecordHandler());
parser.parse();
}
@Override
public void happen(Exception exception) {
throw new IllegalStateException(exception);
}
};
}
class Record {
String value;
String name;
}
public class RecordHandler extends DefaultHandler {
Pattern isDigit = Pattern.compile("\\p{Digit}+");
public RecordHandler() {
super("record");
}
@Override
public void handle(XMLStreamReader reader) throws XMLStreamException {
XMLEventParser parser = new XMLEventParser(reader);
Record r = new Record();
parser.add(new ValueHandler(r));
parser.add(new NameHandler(r));
parser.parse(getTagName());
if (this.isDigit.matcher(r.value).matches()) {
WKProtocols.this.add(Short.valueOf(r.value), r.name);
}
}
}
public class ValueHandler extends DefaultHandler {
Record r;
public ValueHandler(Record r) {
super("value");
this.r = r;
}
@Override
public void handle(XMLStreamReader reader) throws XMLStreamException {
this.r.value = reader.getElementText();
}
}
public class NameHandler extends DefaultHandler {
Record r;
public NameHandler(Record r) {
super("name");
this.r = r;
}
@Override
public void handle(XMLStreamReader reader) throws XMLStreamException {
this.r.name = reader.getElementText();
}
}
protected void add(Short value, String name) {
this.protocols.put(value, name);
}
public String find(short ubyte) {
String s = this.protocols.get(ubyte);
if (StringUtil.isEmpty(s)) {
return UNKNOWN_PROTOCOL;
}
return s;
}
}

View File

@ -0,0 +1,48 @@
package org.handwerkszeug.dns.conf;
import java.util.ArrayList;
import java.util.List;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.ChainResult;
import org.handwerkszeug.chain.impl.DefaultChainExecutor;
import org.handwerkszeug.dns.NameServerContainer;
import org.handwerkszeug.dns.NameServerContainerProvider;
public
class DefaultNameServerContainer implements NameServerContainer {
protected Chain<List<String>, ChainResult> executor;
@Override
public
String name() {
return NameServerContainerProvider.DEFAULT_NAME;
}
@Override
public
List<String> nameservers() {
List<String> result = new ArrayList<String>();
this.executor.execute(result);
return result;
}
@Override
public
void initialize() {
// FIXME this code run only sun JRE.
// find from IBM JDK. JRockit.
DefaultChainExecutor<List<String>, ChainResult> dce = new DefaultChainExecutor<List<String>, ChainResult>();
dce.add(new SystemProperties());
dce.add(new SunJRE());
dce.add(new ResolvConf());
this.executor = dce;
}
@Override
public
void dispose() {
// do nothing.
}
}

View File

@ -0,0 +1,27 @@
package org.handwerkszeug.dns.conf;
import org.handwerkszeug.dns.ResourceRecord;
public interface MasterDataHandler {
// lifecycle methods
void initialize(ServerConfiguration conf);
void commit();
void rollback();
void dispose();
// directives
// void do$origin(Name origin);
// process parser
// void do$include(String line);
// void do$ttl(long ttl);
// void do$unknown(String line);
void add(ResourceRecord record);
}

View File

@ -0,0 +1,10 @@
package org.handwerkszeug.dns.conf;
public interface MasterDataResource {
void initialize(ServerConfiguration conf);
void process(MasterDataHandler processor);
void dispose();
}

View File

@ -0,0 +1,143 @@
package org.handwerkszeug.dns.conf;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.handwerkszeug.dns.Constants;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.nls.Messages;
import org.handwerkszeug.util.AddressUtil;
import org.handwerkszeug.yaml.DefaultHandler;
import org.handwerkszeug.yaml.SequenceHandler;
import org.handwerkszeug.yaml.YamlNodeHandler;
import org.handwerkszeug.yaml.YamlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import werkzeugkasten.common.util.StringUtil;
/**
* <p>
* convert Node to Address
* </p>
* you can use following examples.
* <p>
* <blockquote>
*
* <pre>
* #sequence to addresses
* - address : 127.0.0.1
* port : 53
* - 127.0.0.1 : 53
* - 127.0.0.1:53
* #mapping to address
* 127.0.0.1 : 53
* address : 127.0.0.1
* port : 53
* #scalar to address
* 127.0.0.1:53
* </pre>
*
* </blockquote>
* </p>
*
* @author taichi
*/
public class NodeToAddress extends DefaultHandler<Set<SocketAddress>> {
static final Logger LOG = LoggerFactory.getLogger(NodeToAddress.class);
Map<NodeId, YamlNodeHandler<Set<SocketAddress>>> converters = new HashMap<NodeId, YamlNodeHandler<Set<SocketAddress>>>();
public NodeToAddress() {
this.converters.put(NodeId.scalar, new ScalarToAddress());
this.converters.put(NodeId.mapping, new MappingToAddress());
this.converters.put(NodeId.sequence, new SequenceHandler<Set<SocketAddress>>(this));
}
@Override
public void handle(Node node, Set<SocketAddress> context) {
YamlNodeHandler<Set<SocketAddress>> handler = this.converters.get(node
.getNodeId());
if (handler == null) {
LOG.debug(Markers.DETAIL, Messages.UnsupportedNode, node);
} else {
handler.handle(node, context);
}
}
public static class ScalarToAddress extends DefaultHandler<Set<SocketAddress>> {
protected int defaultPort = Constants.DEFAULT_PORT;
public ScalarToAddress() {
}
public ScalarToAddress(int port) {
this.defaultPort = port;
}
@Override
public void handle(Node node, Set<SocketAddress> context) {
if (node instanceof ScalarNode) {
ScalarNode sn = (ScalarNode) node;
SocketAddress addr = AddressUtil.convertTo(sn.getValue(), this.defaultPort);
if (addr != null) {
context.add(addr);
} else {
LOG.debug(Markers.DETAIL, Messages.InvalidAddressFormat,
sn.getValue());
}
}
}
}
public static class MappingToAddress extends DefaultHandler<Set<SocketAddress>> {
protected int defaultPort = Constants.DEFAULT_PORT;
public MappingToAddress() {
}
public MappingToAddress(int port) {
this.defaultPort = port;
}
@Override
public void handle(Node node, Set<SocketAddress> context) {
if (node instanceof MappingNode) {
MappingNode mn = (MappingNode) node;
String[] ary = new String[2];
for (NodeTuple nt : mn.getValue()) {
String key = YamlUtil.getStringValue(nt.getKeyNode());
String value = YamlUtil.getStringValue(nt.getValueNode());
if ("address".equalsIgnoreCase(key)) {
ary[0] = value;
} else if ("port".equalsIgnoreCase(key)) {
ary[1] = value;
} else {
ary[0] = key;
ary[1] = value;
break;
}
}
if (StringUtil.isEmpty(ary[0]) == false) {
SocketAddress addr = AddressUtil.convertTo(ary[0],
AddressUtil.toInt(ary[1], this.defaultPort));
if (addr != null) {
context.add(addr);
} else {
LOG.debug(Markers.DETAIL,
Messages.InvalidAddressFormat, mn.getValue());
}
}
}
}
}
}

View File

@ -0,0 +1,94 @@
package org.handwerkszeug.dns.conf;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.handwerkszeug.dns.Constants;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.NameServerContainer;
import org.handwerkszeug.dns.NameServerContainerProvider;
import org.handwerkszeug.dns.nls.Messages;
import org.handwerkszeug.yaml.DefaultHandler;
import org.handwerkszeug.yaml.SequenceHandler;
import org.handwerkszeug.yaml.YamlNodeHandler;
import org.handwerkszeug.yaml.YamlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import werkzeugkasten.common.util.StringUtil;
public class NodeToForwarders extends DefaultHandler<ServerConfiguration> {
static final Logger LOG = LoggerFactory.getLogger(NodeToForwarders.class);
protected NodeToAddress nodeToAddress;
protected Map<NodeId, YamlNodeHandler<ServerConfiguration>> converters = new HashMap<NodeId, YamlNodeHandler<ServerConfiguration>>();
public NodeToForwarders(NodeToAddress nodeToAddress) {
super("forwarders");
this.converters.put(NodeId.scalar,
new ScalarToForwarders(nodeToAddress));
this.converters.put(NodeId.sequence,
new SequenceHandler<ServerConfiguration>(this));
this.converters.put(NodeId.mapping, new MappingToForwarders(
nodeToAddress));
}
@Override
public void handle(Node node, ServerConfiguration context) {
YamlNodeHandler<ServerConfiguration> handler = this.converters.get(node.getNodeId());
if (handler == null) {
LOG.debug(Markers.DETAIL, Messages.UnsupportedNode, node);
} else {
handler.handle(node, context);
}
}
static class MappingToForwarders extends
DefaultHandler<ServerConfiguration> {
NodeToAddress nodeToAddress;
public MappingToForwarders(NodeToAddress nodeToAddress) {
this.nodeToAddress = nodeToAddress;
}
@Override
public void handle(Node node, ServerConfiguration context) {
this.nodeToAddress.handle(node, context.getForwarders());
}
}
static class ScalarToForwarders extends DefaultHandler<ServerConfiguration> {
NodeToAddress nodeToAddress;
Pattern isAutoDetect = Pattern.compile("auto[_]?detect",
Pattern.CASE_INSENSITIVE);
public ScalarToForwarders(NodeToAddress nodeToAddress) {
this.nodeToAddress = nodeToAddress;
}
@Override
public void handle(Node node, ServerConfiguration context) {
String value = YamlUtil.getStringValue(node);
if ((StringUtil.isEmpty(value) == false)
&& this.isAutoDetect.matcher(value).matches()) {
NameServerContainerProvider provider = new NameServerContainerProvider();
provider.initialize();
NameServerContainer container = provider.getContainer();
container.initialize();
for (String s : container.nameservers()) {
LOG.info(Markers.BOUNDARY,
Messages.DetectedForwardingServer, s);
context.getForwarders().add(new InetSocketAddress(s,
Constants.DEFAULT_PORT));
}
} else {
this.nodeToAddress.handle(node, context.getForwarders());
}
}
}
}

View File

@ -0,0 +1,67 @@
package org.handwerkszeug.dns.conf;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.List;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.ChainResult;
import org.handwerkszeug.chain.impl.SimpleChainResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import werkzeugkasten.common.util.Streams;
import werkzeugkasten.common.util.StringUtil;
public
class ResolvConf implements Chain<List<String>, ChainResult> {
static final Logger LOG = LoggerFactory.getLogger(ResolvConf.class);
@Override
public
ChainResult execute(List<String> context) {
handle("/etc/resolv.conf", context);
return SimpleChainResult.Continue;
}
protected
void handle(String path, final List<String> list) {
final File file = new File(path);
if (file.exists()) {
new Streams.using<BufferedReader, Exception>() {
@Override
public
BufferedReader open() throws Exception {
return new BufferedReader(new InputStreamReader(new FileInputStream(file)));
}
@Override
public
void handle(BufferedReader stream) throws Exception {
while (stream.ready()) {
parse(stream.readLine(), list);
}
}
@Override
public
void happen(Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
};
}
}
protected
void parse(String line, List<String> list) {
if (StringUtil.isEmpty(line) == false) {
String[] ary = line.split("\\s");
if ((1 < ary.length) && ary[0].equalsIgnoreCase("nameserver")) {
list.add(ary[1]);
}
}
}
}

View File

@ -0,0 +1,15 @@
package org.handwerkszeug.dns.conf;
import java.net.SocketAddress;
import java.util.Set;
public interface ServerConfiguration {
Set<SocketAddress> getBindingHosts();
int getThreadPoolSize();
Set<SocketAddress> getForwarders();
void setThreadPoolSize(int threadPoolSize);
}

View File

@ -0,0 +1,107 @@
package org.handwerkszeug.dns.conf;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.net.SocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.handwerkszeug.dns.Zone;
import org.handwerkszeug.util.AddressUtil;
import org.handwerkszeug.yaml.DefaultHandler;
import org.handwerkszeug.yaml.MappingHandler;
import org.handwerkszeug.yaml.YamlNodeAccepter;
import org.handwerkszeug.yaml.YamlNodeHandler;
import org.handwerkszeug.yaml.YamlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.nodes.Node;
import werkzeugkasten.common.util.Streams;
public class ServerConfigurationImpl implements ServerConfiguration {
static final Logger LOG = LoggerFactory
.getLogger(ServerConfigurationImpl.class);
protected Set<SocketAddress> bindingHosts = new HashSet<SocketAddress>();
protected Set<SocketAddress> forwarders = new HashSet<SocketAddress>();
protected List<Zone> zones = new ArrayList<Zone>();
protected int threadPoolSize = 10;
public ServerConfigurationImpl() {
}
public void load(final URL url) {
new Streams.using<BufferedInputStream, Exception>() {
@Override
public BufferedInputStream open() throws Exception {
return new BufferedInputStream(url.openStream());
}
@Override
public void handle(BufferedInputStream stream) throws Exception {
load(stream);
}
@Override
public void happen(Exception exception) {
throw new RuntimeException(exception);
}
};
}
public void load(InputStream in) {
YamlNodeHandler<ServerConfiguration> root = createRootHandler();
YamlNodeAccepter<ServerConfiguration> accepter = new YamlNodeAccepter<ServerConfiguration>(
root);
accepter.accept(in, this);
}
protected YamlNodeHandler<ServerConfiguration> createRootHandler() {
MappingHandler<ServerConfiguration> root = new MappingHandler<ServerConfiguration>();
final NodeToAddress node2addr = new NodeToAddress();
root.add(new DefaultHandler<ServerConfiguration>("bindingHosts") {
@Override
public void handle(Node node, ServerConfiguration context) {
node2addr.handle(node, context.getBindingHosts());
}
});
root.add(new NodeToForwarders(node2addr));
// TODO logging
root.add(new DefaultHandler<ServerConfiguration>("threadPoolSize") {
@Override
public void handle(Node node, ServerConfiguration conf) {
String value = YamlUtil.getStringValue(node);
conf.setThreadPoolSize(AddressUtil.toInt(value, 10));
}
});
return root;
}
@Override
public Set<SocketAddress> getBindingHosts() {
return this.bindingHosts;
}
@Override
public int getThreadPoolSize() {
return this.threadPoolSize;
}
@Override
public Set<SocketAddress> getForwarders() {
return this.forwarders;
}
@Override
public void setThreadPoolSize(int threadPoolSize) {
this.threadPoolSize = threadPoolSize;
}
}

View File

@ -0,0 +1,32 @@
package org.handwerkszeug.dns.conf;
import java.lang.reflect.Method;
import java.util.List;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.ChainResult;
import org.handwerkszeug.chain.impl.SimpleChainResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SunJRE implements Chain<List<String>, ChainResult> {
static final Logger LOG = LoggerFactory.getLogger(SunJRE.class);
@Override
public ChainResult execute(List<String> context) {
try {
Class<?> clazz = Class.forName("sun.net.dns.ResolverConfiguration");
Method open = clazz.getDeclaredMethod("open");
Method nameservers = clazz.getDeclaredMethod("nameservers");
Object conf = open.invoke(null);
Object maybelist = nameservers.invoke(conf);
for (Object o : List.class.cast(maybelist)) {
context.add(o.toString());
}
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
}
return SimpleChainResult.Continue;
}
}

View File

@ -0,0 +1,24 @@
package org.handwerkszeug.dns.conf;
import java.util.List;
import org.handwerkszeug.chain.Chain;
import org.handwerkszeug.chain.ChainResult;
import org.handwerkszeug.chain.impl.SimpleChainResult;
import org.handwerkszeug.dns.Constants;
import werkzeugkasten.common.util.StringUtil;
public class SystemProperties implements Chain<List<String>, ChainResult> {
@Override
public ChainResult execute(List<String> context) {
String servers = System.getProperty(Constants.SYSTEM_PROPERTY_NAMESERVERS);
if (StringUtil.isEmpty(servers) == false) {
for (String s : servers.split(",")) {
context.add(s);
}
}
return SimpleChainResult.Continue;
}
}

View File

@ -0,0 +1,5 @@
RFC4741 NETCONF Configuration Protocol
RFC5381 Experience of Implementing NETCONF over SOAP
そのものを利用する事は無くても、方向性として考慮する必要はありそう。
ここに書いてある程度の事は出来なければ、別解を用意する意味は無い。

View File

@ -0,0 +1,229 @@
package org.handwerkszeug.dns.conf.masterfile;
import static org.handwerkszeug.util.Validation.notNull;
import java.io.File;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.conf.MasterDataHandler;
import org.handwerkszeug.dns.conf.MasterDataResource;
import org.handwerkszeug.dns.conf.ServerConfiguration;
import org.handwerkszeug.dns.conf.masterfile.Partition.PartitionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import werkzeugkasten.common.util.FileUtil;
/**
* RFC1035 5. MASTER FILES
*
* @author taichi
*/
public class MasterFileParser implements MasterDataResource {
static final Logger LOG = LoggerFactory.getLogger(MasterFileParser.class);
static final int MAX_INCLUDE_DEPTH = 10; // TODO from configuration ?
final Partitioner partitioner;
ServerConfiguration conf;
Name origin;
long ttl;
IncludeContext includeContext;
int currentLine = 1; // TODO for error messages.
class IncludeContext {
int includeDepth = 0;
Set<String> includedPath = new HashSet<String>();
}
public MasterFileParser(String origin, File master) {
this(origin, FileUtil.open(master));
this.includeContext.includedPath.add(master.getAbsolutePath());
}
public MasterFileParser(String origin, InputStream in) {
this.includeContext = new IncludeContext();
this.partitioner = new Partitioner(in);
this.origin = new Name(origin);
}
protected MasterFileParser(Name origin, File file, IncludeContext context) {
this.includeContext = context;
this.partitioner = new Partitioner(FileUtil.open(file));
this.origin = origin;
}
@Override
public void initialize(ServerConfiguration conf) {
notNull(conf, "conf");
if (LOG.isInfoEnabled()) {
LOG.info("initialize");
}
this.conf = conf;
}
@Override
public void dispose() {
if (LOG.isInfoEnabled()) {
LOG.info("dispose");
}
this.partitioner.close();
}
@Override
public void process(MasterDataHandler handler) {
notNull(handler, "processor");
try {
handler.initialize(this.conf);
} catch (RuntimeException e) {
handler.rollback();
throw e;
} finally {
handler.dispose();
}
}
protected void internalProcess(MasterDataHandler handler) {
Name currentName = null;
long currentTTL = 0L;
DNSClass currentClass = DNSClass.IN;
while (true) {
Iterator<Partition> line = readLine();
if (line.hasNext()) {
break;
}
Partition first = line.next();
if (isDirective(first)) {
String directive = first.getString().toUpperCase();
if ("$INCLUDE".equals(directive)) {
String path = null;
Name newOrigin = this.origin;
if (line.hasNext()) {
path = line.next().getString();
} else {
// TODO parser error
throw new IllegalStateException();
}
if (line.hasNext()) {
String s = line.next().getString();
newOrigin = new Name(s);
}
processInclude(path, newOrigin, handler);
} else if ("$ORIGIN".equals(directive)) {
if (line.hasNext()) {
String origin = line.next().getString();
this.origin = new Name(origin);
}
} else if ("$TTL".equals(directive)) {
if (line.hasNext()) {
String num = line.next().getString();
if (isTTL(num)) {
this.ttl = Long.parseLong(num);
}
}
} else {
LOG.warn("unknown directive {}", directive);
}
continue;
}
if (first.type().equals(PartitionType.Default)) {
currentName = new Name(first.getString());
}
if (line.hasNext()) {
String second = line.next().getString();
// ttl class type
// ttl type
// class ttl type
// class type
// type
if (isTTL(second)) {
currentTTL = Long.parseLong(second);
if (line.hasNext()) {
String third = line.next().getString();
if (isDNSClass(third)) {
} else if (isRRType(third)) {
} else {
// TODO parser error
}
} else {
// TODO parser error
}
} else if (isDNSClass(second)) {
currentClass = DNSClass.valueOf(second.toUpperCase());
} else if (isRRType(second)) {
RRType type = RRType.valueOf(second.toUpperCase());
} else {
// TODO parser error.
}
} else {
// TODO parser error
}
}
}
protected void processInclude(String path, Name origin,
MasterDataHandler handler) {
if (MAX_INCLUDE_DEPTH < ++this.includeContext.includeDepth) {
// TODO error message.
throw new IllegalStateException();
}
File file = new File(path);
if (this.includeContext.includedPath.add(file.getAbsolutePath()) == false) {
// TODO error message. cyclic include.
throw new IllegalStateException();
}
MasterFileParser newone = new MasterFileParser(origin, file,
this.includeContext);
try {
newone.initialize(this.conf);
newone.process(handler);
} finally {
newone.dispose();
}
}
protected Iterator<Partition> readLine() {
// 改行のみ 空白のみ コメントのみ は読み飛ばす
// 先頭のWhitespaceは読み飛ばさないが他のWhitespaceは読み飛ばす
return null;
}
protected boolean isDirective(Partition p) {
byte[] b = p.division();
return b != null && 0 < b.length && b[0] == '$';
}
protected boolean isWhitespace(Partition p) {
return PartitionType.Whitespace.equals(p.type());
}
protected boolean isTTL(String p) {
return false;
}
protected boolean isDNSClass(String p) {
DNSClass dc = DNSClass.find(p);
return dc != null;
}
protected boolean isRRType(String p) {
return false;
}
}

View File

@ -0,0 +1,86 @@
package org.handwerkszeug.dns.conf.masterfile;
import java.util.Arrays;
public class Partition {
public enum PartitionType {
Default, Quoted, LP, RP, Comment, Whitespace, EOL, EOF
}
public static final Partition EOF = new Partition(PartitionType.EOF);
public static final Partition EOL = new Partition(PartitionType.EOL);
public static final Partition LP = new Partition(PartitionType.LP);
public static final Partition RP = new Partition(PartitionType.RP);
final PartitionType type;
final byte[] division;
public Partition(PartitionType type) {
this(type, null);
}
public Partition(PartitionType type, byte[] buffer) {
this.type = type;
this.division = buffer;
}
public PartitionType type() {
return this.type;
}
public byte[] division() {
return this.division;
}
public String getString() {
return new String(this.division);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.type == null) ? 0 : this.type.hashCode());
result = prime * result + Arrays.hashCode(this.division);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Partition other = (Partition) obj;
if (this.type != other.type) {
return false;
}
if (!Arrays.equals(this.division, other.division)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder stb = new StringBuilder();
stb.append("[");
stb.append(this.type);
stb.append("]");
if (this.division != null) {
stb.append("<");
stb.append(new String(this.division));
stb.append(">");
}
return stb.toString();
}
}

View File

@ -0,0 +1,188 @@
package org.handwerkszeug.dns.conf.masterfile;
import java.io.IOException;
import java.io.InputStream;
import org.handwerkszeug.dns.conf.masterfile.Partition.PartitionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class Partitioner {
static final Logger LOG = LoggerFactory.getLogger(Partitioner.class);
final InputStream source;
static final int DEFAULT_BUFFER_SIZE = 2000;
protected ByteBuf working;
protected Partition next;
public Partitioner(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public Partitioner(InputStream in, int size) {
this.source = in;
this.working = Unpooled.buffer(size);
}
public Partition partition() {
Partition result = this.next;
if (result != null) {
this.next = null;
return result;
}
while (true) {
byte ch = readByte();
if (ch == -1) {
if (0 < this.working.readerIndex()) {
result = makePartition(PartitionType.Default, 0);
discardBefore(0);
} else {
break;
}
}
if (ch == '\r') {
byte n = readByte();
if (n == '\n') {
if (this.working.readerIndex() < 3) {
this.working.discardReadBytes();
return Partition.EOL;
}
this.next = Partition.EOL;
result = makePartition(PartitionType.Default, 2);
discardBefore(0);
} else {
this.working.readerIndex(this.working.readerIndex() - 1);
}
}
if (ch == '\n') {
if (this.working.readerIndex() < 2) {
this.working.discardReadBytes();
return Partition.EOL;
}
this.next = Partition.EOL;
result = makePartition(PartitionType.Default, 1);
discardBefore(0);
}
if (ch == ';') {
result = readTo(partitionBefore(), PartitionType.Comment, '\n');
}
if (ch == '(') {
result = currentOrNext(partitionBefore(), Partition.LP);
}
if (ch == ')') {
result = currentOrNext(partitionBefore(), Partition.RP);
}
if ((ch == '"')) {
result = readTo(partitionBefore(), PartitionType.Quoted, '"');
}
if (((ch == ' ') || (ch == '\t'))) {
result = partitionBefore();
int begin = this.working.readerIndex() - 1;
while (true) {
byte c = readByte();
if ((c != ' ') && (c != '\t')) {
int end = this.working.readerIndex() - 1;
Partition ws = makePartition(PartitionType.Whitespace,
begin, end);
result = currentOrNext(result, ws, 1);
break;
}
}
}
if (result != null) {
return result;
}
}
return Partition.EOF;
}
protected Partition currentOrNext(Partition before, Partition p) {
return currentOrNext(before, p, 0);
}
protected Partition currentOrNext(Partition before, Partition p, int discard) {
Partition result = before;
if (before == null) {
result = p;
} else {
this.next = p;
}
discardBefore(discard);
return result;
}
protected Partition readTo(Partition back, PartitionType type, char stop) {
Partition result = back;
int begin = this.working.readerIndex() - 1;
while (true) {
byte c = readByte();
if ((c == stop) || (c == -1)) {
int end = this.working.readerIndex();
Partition p = makePartition(type, begin, end);
result = currentOrNext(back, p);
break;
}
}
return result;
}
protected byte readByte() {
try {
if (this.working.isReadable() == false) {
if (0 < this.source.available()) {
this.working.writeBytes(this.source, DEFAULT_BUFFER_SIZE);
}
}
if (this.working.isReadable()) {
return this.working.readByte();
}
return -1;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected Partition makePartition(PartitionType type, int begin, int end) {
byte[] newone = new byte[end - begin];
this.working.getBytes(begin, newone);
return new Partition(type, newone);
}
protected Partition makePartition(PartitionType type, int stripSize) {
int newsize = this.working.readerIndex() - stripSize;
byte[] newone = new byte[newsize];
this.working.getBytes(0, newone);
return new Partition(type, newone);
}
protected Partition partitionBefore() {
if (1 < this.working.readerIndex()) {
return makePartition(PartitionType.Default, 1);
}
return null;
}
protected void discardBefore(int backSize) {
this.working.readerIndex(this.working.readerIndex() - backSize);
this.working.discardReadBytes();
}
public void close() {
try {
this.source.close();
this.working.release();
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
}

View File

@ -0,0 +1,40 @@
package org.handwerkszeug.dns.nls;
public class Messages {
public static String InvalidParameter = "{} needs {} but {}";
public static String ComposeNode = "compose yaml node";
public static String UnsupportedAttribute = "unsupported attribute {}";
public static String UnsupportedNode = "unsupported node {}";
public static String InvalidAddressFormat = "invalid address format {}";
public static String DetectedForwardingServer = "detected forwarding server {}";
public static String NullLabelIsNotValid = "null label is not valid";
public static String InvalidCompressionMask = "Invalid compression mask %s";
public static String LabelsMustBe63orLess = "Labels must be 63 characters or less. current input=%s";
public static String NamesMustBe255orLess = "Labels must be 255 characters or less. current size=%s";
public static String EscapedDecimalIsInvalid = "escaped decimal is invalid value=%s";
public static String MixtureOfEscapedDigitAndNonDigit = "mixture of escaped digit and non-digit namedata=%s";
public static String InvalidEscapeSequence = "invalid escape sequence namedata=%s";
public static String StringMustBe255orLess = "String must be 255 characters or less. current length=%s";
public static String DataMustBe65535orLess = "Data must be 65535 characters or less. current length=%s";
public static String InvalidPortNumber = "invalid port number %s%n";
public static String NoResourceRecord = "%s has no resource record.";
public static String Not16bitValue = "%s is not 16bit value. current value=%s";
}

View File

@ -0,0 +1,120 @@
package org.handwerkszeug.dns.record;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.AddressUtil;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* <a href="http://datatracker.ietf.org/doc/rfc3596/">RFC3596</a>
*
* @author taichi
*/
public
class AAAARecord extends AbstractRecord<AAAARecord> {
/**
* A 128 bit IPv6 address is encoded in the data portion of an AAAA resource
* record in network byte order (high-order byte first).
*/
protected byte[] address;
public
AAAARecord() {
super(RRType.AAAA);
}
public
AAAARecord(AAAARecord from) {
super(from);
byte[] ary = from.address;
if (ary != null) {
this.address = Arrays.copyOf(ary, ary.length);
}
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
byte[] newone = new byte[16];
buffer.readBytes(newone);
this.address = newone;
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
buffer.writeBytes(this.address);
}
@Override
protected
ResourceRecord newInstance() {
return new AAAARecord(this);
}
@Override
public
int compareTo(AAAARecord o) {
if ((this != o) && (super.compareTo(o) == 0)) {
return CompareUtil.compare(this.address, o.address);
}
return 0;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
InetAddress ia = address();
if (ia == null) {
stb.append("null");
}
else {
stb.append(ia.getHostAddress());
}
return stb.toString();
}
public
InetAddress address() {
try {
return InetAddress.getByAddress(this.address);
} catch (UnknownHostException e) {
return null;
}
}
@Override
public
void setRDATA(List<String> list) {
if (0 < list.size()) {
String s = list.get(0);
if (AddressUtil.v6Address.matcher(s)
.matches()) {
InetAddress addr = AddressUtil.getByName(s);
this.address = addr.getAddress();
}
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
}
public
void address(Inet6Address v6address) {
this.address = v6address.getAddress();
}
}

View File

@ -0,0 +1,130 @@
package org.handwerkszeug.dns.record;
import java.net.InetAddress;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.AddressUtil;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.4.1. A RDATA format
*
* @author taichi
*/
public
class ARecord extends AbstractRecord<ARecord> {
/**
* A 32 bit Internet address.
*/
protected long address;
public
ARecord() {
super(RRType.A);
}
public
ARecord(ARecord from) {
super(from);
this.address = from.address;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.address = buffer.readUnsignedInt();
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
buffer.writeInt((int) this.address);
}
@Override
protected
ResourceRecord newInstance() {
return new ARecord(this);
}
@Override
public
int compareTo(ARecord o) {
if ((this != o) && (super.compareTo(o) == 0)) {
return CompareUtil.compare(this.address, o.address);
}
return 0;
}
@Override
public
int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (int) (this.address ^ (this.address >>> 32));
return result;
}
@Override
public
boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ARecord other = (ARecord) obj;
if (this.address != other.address) {
return false;
}
return true;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.address()
.getHostAddress());
return stb.toString();
}
public
InetAddress address() {
return AddressUtil.getByAddress(this.address);
}
@Override
public
void setRDATA(List<String> list) {
if (0 < list.size()) {
String s = list.get(0);
if (AddressUtil.v4Address.matcher(s)
.matches()) {
InetAddress addr = AddressUtil.getByName(s);
this.address = AddressUtil.toLong(addr);
}
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
}
public
void address(InetAddress v4address) {
this.address = AddressUtil.toLong(v4address);
}
}

View File

@ -0,0 +1,361 @@
package org.handwerkszeug.dns.record;
import java.io.ByteArrayOutputStream;
import java.text.DecimalFormat;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.nls.Messages;
import io.netty.buffer.ByteBuf;
import werkzeugkasten.common.util.StringUtil;
public abstract
class AbstractRecord<T extends ResourceRecord> implements ResourceRecord, Comparable<T> {
public static byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static int MAX_STRING_LENGTH = 255;
protected RRType type;
protected Name name;
protected DNSClass dnsClass = DNSClass.IN;
protected long ttl;
protected int rdlength;
public
AbstractRecord(RRType type) {
this.type = type;
}
public
AbstractRecord(AbstractRecord<T> from) {
this.type = from.type();
this.name(from.name());
this.dnsClass(from.dnsClass());
this.ttl(from.ttl());
this.rdlength(from.rdlength());
}
@Override
public
RRType type() {
return this.type;
}
@Override
public
Name name() {
return this.name;
}
@Override
public
void name(Name name) {
this.name = name;
}
@Override
public
DNSClass dnsClass() {
return this.dnsClass;
}
@Override
public
void dnsClass(DNSClass dnsClass) {
this.dnsClass = dnsClass;
}
@Override
public
long ttl() {
return this.ttl;
}
@Override
public
void ttl(long ttl) {
this.ttl = ttl;
}
@Override
public
int rdlength() {
return this.rdlength;
}
@Override
public
void rdlength(int value) {
this.rdlength = value;
}
@Override
public
void parse(ByteBuf buffer) {
this.ttl(buffer.readUnsignedInt());
this.rdlength(buffer.readUnsignedShort());
parseRDATA(buffer);
}
/**
* 4.1.3. Resource record format<br/>
* <b>RDATA</b> a variable length string of octets that describes the
* resource. The format of this information varies according to the TYPE and
* CLASS of the resource record.
*
* @param buffer
*/
protected abstract
void parseRDATA(ByteBuf buffer);
@Override
public
void write(ByteBuf buffer, NameCompressor compressor) {
buffer.writeInt((int) this.ttl());
int rdlengthIndex = buffer.writerIndex();
buffer.writeShort(0); // at first, write zero.
writeRDATA(buffer, compressor);
int rdlength = (buffer.writerIndex() - rdlengthIndex - 2) & 0xFFFF;
buffer.setShort(rdlengthIndex, rdlength);
}
protected abstract
void writeRDATA(ByteBuf buffer, NameCompressor compressor);
@Override
public
ResourceRecord toQnameRecord(Name qname) {
ResourceRecord newone = newInstance();
newone.name(qname);
return newone;
}
protected abstract
ResourceRecord newInstance();
public static
ResourceRecord parseSection(ByteBuf buffer) {
Name n = new Name(buffer);
RRType t = RRType.valueOf(buffer.readUnsignedShort());
DNSClass dc = DNSClass.valueOf(buffer.readUnsignedShort());
ResourceRecord result = t.newRecord();
result.name(n);
result.dnsClass(dc);
return result;
}
public static
void writeSection(ByteBuf buffer, NameCompressor compressor, ResourceRecord rr) {
rr.name()
.write(buffer, compressor);
buffer.writeShort(rr.type()
.value());
buffer.writeShort(rr.dnsClass()
.value());
}
/**
* 3.3. Standard RRs
* <p>
* &lt;character-string&gt; is a single length octet followed by that number
* of characters. &lt;character-string&gt; is treated as binary information,
* and can be up to 256 characters in length (including the length octet).
* </p>
*
* @param buffer
*/
protected
byte[] readString(ByteBuf buffer) {
short length = buffer.readUnsignedByte();
if (MAX_STRING_LENGTH < length) {
throw new IllegalStateException(String.format(Messages.StringMustBe255orLess, length));
}
byte[] newone = new byte[length];
buffer.readBytes(newone);
return newone;
}
protected
void writeString(ByteBuf buffer, byte[] ary) {
int length = ary.length;
buffer.writeByte(length);
buffer.writeBytes(ary);
}
protected
StringBuilder toQuoteString(byte[] ary) {
StringBuilder result = new StringBuilder();
result.append('"');
result.append(toString(ary));
result.append('"');
return result;
}
/**
* 5.1. Format
*
* @param ary
*/
protected
StringBuilder toString(byte[] ary) {
DecimalFormat fmt = new DecimalFormat("###");
StringBuilder result = new StringBuilder();
for (byte b : ary) {
int i = b & 0xFF;
if ((i < 0x20) || (0x7E < i)) { // control code
result.append('\\');
result.append(fmt.format(i));
}
else if ((i == '"') || (i == '\\')) {
result.append('\\');
result.append((char) i);
}
else {
result.append((char) i);
}
}
return result;
}
protected
byte[] toArrayFromQuoted(String string) {
if (StringUtil.isEmpty(string)) {
return EMPTY_BYTE_ARRAY;
}
if (string.length() < 3) {
return EMPTY_BYTE_ARRAY;
}
return toArray(string.substring(1, string.length() - 1));
}
protected
byte[] toArray(String string) {
if (StringUtil.isEmpty(string)) {
return EMPTY_BYTE_ARRAY;
}
byte[] bytes = string.getBytes();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int i = 0, length = bytes.length; i < length; i++) {
byte b = bytes[i];
if (b == '\\') {
byte next = bytes[++i];
if (Character.isDigit(next)) {
int value = ((next - '0') * 100) + ((bytes[++i] - '0') * 10) + bytes[++i] - '0';
out.write(value);
}
else {
out.write(next);
}
}
else {
out.write(b);
}
}
return out.toByteArray();
}
@Override
public
int compareTo(T o) {
if (o == null) {
return 1;
}
int result = this.type()
.compareTo(o.type());
if (result != 0) {
return result;
}
result = this.name()
.compareTo(o.name());
if (result != 0) {
return result;
}
return this.dnsClass()
.compareTo(o.dnsClass());
}
@Override
public
int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.dnsClass == null) ? 0 : this.dnsClass.hashCode());
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
result = prime * result + this.rdlength;
result = prime * result + (int) (this.ttl ^ (this.ttl >>> 32));
result = prime * result + ((this.type == null) ? 0 : this.type.hashCode());
return result;
}
@Override
public
boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof AbstractRecord) {
@SuppressWarnings("unchecked")
AbstractRecord<T> other = (AbstractRecord<T>) obj;
return equals(other);
}
return false;
}
public
boolean equals(AbstractRecord<T> other) {
if (this.dnsClass != other.dnsClass) {
return false;
}
if (this.name == null) {
if (other.name != null) {
return false;
}
}
else if (!this.name.equals(other.name)) {
return false;
}
if (this.rdlength != other.rdlength) {
return false;
}
if (this.ttl != other.ttl) {
return false;
}
if (this.type != other.type) {
return false;
}
return true;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(this.name()
.toString());
StringUtil.padRight(stb, ' ', 23);
stb.append(' ');
stb.append(this.ttl());
StringUtil.padRight(stb, ' ', 31);
stb.append(' ');
stb.append(this.dnsClass()
.name());
stb.append(' ');
stb.append(this.type()
.name());
StringUtil.padRight(stb, ' ', 39);
return stb.toString();
}
}

View File

@ -0,0 +1,134 @@
package org.handwerkszeug.dns.record;
import java.util.Arrays;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.3.2. HINFO RDATA format
* <p>
* <p>
* Standard values for CPU and OS can be found in [RFC-1010].
* </p>
*
* @author taichi
*/
public
class HINFORecord extends AbstractRecord<HINFORecord> {
/**
* A <character-string> which specifies the CPU type.
*/
protected byte[] cpu;
/**
* A <character-string> which specifies the operating system type.
*/
protected byte[] os;
public
HINFORecord() {
super(RRType.HINFO);
}
public
HINFORecord(HINFORecord from) {
super(from);
byte[] c = from.cpu;
if (c != null) {
this.cpu = Arrays.copyOf(c, c.length);
}
byte[] o = from.os;
if (o != null) {
this.os = Arrays.copyOf(o, o.length);
}
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.cpu = readString(buffer);
this.os = readString(buffer);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
writeString(buffer, this.cpu);
writeString(buffer, this.os);
}
@Override
protected
ResourceRecord newInstance() {
return new HINFORecord(this);
}
@Override
public
int compareTo(HINFORecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result == 0) {
result = CompareUtil.compare(this.cpu, o.cpu);
if (result == 0) {
result = CompareUtil.compare(this.os, o.os);
}
}
return result;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.cpu());
stb.append(' ');
stb.append(this.os());
return stb.toString();
}
public
String cpu() {
return new String(this.cpu);
}
public
String os() {
return new String(this.os);
}
@Override
public
void setRDATA(List<String> list) {
if (list.size() == 2) {
this.cpu(list.get(0));
this.os(list.get(1));
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
}
public
void cpu(String cpu) {
this.cpu = cpu.getBytes(); // TODO encoding ?
}
public
void os(String os) {
this.os = os.getBytes(); // TODO encoding ?
}
}

View File

@ -0,0 +1,135 @@
package org.handwerkszeug.dns.record;
import java.util.List;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import io.netty.buffer.ByteBuf;
/**
* 3.3.7. MINFO RDATA format (EXPERIMENTAL)
*
* @author taichi
*/
public
class MINFORecord extends AbstractRecord<MINFORecord> {
/**
* A <domain-name> which specifies a mailbox which is responsible for the
* mailing list or mailbox. If this domain name names the root, the owner of
* the MINFO RR is responsible for itself. Note that many existing mailing
* lists use a mailbox X-request for the RMAILBX field of mailing list X,
* e.g., Msgroup-request for Msgroup. This field provides a more general
* mechanism.
*/
protected Name rmailbx;
/**
* A <domain-name> which specifies a mailbox which is to receive error
* messages related to the mailing list or mailbox specified by the owner of
* the MINFO RR (similar to the ERRORS-TO: field which has been proposed).
* If this domain name names the root, errors should be returned to the
* sender of the message.
*/
protected Name emailbx;
public
MINFORecord() {
super(RRType.MINFO);
}
public
MINFORecord(MINFORecord from) {
super(from);
this.rmailbx(from.rmailbx());
this.emailbx(from.emailbx());
}
public
Name rmailbx() {
return this.rmailbx;
}
public
void rmailbx(Name name) {
this.rmailbx = name;
}
public
Name emailbx() {
return this.emailbx;
}
public
void emailbx(Name name) {
this.emailbx = name;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.rmailbx = new Name(buffer);
this.emailbx = new Name(buffer);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
this.rmailbx()
.write(buffer, compressor);
this.emailbx()
.write(buffer, compressor);
}
@Override
protected
ResourceRecord newInstance() {
return new MINFORecord(this);
}
@Override
public
int compareTo(MINFORecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result == 0) {
result = this.rmailbx()
.compareTo(o.rmailbx());
if (result == 0) {
result = this.emailbx()
.compareTo(o.emailbx());
}
}
return result;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.rmailbx());
stb.append(' ');
stb.append(this.emailbx());
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
if (2 == list.size()) {
this.rmailbx(new Name(list.get(0)));
this.emailbx(new Name(list.get(1)));
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,134 @@
package org.handwerkszeug.dns.record;
import java.util.List;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.3.9. MX RDATA format
* <p>
* <pre>
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | PREFERENCE |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* / EXCHANGE /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* </pre>
*
* @author taichi
*/
public
class MXRecord extends AbstractRecord<MXRecord> {
/**
* A 16 bit integer which specifies the preference given to this RR among
* others at the same owner. Lower values are preferred.
*/
protected int preference;
/**
* RFC 974 the name of a host.
*/
protected Name exchange;
public
MXRecord() {
super(RRType.MX);
}
public
MXRecord(MXRecord from) {
super(from);
this.preference = from.preference();
this.exchange = from.exchange();
}
public
int preference() {
return this.preference;
}
public
Name exchange() {
return this.exchange;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.preference = buffer.readUnsignedShort();
this.exchange = new Name(buffer);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
buffer.writeShort(this.preference);
this.exchange.write(buffer, compressor);
}
@Override
protected
ResourceRecord newInstance() {
return new MXRecord(this);
}
@Override
public
int compareTo(MXRecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result == 0) {
result = CompareUtil.compare(this.preference(), o.preference());
if (result == 0) {
result = this.exchange()
.compareTo(o.exchange());
}
}
return result;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.preference());
stb.append(' ');
stb.append(this.exchange());
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
if (2 == list.size()) {
int pref = Integer.parseInt(list.get(0));
if (-1 < pref && pref < 65536) {
this.preference = pref;
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
this.exchange = new Name(list.get(1));
}
else {
// TODO error message
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,88 @@
package org.handwerkszeug.dns.record;
import java.util.Arrays;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.nls.Messages;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.3.10. NULL RDATA format (EXPERIMENTAL)
*
* @author taichi
*/
public
class NULLRecord extends AbstractRecord<NULLRecord> {
/**
* Anything at all may be in the RDATA field so long as it is 65535 octets
* or less.
*/
protected byte[] anything;
public
NULLRecord() {
super(RRType.NULL);
}
public
NULLRecord(NULLRecord from) {
super(from);
byte[] ary = from.anything();
if (ary != null) {
this.anything(Arrays.copyOf(ary, ary.length));
}
}
public
byte[] anything() {
return this.anything;
}
public
void anything(byte[] data) {
if (65535 < data.length) {
throw new IllegalArgumentException(String.format(Messages.DataMustBe65535orLess, data.length));
}
this.anything = data;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.anything = new byte[rdlength()];
buffer.readBytes(this.anything);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
buffer.writeBytes(anything());
}
@Override
protected
ResourceRecord newInstance() {
return new NULLRecord(this);
}
@Override
public
int compareTo(NULLRecord o) {
if ((this != o) && (super.compareTo(o) == 0)) {
return CompareUtil.compare(this.anything(), o.anything());
}
return 0;
}
@Override
public
void setRDATA(List<String> list) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,96 @@
package org.handwerkszeug.dns.record;
import java.util.List;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import io.netty.buffer.ByteBuf;
/**
* <ul>
* <li>3.3.1. CNAME RDATA format</li>
* <li>3.3.11. NS RDATA format</li>
* <li>3.3.12. PTR RDATA format</li>
* </ul>
*
* @author taichi
*/
public
class OPTNameRecord extends AbstractRecord<OPTNameRecord> {
protected Name oneName;
public
OPTNameRecord(RRType type) {
super(type);
}
public
OPTNameRecord(RRType type, Name oneName) {
super(type);
this.oneName = oneName;
}
public
OPTNameRecord(OPTNameRecord from) {
super(from);
this.oneName = from.oneName();
}
public
Name oneName() {
return this.oneName;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.oneName = new Name(buffer);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
this.oneName.write(buffer, compressor);
}
@Override
protected
ResourceRecord newInstance() {
return new OPTNameRecord(this);
}
@Override
public
int compareTo(OPTNameRecord o) {
if ((this != o) && (super.compareTo(o) == 0)) {
return this.oneName()
.compareTo(o.oneName());
}
return 0;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.oneName());
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
if (0 < list.size()) {
this.oneName = new Name(list.get(0));
}
else {
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,280 @@
package org.handwerkszeug.dns.record;
import java.util.List;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.3.13. SOA RDATA format
* <p>
* <pre>
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* / MNAME /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* / RNAME /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | SERIAL |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | REFRESH |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | RETRY |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | EXPIRE |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | MINIMUM |
* | |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* </pre>
*/
public
class SOARecord extends AbstractRecord<SOARecord> {
/**
* The <domain-name> of the name server that was the original or primary
* source of data for this zone.
*/
protected Name mname;
/**
* A <domain-name> which specifies the mailbox of the person responsible for
* this zone.
*/
protected Name rname;
/**
* The unsigned 32 bit version number of the original copy of the zone. Zone
* transfers preserve this value. This value wraps and should be compared
* using sequence space arithmetic.
*/
protected long serial;
/**
* A 32 bit time interval before the zone should be refreshed.
*/
protected long refresh;
/**
* A 32 bit time interval that should elapse before a failed refresh should
* be retried.
*/
protected long retry;
/**
* A 32 bit time value that specifies the upper limit on the time interval
* that can elapse before the zone is no longer authoritative.
*/
protected long expire;
/**
* The unsigned 32 bit minimum TTL field that should be exported with any RR
* from this zone.
*/
protected long minimum;
public
SOARecord() {
super(RRType.SOA);
}
public
SOARecord(SOARecord from) {
super(from);
this.mname(from.mname());
this.rname(from.rname());
this.serial(from.serial());
this.refresh(from.refresh());
this.retry(from.retry());
this.expire(from.expire());
this.minimum(from.minimum());
}
/**
* The <domain-name> of the name server that was the original or primary
* source of data for this zone.
*/
public
Name mname() {
return this.mname;
}
public
void mname(Name name) {
this.mname = name;
}
public
Name rname() {
return this.rname;
}
public
void rname(Name name) {
this.rname = name;
}
public
long serial() {
return this.serial;
}
public
void serial(long uint) {
this.serial = uint & 0xFFFFFFFFL;
}
public
long refresh() {
return this.refresh;
}
public
void refresh(long uint) {
this.refresh = uint & 0xFFFFFFFFL;
}
public
long retry() {
return this.retry;
}
public
void retry(long uint) {
this.retry = uint & 0xFFFFFFFFL;
}
public
long expire() {
return this.expire;
}
public
void expire(long uint) {
this.expire = uint & 0xFFFFFFFFL;
}
public
long minimum() {
return this.minimum;
}
public
void minimum(long uint) {
this.minimum = uint & 0xFFFFFFFFL;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.mname(new Name(buffer));
this.rname(new Name(buffer));
this.serial(buffer.readUnsignedInt());
this.refresh(buffer.readUnsignedInt());
this.retry(buffer.readUnsignedInt());
this.expire(buffer.readUnsignedInt());
this.minimum(buffer.readUnsignedInt());
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
this.mname()
.write(buffer, compressor);
this.rname()
.write(buffer, compressor);
buffer.writeInt((int) this.serial());
buffer.writeInt((int) this.refresh());
buffer.writeInt((int) this.retry());
buffer.writeInt((int) this.expire());
buffer.writeInt((int) this.minimum());
}
@Override
protected
ResourceRecord newInstance() {
return new SOARecord(this);
}
@Override
public
int compareTo(SOARecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result != 0) {
return result;
}
result = this.mname()
.compareTo(o.mname());
if (result != 0) {
return result;
}
result = this.rname()
.compareTo(o.rname());
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.serial(), o.serial());
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.refresh(), o.refresh());
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.retry(), o.retry());
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.expire(), o.expire());
if (result != 0) {
return result;
}
return CompareUtil.compare(this.minimum(), o.minimum());
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.mname());
stb.append(' ');
stb.append(this.rname());
stb.append(' ');
stb.append(this.serial());
stb.append(' ');
stb.append(this.refresh());
stb.append(' ');
stb.append(this.retry());
stb.append(' ');
stb.append(this.expire());
stb.append(' ');
stb.append(this.minimum());
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
if (6 < list.size()) {
// XXX
}
else {
// TODO error message.
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,96 @@
package org.handwerkszeug.dns.record;
import java.util.List;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import io.netty.buffer.ByteBuf;
/**
* <ul>
* <li>3.3.1. CNAME RDATA format</li>
* <li>3.3.11. NS RDATA format</li>
* <li>3.3.12. PTR RDATA format</li>
* </ul>
*
* @author taichi
*/
public
class SingleNameRecord extends AbstractRecord<SingleNameRecord> {
protected Name oneName;
public
SingleNameRecord(RRType type) {
super(type);
}
public
SingleNameRecord(RRType type, Name oneName) {
super(type);
this.oneName = oneName;
}
public
SingleNameRecord(SingleNameRecord from) {
super(from);
this.oneName = from.oneName();
}
public
Name oneName() {
return this.oneName;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.oneName = new Name(buffer);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
this.oneName.write(buffer, compressor);
}
@Override
protected
ResourceRecord newInstance() {
return new SingleNameRecord(this);
}
@Override
public
int compareTo(SingleNameRecord o) {
if ((this != o) && (super.compareTo(o) == 0)) {
return this.oneName()
.compareTo(o.oneName());
}
return 0;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.oneName());
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
if (0 < list.size()) {
this.oneName = new Name(list.get(0));
}
else {
throw new IllegalArgumentException();
}
}
}

View File

@ -0,0 +1,112 @@
package org.handwerkszeug.dns.record;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.3.14. TXT RDATA format
*
* @author taichi
*/
public
class TXTRecord extends AbstractRecord<TXTRecord> {
protected List<byte[]> strings;
public
TXTRecord() {
super(RRType.TXT);
}
public
TXTRecord(TXTRecord from) {
super(from);
if (from.strings != null) {
List<byte[]> newone = new ArrayList<byte[]>(from.strings.size());
for (byte[] b : from.strings) {
newone.add(Arrays.copyOf(b, b.length));
}
}
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.strings = new ArrayList<byte[]>();
ByteBuf part = buffer.readSlice(rdlength());
while (part.isReadable()) {
this.strings.add(readString(part));
}
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
for (byte[] ary : this.strings) {
writeString(buffer, ary);
}
}
@Override
protected
ResourceRecord newInstance() {
return new TXTRecord(this);
}
@Override
public
int compareTo(TXTRecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result != 0) {
return result;
}
int mySize = this.strings.size();
int yrSize = o.strings.size();
int min = Math.min(mySize, yrSize);
for (int i = 0; i < min; i++) {
result = CompareUtil.compare(this.strings.get(i), o.strings.get(i));
if (result != 0) {
return result;
}
}
return mySize - yrSize;
}
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
for (Iterator<byte[]> i = this.strings.iterator(); i.hasNext(); ) {
stb.append(toQuoteString(i.next()));
if (i.hasNext()) {
stb.append(' ');
}
}
return stb.toString();
}
@Override
public
void setRDATA(List<String> list) {
for (String s : list) {
this.strings.add(s.getBytes());
}
}
public
String txt() {
return this.toString();
}
}

View File

@ -0,0 +1,169 @@
package org.handwerkszeug.dns.record;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import org.handwerkszeug.dns.NameCompressor;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.client.WKPortNumbers;
import org.handwerkszeug.dns.client.WKProtocols;
import org.handwerkszeug.util.AddressUtil;
import org.handwerkszeug.util.CompareUtil;
import io.netty.buffer.ByteBuf;
/**
* 3.4.2. WKS RDATA format
* <p>
* <pre>
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ADDRESS |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | PROTOCOL | |
* +--+--+--+--+--+--+--+--+ |
* | |
* / <BIT MAP> /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* </pre>
* <p>
* The WKS record is used to describe the well known services supported by a
* particular protocol on a particular internet address. The PROTOCOL field
* specifies an IP protocol number, and the bit map has one bit per port of the
* specified protocol. The first bit corresponds to port 0, the second to port
* 1, etc. If the bit map does not include a bit for a protocol of interest,
* that bit is assumed zero. The appropriate values and mnemonics for ports and
* protocols are specified in [RFC-1010].
* </p>
*
* @author taichi
*/
public
class WKSRecord extends AbstractRecord<WKSRecord> {
/**
* An 32 bit Internet address
*/
protected long address;
/**
* An 8 bit IP protocol number
*/
protected short protocol;
/**
* A variable length bit map. The bit map must be a multiple of 8 bits long.
*/
protected byte[] bitmap;
public
WKSRecord() {
super(RRType.WKS);
}
public
WKSRecord(WKSRecord from) {
super(from);
this.address = from.address;
this.protocol = from.protocol();
byte[] ary = from.bitmap();
if (ary != null) {
this.bitmap(Arrays.copyOf(ary, ary.length));
}
}
public
short protocol() {
return this.protocol;
}
public
byte[] bitmap() {
return this.bitmap;
}
public
void bitmap(byte[] bytes) {
this.bitmap = bytes;
}
@Override
protected
void parseRDATA(ByteBuf buffer) {
this.address = buffer.readUnsignedInt();
this.protocol = buffer.readUnsignedByte();
this.bitmap = new byte[rdlength() - 5];// (32bit + 8bit) / 8bit
buffer.readBytes(this.bitmap);
}
@Override
protected
void writeRDATA(ByteBuf buffer, NameCompressor compressor) {
buffer.writeInt((int) this.address);
buffer.writeByte(this.protocol);
buffer.writeBytes(this.bitmap);
}
@Override
protected
ResourceRecord newInstance() {
return new WKSRecord(this);
}
@Override
public
int compareTo(WKSRecord o) {
if (this == o) {
return 0;
}
int result = super.compareTo(o);
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.address, o.address);
if (result != 0) {
return result;
}
result = CompareUtil.compare(this.protocol(), o.protocol());
if (result != 0) {
return result;
}
return CompareUtil.compare(this.bitmap(), o.bitmap());
}
/**
* @see WKProtocols
* @see WKPortNumbers
*/
@Override
public
String toString() {
StringBuilder stb = new StringBuilder();
stb.append(super.toString());
stb.append(' ');
stb.append(this.address()
.getHostAddress());
stb.append(' ');
stb.append(this.protocol());
return stb.toString();
}
public
InetAddress address() {
return AddressUtil.getByAddress(this.address);
}
@Override
public
void setRDATA(List<String> list) {
// TODO Auto-generated method stub
}
public
void protocol(short no) {
this.protocol = no;
}
}

View File

@ -0,0 +1,27 @@
package org.handwerkszeug.dns.server;
import org.handwerkszeug.dns.*;
import org.handwerkszeug.dns.record.SingleNameRecord;
public
class CNAMEResponse extends DefaultResponse {
final SingleNameRecord cname;
final RRType qtype;
public
CNAMEResponse(ResourceRecord cname, RRType qtype) {
super(RCode.NoError);
this.cname = SingleNameRecord.class.cast(cname);
this.qtype = qtype;
}
@Override
public
void postProcess(ResolveContext context) {
context.response()
.answer()
.add(this.cname);
Response r = context.resolve(this.cname.oneName(), this.qtype);
r.postProcess(context);
}
}

View File

@ -0,0 +1,50 @@
package org.handwerkszeug.dns.server;
import static org.handwerkszeug.util.Validation.notNull;
import org.handwerkszeug.dns.*;
import org.handwerkszeug.dns.record.SingleNameRecord;
public
class DNAMEResponse extends DefaultResponse {
final SingleNameRecord dname;
final Name qname;
final RRType qtype;
public
DNAMEResponse(ResourceRecord dname, Name qname, RRType qtype) {
super(RCode.NoError);
notNull(dname, "dname");
notNull(qname, "qname");
notNull(qtype, "qtype");
this.dname = SingleNameRecord.class.cast(dname);
this.qname = qname;
this.qtype = qtype;
}
@Override
public
void postProcess(ResolveContext context) {
DNSMessage res = context.response();
res.answer()
.add(this.dname);
Name name = this.qname.replace(this.dname.name(), this.dname.oneName());
if (name == null) {
context.response()
.header()
.rcode(RCode.YXDomain);
}
else {
SingleNameRecord cname = new SingleNameRecord(RRType.CNAME, name);
cname.name(this.qname);
res.answer()
.add(cname);
res.header()
.aa(true);
Response r = context.resolve(name, this.qtype);
r.postProcess(context);
}
}
}

View File

@ -0,0 +1,9 @@
package org.handwerkszeug.dns.server;
public
class DNSCacheEntry {
protected long expiration;
}

View File

@ -0,0 +1,59 @@
package org.handwerkszeug.dns.server;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.util.Validation;
public
class DNSCacheKey {
Name name;
RRType t;
DNSClass c;
public
DNSCacheKey(Name name, RRType t, DNSClass c) {
super();
Validation.notNull(name, "name");
Validation.notNull(t, "t");
Validation.notNull(c, "c");
this.name = name;
this.t = t;
this.c = c;
}
@Override
public
int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.c == null) ? 0 : this.c.hashCode());
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
result = prime * result + ((this.t == null) ? 0 : this.t.hashCode());
return result;
}
@Override
public
boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof DNSCacheKey) {
return equals(DNSCacheKey.class.cast(other));
}
return false;
}
public
boolean equals(DNSCacheKey other) {
boolean ne = this.name.equals(other.name);
boolean t = (this.t.equals(RRType.ANY) || other.t.equals(RRType.ANY) || this.t.equals(other.t));
boolean c = (this.c.equals(DNSClass.ANY) || other.c.equals(DNSClass.ANY) || this.c.equals(other.c));
return ne && t && c;
}
}

View File

@ -0,0 +1,22 @@
package org.handwerkszeug.dns.server;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.RRType;
/**
* @author taichi
*/
public
class DNSMessageCache {
public
DNSMessage lookup(Name name, RRType t, DNSClass c) {
return null;
}
public
void store(DNSMessage message) {
}
}

View File

@ -0,0 +1,171 @@
package org.handwerkszeug.dns.server;
import java.net.InetSocketAddress;
import dorkbox.network.dns.records.DnsMessage;
import dorkbox.util.NamedThreadFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.util.internal.PlatformDependent;
@ChannelHandler.Sharable
public
class DNSMessageDecoder extends ChannelInboundHandlerAdapter {
/**
* This is what is called whenever a DNS packet is received. Currently only support UDP packets.
* <p>
* Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
* to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
* <p>
* Sub-classes may override this method to change behavior.
*/
@Override
public
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof io.netty.channel.socket.DatagramPacket) {
ByteBuf content = ((DatagramPacket) msg).content();
if (content.readableBytes() == 0) {
// we can't read this message, there's nothing there!
System.err.println("NO CONTENT ");
ctx.fireChannelRead(msg);
return;
}
DnsMessage msg1 = new DnsMessage(content);
// should get one from a pool!
Bootstrap dnsBootstrap = new Bootstrap();
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
SecurityManager s = System.getSecurityManager();
ThreadGroup nettyGroup = new ThreadGroup(s != null
? s.getThreadGroup()
: Thread.currentThread()
.getThreadGroup(), "DnsClient (Netty)");
EventLoopGroup group;
if (PlatformDependent.isAndroid()) {
group = new OioEventLoopGroup(0, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
dnsBootstrap.channel(OioDatagramChannel.class);
}
else {
group = new NioEventLoopGroup(2, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
dnsBootstrap.channel(NioDatagramChannel.class);
}
dnsBootstrap.group(group);
dnsBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
// dnsBootstrap.handler(new DnsHandler());
// sending the question
final ChannelFuture future = dnsBootstrap.connect(new InetSocketAddress("8.8.8.8", 53));
try {
future.await();
if (future.isSuccess()) {
// woo, connected!
System.err.println("CONNECTED");
// this.dnsServer = dnsServer;
}
else {
System.err.println("CANNOT CONNECT!");
// this.dnsServer = null;
// Logger logger2 = this.logger;
// if (logger2.isDebugEnabled()) {
// logger2.error("Could not connect to the DNS server.", this.future.cause());
// }
// else {
// logger2.error("Could not connect to the DNS server.");
// }
}
} catch (Exception e) {
e.printStackTrace();
// Logger logger2 = this.logger;
// if (logger2.isDebugEnabled()) {
// logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort(), e.getCause());
// }
// else {
// logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort());
// }
}
//
// 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<SocketAddress> newlist = new ArrayList<SocketAddress>(this.config.getForwarders());
// sendRequest(e, original, cb, newlist);
}
else {
ctx.fireChannelRead(msg);
}
}
// protected
// void sendRequest(final MessageEvent e, final DNSMessage original, final ClientBootstrap bootstrap, final List<SocketAddress> 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);
// }
// }
}

View File

@ -0,0 +1,45 @@
package org.handwerkszeug.dns.server;
import static org.handwerkszeug.util.Validation.notNull;
import org.handwerkszeug.dns.*;
public
class DefaultResolveContext implements ResolveContext {
final DNSMessage request;
final DNSMessage response;
public
DefaultResolveContext(DNSMessage request) {
this(request, new DNSMessage());
}
public
DefaultResolveContext(DNSMessage request, DNSMessage response) {
notNull(request, "request");
notNull(response, "response");
this.request = request;
this.response = response;
}
@Override
public
DNSMessage request() {
return this.request;
}
@Override
public
DNSMessage response() {
return this.response;
}
@Override
public
Response resolve(Name qname, RRType qtype) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,20 @@
package org.handwerkszeug.dns.server;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.Response;
public abstract
class DefaultResponse implements Response {
final RCode rcode;
protected
DefaultResponse(RCode rcode) {
this.rcode = rcode;
}
@Override
public
RCode rcode() {
return this.rcode;
}
}

View File

@ -0,0 +1,39 @@
package org.handwerkszeug.dns.server;
import java.util.Set;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.ResolveContext;
import org.handwerkszeug.dns.ResourceRecord;
public
class NoErrorResponse extends DefaultResponse {
final Set<ResourceRecord> records;
final boolean aa;
public
NoErrorResponse(Set<ResourceRecord> records) {
this(records, true);
}
public
NoErrorResponse(Set<ResourceRecord> records, boolean aa) {
super(RCode.NoError);
this.records = records;
this.aa = aa;
}
@Override
public
void postProcess(ResolveContext context) {
DNSMessage res = context.response();
res.header()
.rcode(this.rcode());
res.header()
.aa(this.aa);
res.answer()
.addAll(this.records);
// TODO additional section ?
}
}

View File

@ -0,0 +1,27 @@
package org.handwerkszeug.dns.server;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.ResolveContext;
import org.handwerkszeug.dns.record.SOARecord;
public
class NotFoundResponse extends DefaultResponse {
final SOARecord soaRecord;
public
NotFoundResponse(RCode rcode, SOARecord soaRecord) {
super(rcode);
this.soaRecord = soaRecord;
}
@Override
public
void postProcess(ResolveContext context) {
DNSMessage res = context.response();
res.header()
.rcode(this.rcode());
res.authority()
.add(this.soaRecord);
}
}

View File

@ -0,0 +1,29 @@
package org.handwerkszeug.dns.server;
import java.util.Set;
import org.handwerkszeug.dns.DNSMessage;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.ResolveContext;
import org.handwerkszeug.dns.ResourceRecord;
public
class ReferralResponse extends DefaultResponse {
final Set<ResourceRecord> nsRecords;
public
ReferralResponse(Set<ResourceRecord> records) {
super(RCode.NoError);
this.nsRecords = records;
}
@Override
public
void postProcess(ResolveContext context) {
DNSMessage res = context.response();
res.header()
.rcode(this.rcode());
res.authority()
.addAll(this.nsRecords);
}
}

View File

@ -0,0 +1,41 @@
package org.handwerkszeug.dns.zone;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.Zone;
import org.handwerkszeug.dns.ZoneType;
public abstract class AbstractZone implements Zone {
protected ZoneType type;
protected DNSClass dnsClass;
protected Name name;
public AbstractZone(ZoneType type, Name name) {
this(type, DNSClass.IN, name);
}
public AbstractZone(ZoneType type, DNSClass dnsClass, Name name) {
this.type = type;
this.dnsClass = dnsClass;
this.name = name;
}
@Override
public ZoneType type() {
return this.type;
}
@Override
public DNSClass dnsClass() {
return this.dnsClass;
}
@Override
public Name name() {
return this.name;
}
}

View File

@ -0,0 +1,34 @@
package org.handwerkszeug.dns.zone;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.Response;
import org.handwerkszeug.dns.ZoneType;
public class ForwardZone extends AbstractZone {
protected List<InetAddress> forwarders = new ArrayList<InetAddress>();
public ForwardZone(Name name) {
super(ZoneType.forward, name);
}
public ForwardZone(DNSClass dnsclass, Name name) {
super(ZoneType.forward, dnsclass, name);
}
public void addForwardHost(InetAddress host) {
this.forwarders.add(host);
}
@Override
public Response find(Name qname, RRType type) {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,191 @@
package org.handwerkszeug.dns.zone;
import static org.handwerkszeug.util.Validation.notNull;
import java.util.HashSet;
import java.util.NavigableSet;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.RCode;
import org.handwerkszeug.dns.RRType;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.Response;
import org.handwerkszeug.dns.ZoneType;
import org.handwerkszeug.dns.record.SOARecord;
import org.handwerkszeug.dns.server.CNAMEResponse;
import org.handwerkszeug.dns.server.DNAMEResponse;
import org.handwerkszeug.dns.server.NoErrorResponse;
import org.handwerkszeug.dns.server.NotFoundResponse;
import org.handwerkszeug.dns.server.ReferralResponse;
public class MasterZone extends AbstractZone {
final ConcurrentMap<Name, ConcurrentMap<RRType, NavigableSet<ResourceRecord>>> records = new ConcurrentSkipListMap<Name, ConcurrentMap<RRType, NavigableSet<ResourceRecord>>>();
final Response nxDomain;
final Response nxRRSet;
public MasterZone(Name name, SOARecord soaRecord) {
super(ZoneType.master, name);
this.nxDomain = new NotFoundResponse(RCode.NXDomain, soaRecord);
this.nxRRSet = new NotFoundResponse(RCode.NXRRSet, soaRecord);
}
@Override
public Response find(Name qname, RRType qtype) {
notNull(qname, "qname");
notNull(qtype, "qtype");
if (qname.contains(this.name()) == false) {
return this.nxDomain;
}
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> exactMatch = this.records
.get(qname);
if (exactMatch != null) {
NavigableSet<ResourceRecord> rrs = exactMatch.get(qtype);
if (rrs != null) {
synchronized (rrs) {
if (rrs.isEmpty() == false) {
return new NoErrorResponse(rrs);
}
}
}
if (RRType.ANY.equals(qtype)) {
Set<ResourceRecord> newset = new HashSet<ResourceRecord>();
for (RRType type : exactMatch.keySet()) {
Set<ResourceRecord> s = exactMatch.get(type);
if (s != null) {
synchronized (s) {
newset.addAll(s);
}
}
}
if (newset.isEmpty() == false) {
return new NoErrorResponse(newset);
}
}
if (RRType.CNAME.equals(qtype) == false) {
rrs = exactMatch.get(RRType.CNAME);
if (rrs != null) {
synchronized (rrs) {
if (rrs.isEmpty() == false) {
return new CNAMEResponse(rrs.first(), qtype);
}
}
}
}
return this.nxRRSet;
}
for (Name qn = qname.toParent(); this.name().equals(qn) == false; qn = qn
.toParent()) {
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> match = this.records
.get(qn);
if (match != null) {
synchronized (match) {
if (match.isEmpty() == false) {
NavigableSet<ResourceRecord> set = match.get(RRType.NS);
if ((set != null) && (set.isEmpty() == false)) {
return new ReferralResponse(set);
}
set = match.get(RRType.DNAME);
if ((set != null) && (set.isEmpty() == false)) {
return new DNAMEResponse(set.first(), qname, qtype);
}
}
}
}
}
for (Name qn = qname; this.name().equals(qn) == false; qn = qn
.toParent()) {
Name wild = qn.toWildcard();
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> match = this.records
.get(wild);
if (match != null) {
synchronized (match) {
if (match.isEmpty() == false) {
Set<ResourceRecord> matchSet = match.get(qtype);
if (matchSet.isEmpty() == false) {
Set<ResourceRecord> set = new HashSet<ResourceRecord>(
matchSet.size());
for (ResourceRecord rr : matchSet) {
set.add(rr.toQnameRecord(qname));
}
return new NoErrorResponse(set);
}
}
}
}
}
return this.nxDomain;
}
// add and remove needs queuing?
// if modify operations works on single thread, not conflict.
public void add(ResourceRecord rr) {
notNull(rr, "rr");
for (;;) {
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> current = this.records
.get(rr.name());
if (current == null) {
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> newone = new ConcurrentSkipListMap<RRType, NavigableSet<ResourceRecord>>();
NavigableSet<ResourceRecord> newset = new ConcurrentSkipListSet<ResourceRecord>();
newset.add(rr);
newone.put(rr.type(), newset);
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> prevTypes = this.records
.putIfAbsent(rr.name(), newone);
if (prevTypes == null) {
break;
}
synchronized (prevTypes) {
Set<ResourceRecord> prevRecs = prevTypes.putIfAbsent(
rr.type(), newset);
if (prevRecs == null) {
break;
}
prevRecs.add(rr);
break;
}
} else {
synchronized (current) {
Set<ResourceRecord> rrs = current.get(rr.type());
if (rrs == null) {
NavigableSet<ResourceRecord> newset = new ConcurrentSkipListSet<ResourceRecord>();
newset.add(rr);
current.put(rr.type(), newset);
break;
}
if (rrs.isEmpty() == false) {
rrs.add(rr);
break;
}
}
}
}
}
public void remove(ResourceRecord rr, boolean checkSets, boolean checkMap) {
notNull(rr, "rr");
ConcurrentMap<RRType, NavigableSet<ResourceRecord>> current = this.records
.get(rr.name());
if (current != null) {
synchronized (current) {
NavigableSet<ResourceRecord> sets = current.get(rr.type());
sets.remove(rr);
if (checkSets && sets.isEmpty()) {
current.remove(rr.type());
if (checkMap && current.isEmpty()) {
this.records.remove(rr.name());
}
}
}
}
}
}

View File

@ -0,0 +1,70 @@
package org.handwerkszeug.dns.zone;
import static org.handwerkszeug.util.Validation.notNull;
import java.util.List;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.Zone;
/**
* @author taichi
*/
public class Query {
protected Name origin;
protected Name current;
protected DNSClass dnsClass;
protected Zone target;
protected ZoneDatabase database;
public Query(Name origin, Name current, DNSClass dnsClass, Zone target,
ZoneDatabase database) {
super();
notNull(origin, "origin");
notNull(current, "current");
notNull(dnsClass, "dnsClass");
notNull(target, "target");
notNull(database, "database");
this.origin = origin;
this.current = current;
this.dnsClass = dnsClass;
this.target = target;
this.database = database;
}
// public SearchResult execute(/* ResolveContext? */) {
// List<ResourceRecord> rrs = this.target.resolve(this.current,
// this.dnsClass);
// SearchResult result = new SearchResult(rrs);
// if (rrs.isEmpty()) {
// result.status = Status.NXDOMAIN;
// return result;
// } else if (contains(rrs)) {
// result.status = Status.SUCCESS;
// return result;
// } else {
// Query q = this.database.prepare(this.origin, this.dnsClass);
// SearchResult sr = q.execute();
// result.rrs.addAll(sr.rrs);
// result.status = sr.status;
// return sr;
// }
// }
protected boolean contains(List<ResourceRecord> rrs) {
for (ResourceRecord rr : rrs) {
if (this.origin.equals(rr.name())) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package org.handwerkszeug.dns.zone;
import java.util.List;
import org.handwerkszeug.dns.ResourceRecord;
public class SearchResult {
public enum Status {
NXDOMAIN, SUCCESS;
}
List<ResourceRecord> rrs;
Status status = Status.NXDOMAIN;
public SearchResult(List<ResourceRecord> rrs) {
super();
this.rrs = rrs;
}
}

View File

@ -0,0 +1,115 @@
package org.handwerkszeug.dns.zone;
import static org.handwerkszeug.util.Validation.notNull;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import org.handwerkszeug.dns.DNSClass;
import org.handwerkszeug.dns.Name;
import org.handwerkszeug.dns.ResourceRecord;
import org.handwerkszeug.dns.Zone;
public class ZoneDatabase {
protected Map<ZoneDatabaseKey, Zone> zones = new ConcurrentSkipListMap<ZoneDatabaseKey, Zone>();
public Query prepare(Name name, DNSClass dnsClass) {
notNull(name, "name");
notNull(dnsClass, "dnsClass");
ZoneDatabaseKey zk = new ZoneDatabaseKey(name, dnsClass);
Zone found = this.zones.get(zk);
if (found != null) {
// exact match
return new Query(name, name, dnsClass, found, this);
}
Name child = name;
// partial match
for (int i = 0, size = this.zones.size(); i < size; i++) {
Name p = child.toParent();
zk.name(p);
found = this.zones.get(zk);
if (found == null) {
child = p;
} else {
return new Query(name, p, dnsClass, found, this);
}
}
// not found.
return null;
}
public void add(Zone zone/* TODO ZoneConfig? */) {
notNull(zone, "zone");
this.zones.put(new ZoneDatabaseKey(zone), zone);
}
static class ZoneDatabaseKey implements Comparable<ZoneDatabaseKey> {
Name name;
DNSClass dnsclass;
public ZoneDatabaseKey(Zone z) {
this(z.name(), z.dnsClass());
}
public ZoneDatabaseKey(ResourceRecord rr) {
this(rr.name(), rr.dnsClass());
}
public ZoneDatabaseKey(Name name, DNSClass dnsclass) {
notNull(name, "name");
notNull(dnsclass, "dnsclass");
this.name = name;
this.dnsclass = dnsclass;
}
public Name name() {
return this.name;
}
public void name(Name name) {
notNull(name, "name");
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.dnsclass.hashCode();
result = prime * result + this.name.hashCode();
return result;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (getClass() != other.getClass()) {
return false;
}
return equals((ZoneDatabaseKey) other);
}
public boolean equals(ZoneDatabaseKey other) {
return (this.dnsclass == other.dnsclass)
&& this.name.equals(other.name);
}
@Override
public int compareTo(ZoneDatabaseKey o) {
if (o == null) {
return 1;
}
if (equals(o)) {
return 0;
}
return this.hashCode() - o.hashCode();
}
}
}

View File

@ -0,0 +1,157 @@
package org.handwerkszeug.util;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @see <a href="http://tools.ietf.org/html/rfc4291">[RFC4291] IP Version 6
* Addressing Architecture</a>
* @see <a href="http://tools.ietf.org/html/rfc5952">[RFC5952] A Recommendation
* for IPv6 Address Text Representation</a>
* @see http://www.intermapper.com/ipv6validator
* @see http://download.dartware.com/thirdparty/test-ipv6-regex.pl
* @author taichi
*/
public class AddressUtil {
static final Logger LOG = LoggerFactory.getLogger(AddressUtil.class);
/**
* @see <a href="http://tools.ietf.org/html/rfc952">[RFC952] DOD INTERNET
* HOST TABLE SPECIFICATION</a>
*/
public static final Pattern hostname = Pattern
.compile("([a-zA-Z][\\w-]*[\\w]*(\\.[a-zA-Z][\\w-]*[\\w]*)*)");
public static final Pattern hostWithPort = withPortNumber(hostname);
public static final String under65536 = "(6553[0-5]|6(55[012]|(5[0-4]|[0-4]\\d)\\d)\\d|[1-5]?\\d{1,4})";
public static final Pattern v4Address = Pattern
.compile("((25[0-5]|(2[0-4]|1\\d|[1-9]?)\\d)(\\.|\\b)){4}(?<!\\.)");
public static final Pattern v4withPort = withPortNumber(v4Address);
protected static Pattern withPortNumber(Pattern p) {
return Pattern.compile("(" + p.pattern() + ")(:" + under65536 + ")?");
}
protected static final String internal_v6address = "((((?=(?>.*?::)(?!.*::)))(::)?([0-9a-f]{1,4}::?){0,5}|([0-9a-f]{1,4}:){6})(((25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])(\\.|\\b)){4}|\\3([0-9a-f]{1,4}(::?|\\b)){0,2}|[0-9a-f]{1,4}:[0-9a-f]{1,4})(?<![^:]:)(?<!\\.))";
public static final Pattern v6Address = Pattern.compile(internal_v6address
+ "$");
public static final Pattern v6withSuffixPort = Pattern
.compile(internal_v6address + "(?:(#|\\.)" + under65536 + ")?$");
public static final Pattern v6withBracketPort = Pattern.compile("\\["
+ internal_v6address + "\\](:" + under65536 + ")?$"); // 1 15
protected static final List<SocketAddressConverter> CONVERTERS = new ArrayList<SocketAddressConverter>();
static {
CONVERTERS.add(new FromV4Address());
CONVERTERS.add(new FromBracketV6Address());
CONVERTERS.add(new FromV6Address());
CONVERTERS.add(new FromHostname());
}
public static InetSocketAddress convertTo(String addressWithPort,
int defaultPort) {
InetSocketAddress result = null;
for (SocketAddressConverter sac : CONVERTERS) {
result = sac.to(addressWithPort, defaultPort);
if (result != null) {
break;
}
}
return result;
}
public interface SocketAddressConverter {
InetSocketAddress to(String addr, int defaultPort);
}
public static class FromHostname implements SocketAddressConverter {
@Override
public InetSocketAddress to(String addr, int defaultPort) {
return toSocketAddress(addr, defaultPort, hostWithPort, 1, 5);
}
}
public static class FromV4Address implements SocketAddressConverter {
@Override
public InetSocketAddress to(String addr, int defaultPort) {
return toSocketAddress(addr, defaultPort, v4withPort, 1, 7);
}
}
public static class FromBracketV6Address implements SocketAddressConverter {
@Override
public InetSocketAddress to(String addr, int defaultPort) {
return toSocketAddress(addr, defaultPort, v6withBracketPort, 1, 15);
}
}
public static class FromV6Address implements SocketAddressConverter {
@Override
public InetSocketAddress to(String addr, int defaultPort) {
return toSocketAddress(addr, defaultPort, v6withSuffixPort, 1, 15);
}
}
protected static InetSocketAddress toSocketAddress(String addressWithPort,
int defaultPort, Pattern p, int HOST_INDEX, int PORT_INDEX) {
Matcher m = p.matcher(addressWithPort);
if (m.matches() && m.reset().find()) {
int port = toInt(m.group(PORT_INDEX), defaultPort);
return new InetSocketAddress(m.group(HOST_INDEX), port);
}
return null;
}
public static int toInt(String s, int defaultValue) {
int result = defaultValue;
try {
if ((s != null) && (s.isEmpty() == false)) {
result = Integer.parseInt(s);
}
} catch (NumberFormatException e) {
}
return result;
}
public static InetAddress getByAddress(long v4address) {
byte[] a = new byte[4];
for (int i = 0; i < 4; i++) {
a[i] = (byte) ((v4address >>> ((3 - i) * 8)) & 0xFF);
}
try {
return InetAddress.getByAddress(a);
} catch (UnknownHostException e) {
LOG.error(e.getLocalizedMessage(), e);
return null;
}
}
public static long toLong(InetAddress v4address) {
byte[] a = v4address.getAddress();
long result = 0;
for (int i = 0; i < 4; i++) {
result |= (long) ((a[i] & 0xFF)) << ((3 - i) * 8);
}
return result;
}
public static InetAddress getByName(String host) {
try {
return InetAddress.getByName(host);
} catch (UnknownHostException e) {
LOG.error(host, e);
throw new IllegalArgumentException(e);
}
}
}

View File

@ -0,0 +1,15 @@
package org.handwerkszeug.util;
public class ClassUtil {
public static String toPackagePath(Class<?> clazz) {
String result = clazz.getName();
int index = result.lastIndexOf('.');
if (index < 1) {
return "";
}
result = result.substring(0, index);
result = result.replace('.', '/');
return result;
}
}

View File

@ -0,0 +1,19 @@
package org.handwerkszeug.util;
import werkzeugkasten.common.util.ArrayUtil;
public class CompareUtil {
public static int compare(long left, long right) {
if (left < right) {
return -1;
} else if (left > right) {
return 1;
}
return 0;
}
public static int compare(byte[] lefts, byte[] rights) {
return ArrayUtil.compare(lefts, rights);
}
}

View File

@ -0,0 +1,38 @@
package org.handwerkszeug.util;
public class EnumUtil {
public static <E extends Enum<E> & VariableEnum> E find(E[] values,
int value) {
E result = find(values, value, null);
if (result == null) {
throw new IllegalArgumentException("value=" + value);
}
return result;
}
public static <E extends Enum<E> & VariableEnum> E find(E[] values,
int value, E defaultValue) {
for (E e : values) {
if (e.value() == value) {
return e;
}
}
return defaultValue;
}
public static <E extends Enum<E>> E find(E[] values, String value,
E defaultValue) {
if (value == null || value.isEmpty()) {
return defaultValue;
}
String key = value.toUpperCase();
for (E e : values) {
if (e.name().equals(key)) {
return e;
}
}
return defaultValue;
}
}

View File

@ -0,0 +1,18 @@
package org.handwerkszeug.util;
import java.util.regex.Pattern;
public class Validation {
static final Pattern isPositiveNumber = Pattern.compile("\\d+");
public static void notNull(Object o, String name) {
if (o == null) {
throw new IllegalArgumentException(name);
}
}
public static boolean isPositiveNumber(String s) {
return s != null && isPositiveNumber.matcher(s).matches();
}
}

View File

@ -0,0 +1,6 @@
package org.handwerkszeug.util;
public interface VariableEnum {
int value();
}

View File

@ -0,0 +1,17 @@
package org.handwerkszeug.yaml;
public abstract class DefaultHandler<CTX> implements YamlNodeHandler<CTX> {
protected String name;
public DefaultHandler() {
}
public DefaultHandler(String name) {
this.name = name;
}
@Override
public String getNodeName() {
return this.name;
}
}

View File

@ -0,0 +1,53 @@
package org.handwerkszeug.yaml;
import java.util.HashMap;
import java.util.Map;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.nls.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
public class MappingHandler<CTX> extends DefaultHandler<CTX> {
static final Logger LOG = LoggerFactory.getLogger(MappingHandler.class);
Map<String, YamlNodeHandler<CTX>> handlers = new HashMap<String, YamlNodeHandler<CTX>>();
public MappingHandler() {
}
public MappingHandler(String name) {
super(name);
}
public void add(YamlNodeHandler<CTX> handler) {
if (handler != null) {
this.handlers.put(handler.getNodeName().toLowerCase(), handler);
}
}
@Override
public void handle(Node node, CTX context) {
if (node instanceof MappingNode) {
MappingNode mn = (MappingNode) node;
for (NodeTuple nt : mn.getValue()) {
String key = YamlUtil.getStringValue(nt.getKeyNode());
YamlNodeHandler<CTX> h = this.handlers.get(key.toLowerCase());
if (h != null) {
Node value = nt.getValueNode();
h.handle(value, context);
} else {
LOG.debug(Markers.DETAIL, Messages.UnsupportedAttribute,
key);
}
}
} else {
LOG.debug(Markers.DETAIL, Messages.InvalidParameter, new Object[] {
"MappingHandler#handle", MappingNode.class, node });
}
}
}

View File

@ -0,0 +1,37 @@
package org.handwerkszeug.yaml;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.nls.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.SequenceNode;
public class SequenceHandler<CTX> extends DefaultHandler<CTX> {
static final Logger LOG = LoggerFactory.getLogger(SequenceHandler.class);
protected YamlNodeHandler<CTX> handler;
public SequenceHandler(YamlNodeHandler<CTX> handler) {
this.handler = handler;
}
public SequenceHandler(String name, YamlNodeHandler<CTX> handler) {
super(name);
this.handler = handler;
}
@Override
public void handle(Node node, CTX context) {
if (node instanceof SequenceNode) {
SequenceNode sn = (SequenceNode) node;
for (Node n : sn.getValue()) {
this.handler.handle(n, context);
}
} else {
LOG.debug(Markers.DETAIL, Messages.InvalidParameter, new Object[] {
"SequenceHandler#handle", SequenceNode.class, node });
}
}
}

View File

@ -0,0 +1,35 @@
package org.handwerkszeug.yaml;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.handwerkszeug.dns.Markers;
import org.handwerkszeug.dns.nls.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.composer.Composer;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.parser.ParserImpl;
import org.yaml.snakeyaml.reader.StreamReader;
import org.yaml.snakeyaml.resolver.Resolver;
public class YamlNodeAccepter<CTX> {
static final Logger LOG = LoggerFactory.getLogger(YamlNodeAccepter.class);
protected final YamlNodeHandler<CTX> rootHandler;
public YamlNodeAccepter(YamlNodeHandler<CTX> root) {
this.rootHandler = root;
}
public void accept(InputStream in, CTX context) {
if (LOG.isDebugEnabled()) {
LOG.trace(Markers.BOUNDARY, Messages.ComposeNode);
}
Composer composer = new Composer(new ParserImpl(new StreamReader(
new InputStreamReader(in))), new Resolver());
Node node = composer.getSingleNode();
this.rootHandler.handle(node, context);
}
}

View File

@ -0,0 +1,10 @@
package org.handwerkszeug.yaml;
import org.yaml.snakeyaml.nodes.Node;
public interface YamlNodeHandler<CTX> {
String getNodeName();
void handle(Node node, CTX context);
}

View File

@ -0,0 +1,15 @@
package org.handwerkszeug.yaml;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.ScalarNode;
public class YamlUtil {
public static String getStringValue(Node node) {
if (node instanceof ScalarNode) {
ScalarNode sn = (ScalarNode) node;
return sn.getValue();
}
return null;
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS2.clients;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import dorkbox.network.dns.utils.Options;
class Client {
protected long endTime;
protected SelectionKey key;
/**
* Packet logger, if available.
*/
private static PacketLogger packetLogger = null;
protected
Client(SelectableChannel channel, long endTime) throws IOException {
boolean done = false;
Selector selector = null;
this.endTime = endTime;
try {
selector = Selector.open();
channel.configureBlocking(false);
key = channel.register(selector, SelectionKey.OP_READ);
done = true;
} finally {
if (!done && selector != null) {
selector.close();
}
if (!done) {
channel.close();
}
}
}
static protected
void blockUntil(SelectionKey key, long endTime) throws IOException {
long timeout = endTime - System.currentTimeMillis();
int nkeys = 0;
if (timeout > 0) {
nkeys = key.selector()
.select(timeout);
}
else if (timeout == 0) {
nkeys = key.selector()
.selectNow();
}
if (nkeys == 0) {
throw new SocketTimeoutException();
}
}
static protected
void verboseLog(String prefix, SocketAddress local, SocketAddress remote, byte[] data) {
if (Options.check("verbosemsg")) {
System.err.println(hexdump.dump(prefix, data));
}
if (packetLogger != null) {
packetLogger.log(prefix, local, remote, data);
}
}
void cleanup() throws IOException {
key.selector()
.close();
key.channel()
.close();
}
static
void setPacketLogger(PacketLogger logger) {
packetLogger = logger;
}
}

View File

@ -0,0 +1,310 @@
// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS2.clients;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import dorkbox.network.dns.Name;
import dorkbox.network.dns.constants.DnsClass;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.exceptions.InvalidTypeException;
import dorkbox.network.dns.exceptions.TextParseException;
import dorkbox.network.dns.records.DnsRecord;
import dorkbox.network.dns.utils.Options;
/**
* A representation of a $GENERATE statement in a master file.
*
* @author Brian Wellington
*/
public
class Generator {
/**
* The start of the range.
*/
public long start;
/**
* The end of the range.
*/
public long end;
/**
* The step value of the range.
*/
public long step;
/**
* The pattern to use for generating record names.
*/
public final String namePattern;
/**
* The type of the generated records.
*/
public final int type;
/**
* The class of the generated records.
*/
public final int dclass;
/**
* The ttl of the generated records.
*/
public final long ttl;
/**
* The pattern to use for generating record data.
*/
public final String rdataPattern;
/**
* The origin to append to relative names.
*/
public final Name origin;
private long current;
/**
* Creates a specification for generating records, as a $GENERATE
* statement in a master file.
*
* @param start The start of the range.
* @param end The end of the range.
* @param step The step value of the range.
* @param namePattern The pattern to use for generating record names.
* @param type The type of the generated records. The supported types are
* PTR, CNAME, DNAME, A, AAAA, and NS.
* @param dclass The class of the generated records.
* @param ttl The ttl of the generated records.
* @param rdataPattern The pattern to use for generating record data.
* @param origin The origin to append to relative names.
*
* @throws IllegalArgumentException The range is invalid.
* @throws IllegalArgumentException The type does not support generation.
* @throws IllegalArgumentException The dclass is not a valid class.
*/
public
Generator(long start, long end, long step, String namePattern, int type, int dclass, long ttl, String rdataPattern, Name origin) {
if (start < 0 || end < 0 || start > end || step <= 0) {
throw new IllegalArgumentException("invalid range specification");
}
if (!supportedType(type)) {
throw new IllegalArgumentException("unsupported type");
}
DnsClass.check(dclass);
this.start = start;
this.end = end;
this.step = step;
this.namePattern = namePattern;
this.type = type;
this.dclass = dclass;
this.ttl = ttl;
this.rdataPattern = rdataPattern;
this.origin = origin;
this.current = start;
}
/**
* Indicates whether generation is supported for this type.
*
* @throws InvalidTypeException The type is out of range.
*/
public static
boolean supportedType(int type) {
DnsRecordType.check(type);
return (type == DnsRecordType.PTR || type == DnsRecordType.CNAME || type == DnsRecordType.DNAME || type == DnsRecordType.A || type == DnsRecordType.AAAA || type == DnsRecordType.NS);
}
/**
* Constructs and returns the next record in the expansion.
*
* @throws IOException The name or rdata was invalid after substitutions were
* performed.
*/
public
DnsRecord nextRecord() throws IOException {
if (current > end) {
return null;
}
String namestr = substitute(namePattern, current);
Name name = Name.Companion.fromString(namestr, origin);
String rdata = substitute(rdataPattern, current);
current += step;
return DnsRecord.fromString(name, type, dclass, ttl, rdata, origin);
}
private
String substitute(String spec, long n) throws IOException {
boolean escaped = false;
byte[] str = spec.getBytes();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length; i++) {
char c = (char) (str[i] & 0xFF);
if (escaped) {
sb.append(c);
escaped = false;
}
else if (c == '\\') {
if (i + 1 == str.length) {
throw new TextParseException("invalid escape character");
}
escaped = true;
}
else if (c == '$') {
boolean negative = false;
long offset = 0;
long width = 0;
long base = 10;
boolean wantUpperCase = false;
if (i + 1 < str.length && str[i + 1] == '$') {
// '$$' == literal '$' for backwards
// compatibility with old versions of BIND.
c = (char) (str[++i] & 0xFF);
sb.append(c);
continue;
}
else if (i + 1 < str.length && str[i + 1] == '{') {
// It's a substitution with modifiers.
i++;
if (i + 1 < str.length && str[i + 1] == '-') {
negative = true;
i++;
}
while (i + 1 < str.length) {
c = (char) (str[++i] & 0xFF);
if (c == ',' || c == '}') {
break;
}
if (c < '0' || c > '9') {
throw new TextParseException("invalid offset");
}
c -= '0';
offset *= 10;
offset += c;
}
if (negative) {
offset = -offset;
}
if (c == ',') {
while (i + 1 < str.length) {
c = (char) (str[++i] & 0xFF);
if (c == ',' || c == '}') {
break;
}
if (c < '0' || c > '9') {
throw new TextParseException("invalid width");
}
c -= '0';
width *= 10;
width += c;
}
}
if (c == ',') {
if (i + 1 == str.length) {
throw new TextParseException("invalid base");
}
c = (char) (str[++i] & 0xFF);
if (c == 'o') {
base = 8;
}
else if (c == 'x') {
base = 16;
}
else if (c == 'X') {
base = 16;
wantUpperCase = true;
}
else if (c != 'd') {
throw new TextParseException("invalid base");
}
}
if (i + 1 == str.length || str[i + 1] != '}') {
throw new TextParseException("invalid modifiers");
}
i++;
}
long v = n + offset;
if (v < 0) {
throw new TextParseException("invalid offset expansion");
}
String number;
if (base == 8) {
number = Long.toOctalString(v);
}
else if (base == 16) {
number = Long.toHexString(v);
}
else {
number = Long.toString(v);
}
if (wantUpperCase) {
number = number.toUpperCase();
}
if (width != 0 && width > number.length()) {
int zeros = (int) width - number.length();
while (zeros-- > 0) {
sb.append('0');
}
}
sb.append(number);
}
else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Constructs and returns all records in the expansion.
*
* @throws IOException The name or rdata of a record was invalid after
* substitutions were performed.
*/
public
DnsRecord[] expand() throws IOException {
List list = new ArrayList();
for (long i = start; i < end; i += step) {
String namestr = substitute(namePattern, current);
Name name = Name.Companion.fromString(namestr, origin);
String rdata = substitute(rdataPattern, current);
list.add(DnsRecord.fromString(name, type, dclass, ttl, rdata, origin));
}
return (DnsRecord[]) list.toArray(new DnsRecord[list.size()]);
}
/**
* Converts the generate specification to a string containing the corresponding
* $GENERATE statement.
*/
public
String toString() {
StringBuilder sb = new StringBuilder();
sb.append("$GENERATE ");
sb.append(start + "-" + end);
if (step > 1) {
sb.append("/" + step);
}
sb.append(" ");
sb.append(namePattern + " ");
sb.append(ttl + " ");
if (dclass != DnsClass.IN || !Options.check("noPrintIN")) {
sb.append(DnsClass.string(dclass) + " ");
}
sb.append(DnsRecordType.string(type) + " ");
sb.append(rdataPattern + " ");
return sb.toString();
}
}

View File

@ -0,0 +1,766 @@
// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org)
package org.xbill.DNS2.clients;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.xbill.DNS2.resolver.Cache;
import org.xbill.DNS2.resolver.Credibility;
import org.xbill.DNS2.resolver.ExtendedResolver;
import org.xbill.DNS2.resolver.Resolver;
import org.xbill.DNS2.resolver.ResolverConfig;
import org.xbill.DNS2.resolver.SetResponse;
import dorkbox.network.dns.Mnemonic;
import dorkbox.network.dns.Name;
import dorkbox.network.dns.constants.DnsClass;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.constants.DnsResponseCode;
import dorkbox.network.dns.exceptions.NameTooLongException;
import dorkbox.network.dns.exceptions.TextParseException;
import dorkbox.network.dns.records.CNAMERecord;
import dorkbox.network.dns.records.DNAMERecord;
import dorkbox.network.dns.records.DnsMessage;
import dorkbox.network.dns.records.DnsRecord;
import dorkbox.network.dns.records.RRset;
import dorkbox.network.dns.utils.Options;
/**
* The Lookup object issues queries to caching DNS servers. The input consists
* of a name, an optional type, and an optional class. Caching is enabled
* by default and used when possible to reduce the number of DNS requests.
* A Resolver, which defaults to an ExtendedResolver initialized with the
* resolvers located by the ResolverConfig class, performs the queries. A
* search path of domain suffixes is used to resolve relative names, and is
* also determined by the ResolverConfig class.
* <p>
* A Lookup object may be reused, but should not be used by multiple threads.
*
* @author Brian Wellington
* @see Cache
* @see Resolver
* @see ResolverConfig
*/
public final
class Lookup {
private static Resolver defaultResolver;
private static Name[] defaultSearchPath;
private static Map defaultCaches;
private static int defaultNdots;
private Resolver resolver;
private Name[] searchPath;
private Cache cache;
private boolean temporary_cache;
private int credibility;
private Name name;
private int type;
private int dclass;
private boolean verbose;
private int iterations;
private boolean foundAlias;
private boolean done;
private boolean doneCurrent;
private List aliases;
private DnsRecord[] answers;
private int result;
private String error;
private boolean nxdomain;
private boolean badresponse;
private String badresponse_error;
private boolean networkerror;
private boolean timedout;
private boolean nametoolong;
private boolean referral;
private static final Name[] noAliases = new Name[0];
/**
* The lookup was successful.
*/
public static final int SUCCESSFUL = 0;
/**
* The lookup failed due to a data or server error. Repeating the lookup
* would not be helpful.
*/
public static final int UNRECOVERABLE = 1;
/**
* The lookup failed due to a network error. Repeating the lookup may be
* helpful.
*/
public static final int TRY_AGAIN = 2;
/**
* The host does not exist.
*/
public static final int HOST_NOT_FOUND = 3;
/**
* The host exists, but has no records associated with the queried type.
*/
public static final int TYPE_NOT_FOUND = 4;
public static synchronized
void refreshDefault() {
try {
defaultResolver = new ExtendedResolver();
} catch (UnknownHostException e) {
throw new RuntimeException("Failed to initialize resolver");
}
defaultSearchPath = ResolverConfig.getCurrentConfig()
.searchPath();
defaultCaches = new HashMap();
defaultNdots = ResolverConfig.getCurrentConfig()
.ndots();
}
static {
refreshDefault();
}
/**
* Sets the Cache to be used as the default for the specified class by future
* Lookups.
*
* @param cache The default cache for the specified class.
* @param dclass The class whose cache is being set.
*/
public static synchronized
void setDefaultCache(Cache cache, int dclass) {
DnsClass.check(dclass);
defaultCaches.put(Mnemonic.toInteger(dclass), cache);
}
/**
* Sets the search path to be used as the default by future Lookups.
*
* @param domains The default search path.
*/
public static synchronized
void setDefaultSearchPath(Name[] domains) {
defaultSearchPath = domains;
}
/**
* Sets a custom logger that will be used to log the send and received packets.
*
* @param logger
*/
public static synchronized
void setPacketLogger(PacketLogger logger) {
Client.setPacketLogger(logger);
}
private
void reset() {
iterations = 0;
foundAlias = false;
done = false;
doneCurrent = false;
aliases = null;
answers = null;
result = -1;
error = null;
nxdomain = false;
badresponse = false;
badresponse_error = null;
networkerror = false;
timedout = false;
nametoolong = false;
referral = false;
if (temporary_cache) {
cache.clearCache();
}
}
/**
* Create a Lookup object that will find records of the given name and type
* in the IN class.
*
* @param name The name of the desired records
* @param type The type of the desired records
*
* @throws IllegalArgumentException The type is a meta type other than ANY.
* @see #Lookup(Name, int, int)
*/
public
Lookup(Name name, int type) {
this(name, type, DnsClass.IN);
}
/**
* Create a Lookup object that will find records of the given name, type,
* and class. The lookup will use the default cache, resolver, and search
* path, and look for records that are reasonably credible.
*
* @param name The name of the desired records
* @param type The type of the desired records
* @param dclass The class of the desired records
*
* @throws IllegalArgumentException The type is a meta type other than ANY.
* @see Cache
* @see Resolver
* @see Credibility
* @see Name
* @see DnsRecordType
* @see DnsClass
*/
public
Lookup(Name name, int type, int dclass) {
DnsRecordType.check(type);
DnsClass.check(dclass);
if (!DnsRecordType.isRR(type) && type != DnsRecordType.ANY) {
throw new IllegalArgumentException("Cannot query for " + "meta-types other than ANY");
}
this.name = name;
this.type = type;
this.dclass = dclass;
synchronized (Lookup.class) {
this.resolver = getDefaultResolver();
this.searchPath = getDefaultSearchPath();
this.cache = getDefaultCache(dclass);
}
this.credibility = Credibility.NORMAL;
this.verbose = Options.check("verbose");
this.result = -1;
}
/**
* Gets the Resolver that will be used as the default by future Lookups.
*
* @return The default resolver.
*/
public static synchronized
Resolver getDefaultResolver() {
return defaultResolver;
}
/**
* Sets the default Resolver to be used as the default by future Lookups.
*
* @param resolver The default resolver.
*/
public static synchronized
void setDefaultResolver(Resolver resolver) {
defaultResolver = resolver;
}
/**
* Gets the Cache that will be used as the default for the specified
* class by future Lookups.
*
* @param dclass The class whose cache is being retrieved.
*
* @return The default cache for the specified class.
*/
public static synchronized
Cache getDefaultCache(int dclass) {
DnsClass.check(dclass);
Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass));
if (c == null) {
c = new Cache(dclass);
defaultCaches.put(Mnemonic.toInteger(dclass), c);
}
return c;
}
/**
* Gets the search path that will be used as the default by future Lookups.
*
* @return The default search path.
*/
public static synchronized
Name[] getDefaultSearchPath() {
return defaultSearchPath;
}
/**
* Sets the search path that will be used as the default by future Lookups.
*
* @param domains The default search path.
*
* @throws TextParseException A name in the array is not a valid DNS name.
*/
public static synchronized
void setDefaultSearchPath(String[] domains) throws TextParseException {
if (domains == null) {
defaultSearchPath = null;
return;
}
Name[] newdomains = new Name[domains.length];
for (int i = 0; i < domains.length; i++) {
newdomains[i] = Name.Companion.fromString(domains[i], Name.root);
}
defaultSearchPath = newdomains;
}
/**
* Create a Lookup object that will find records of type A at the given name
* in the IN class.
*
* @param name The name of the desired records
*
* @see #Lookup(Name, int, int)
*/
public
Lookup(Name name) {
this(name, DnsRecordType.A, DnsClass.IN);
}
/**
* Create a Lookup object that will find records of the given name, type,
* and class.
*
* @param name The name of the desired records
* @param type The type of the desired records
* @param dclass The class of the desired records
*
* @throws TextParseException The name is not a valid DNS name
* @throws IllegalArgumentException The type is a meta type other than ANY.
* @see #Lookup(Name, int, int)
*/
public
Lookup(String name, int type, int dclass) throws TextParseException {
this(Name.Companion.fromString(name), type, dclass);
}
/**
* Create a Lookup object that will find records of the given name and type
* in the IN class.
*
* @param name The name of the desired records
* @param type The type of the desired records
*
* @throws TextParseException The name is not a valid DNS name
* @throws IllegalArgumentException The type is a meta type other than ANY.
* @see #Lookup(Name, int, int)
*/
public
Lookup(String name, int type) throws TextParseException {
this(Name.Companion.fromString(name), type, DnsClass.IN);
}
/**
* Create a Lookup object that will find records of type A at the given name
* in the IN class.
*
* @param name The name of the desired records
*
* @throws TextParseException The name is not a valid DNS name
* @see #Lookup(Name, int, int)
*/
public
Lookup(String name) throws TextParseException {
this(Name.Companion.fromString(name), DnsRecordType.A, DnsClass.IN);
}
/**
* Sets the resolver to use when performing this lookup. This overrides the
* default value.
*
* @param resolver The resolver to use.
*/
public
void setResolver(Resolver resolver) {
this.resolver = resolver;
}
/**
* Sets the search path to use when performing this lookup. This overrides the
* default value.
*
* @param domains An array of names containing the search path.
*/
public
void setSearchPath(Name[] domains) {
this.searchPath = domains;
}
/**
* Sets the search path to use when performing this lookup. This overrides the
* default value.
*
* @param domains An array of names containing the search path.
*
* @throws TextParseException A name in the array is not a valid DNS name.
*/
public
void setSearchPath(String[] domains) throws TextParseException {
if (domains == null) {
this.searchPath = null;
return;
}
Name[] newdomains = new Name[domains.length];
for (int i = 0; i < domains.length; i++) {
newdomains[i] = Name.Companion.fromString(domains[i], Name.root);
}
this.searchPath = newdomains;
}
/**
* Sets the cache to use when performing this lookup. This overrides the
* default value. If the results of this lookup should not be permanently
* cached, null can be provided here.
*
* @param cache The cache to use.
*/
public
void setCache(Cache cache) {
if (cache == null) {
this.cache = new Cache(dclass);
this.temporary_cache = true;
}
else {
this.cache = cache;
this.temporary_cache = false;
}
}
/**
* Sets ndots to use when performing this lookup, overriding the default value.
* Specifically, this refers to the number of "dots" which, if present in a
* name, indicate that a lookup for the absolute name should be attempted
* before appending any search path elements.
*
* @param ndots The ndots value to use, which must be greater than or equal to
* 0.
*/
public
void setNdots(int ndots) {
if (ndots < 0) {
throw new IllegalArgumentException("Illegal ndots value: " + ndots);
}
defaultNdots = ndots;
}
/**
* Sets the minimum credibility level that will be accepted when performing
* the lookup. This defaults to Credibility.NORMAL.
*
* @param credibility The minimum credibility level.
*/
public
void setCredibility(int credibility) {
this.credibility = credibility;
}
private
void follow(Name name, Name oldname) {
foundAlias = true;
badresponse = false;
networkerror = false;
timedout = false;
nxdomain = false;
referral = false;
iterations++;
if (iterations >= 10 || name.equals(oldname)) {
result = UNRECOVERABLE;
error = "CNAME loop";
done = true;
return;
}
if (aliases == null) {
aliases = new ArrayList();
}
aliases.add(oldname);
lookup(name);
}
private
void processResponse(Name name, SetResponse response) {
if (response.isSuccessful()) {
RRset[] rrsets = response.answers();
List l = new ArrayList();
Iterator it;
int i;
for (i = 0; i < rrsets.length; i++) {
it = rrsets[i].rrs();
while (it.hasNext()) {
l.add(it.next());
}
}
result = SUCCESSFUL;
answers = (DnsRecord[]) l.toArray(new DnsRecord[l.size()]);
done = true;
}
else if (response.isNXDOMAIN()) {
nxdomain = true;
doneCurrent = true;
if (iterations > 0) {
result = HOST_NOT_FOUND;
done = true;
}
}
else if (response.isNXRRSET()) {
result = TYPE_NOT_FOUND;
answers = null;
done = true;
}
else if (response.isCNAME()) {
CNAMERecord cname = response.getCNAME();
follow(cname.getTarget(), name);
}
else if (response.isDNAME()) {
DNAMERecord dname = response.getDNAME();
try {
follow(name.fromDNAME(dname), name);
} catch (NameTooLongException e) {
result = UNRECOVERABLE;
error = "Invalid DNAME target";
done = true;
}
}
else if (response.isDelegation()) {
// We shouldn't get a referral. Ignore it.
referral = true;
}
}
private
void lookup(Name current) {
SetResponse sr = cache.lookupRecords(current, type, credibility);
if (verbose) {
System.err.println("lookup " + current + " " + DnsRecordType.string(type));
System.err.println(sr);
}
processResponse(current, sr);
if (done || doneCurrent) {
return;
}
DnsRecord question = DnsRecord.newRecord(current, type, dclass);
DnsMessage query = DnsMessage.newQuery(question);
DnsMessage response = null;
try {
response = resolver.send(query);
} catch (IOException e) {
// A network error occurred. Press on.
if (e instanceof InterruptedIOException) {
timedout = true;
}
else {
networkerror = true;
}
return;
}
int rcode = response.getHeader()
.getRcode();
if (rcode != DnsResponseCode.NOERROR && rcode != DnsResponseCode.NXDOMAIN) {
// The server we contacted is broken or otherwise unhelpful.
// Press on.
badresponse = true;
badresponse_error = DnsResponseCode.string(rcode);
return;
}
if (!query.getQuestion()
.equals(response.getQuestion())) {
// The answer doesn't match the question. That's not good.
badresponse = true;
badresponse_error = "response does not match query";
return;
}
sr = cache.addMessage(response);
if (sr == null) {
sr = cache.lookupRecords(current, type, credibility);
}
if (verbose) {
System.err.println("queried " + current + " " + DnsRecordType.string(type));
System.err.println(sr);
}
processResponse(current, sr);
}
private
void resolve(Name current, Name suffix) {
doneCurrent = false;
Name tname = null;
if (suffix == null) {
tname = current;
}
else {
try {
tname = Name.concatenate(current, suffix);
} catch (NameTooLongException e) {
nametoolong = true;
return;
}
}
lookup(tname);
}
/**
* Performs the lookup, using the specified Cache, Resolver, and search path.
*
* @return The answers, or null if none are found.
*/
public
DnsRecord[] run() {
if (done) {
reset();
}
if (name.isAbsolute()) {
resolve(name, null);
}
else if (searchPath == null) {
resolve(name, Name.root);
}
else {
if (name.labels() > defaultNdots) {
resolve(name, Name.root);
}
if (done) {
return answers;
}
for (int i = 0; i < searchPath.length; i++) {
resolve(name, searchPath[i]);
if (done) {
return answers;
}
else if (foundAlias) {
break;
}
}
}
if (!done) {
if (badresponse) {
result = TRY_AGAIN;
error = badresponse_error;
done = true;
}
else if (timedout) {
result = TRY_AGAIN;
error = "timed out";
done = true;
}
else if (networkerror) {
result = TRY_AGAIN;
error = "network error";
done = true;
}
else if (nxdomain) {
result = HOST_NOT_FOUND;
done = true;
}
else if (referral) {
result = UNRECOVERABLE;
error = "referral";
done = true;
}
else if (nametoolong) {
result = UNRECOVERABLE;
error = "name too long";
done = true;
}
}
return answers;
}
/**
* Returns the answers from the lookup.
*
* @return The answers, or null if none are found.
*
* @throws IllegalStateException The lookup has not completed.
*/
public
DnsRecord[] getAnswers() {
checkDone();
return answers;
}
private
void checkDone() {
if (done && result != -1) {
return;
}
StringBuilder sb = new StringBuilder("Lookup of " + name + " ");
if (dclass != DnsClass.IN) {
sb.append(DnsClass.string(dclass))
.append(" ");
}
sb.append(DnsRecordType.string(type))
.append(" isn't done");
throw new IllegalStateException(sb.toString());
}
/**
* Returns all known aliases for this name. Whenever a CNAME/DNAME is
* followed, an alias is added to this array. The last element in this
* array will be the owner name for records in the answer, if there are any.
*
* @return The aliases.
*
* @throws IllegalStateException The lookup has not completed.
*/
public
Name[] getAliases() {
checkDone();
if (aliases == null) {
return noAliases;
}
return (Name[]) aliases.toArray(new Name[aliases.size()]);
}
/**
* Returns the result code of the lookup.
*
* @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN,
* HOST_NOT_FOUND, or TYPE_NOT_FOUND.
*
* @throws IllegalStateException The lookup has not completed.
*/
public
int getResult() {
checkDone();
return result;
}
/**
* Returns an error string describing the result code of this lookup.
*
* @return A string, which may either directly correspond the result code
* or be more specific.
*
* @throws IllegalStateException The lookup has not completed.
*/
public
String getErrorString() {
checkDone();
if (error != null) {
return error;
}
switch (result) {
case SUCCESSFUL:
return "successful";
case UNRECOVERABLE:
return "unrecoverable error";
case TRY_AGAIN:
return "try again";
case HOST_NOT_FOUND:
return "host not found";
case TYPE_NOT_FOUND:
return "type not found";
}
throw new IllegalStateException("unknown result");
}
}

Some files were not shown because too many files have changed in this diff Show More