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:
parent
e11c077112
commit
7a3af5b729
|
@ -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;
|
||||
|
|
|
@ -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
1
msg.go
|
@ -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"}
|
||||
|
|
|
@ -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
2
xfr.go
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
}
|
59
zscan.go
59
zscan.go
|
@ -34,6 +34,7 @@ const (
|
|||
_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,38 +425,11 @@ 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) {
|
||||
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue