diff --git a/customrr.go b/customrr.go new file mode 100644 index 00000000..1d17d862 --- /dev/null +++ b/customrr.go @@ -0,0 +1,90 @@ +package dns + +type CustomRData interface { + String() string + ReadText([]string) error + Write([]byte) (int, error) + Read([]byte) (int, error) + CopyTo(CustomRData) error +} + +// CopyTo needs to be here to avoid write/read pass that would require make([]byte, xxxxxx) + +type CustomRR struct { + Hdr RR_Header + Data CustomRData +} + +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 { + // make new RR like this: + rrfunc, ok := typeToRR[r.Hdr.Rrtype] + if !ok { + panic("dns: invalid operation with custom RR " + r.Hdr.String()) + } + rr := rrfunc() + r.Header().CopyTo(rr) + + rrcust, ok := rr.(*CustomRR) + if !ok { + panic("dns: custom 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") + } + + return rr +} + +func (r *CustomRR) len() int { panic("TODO: WHERE THIS IS USED?"); return 0 } + +func RegisterCustomRR(rtypestr string, rtype uint16, generator func() CustomRData) { + typeToRR[rtype] = func() RR { return &CustomRR{RR_Header{}, generator()} } + TypeToString[rtype] = rtypestr + StringToType[rtypestr] = rtype + + setCustomRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { + rrfunc := typeToRR[h.Rrtype] + rr, ok := rrfunc().(*CustomRR) + if !ok { + panic("dns: invalid handler registered for custom RR " + rtypestr) + } + h.CopyTo(rr) + + var l lex + text := make([]string, 0) + for end := false; !end; { + switch l = <-c; l.value { + case _NEWLINE, _EOF: + end = true + case _STRING: + text = append(text, l.token) + case _BLANK: + continue + } + } + + err := rr.Data.ReadText(text) + if err != nil { + return nil, &ParseError{f, err.Error(), l}, "" + } + + return rr, nil, "" + } + + typeToparserFunc[rtype] = parserFunc{setCustomRR, false} +} + +func UnregisterCustomRR(rtype uint16) { + rtypestr, ok := TypeToString[rtype] + if ok { + delete(typeToRR, rtype) + delete(TypeToString, rtype) + delete(typeToparserFunc, rtype) + delete(StringToType, rtypestr) + } + return +} diff --git a/customrr_test.go b/customrr_test.go new file mode 100644 index 00000000..89fb102d --- /dev/null +++ b/customrr_test.go @@ -0,0 +1,116 @@ +package dns_test + +import ( + "github.com/miekg/dns" + "strings" + "testing" +) + +const TypeISBN uint16 = 0x0F01 + +// sorry DNS RFC writers, here we go with crazy idea test :) + +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.CustomRData { return &ISBN{""} } + +var testrecord = "example.org.\t3600\tIN\tISBN\t12-3 456789-0-123" + +func (rd *ISBN) 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 *ISBN) Read(buf []byte) (int, error) { + rd.x = string(buf) + return len(buf), nil +} + +func (rd *ISBN) CopyTo(dest dns.CustomRData) error { + isbn, ok := dest.(*ISBN) + if !ok { + return dns.ErrRdata + } + isbn.x = rd.x + return nil +} + +func TestCustomText(t *testing.T) { + dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN) + defer dns.UnregisterCustomRR(TypeISBN) + + rr, err := dns.NewRR(testrecord) + if err != nil { + t.Fatal(err) + } + if rr.String() != testrecord { + t.Errorf("Record string representation did not match original %#v != %#v", rr.String(), testrecord) + } else { + t.Log(rr.String()) + } +} + +func TestCustomWire(t *testing.T) { + dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN) + defer dns.UnregisterCustomRR(TypeISBN) + + rr, err := dns.NewRR(testrecord) + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, 100) + off, err := dns.PackRR(rr, buf, 0, nil, false) + if err != nil { + 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) + } + + rr1, off1, err := dns.UnpackRR(buf[:off], 0) + if err != nil { + t.Errorf("Got error unpacking ISBN: %s", err) + } + + if off1 != off { + t.Errorf("Offset after unpacking differs: %d != %d", off1, off) + } + + if rr1.String() != testrecord { + t.Errorf("Record string representation did not match original %#v != %#v", rr1.String(), testrecord) + } else { + t.Log(rr1.String()) + } +} + +var smallzone = `$ORIGIN example.org. +@ 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 +www ISBN 1231-92110-16 +* CNAME @ +` + +func TestCustomZoneParser(t *testing.T) { + dns.RegisterCustomRR("ISBN", TypeISBN, NewISBN) + defer dns.UnregisterCustomRR(TypeISBN) + r := strings.NewReader(smallzone) + for x := range dns.ParseZone(r, ".", "") { + if err := x.Error; err != nil { + t.Fatal(err) + } + t.Log(x.RR) + } +} diff --git a/dns.go b/dns.go index 7540c0d5..efe437e0 100644 --- a/dns.go +++ b/dns.go @@ -154,6 +154,16 @@ func (h *RR_Header) copyHeader() *RR_Header { return r } +func (h *RR_Header) CopyTo(rr RR) *RR_Header { + rrh := rr.Header() + rrh.Name = h.Name + rrh.Rrtype = h.Rrtype + rrh.Class = h.Class + rrh.Ttl = h.Ttl + rrh.Rdlength = h.Rdlength + return rrh +} + func (h *RR_Header) String() string { var s string diff --git a/msg.go b/msg.go index 45f9f488..b3cb6287 100644 --- a/msg.go +++ b/msg.go @@ -576,6 +576,16 @@ func packStructValue(val reflect.Value, msg []byte, off int, compression map[str switch fv := val.Field(i); fv.Kind() { default: return lenmsg, &Error{err: "bad kind packing"} + case reflect.Interface: + if data, ok := fv.Interface().(CustomRData); ok { + n, err := data.Write(msg[off:]) + if err != nil { + return lenmsg, err + } + off += n + } else { + return lenmsg, &Error{err: "bad kind interface packing"} + } case reflect.Slice: switch typefield.Tag { default: @@ -869,6 +879,16 @@ func unpackStructValue(val reflect.Value, msg []byte, off int) (off1 int, err er switch fv := val.Field(i); fv.Kind() { default: return lenmsg, &Error{err: "bad kind unpacking"} + case reflect.Interface: + if data, ok := fv.Interface().(CustomRData); ok { + n, err := data.Read(msg[off:rdend]) + if err != nil { + return lenmsg, err + } + off += n + } else { + return lenmsg, &Error{err: "bad kind interface unpacking"} + } case reflect.Slice: switch val.Type().Field(i).Tag { default: