commit be1b378ac210c29e299fe77f23e19fd81fc332d7
Author: Frederic Cambus <fcambus@users.sourceforge.net>
Date: Mon, 1 Jul 2013 12:58:06 +0200
Initial RRDA release
Diffstat:
A | AUTHORS | | | 6 | ++++++ |
A | LICENSE | | | 28 | ++++++++++++++++++++++++++++ |
A | README.md | | | 105 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | TODO | | | 3 | +++ |
A | rrda.go | | | 182 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 324 insertions(+), 0 deletions(-)
diff --git a/AUTHORS b/AUTHORS
@@ -0,0 +1,6 @@
+RRDA is developed by :
+
+Frederic Cambus <fcambus AT users DOT sourceforge DOT net>
+
+Site : http://www.cambus.net
+Twitter : @fcambus
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2012-2013, Frederic Cambus
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of RRDA nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
@@ -0,0 +1,104 @@
+## Description
+
+RRDA is a REST API written in Go allowing to perform DNS queries over HTTP, and to get reverse PTR records for both IPv4 and IPv6 addresses. It outputs JSON-encoded DNS responses.
+
+RRDA is a recursive acronym for "RRDA REST DNS API".
+
+## Requirements
+
+RRDA requires the following Go libraries :
+
+- dns : DNS library in Go - https://github.com/miekg/dns
+- pat : pattern muxer for Go - https://github.com/bmizerany/pat
+
+## Installation
+
+Build and install with the `go` tool :
+
+ go build rrda
+ go install rrda
+
+## Usage
+
+ Usage of ./rrda:
+ -host="127.0.0.1": Set the server host
+ -port="8080": Set the server port
+
+## Making Queries
+
+The following examples assume there is a resolver on localhost listening on port 53.
+
+### Getting Resources Records
+
+URL Scheme : http://server:port/resolver:port/domain/querytype
+
+Example : http://127.0.0.1:8080/127.0.0.1:53/example.org/ns
+
+### Getting Reverse PTR Records (for both IPv4 and IPv6 addresses)
+
+URL Scheme : http://server:port/resolver:port/x/ip
+
+Example (IPv4) : http://127.0.0.1:8080/127.0.0.1:53/x/193.0.6.139
+Example (IPv6) : http://127.0.0.1:8080/127.0.0.1:53/x/2001:67c:2e8:22::c100:68b
+
+## JSON Output Schema
+
+The output is a JSON object containing the following arrays, representing the appropriate sections of DNS packets :
+
+- question
+- answer
+- authority (omitted if empty)
+- additional (omitted if empty)
+
+### Question section
+
+- name
+- type
+- class
+
+### Answer, Authority, Additional sections
+
+- name
+- type
+- class
+- ttl
+- rdlength
+- rdata
+
+## Client Errors
+
+When incorrect user input is entered, the server returns an HTTP 400 Error (Bad Request), along with a JSON-encoded error message.
+
+Code 401 : Input string could not be parsed
+Code 402 : Input string is not a well-formed domain name
+Code 403 : Input string is not a valid IP address
+Code 404 : Invalid DNS query type
+
+## Server Errors
+
+When the DNS server cannot be reached or returns an error, the server returns an HTTP 500 Error (Internal Server Error), along with a JSON-encoded error message.
+
+Code 501 : DNS server could not be reached
+Code 502 : The name server encountered an internal failure while processing this request (SERVFAIL)
+Code 503 : Some name that ought to exist, does not exist (NXDOMAIN)
+Code 505 : The name server refuses to perform the specified operation for policy or security reasons (REFUSED)
+
+## Sites using RRDA
+
+- StatDNS : Rest DNS API - http://www.statdns.com/api/
+- DNS-LG : Multilocation DNS Looking Glass - http://www.dns-lg.com
+
+## License
+
+RRDA is released under the BSD 3-Clause license. See `LICENSE` file for details.
+
+## Author
+
+RRDA is developed by Frederic Cambus
+
+- Site : http://www.cambus.net
+- Twitter: http://twitter.com/fcambus
+
+## Resources
+
+Project Homepage : http://www.statdns.com
+\ No newline at end of file
diff --git a/TODO b/TODO
@@ -0,0 +1,3 @@
+- Add a Makefile to install binaries and init files
+- Add automated testing (Error codes, Domains > 253 characters, Labels > 63 characters)
+- Add DNS Header Flags and RCODEs to the JSON structure?
diff --git a/rrda.go b/rrda.go
@@ -0,0 +1,182 @@
+/*****************************************************************************/
+/* */
+/* RRDA (RRDA REST DNS API) 1.00 (c) by Frederic Cambus 2012-2013 */
+/* http://www.statdns.com */
+/* */
+/* Created: 2012/03/11 */
+/* Last Updated: 2013/07/01 */
+/* */
+/* RRDA is released under the BSD 3-Clause license. */
+/* See LICENSE file for details. */
+/* */
+/*****************************************************************************/
+
+package main
+
+import (
+ "code.google.com/p/go.net/idna"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "github.com/bmizerany/pat"
+ "github.com/miekg/dns"
+ "io"
+ "net/http"
+ "os"
+ "strings"
+)
+
+type Error struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+type Question struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Class string `json:"class"`
+}
+
+type Section struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Class string `json:"class"`
+ Ttl uint32 `json:"ttl"`
+ Rdlength uint16 `json:"rdlength"`
+ Rdata string `json:"rdata"`
+}
+
+type Message struct {
+ Question []*Question `json:"question"`
+ Answer []*Section `json:"answer"`
+ Authority []*Section `json:"authority,omitempty"`
+ Additional []*Section `json:"additional,omitempty"`
+}
+
+// Return rdata
+func rdata(RR dns.RR) string {
+ return strings.Replace(RR.String(), RR.Header().String(), "", -1)
+}
+
+// Return an HTTP Error along with a JSON-encoded error message
+func error(w http.ResponseWriter, status int, code int, message string) {
+ if output, err := json.Marshal(Error{Code: code, Message: message}); err == nil {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ fmt.Fprintln(w, string(output))
+ }
+}
+
+// Generate JSON output
+func jsonify(w http.ResponseWriter, question []dns.Question, answer []dns.RR, authority []dns.RR, additional []dns.RR) {
+ var answerArray, authorityArray, additionalArray []*Section
+
+ for _, answer := range answer {
+ answerArray = append(answerArray, &Section{answer.Header().Name, dns.TypeToString[answer.Header().Rrtype], dns.ClassToString[answer.Header().Class], answer.Header().Ttl, answer.Header().Rdlength, rdata(answer)})
+ }
+
+ for _, authority := range authority {
+ authorityArray = append(authorityArray, &Section{authority.Header().Name, dns.TypeToString[authority.Header().Rrtype], dns.ClassToString[authority.Header().Class], authority.Header().Ttl, authority.Header().Rdlength, rdata(authority)})
+ }
+
+ for _, additional := range additional {
+ additionalArray = append(additionalArray, &Section{additional.Header().Name, dns.TypeToString[additional.Header().Rrtype], dns.ClassToString[additional.Header().Class], additional.Header().Ttl, additional.Header().Rdlength, rdata(additional)})
+ }
+
+ if output, err := json.MarshalIndent(Message{[]*Question{&Question{question[0].Name, dns.TypeToString[question[0].Qtype], dns.ClassToString[question[0].Qclass]}}, answerArray, authorityArray, additionalArray}, "", " "); err == nil {
+ io.WriteString(w, string(output))
+ }
+}
+
+// Perform DNS resolution
+func resolve(w http.ResponseWriter, server string, domain string, querytype uint16) {
+ m := new(dns.Msg)
+ m.SetQuestion(domain, querytype)
+ m.MsgHdr.RecursionDesired = true
+
+ w.Header().Set("Content-Type", "application/json")
+
+ c := new(dns.Client)
+
+Redo:
+ if in, _, err := c.Exchange(m, server); err == nil { // Second return value is RTT, not used for now
+ if in.MsgHdr.Truncated {
+ c.Net = "tcp"
+ goto Redo
+ }
+
+ switch in.MsgHdr.Rcode {
+ case dns.RcodeServerFailure:
+ error(w, 500, 502, "The name server encountered an internal failure while processing this request (SERVFAIL)")
+ case dns.RcodeNameError:
+ error(w, 500, 503, "Some name that ought to exist, does not exist (NXDOMAIN)")
+ case dns.RcodeRefused:
+ error(w, 500, 505, "The name server refuses to perform the specified operation for policy or security reasons (REFUSED)")
+ default:
+ jsonify(w, in.Question, in.Answer, in.Ns, in.Extra)
+ }
+ } else {
+ error(w, 500, 501, "DNS server could not be reached")
+ }
+}
+
+// Handler for DNS queries
+func query(w http.ResponseWriter, r *http.Request) {
+ server := r.URL.Query().Get(":server")
+ domain := dns.Fqdn(r.URL.Query().Get(":domain"))
+ querytype := r.URL.Query().Get(":querytype")
+
+ if domain, err := idna.ToASCII(domain); err == nil { // ValidateValid IP address (IPv4 or IPv6)
+ if _, _, isDomain := dns.IsDomainName(domain); isDomain { // Well-formed domain name
+ if querytype, ok := dns.StringToType[strings.ToUpper(querytype)]; ok { // Valid DNS query type
+ resolve(w, server, domain, querytype)
+ } else {
+ error(w, 400, 404, "Invalid DNS query type")
+ }
+ } else {
+ error(w, 400, 402, "Input string is not a well-formed domain name")
+ }
+ } else {
+ error(w, 400, 401, "Input string could not be parsed")
+ }
+}
+
+// Handler for reverse DNS queries
+func ptr(w http.ResponseWriter, r *http.Request) {
+ server := r.URL.Query().Get(":server")
+ ip := r.URL.Query().Get(":ip")
+
+ if arpa, err := dns.ReverseAddr(ip); err == nil { // Valid IP address (IPv4 or IPv6)
+ resolve(w, server, arpa, dns.TypePTR)
+ } else {
+ error(w, 400, 403, "Input string is not a valid IP address")
+ }
+}
+
+func main() {
+ header := `-------------------------------------------------------------------------------
+RRDA (RRDA REST DNS API) (c) by Frederic Cambus 2012-2013
+-------------------------------------------------------------------------------`
+
+ host := flag.String("host", "127.0.0.1", "Set the server host")
+ port := flag.String("port", "8080", "Set the server port")
+
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+
+ fmt.Println(header)
+
+ m := pat.New()
+ m.Get("/:server/x/:ip", http.HandlerFunc(ptr))
+ m.Get("/:server/:domain/:querytype", http.HandlerFunc(query))
+
+ if err := http.ListenAndServe(*host+":"+*port, m); err != nil {
+ fmt.Println("\nERROR :", err)
+ os.Exit(1)
+ }
+
+ fmt.Println("\nListening on port :", *port)
+}