Skip to content

Commit afafaf8

Browse files
committed
first commit
0 parents  commit afafaf8

9 files changed

+872
-0
lines changed

LICENSE

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[Executive summary: "BSD-3clause" license. Free software. See
2+
<http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses>]
3+
4+
--------------------------------------------------------------------
5+
Copyright (c) 2009 Stéphane Bortzmeyer <[email protected]>
6+
7+
All rights reserved.
8+
9+
Redistribution and use in source and binary forms, with or without
10+
modification, are permitted provided that the following conditions
11+
are met:
12+
1. Redistributions of source code must retain the above copyright
13+
notice, this list of conditions and the following disclaimer.
14+
2. Redistributions in binary form must reproduce the above copyright
15+
notice, this list of conditions and the following disclaimer in the
16+
documentation and/or other materials provided with the distribution.
17+
3. The name of the author may not be used to endorse or promote products
18+
derived from this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Makefile

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
include $(GOROOT)/src/Make.$(GOARCH)
2+
3+
TARBALL=/tmp/grong.tar.gz
4+
5+
all: server
6+
7+
test: server
8+
./server -debug=4
9+
10+
server.$O: responder.$O types.$O
11+
12+
responder.$O: types.$O
13+
14+
%.$O: %.go
15+
${GC} $<
16+
gopack grc types.a types.8 # Workaround a bug in the linker
17+
18+
server: server.$O
19+
${LD} -o $@ server.$O
20+
21+
dist: clean
22+
(cd ..; tar czvf ${TARBALL} grong/*)
23+
24+
clean:
25+
rm -f server *~ *.$O *.a

README

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
GRONG (Gross and ROugh Nameserver written in Go) is a DNS (Domain Name
2+
System) authoritative name server. It is intended as a research
3+
project and is *not* suitable for use on the wild Internet (for
4+
instance, it has little protection against rogue packets).
5+
6+
DO NOT USE ON A PRODUCTION SITE
7+
YOU HAVE BEEN WARNED!!!
8+
9+
Disclaimer: I've never been to this city
10+
<http://en.wikipedia.org/wiki/Grong>
11+
12+
GRONG can only be used as an authoritative name server (like, for
13+
instance, nsd), not as a recursive one.
14+
15+
GRONG provides a general DNS engine, the front-end, which receives
16+
packets, parses them and sends a proper response, and several possible
17+
back-ends (named "responders") which generates a response, given the
18+
query. Some are provided with GRONG and you are welcome to write
19+
others.
20+
21+
Usage
22+
*****
23+
24+
./dnsserver [-address="[ADDRESS]:PORT"] [-debug=N]
25+
26+
Run with -h to see the defaults.
27+
28+
The -address option takes either a port (in the syntax ":NNN"), in
29+
that case GRONG listens on all IP addresses, or one address (in the
30+
syntax "x.y.z.T:NNN" for IPv4 and "[xxxx:yyyy::zzzz]:NNN" for
31+
IPv6). There is currently no way to listen on some (but not all) of the IP
32+
addresses.
33+
34+
The back-end is choosen at compile-time only (I have no idea about the
35+
support for dynamic linking in Go)
36+
37+
Among the provided responders:
38+
* rude-responder: responds REFUSED to every query
39+
* reflector-responder: for TXT requests, responds with the IP address of the client
40+
* as112: an AS 112 name server (see http://www.as112.net/)
41+
42+
For the person who compiles
43+
**************************
44+
45+
You need a working Go <http://golang.org> environment. Today, only the
46+
gc compiler is supported.
47+
48+
To choose a responder (here, foobar-responder):
49+
50+
make clean
51+
ln -sf foobar-responder.go responder.go
52+
make
53+
mv ./server /where/you/want/grong-foobar
54+
55+
56+
For the person who writes a responder
57+
************************************
58+
59+
The interface of the responder is:
60+
61+
It must be in package "responder" and imports package "types". Read
62+
"types.go" first, it contains useful constants (named from the RFC
63+
1035).
64+
65+
The front-end checks that the request is a query and, if so, calls the
66+
responder. The prototype is:
67+
68+
func Respond(query types.DNSquery) types.DNSresponse
69+
70+
In the DNSresponse, RRs (Resource Records) have to be in the wire
71+
format (the front-end does not know the format of the RR, to keep it
72+
generic). For instance, data in TXT RR has to be {length,
73+
value}. There are some utilities functions in types to help you to do
74+
so.
75+
76+
Implementation notes
77+
********************
78+
79+
One goroutine is run for every DNS request. Makes the code much
80+
simpler and easier to read but may explain the fact that performance
81+
are behind BIND.
82+
83+
TODO
84+
****
85+
86+
Put on github
87+
88+
Debugging of Go runtime issues, queryperf loses too many packets
89+
90+
Requests A and AAAA for the reflecting responder
91+
92+
EDNS, specially for NSID (RFC 5001)
93+
94+
Give the responder some global info such as the debug level and some
95+
per-query info such as the buffer size (512 by default)
96+
97+
Pass unknown command-line options to the responder
98+
99+
Hardening against rogue packets
100+
101+
Use the log package
102+
103+
Better handling of errors, an invalid packet should not stop the name
104+
server. Test with typing junk in telnet. Or learn Scapy, which seems
105+
more interesting. See for instance the example in
106+
<http://www.secdev.org/projects/scapy/demo.html>
107+
108+
Finish the AS112 responder
109+
110+
The abiity to listen to more than one address (but not all). Can I
111+
give several -address option to the flag module? If so, it probably
112+
just means firing several udpListeners and several tcpListeners
113+
114+
Test with gccgo
115+
116+
DNSSEC (no, I'm joking)
117+
118+
119+
Author
120+
******
121+
122+
Stéphane Bortzmeyer <[email protected]>
123+
124+
125+
License
126+
*******
127+
128+
This is free software. Free as in free speech, not as in free beer.
129+
130+
See the actual license in LICENSE

as112.go

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/* A name server for the AS112 sink system. See http://www.as112.net/
2+
3+
Stephane Bortzmeyer <[email protected]>
4+
5+
*/
6+
7+
package responder
8+
9+
import (
10+
"regexp"
11+
"strings"
12+
"os"
13+
"fmt"
14+
"./types"
15+
)
16+
17+
var (
18+
as112Domain, as112SubDomain *regexp.Regexp
19+
)
20+
21+
const as112Regexp = "(168\\.192\\.in-addr\\.arpa|154\\.169\\.in-addr\\.arpa|16\\.172\\.in-addr\\.arpa|17\\.172\\.in-addr\\.arpa|18\\.172\\.in-addr\\.arpa|19\\.172\\.in-addr\\.arpa|20\\.172\\.in-addr\\.arpa|21\\.172\\.in-addr\\.arpa|22\\.172\\.in-addr\\.arpa|23\\.172\\.in-addr\\.arpa|24\\.172\\.in-addr\\.arpa|25\\.172\\.in-addr\\.arpa|26\\.172\\.in-addr\\.arpa|27\\.172\\.in-addr\\.arpa|28\\.172\\.in-addr\\.arpa|29\\.172\\.in-addr\\.arpa|30\\.172\\.in-addr\\.arpa|31\\.172\\.in-addr\\.arpa|10\\.in-addr\\.arpa)$"
22+
23+
const defaultTtl = 3600
24+
25+
// Answers to "TXT hostname.as112.net"
26+
var hostnameAnswers []string // Should be "const" but Go does not have const arrays :-(
27+
// So, see the body of init() for the values
28+
29+
// Name servers of AS112, currently two (see init())
30+
var as112nameServers [2]string
31+
32+
var hostnamesoa, as112soa types.SOArecord // See init() for the value
33+
34+
func nsRecords(domain string) (result []types.RR) {
35+
result = make([]types.RR, len(as112nameServers))
36+
for i, text := range as112nameServers {
37+
result[i].Name = domain
38+
result[i].Ttl = defaultTtl
39+
result[i].Tipe = types.NS
40+
result[i].Class = types.IN
41+
result[i].Data = types.Encode(text)
42+
}
43+
return
44+
}
45+
46+
func soaRecord(domain string, soa types.SOArecord) (result types.RR) {
47+
result.Name = domain
48+
result.Ttl = defaultTtl
49+
result.Tipe = types.SOA
50+
result.Class = types.IN
51+
result.Data = types.EncodeSOA(soa)
52+
return
53+
}
54+
55+
func Respond(query types.DNSquery) (result types.DNSresponse) {
56+
result.Asection = nil
57+
qname := strings.ToLower(query.Qname)
58+
if query.Qclass == types.IN {
59+
switch {
60+
case as112Domain.Match(strings.Bytes(qname)):
61+
result.Responsecode = types.NOERROR
62+
switch {
63+
case query.Qtype == types.NS:
64+
result.Asection = nsRecords(qname)
65+
case query.Qtype == types.SOA:
66+
result.Asection = make([]types.RR, 1)
67+
result.Asection[0] = soaRecord(qname, as112soa)
68+
case true:
69+
// Do nothing
70+
}
71+
case as112SubDomain.Match(strings.Bytes(qname)):
72+
result.Responsecode = types.NXDOMAIN
73+
// TODO: send the proper SOA in the authority section (so we
74+
// must find which domain matched)
75+
case qname == "hostname.as112.net":
76+
result.Responsecode = types.NOERROR
77+
switch { // TODO: handle ANY qtypes
78+
case query.Qtype == types.TXT:
79+
result.Asection = make([]types.RR, len(hostnameAnswers))
80+
for i, text := range hostnameAnswers {
81+
result.Asection[i].Name = "hostname.as112.net"
82+
result.Asection[i].Ttl = defaultTtl
83+
result.Asection[i].Tipe = types.TXT
84+
result.Asection[i].Class = types.IN
85+
result.Asection[i].Data = types.ToTXT(text)
86+
}
87+
case query.Qtype == types.NS:
88+
result.Asection = nsRecords("hostname.as112.net")
89+
case query.Qtype == types.SOA:
90+
result.Asection = make([]types.RR, 1)
91+
result.Asection[0] = soaRecord("hostname.as112.net", hostnamesoa)
92+
case true:
93+
// Do nothing
94+
}
95+
case true:
96+
result.Responsecode = types.SERVFAIL
97+
}
98+
} else {
99+
result.Responsecode = types.SERVFAIL
100+
}
101+
return result
102+
}
103+
104+
func init() {
105+
var (
106+
error os.Error
107+
)
108+
// Do not forget to change the size in the call to make() if you add a
109+
// string (yes, Go is painful in that respect)
110+
hostnameAnswers = make([]string, 3)
111+
hostnameAnswers[0] = "Unknown location on Earth."
112+
hostnameAnswers[1] = "GRONG, name server written in Go."
113+
hostnameAnswers[2] = "See http://as112.net/ for more information."
114+
as112nameServers[0] = "blackhole-1.iana.org"
115+
as112nameServers[1] = "blackhole-2.iana.org"
116+
as112SubDomain, error = regexp.Compile(as112Regexp)
117+
hostnamesoa.Mname = "NOT-CONFIGURED.as112.example.net" // Put the real host name
118+
hostnamesoa.Rname = "UNKNOWN.as112.example.net" // Put your email address (with @ replaced by .)
119+
hostnamesoa.Serial = 2003030100
120+
hostnamesoa.Refresh = 3600
121+
hostnamesoa.Retry = 600
122+
hostnamesoa.Expire = 2592000
123+
hostnamesoa.Minimum = 15
124+
as112soa.Mname = "prisoner.iana.org"
125+
as112soa.Rname = "hostmaster.root-servers.org"
126+
as112soa.Serial = 2002040800
127+
as112soa.Refresh = 1800
128+
as112soa.Retry = 900
129+
as112soa.Expire = 604800
130+
as112soa.Minimum = 604800
131+
if error != nil {
132+
fmt.Printf("Internal error: wrong regular expression: %s", error)
133+
os.Exit(1)
134+
}
135+
as112Domain, error = regexp.Compile("^" + as112Regexp)
136+
if error != nil {
137+
fmt.Printf("Internal error: wrong regular expression: %s\n", error)
138+
os.Exit(1)
139+
}
140+
}

reflector-responder.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package responder
2+
3+
import (
4+
"net"
5+
"./types"
6+
)
7+
8+
func txtRecord(client net.Addr) []byte {
9+
sclient := client.String()
10+
return types.ToTXT(sclient)
11+
}
12+
13+
func Respond(query types.DNSquery) types.DNSresponse {
14+
var (
15+
result types.DNSresponse
16+
)
17+
result.Asection = nil
18+
switch {
19+
case query.Qclass != types.IN:
20+
result.Responsecode = types.SERVFAIL
21+
case query.Qtype == types.TXT:
22+
result.Responsecode = types.NOERROR
23+
ancount := 1
24+
result.Asection = make([]types.RR, ancount)
25+
result.Asection[0].Name = query.Qname
26+
result.Asection[0].Tipe = types.TXT
27+
result.Asection[0].Class = types.IN
28+
result.Asection[0].Ttl = 0
29+
// TODO: better formatting, for instance allowing to have only the IP address
30+
result.Asection[0].Data = txtRecord(query.Client)
31+
case query.Qtype == types.ALL:
32+
result.Responsecode = types.NOERROR
33+
ancount := 1 // TODO: add an A or a AAAA
34+
// TODO: reuse the code for the case of QTYPE=TXT
35+
result.Asection = make([]types.RR, ancount)
36+
result.Asection[0].Name = query.Qname
37+
result.Asection[0].Tipe = types.TXT
38+
result.Asection[0].Class = types.IN
39+
result.Asection[0].Ttl = 0
40+
result.Asection[0].Data = txtRecord(query.Client)
41+
// TODO: handle A and AAAA QTYPEs
42+
case true:
43+
result.Responsecode = types.NOERROR
44+
}
45+
return result
46+
}
47+
48+
func init() {}

responder.go

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
reflector-responder.go

rude-responder.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package responder
2+
3+
import (
4+
"./types"
5+
)
6+
7+
func Respond(query types.DNSquery) types.DNSresponse {
8+
var (
9+
result types.DNSresponse
10+
)
11+
result.Responsecode = types.REFUSED
12+
return result
13+
}
14+
15+
func init() {}

0 commit comments

Comments
 (0)