Support $GENERATE (the bind extension).

It is even better than the bind one, because all records are
supported (as long as the output is valid)
This commit is contained in:
Miek Gieben 2012-05-20 15:28:27 +02:00
parent e11c077112
commit 7a3af5b729
7 changed files with 203 additions and 39 deletions

View File

@ -17,7 +17,8 @@ can build servers and resolvers with it.
# Features
* UDP/TCP queries, IPv4 and IPv6;
* RFC 1035 zone file parsing;
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE are
supported);
* Fast:
* Reply speed around 35/40K qps (faster hardware results in more qps);
* Parsing RRs (zone files) with 95/100K RR/s, that's 5M records in about 50 seconds;

View File

@ -228,7 +228,7 @@ func IsDomainName(s string) (uint8, uint8, bool) { // copied from net package.
switch {
default:
return 0, uint8(l - longer), false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c == '*':
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c == '*' || c == '/':
ok = true
partlen++
case c == '\\':

1
msg.go
View File

@ -46,7 +46,6 @@ var (
ErrSigGen error = &Error{Err: "dns: bad signature generation"}
ErrAuth error = &Error{Err: "dns: bad authentication"}
ErrXfrSoa error = &Error{Err: "dns: no SOA seen"}
ErrXfrLast error = &Error{Err: "dns: last SOA"}
ErrXfrType error = &Error{Err: "dns: no ixfr, nor axfr"}
ErrHandle error = &Error{Err: "dns: handle is nil"}
ErrChan error = &Error{Err: "dns: channel is nil"}

View File

@ -553,3 +553,16 @@ func TestRfc1982(t *testing.T) {
}
}
}
func TestGenerate(t *testing.T) {
zone := "$GENERATE 65-126 $ 1D AAAA $.64/26"
to := ParseZone(strings.NewReader(zone), "miek.nl.", "")
for x := range to {
if x.Error == nil {
t.Logf("Read %s\n", x.RR.String())
} else {
t.Logf("Failed to parse %v\n", x.Error)
t.Fail()
}
}
}

2
xfr.go
View File

@ -63,7 +63,7 @@ func (w *reply) axfrReceive(c chan *Exchange) {
if !first {
w.tsigTimersOnly = true // Subsequent envelopes use this.
if checkXfrSOA(in, false) {
c <- &Exchange{w.req, in, w.rtt, w.conn.RemoteAddr(), ErrXfrLast}
c <- &Exchange{w.req, in, w.rtt, w.conn.RemoteAddr(), nil}
return
}
c <- &Exchange{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil}

156
zgenerate.go Normal file
View File

@ -0,0 +1,156 @@
package dns
import (
"fmt"
"strconv"
"strings"
)
// Parse the $GENERATE statement as used in BIND9 zones.
// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
// We are called after '$GENERATE '. After which we expect:
// * the range (12-24/2)
// * lhs (ownername)
// * [[ttl][class]]
// * type
// * rhs (rdata)
func generate(l lex, c chan lex, t chan Token, o string) string {
step := 1
if i := strings.IndexAny(l.token, "/"); i != -1 {
if i+1 == len(l.token) {
return "bad step in $GENERATE range"
}
if s, e := strconv.Atoi(l.token[i+1:]); e != nil {
return "bad step in $GENERATE range"
} else {
if s < 0 {
return "bad step in $GENERATE range"
}
step = s
}
l.token = l.token[:i]
}
sx := strings.SplitN(l.token, "-", 2)
if len(sx) != 2 {
return "bad start/stop in $GENERATE range"
}
start, err := strconv.Atoi(sx[0])
if err != nil {
return "bad start in $GENERATE range"
}
end, err := strconv.Atoi(sx[1])
if err != nil {
return "bad end in $GENERATE range"
}
if end < 0 || start < 0 || end <= start {
return "bad range in $GENERATE range"
}
<-c // _BLANK
// Create a complete new string, which we then parse again.
s := ""
BuildRR:
l = <-c
if l.value != _NEWLINE && l.value != _EOF {
s += l.token
goto BuildRR
}
for i := start; i <= end; i += step {
escape := false
dom := ""
// Defaults
mod := "%d"
offset := 0
var err error
// Build the domain name.
for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
switch s[j] {
case '\\':
if escape {
dom += "\\"
escape = false
continue
}
escape = true
case '$':
mod = "%d"
offset = 0
if escape {
dom += "$"
escape = false
continue
}
escape = false
if j+1 >= len(s) { // End of the string
dom += fmt.Sprintf(mod, i+offset)
continue
} else {
if s[j+1] == '$' {
dom += "$"
j++
continue
}
}
// Search for { and }
if s[j+1] == '{' { // Modifier block
sep := strings.Index(s[j+2:], "}")
if sep == -1 {
return "bad modifier in $GENERATE"
}
//println("checking", s[j+2:j+2+sep])
mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
if err != nil {
return "bad modifier in $GENERATE"
}
j += 2 + sep // Jump to it
}
//println("mod", mod)
dom += fmt.Sprintf(mod, i+offset)
default:
if escape { // Pretty useless here
escape = false
continue
}
dom += string(s[j])
}
}
// Re-parse the RR and send it on the current channel t
rx, err := NewRR("$ORIGIN " + o + "\n" + dom)
if err != nil {
return err.(*ParseError).err
}
t <- Token{RR: rx}
}
return ""
}
// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
func modToPrintf(s string) (string, int, error) {
xs := strings.SplitN(s, ",", 3)
if len(xs) != 3 {
return "", 0, nil // make error
}
// xs[0] is offset, xs[1] is width, xs[2] is base
if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
return "", 0, nil // make error
}
offset, err := strconv.Atoi(xs[0])
if err != nil {
return "", 0, err
}
width, err := strconv.Atoi(xs[1])
if err != nil {
return "", offset, err
}
printf := "%"
switch {
case width < 0:
return "", offset, nil // make error
case width == 0:
printf += xs[1]
default:
printf += "0" + xs[1]
}
printf += xs[2]
return printf, offset, nil
}

View File

@ -31,9 +31,10 @@ const (
_RRTYPE
_OWNER
_CLASS
_DIRORIGIN // $ORIGIN
_DIRTTL // $TTL
_DIRINCLUDE // $INCLUDE
_DIRORIGIN // $ORIGIN
_DIRTTL // $TTL
_DIRINCLUDE // $INCLUDE
_DIRGENERATE // $GENERATE
// Privatekey file
_VALUE
@ -55,6 +56,8 @@ const (
_EXPECT_DIRORIGIN // Directive $ORIGIN
_EXPECT_DIRINCLUDE_BL // Space after directive $INCLUDE
_EXPECT_DIRINCLUDE // Directive $INCLUDE
_EXPECT_DIRGENERATE // Directive $GENERATE
_EXPECT_DIRGENERATE_BL // Space after directive $GENERATE
)
// ParseError is a parsing error. It contains the parse error and the location in the io.Reader
@ -209,6 +212,8 @@ func parseZone(r io.Reader, origin, f string, t chan Token, include int) {
st = _EXPECT_DIRORIGIN_BL
case _DIRINCLUDE:
st = _EXPECT_DIRINCLUDE_BL
case _DIRGENERATE:
st = _EXPECT_DIRGENERATE_BL
case _RRTYPE: // Everthing has been omitted, this is the first thing on the line
h.Name = prevName
h.Rrtype = l.torc
@ -306,6 +311,22 @@ func parseZone(r io.Reader, origin, f string, t chan Token, include int) {
origin = l.token
}
st = _EXPECT_OWNER_DIR
case _EXPECT_DIRGENERATE_BL:
if l.value != _BLANK {
t <- Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}}
return
}
st = _EXPECT_DIRGENERATE
case _EXPECT_DIRGENERATE:
if l.value != _STRING {
t <- Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}}
return
}
if e := generate(l, c, t, origin); e != "" {
t <- Token{Error: &ParseError{f, e, l}}
return
}
st = _EXPECT_OWNER_DIR
case _EXPECT_OWNER_BL:
if l.value != _BLANK {
t <- Token{Error: &ParseError{f, "no blank after owner", l}}
@ -389,7 +410,6 @@ func parseZone(r io.Reader, origin, f string, t chan Token, include int) {
h.Rrtype = l.torc
st = _EXPECT_RDATA
case _EXPECT_RDATA:
// I could save my token here...? l
r, e := setRR(h, c, origin, f)
if e != nil {
// If e.lex is nil than we have encounter a unknown RR type
@ -405,39 +425,12 @@ func parseZone(r io.Reader, origin, f string, t chan Token, include int) {
}
}
// If we get here, we and the h.Rrtype is still zero, we haven't parsed anything
if h.Rrtype == 0 {
t <- Token{Error: &ParseError{f, "nothing made sense", lex{}}}
}
// Empty zonefile is also still zone file
// if h.Rrtype == 0 {
// t <- Token{Error: &ParseError{f, "nothing made sense", lex{}}}
// }
}
/*
func (l lex) _string() string {
switch l.value {
case _STRING:
return "S:" + l.token + "$"
case _BLANK:
return "_"
case _QUOTE:
return "\""
case _NEWLINE:
return "|"
case _RRTYPE:
return "R:" + l.token + "$"
case _OWNER:
return "O:" + l.token + "$"
case _CLASS:
return "C:" + l.token + "$"
case _DIRTTL:
return "$T:" + l.token + "$"
case _DIRORIGIN:
return "$O:" + l.token + "$"
case _DIRINCLUDE:
return "$I:" + l.token + "$"
}
return "**"
}
*/
// zlexer scans the sourcefile and returns tokens on the channel c.
func zlexer(s *scan, c chan lex) {
var l lex
@ -488,6 +481,8 @@ func zlexer(s *scan, c chan lex) {
l.value = _DIRORIGIN
case "$INCLUDE":
l.value = _DIRINCLUDE
case "$GENERATE":
l.value = _DIRGENERATE
}
c <- l
} else {