diff --git a/customrr.go b/customrr.go index 1d17d862..aaec2a74 100644 --- a/customrr.go +++ b/customrr.go @@ -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) diff --git a/customrr_test.go b/customrr_test.go index 89fb102d..d9cd05e0 100644 --- a/customrr_test.go +++ b/customrr_test.go @@ -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 { diff --git a/customrrex_test.go b/customrrex_test.go new file mode 100644 index 00000000..277795ef --- /dev/null +++ b/customrrex_test.go @@ -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 +} diff --git a/msg.go b/msg.go index b3cb6287..96d7bfa2 100644 --- a/msg.go +++ b/msg.go @@ -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