Added docstrings, tests and example for PrivateRRs.

(renamed CustomRR to PrivateRR)
This commit is contained in:
Alex Sergeyev 2014-09-19 09:40:24 -04:00
parent 7c507e7592
commit 5b8552609c
4 changed files with 211 additions and 42 deletions

View File

@ -1,56 +1,69 @@
package dns
type CustomRData interface {
import (
"strings"
)
// PrivateRData is an interface to implement non-RFC dictated resource records. See also dns.PrivateRR, dns.RegisterPrivateRR and dns.UnregisterPrivateRR
type PrivateRData interface {
String() string
ReadText([]string) error
Write([]byte) (int, error)
Read([]byte) (int, error)
CopyTo(CustomRData) error
CopyTo(PrivateRData) error
RdataLen() int
}
// CopyTo needs to be here to avoid write/read pass that would require make([]byte, xxxxxx)
type CustomRR struct {
// PrivateRR represents RR that uses PrivateRData user-defined type. It mocks normal RRs and implements dns.RR interface.
type PrivateRR struct {
Hdr RR_Header
Data CustomRData
Data PrivateRData
}
func (r *CustomRR) Header() *RR_Header { return &r.Hdr }
func (r *CustomRR) String() string { return r.Hdr.String() + r.Data.String() }
func (r *CustomRR) copy() RR {
// Header returns Private RR header.
func (r *PrivateRR) Header() *RR_Header { return &r.Hdr }
// String returns text representation of a Private Resource Record.
func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() }
// Private len and copy parts to satisfy RR interface.
func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.RdataLen() }
func (r *PrivateRR) copy() RR {
// make new RR like this:
rrfunc, ok := typeToRR[r.Hdr.Rrtype]
if !ok {
panic("dns: invalid operation with custom RR " + r.Hdr.String())
panic("dns: invalid operation with Private RR " + r.Hdr.String())
}
rr := rrfunc()
r.Header().CopyTo(rr)
rrcust, ok := rr.(*CustomRR)
rrcust, ok := rr.(*PrivateRR)
if !ok {
panic("dns: custom RR generator returned wrong interface value")
panic("dns: Private RR generator returned wrong interface value")
}
err := r.Data.CopyTo(rrcust.Data)
if err != nil {
panic("dns: got value that could not be used to copy custom rdata")
panic("dns: got value that could not be used to copy Private rdata")
}
return rr
}
func (r *CustomRR) len() int { panic("TODO: WHERE THIS IS USED?"); return 0 }
// RegisterPrivateRR adds support for user-defined resource record type to internals of dns library. Requires
// string and numeric representation of RR type and generator function as argument.
func RegisterPrivateRR(rtypestr string, rtype uint16, generator func() PrivateRData) {
rtypestr = strings.ToUpper(rtypestr)
func RegisterCustomRR(rtypestr string, rtype uint16, generator func() CustomRData) {
typeToRR[rtype] = func() RR { return &CustomRR{RR_Header{}, generator()} }
typeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} }
TypeToString[rtype] = rtypestr
StringToType[rtypestr] = rtype
setCustomRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
rrfunc := typeToRR[h.Rrtype]
rr, ok := rrfunc().(*CustomRR)
rr, ok := rrfunc().(*PrivateRR)
if !ok {
panic("dns: invalid handler registered for custom RR " + rtypestr)
panic("dns: invalid handler registered for Private RR " + rtypestr)
}
h.CopyTo(rr)
@ -75,10 +88,11 @@ func RegisterCustomRR(rtypestr string, rtype uint16, generator func() CustomRDat
return rr, nil, ""
}
typeToparserFunc[rtype] = parserFunc{setCustomRR, false}
typeToparserFunc[rtype] = parserFunc{setPrivateRR, false}
}
func UnregisterCustomRR(rtype uint16) {
// UnregisterPrivateRR removes defenitions required to support user RR type.
func UnregisterPrivateRR(rtype uint16) {
rtypestr, ok := TypeToString[rtype]
if ok {
delete(typeToRR, rtype)

View File

@ -14,12 +14,13 @@ type ISBN struct {
x string // rdata with 10 or 13 numbers, dashes or spaces allowed
}
func (rd *ISBN) String() string { return rd.x }
func (rd *ISBN) ReadText(txt []string) error { rd.x = strings.Join(txt, " "); return nil }
func NewISBN() dns.PrivateRData { return &ISBN{""} }
func NewISBN() dns.CustomRData { return &ISBN{""} }
var testrecord = "example.org.\t3600\tIN\tISBN\t12-3 456789-0-123"
func (rd *ISBN) String() string { return rd.x }
func (rd *ISBN) ReadText(txt []string) error {
rd.x = strings.TrimSpace(strings.Join(txt, " "))
return nil
}
func (rd *ISBN) Write(buf []byte) (int, error) {
b := []byte(rd.x)
@ -35,7 +36,7 @@ func (rd *ISBN) Read(buf []byte) (int, error) {
return len(buf), nil
}
func (rd *ISBN) CopyTo(dest dns.CustomRData) error {
func (rd *ISBN) CopyTo(dest dns.PrivateRData) error {
isbn, ok := dest.(*ISBN)
if !ok {
return dns.ErrRdata
@ -44,9 +45,15 @@ func (rd *ISBN) CopyTo(dest dns.CustomRData) error {
return nil
}
func TestCustomText(t *testing.T) {
dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN)
defer dns.UnregisterCustomRR(TypeISBN)
func (rd *ISBN) RdataLen() int {
return len([]byte(rd.x))
}
var testrecord = "example.org.\t3600\tIN\tISBN\t12-3 456789-0-123"
func TestPrivateText(t *testing.T) {
dns.RegisterPrivateRR("ISBN", TypeISBN, NewISBN)
defer dns.UnregisterPrivateRR(TypeISBN)
rr, err := dns.NewRR(testrecord)
if err != nil {
@ -59,9 +66,9 @@ func TestCustomText(t *testing.T) {
}
}
func TestCustomWire(t *testing.T) {
dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN)
defer dns.UnregisterCustomRR(TypeISBN)
func TestPrivateWire(t *testing.T) {
dns.RegisterPrivateRR("ISBN", TypeISBN, NewISBN)
defer dns.UnregisterPrivateRR(TypeISBN)
rr, err := dns.NewRR(testrecord)
if err != nil {
@ -74,8 +81,9 @@ func TestCustomWire(t *testing.T) {
t.Errorf("Got error packing ISBN: %s", err)
}
if ln := 40; ln != off {
t.Errorf("Offset is not matching to length of custom RR: %d!=%d", off, ln)
custrr := rr.(*dns.PrivateRR)
if ln := custrr.Data.RdataLen() + len(custrr.Header().Name) + 11; ln != off {
t.Errorf("Offset is not matching to length of Private RR: %d!=%d", off, ln)
}
rr1, off1, err := dns.UnpackRR(buf[:off], 0)
@ -94,18 +102,66 @@ func TestCustomWire(t *testing.T) {
}
}
const TypeVERSION uint16 = 0x0F02
type VERSION struct {
x string // to make it simpler it's just as simple as ISBN but ignoring
}
func NewVersion() dns.PrivateRData { return &VERSION{""} }
func (rd *VERSION) String() string { return rd.x }
func (rd *VERSION) ReadText(txt []string) error {
rd.x = strings.TrimSpace(strings.Join(txt, " "))
return nil
}
func (rd *VERSION) Write(buf []byte) (int, error) {
b := []byte(rd.x)
n := copy(buf, b)
if n != len(b) {
return n, dns.ErrBuf
}
return n, nil
}
func (rd *VERSION) Read(buf []byte) (int, error) {
rd.x = string(buf)
return len(buf), nil
}
func (rd *VERSION) CopyTo(dest dns.PrivateRData) error {
isbn, ok := dest.(*VERSION)
if !ok {
return dns.ErrRdata
}
isbn.x = rd.x
return nil
}
func (rd *VERSION) RdataLen() int {
return len([]byte(rd.x))
}
var smallzone = `$ORIGIN example.org.
@ SOA sns.dns.icann.org. noc.dns.icann.org. 2014091518 7200 3600 1209600 3600
@ SOA sns.dns.icann.org. noc.dns.icann.org. (
2014091518 7200 3600 1209600 3600
)
A 1.2.3.4
ok ISBN 1231-92110-12
go ISBN 1231-92110-13
go VERSION (
1.3.1 ; comment
)
www ISBN 1231-92110-16
* CNAME @
`
func TestCustomZoneParser(t *testing.T) {
dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN)
defer dns.UnregisterCustomRR(TypeISBN)
func TestPrivateZoneParser(t *testing.T) {
dns.RegisterPrivateRR("ISBN", TypeISBN, NewISBN)
dns.RegisterPrivateRR("VERSION", TypeVERSION, NewVersion)
defer dns.UnregisterPrivateRR(TypeISBN)
defer dns.UnregisterPrivateRR(TypeVERSION)
r := strings.NewReader(smallzone)
for x := range dns.ParseZone(r, ".", "") {
if err := x.Error; err != nil {

99
customrrex_test.go Normal file
View File

@ -0,0 +1,99 @@
package dns_test
import (
"errors"
"fmt"
"github.com/miekg/dns"
"log"
"net"
)
const TypeAPAIR = 0x0F99
type APAIR struct {
addr [2]net.IP
}
func NewAPAIR() dns.PrivateRData { return new(APAIR) }
func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
func (rd *APAIR) ReadText(txt []string) error {
if len(txt) != 2 {
return errors.New("Two addresses required for APAIR")
}
for i, s := range txt {
ip := net.ParseIP(s)
if ip == nil {
return errors.New("Invalid IP in APAIR text representation")
}
rd.addr[i] = ip
}
return nil
}
func (rd *APAIR) Write(buf []byte) (int, error) {
b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...)
n := copy(buf, b)
if n != len(b) {
return n, dns.ErrBuf
}
return n, nil
}
func (rd *APAIR) Read(buf []byte) (int, error) {
ln := net.IPv4len * 2
if len(buf) != ln {
return 0, errors.New("Invalid length of APAIR rdata")
}
cp := make([]byte, ln)
copy(cp, buf) // clone bytes to use them in IPs
rd.addr[0] = net.IP(cp[:3])
rd.addr[1] = net.IP(cp[4:])
return len(buf), nil
}
func (rd *APAIR) CopyTo(dest dns.PrivateRData) error {
cp := make([]byte, rd.RdataLen())
_, err := rd.Write(cp)
if err != nil {
return err
}
d := dest.(*APAIR)
d.addr[0] = net.IP(cp[:3])
d.addr[1] = net.IP(cp[4:])
return nil
}
func (rd *APAIR) RdataLen() int {
return net.IPv4len * 2
}
func ExampleRegisterPrivateRR() {
dns.RegisterPrivateRR("APAIR", TypeAPAIR, NewAPAIR)
defer dns.UnregisterPrivateRR(TypeAPAIR)
rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)")
if err != nil {
log.Fatal("Could not parse APAIR record: ", err)
}
fmt.Println(rr)
// Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
m := new(dns.Msg)
m.Id = 12345
m.SetQuestion("miek.nl.", TypeAPAIR)
m.Answer = append(m.Answer, rr)
fmt.Println(m)
// ;; opcode: QUERY, status: NOERROR, id: 12345
// ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
//
// ;; QUESTION SECTION:
// ;miek.nl. IN APAIR
//
// ;; ANSWER SECTION:
// miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5
}

4
msg.go
View File

@ -577,7 +577,7 @@ func packStructValue(val reflect.Value, msg []byte, off int, compression map[str
default:
return lenmsg, &Error{err: "bad kind packing"}
case reflect.Interface:
if data, ok := fv.Interface().(CustomRData); ok {
if data, ok := fv.Interface().(PrivateRData); ok {
n, err := data.Write(msg[off:])
if err != nil {
return lenmsg, err
@ -880,7 +880,7 @@ func unpackStructValue(val reflect.Value, msg []byte, off int) (off1 int, err er
default:
return lenmsg, &Error{err: "bad kind unpacking"}
case reflect.Interface:
if data, ok := fv.Interface().(CustomRData); ok {
if data, ok := fv.Interface().(PrivateRData); ok {
n, err := data.Read(msg[off:rdend])
if err != nil {
return lenmsg, err