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
|
# Features
|
||||||
|
|
||||||
* UDP/TCP queries, IPv4 and IPv6;
|
* UDP/TCP queries, IPv4 and IPv6;
|
||||||
* RFC 1035 zone file parsing;
|
* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE are
|
||||||
|
supported);
|
||||||
* Fast:
|
* Fast:
|
||||||
* Reply speed around 35/40K qps (faster hardware results in more qps);
|
* 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;
|
* 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 {
|
switch {
|
||||||
default:
|
default:
|
||||||
return 0, uint8(l - longer), false
|
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
|
ok = true
|
||||||
partlen++
|
partlen++
|
||||||
case c == '\\':
|
case c == '\\':
|
||||||
|
|
1
msg.go
1
msg.go
|
@ -46,7 +46,6 @@ var (
|
||||||
ErrSigGen error = &Error{Err: "dns: bad signature generation"}
|
ErrSigGen error = &Error{Err: "dns: bad signature generation"}
|
||||||
ErrAuth error = &Error{Err: "dns: bad authentication"}
|
ErrAuth error = &Error{Err: "dns: bad authentication"}
|
||||||
ErrXfrSoa error = &Error{Err: "dns: no SOA seen"}
|
ErrXfrSoa error = &Error{Err: "dns: no SOA seen"}
|
||||||
ErrXfrLast error = &Error{Err: "dns: last SOA"}
|
|
||||||
ErrXfrType error = &Error{Err: "dns: no ixfr, nor axfr"}
|
ErrXfrType error = &Error{Err: "dns: no ixfr, nor axfr"}
|
||||||
ErrHandle error = &Error{Err: "dns: handle is nil"}
|
ErrHandle error = &Error{Err: "dns: handle is nil"}
|
||||||
ErrChan error = &Error{Err: "dns: channel 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 {
|
if !first {
|
||||||
w.tsigTimersOnly = true // Subsequent envelopes use this.
|
w.tsigTimersOnly = true // Subsequent envelopes use this.
|
||||||
if checkXfrSOA(in, false) {
|
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
|
return
|
||||||
}
|
}
|
||||||
c <- &Exchange{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil}
|
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
|
||||||
|
}
|
65
zscan.go
65
zscan.go
|
@ -31,9 +31,10 @@ const (
|
||||||
_RRTYPE
|
_RRTYPE
|
||||||
_OWNER
|
_OWNER
|
||||||
_CLASS
|
_CLASS
|
||||||
_DIRORIGIN // $ORIGIN
|
_DIRORIGIN // $ORIGIN
|
||||||
_DIRTTL // $TTL
|
_DIRTTL // $TTL
|
||||||
_DIRINCLUDE // $INCLUDE
|
_DIRINCLUDE // $INCLUDE
|
||||||
|
_DIRGENERATE // $GENERATE
|
||||||
|
|
||||||
// Privatekey file
|
// Privatekey file
|
||||||
_VALUE
|
_VALUE
|
||||||
|
@ -55,6 +56,8 @@ const (
|
||||||
_EXPECT_DIRORIGIN // Directive $ORIGIN
|
_EXPECT_DIRORIGIN // Directive $ORIGIN
|
||||||
_EXPECT_DIRINCLUDE_BL // Space after directive $INCLUDE
|
_EXPECT_DIRINCLUDE_BL // Space after directive $INCLUDE
|
||||||
_EXPECT_DIRINCLUDE // 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
|
// 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
|
st = _EXPECT_DIRORIGIN_BL
|
||||||
case _DIRINCLUDE:
|
case _DIRINCLUDE:
|
||||||
st = _EXPECT_DIRINCLUDE_BL
|
st = _EXPECT_DIRINCLUDE_BL
|
||||||
|
case _DIRGENERATE:
|
||||||
|
st = _EXPECT_DIRGENERATE_BL
|
||||||
case _RRTYPE: // Everthing has been omitted, this is the first thing on the line
|
case _RRTYPE: // Everthing has been omitted, this is the first thing on the line
|
||||||
h.Name = prevName
|
h.Name = prevName
|
||||||
h.Rrtype = l.torc
|
h.Rrtype = l.torc
|
||||||
|
@ -306,6 +311,22 @@ func parseZone(r io.Reader, origin, f string, t chan Token, include int) {
|
||||||
origin = l.token
|
origin = l.token
|
||||||
}
|
}
|
||||||
st = _EXPECT_OWNER_DIR
|
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:
|
case _EXPECT_OWNER_BL:
|
||||||
if l.value != _BLANK {
|
if l.value != _BLANK {
|
||||||
t <- Token{Error: &ParseError{f, "no blank after owner", l}}
|
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
|
h.Rrtype = l.torc
|
||||||
st = _EXPECT_RDATA
|
st = _EXPECT_RDATA
|
||||||
case _EXPECT_RDATA:
|
case _EXPECT_RDATA:
|
||||||
// I could save my token here...? l
|
|
||||||
r, e := setRR(h, c, origin, f)
|
r, e := setRR(h, c, origin, f)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
// If e.lex is nil than we have encounter a unknown RR type
|
// 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 we get here, we and the h.Rrtype is still zero, we haven't parsed anything
|
||||||
if h.Rrtype == 0 {
|
// Empty zonefile is also still zone file
|
||||||
t <- Token{Error: &ParseError{f, "nothing made sense", lex{}}}
|
// 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.
|
// zlexer scans the sourcefile and returns tokens on the channel c.
|
||||||
func zlexer(s *scan, c chan lex) {
|
func zlexer(s *scan, c chan lex) {
|
||||||
var l lex
|
var l lex
|
||||||
|
@ -488,6 +481,8 @@ func zlexer(s *scan, c chan lex) {
|
||||||
l.value = _DIRORIGIN
|
l.value = _DIRORIGIN
|
||||||
case "$INCLUDE":
|
case "$INCLUDE":
|
||||||
l.value = _DIRINCLUDE
|
l.value = _DIRINCLUDE
|
||||||
|
case "$GENERATE":
|
||||||
|
l.value = _DIRGENERATE
|
||||||
}
|
}
|
||||||
c <- l
|
c <- l
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue