Ian Gudger has uploaded this change for review.
x/net/dns: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. * Support parsing and packing as many valid DNS packets as possible. Updates golang/go#16218 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/dns.go A dns/message.go A dns/message_test.go 3 files changed, 1,645 insertions(+), 0 deletions(-)
diff --git a/dns/dns.go b/dns/dns.go
new file mode 100644
index 0000000..4d96974
--- /dev/null
+++ b/dns/dns.go
@@ -0,0 +1,6 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// Package dns is an experimental DNS library. See RFC 1035.
+package dns
diff --git a/dns/message.go b/dns/message.go
new file mode 100644
index 0000000..854f727
--- /dev/null
+++ b/dns/message.go
@@ -0,0 +1,1306 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package dns
+
+import (
+ "errors"
+ "fmt"
+)
+
+// Packet formats
+
+// Wire constants.
+const (
+ // ResponseHeader.Type and Question.Type
+ TypeA = 1
+ TypeNS = 2
+ TypeCNAME = 5
+ TypeSOA = 6
+ TypeNULL = 10
+ TypeTXT = 16
+ TypeAAAA = 28
+ TypeSRV = 33
+
+ // Question.Type
+ TypeMD = 3
+ TypeMF = 4
+ TypeMB = 7
+ TypeMG = 8
+ TypeMR = 9
+ TypePTR = 12
+ TypeWKS = 11
+ TypeMX = 15
+ TypeHINFO = 13
+ TypeMINFO = 14
+ TypeAXFR = 252
+ TypeMAILB = 253
+ TypeMAILA = 254
+ TypeALL = 255
+
+ // ResponseHeader.Class and Question.Class
+ ClassINET = 1
+ ClassCSNET = 2
+ ClassCHAOS = 3
+ ClassHESIOD = 4
+
+ // Question.Class
+ ClassANY = 255
+
+ // Msg.Rcode
+ RCodeSuccess = 0
+ RCodeFormatError = 1
+ RCodeServerFailure = 2
+ RCodeNameError = 3
+ RCodeNotImplemented = 4
+ RCodeRefused = 5
+)
+
+var (
+ // ErrNotStarted indicates that the prerequisite information isn't
+ // available yet because the previous records haven't been appropriately
+ // parsed or skipped.
+ ErrNotStarted = errors.New("parsing of this type isn't available yet")
+
+ errBaseLen = errors.New("insufficent data for base length type")
+ errCalcLen = errors.New("insufficent data for calculated length type")
+ errReserved = errors.New("segment prefix is reserved")
+ errTooManyPtr = errors.New("too many pointers (>10)")
+ errInvalidPtr = errors.New("invalid pointer")
+ errResponseLen = errors.New("insufficent data for response body length")
+ errSegTooLong = errors.New("segment length too long")
+ errZeroSegLen = errors.New("zero length segment")
+ errRespTooLong = errors.New("response length too long")
+ errTooManyQuest = errors.New("too many questions to pack (>65535)")
+ errTooManyAns = errors.New("too many answers to pack (>65535)")
+ errTooManyNS = errors.New("too many name servers to pack (>65535)")
+ errTooManyExtra = errors.New("too many extras to pack (>65535)")
+)
+
+// MessageHeader is a representation of a DNS packet header.
+type MessageHeader struct {
+ ID uint16
+ Response bool
+ OpCode int
+ Authoritative bool
+ Truncated bool
+ RecursionDesired bool
+ RecursionAvailable bool
+ RCode int
+}
+
+func (m *MessageHeader) pack() (id uint16, bits uint16) {
+ id = m.ID
+ bits = uint16(m.OpCode)<<11 | uint16(m.RCode)
+ if m.RecursionAvailable {
+ bits |= headerBitRA
+ }
+ if m.RecursionDesired {
+ bits |= headerBitRD
+ }
+ if m.Truncated {
+ bits |= headerBitTC
+ }
+ if m.Authoritative {
+ bits |= headerBitAA
+ }
+ if m.Response {
+ bits |= headerBitQR
+ }
+ return
+}
+
+// Message is a representation of a DNS packet.
+type Message struct {
+ MessageHeader
+ Questions []*Question
+ Answers []Response
+ NameServers []Response
+ Extras []Response
+}
+
+func (m *Message) String() string {
+ s := fmt.Sprintf("Message: %#v\n", &m.MessageHeader)
+ if len(m.Questions) > 0 {
+ s += "-- Questions\n"
+ for _, q := range m.Questions {
+ s += fmt.Sprintf("%#v\n", q)
+ }
+ }
+ if len(m.Answers) > 0 {
+ s += "-- Answers\n"
+ for _, a := range m.Answers {
+ s += fmt.Sprintf("%#v\n", a)
+ }
+ }
+ if len(m.NameServers) > 0 {
+ s += "-- NameServers\n"
+ for _, ns := range m.NameServers {
+ s += fmt.Sprintf("%#v\n", ns)
+ }
+ }
+ if len(m.Extras) > 0 {
+ s += "-- Extras\n"
+ for _, e := range m.Extras {
+ s += fmt.Sprintf("%#v\n", e)
+ }
+ }
+ return s
+}
+
+const (
+ headerBitQR = 1 << 15 // query/response (response=1)
+ headerBitAA = 1 << 10 // authoritative
+ headerBitTC = 1 << 9 // truncated
+ headerBitRD = 1 << 8 // recursion desired
+ headerBitRA = 1 << 7 // recursion available
+)
+
+// header is the wire format for a DNS packet header.
+type header struct {
+ id uint16
+ bits uint16
+ questions uint16
+ answers uint16
+ nameServers uint16
+ extras uint16
+}
+
+func (h *header) pack(msg []byte) []byte {
+ msg = packUint16(msg, h.id)
+ msg = packUint16(msg, h.bits)
+ msg = packUint16(msg, h.questions)
+ msg = packUint16(msg, h.answers)
+ msg = packUint16(msg, h.nameServers)
+ return packUint16(msg, h.extras)
+}
+
+func (h *header) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if h.id, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("id: %v", err)
+ }
+ if h.bits, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("bits: %v", err)
+ }
+ if h.questions, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("questions: %v", err)
+ }
+ if h.answers, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("answers: %v", err)
+ }
+ if h.nameServers, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("nameServers: %v", err)
+ }
+ if h.extras, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("extras: %v", err)
+ }
+ return newOff, nil
+}
+
+func (h *header) messageHeader() MessageHeader {
+ return MessageHeader{
+ ID: h.id,
+ Response: (h.bits & headerBitQR) != 0,
+ OpCode: int(h.bits>>11) & 0xF,
+ Authoritative: (h.bits & headerBitAA) != 0,
+ Truncated: (h.bits & headerBitTC) != 0,
+ RecursionDesired: (h.bits & headerBitRD) != 0,
+ RecursionAvailable: (h.bits & headerBitRA) != 0,
+ RCode: int(h.bits & 0xF),
+ }
+}
+
+// tracker tracker keep track of the index and offset for parsing a particular
+// type of Question or Response.
+type tracker struct {
+ off int
+ index int
+}
+
+// A Response is a DNS response resource record.
+type Response interface {
+ // Header return's the Response's ResponseHeader.
+ Header() *ResponseHeader
+
+ // pack packs a response except for its header.
+ pack(msg []byte, compression map[string]int) ([]byte, error)
+
+ // realType returns the actual type of the Response. This is used to fill
+ // in the header Type field.
+ realType() uint16
+}
+
+func packResponse(msg []byte, response Response, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ response.Header().Type = response.realType()
+ msg, length, err := response.Header().pack(msg, compression)
+ if err != nil {
+ return msg, fmt.Errorf("ResponseHeader: %v", err)
+ }
+ preLen := len(msg)
+ msg, err = response.pack(msg, compression)
+ if err != nil {
+ return msg, fmt.Errorf("content: %v", err)
+ }
+ conLen := len(msg) - preLen
+ if conLen > int(^uint16(0)) {
+ return oldMsg, errRespTooLong
+ }
+ // Fill in the length now that we know how long the content is.
+ packUint16(length[:0], uint16(conLen))
+ response.Header().Length = uint16(conLen)
+ return msg, nil
+}
+
+// A Parser allows incrementally parsing a DNS message.
+//
+// When parsing is started, the MessageHeader is parsed. Next, each Question
+// can be either parsed or skipped. Alternatively, all Questions can be skipped
+// at once. When all Questions have been parsed, attempting to parse Questions
+// will return (nil, nil) and attempting to skip Questions will return
+// (true, nil). After all Questions have been either parsed or skipped, all
+// Answers, NameServers and Extras can be either parsed or skipped in the same
+// way, and each type of Response must be fully parsed or skipped before
+// proceeding to the next type of Response.
+//
+// Note that there is no requirement to fully skip or parse the message.
+type Parser struct {
+ msg []byte
+ header header
+ question tracker
+ answer tracker
+ nameServer tracker
+ extra tracker
+}
+
+// Reset resets the Parser so that it is ready to parse another message.
+func (p *Parser) Reset() {
+ *p = Parser{}
+}
+
+// Start parses the header and enables the parsing of Questions. Start can only
+// be called on a Parser that is either new or has been Reset.
+func (p *Parser) Start(msg []byte) (MessageHeader, error) {
+ p.msg = msg
+ var err error
+ if p.question.off, err = p.header.unpack(msg, 0); err != nil {
+ return MessageHeader{}, fmt.Errorf("unpacking header: %v", err)
+ }
+ return p.header.messageHeader(), nil
+}
+
+func (t tracker) check(count uint16, next *tracker) (done bool, err error) {
+ if t.off == 0 {
+ return false, ErrNotStarted
+ }
+ if t.index == int(count) {
+ if next != nil && next.off == 0 {
+ next.off = t.off
+ }
+ return true, nil
+ }
+ return false, nil
+}
+
+func (t *tracker) parseResponse(msg []byte, count uint16, name string, next *tracker) (Response, error) {
+ if done, err := t.check(count, next); done {
+ return nil, nil
+ } else if err != nil {
+ return nil, fmt.Errorf("unpacking %s: %v", name, err)
+ }
+ var r Response
+ var err error
+ r, t.off, err = unpackResponse(msg, t.off)
+ if err != nil {
+ return nil, fmt.Errorf("unpacking %s: %v", name, err)
+ }
+ t.index++
+ return r, nil
+}
+
+func (t *tracker) skipResponse(msg []byte, count uint16, name string, next *tracker) (done bool, err error) {
+ if done, err = t.check(count, next); done {
+ return
+ } else if err != nil {
+ return false, fmt.Errorf("skipping %s: %v", name, err)
+ }
+ t.off, err = skipResponse(msg, t.off)
+ if err != nil {
+ return false, fmt.Errorf("skipping %s: %v", name, err)
+ }
+ t.index++
+ return false, nil
+}
+
+// Question parses a single Question.
+func (p *Parser) Question() (*Question, error) {
+ if done, err := p.question.check(p.header.questions, &p.answer); done || err != nil {
+ return nil, err
+ }
+ name, off, err := unpackName(p.msg, p.question.off)
+ if err != nil {
+ return nil, fmt.Errorf("unpacking Question # Name: %v", p.question.index+1, err)
+ }
+ typ, off, err := unpackUint16(p.msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("unpacking Question # Type: %v", p.question.index+1, err)
+ }
+ class, off, err := unpackUint16(p.msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("unpacking Question # Class: %v", p.question.index+1, err)
+ }
+ p.question.off = off
+ p.question.index++
+ return &Question{name, typ, class}, nil
+}
+
+// AllQuestions parses all Questions.
+func (p *Parser) AllQuestions() ([]*Question, error) {
+ qs := make([]*Question, 0, p.header.questions)
+ for {
+ q, err := p.Question()
+ if err != nil {
+ return nil, err
+ }
+ if q == nil {
+ break
+ }
+ qs = append(qs, q)
+ }
+ return qs, nil
+}
+
+// SkipQuestion skips a single Question.
+func (p *Parser) SkipQuestion() (done bool, err error) {
+ if done, err = p.question.check(p.header.questions, &p.answer); done || err != nil {
+ return
+ }
+ off, err := skipName(p.msg, p.question.off)
+ if err != nil {
+ return false, fmt.Errorf("skipping Question Name: %v", err)
+ }
+ if off, err = skipUint16(p.msg, off); err != nil {
+ return false, fmt.Errorf("skipping Question Type: %v", err)
+ }
+ if off, err = skipUint16(p.msg, off); err != nil {
+ return false, fmt.Errorf("skipping Question Class: %v", err)
+ }
+ p.question.off = off
+ p.question.index++
+ return false, nil
+}
+
+// SkipAllQuestions skips all Questions.
+func (p *Parser) SkipAllQuestions() error {
+ for {
+ if done, err := p.SkipQuestion(); done || err != nil {
+ return err
+ }
+ }
+}
+
+// Answer parses a single Answer response.
+func (p *Parser) Answer() (Response, error) {
+ return p.answer.parseResponse(p.msg, p.header.answers, "Answer", &p.nameServer)
+}
+
+// AllAnswers parses all Answer responses.
+func (p *Parser) AllAnswers() ([]Response, error) {
+ as := make([]Response, 0, p.header.answers)
+ for {
+ a, err := p.Answer()
+ if err != nil {
+ return nil, err
+ }
+ if a == nil {
+ break
+ }
+ as = append(as, a)
+ }
+ return as, nil
+}
+
+// SkipAnswer skips a single Answer response.
+func (p *Parser) SkipAnswer() (done bool, err error) {
+ return p.answer.skipResponse(p.msg, p.header.answers, "Answer", &p.nameServer)
+}
+
+// SkipAllAnswers skips all Answer responses.
+func (p *Parser) SkipAllAnswers() error {
+ for {
+ if done, err := p.SkipAnswer(); done || err != nil {
+ return err
+ }
+ }
+}
+
+// NameServer parses a single NameServer response.
+func (p *Parser) NameServer() (Response, error) {
+ return p.nameServer.parseResponse(p.msg, p.header.nameServers, "NameServer", &p.extra)
+}
+
+// AllNameServers parses all NameServer responses.
+func (p *Parser) AllNameServers() ([]Response, error) {
+ nss := make([]Response, 0, p.header.nameServers)
+ for {
+ ns, err := p.NameServer()
+ if err != nil {
+ return nil, err
+ }
+ if ns == nil {
+ break
+ }
+ nss = append(nss, ns)
+ }
+ return nss, nil
+}
+
+// SkipNameServer skips a single NameServer response.
+func (p *Parser) SkipNameServer() (done bool, err error) {
+ return p.nameServer.skipResponse(p.msg, p.header.nameServers, "NameServer", &p.extra)
+}
+
+// SkipAllNameServers skips all NameServer responses.
+func (p *Parser) SkipAllNameServers() error {
+ for {
+ if done, err := p.SkipNameServer(); done || err != nil {
+ return err
+ }
+ }
+}
+
+// Extra parses a single Extra response.
+func (p *Parser) Extra() (Response, error) {
+ return p.extra.parseResponse(p.msg, p.header.extras, "Extra", nil)
+}
+
+// AllExtras parses all Extra responses.
+func (p *Parser) AllExtras() ([]Response, error) {
+ es := make([]Response, 0, p.header.extras)
+ for {
+ e, err := p.Extra()
+ if err != nil {
+ return nil, err
+ }
+ if e == nil {
+ break
+ }
+ es = append(es, e)
+ }
+ return es, nil
+}
+
+// SkipExtra skips a single Extra response.
+func (p *Parser) SkipExtra() (done bool, err error) {
+ return p.extra.skipResponse(p.msg, p.header.extras, "Extra", nil)
+}
+
+// SkipAllExtras skips all Extra responses.
+func (p *Parser) SkipAllExtras() error {
+ for {
+ if done, err := p.SkipExtra(); done || err != nil {
+ return err
+ }
+ }
+}
+
+// Parse parses a full Message.
+func (m *Message) Parse(msg []byte) error {
+ var p Parser
+ var err error
+ if m.MessageHeader, err = p.Start(msg); err != nil {
+ return err
+ }
+ if m.Questions, err = p.AllQuestions(); err != nil {
+ return err
+ }
+ if m.Answers, err = p.AllAnswers(); err != nil {
+ return err
+ }
+ if m.NameServers, err = p.AllNameServers(); err != nil {
+ return err
+ }
+ if m.Extras, err = p.AllExtras(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Pack packs a full Message.
+func (m *Message) Pack() ([]byte, error) {
+ // Validate the lengths. It is very unlikely that anyone will try to pack
+ // more than 65535 of any particular type, but it is possible and we
+ // should fail gracefully.
+ if len(m.Questions) > int(^uint16(0)) {
+ return nil, errTooManyQuest
+ }
+ if len(m.Answers) > int(^uint16(0)) {
+ return nil, errTooManyAns
+ }
+ if len(m.NameServers) > int(^uint16(0)) {
+ return nil, errTooManyNS
+ }
+ if len(m.Extras) > int(^uint16(0)) {
+ return nil, errTooManyExtra
+ }
+
+ var h header
+ h.id, h.bits = m.MessageHeader.pack()
+
+ h.questions = uint16(len(m.Questions))
+ h.answers = uint16(len(m.Answers))
+ h.nameServers = uint16(len(m.NameServers))
+ h.extras = uint16(len(m.Extras))
+
+ // The starting capacity doesn't matter too much, but most DNS responses
+ // Will be <= 512 bytes as it is the limit for DNS over UDP.
+ msg := make([]byte, 0, 512)
+
+ msg = h.pack(msg)
+
+ // RFC 1035 allows (but does not require) compression for packing. RFC
+ // 1035 requires unpacking implementations to support compression, so
+ // unconditionally enabling it is fine.
+ //
+ // DNS lookups are typically done over UDP, and RFC 1035 states that UDP
+ // DNS packets can be a maximum of 512 bytes long. Without compression,
+ // many DNS response packets are over this limit, so enabling compression
+ // will help ensure compliance.
+ compression := map[string]int{}
+
+ for _, q := range m.Questions {
+ var err error
+ msg, err = q.pack(msg, compression)
+ if err != nil {
+ return nil, fmt.Errorf("packing Question: %v", err)
+ }
+ }
+ for _, a := range m.Answers {
+ var err error
+ msg, err = packResponse(msg, a, compression)
+ if err != nil {
+ return nil, fmt.Errorf("packing Answer: %v", err)
+ }
+ }
+ for _, ns := range m.NameServers {
+ var err error
+ msg, err = packResponse(msg, ns, compression)
+ if err != nil {
+ return nil, fmt.Errorf("packing NameServer: %v", err)
+ }
+ }
+ for _, e := range m.Extras {
+ var err error
+ msg, err = packResponse(msg, e, compression)
+ if err != nil {
+ return nil, fmt.Errorf("packing Extra: %v", err)
+ }
+ }
+
+ return msg, nil
+}
+
+// An ResponseHeader is the header of a DNS response resource record. There are
+// many types of DNS responses, but they all share the same header.
+type ResponseHeader struct {
+ // Name is the domain name for which this response pertains.
+ Name string
+
+ // Type is the type of DNS response.
+ //
+ // This field will be set automaticlly during packing.
+ Type uint16
+
+ // Class is the class of network to which this DNS response pertains.
+ Class uint16
+
+ // TTL is the length of time (measured in seconds) which this response is
+ // valid for (time to live).
+ TTL uint32
+
+ // Length is the length of data in the response after the header.
+ //
+ // This field will be set automaticlly during packing.
+ Length uint16
+}
+
+// Header implements Response.Header.
+func (h *ResponseHeader) Header() *ResponseHeader {
+ return h
+}
+
+// pack packs all of the fields in a ResponseHeader except for the length. The
+// length bytes are returned as a slice so they can be filled in after the rest
+// of the response has been packed.
+func (h *ResponseHeader) pack(oldMsg []byte, compression map[string]int) (msg []byte, length []byte, err error) {
+ msg = oldMsg
+ if msg, err = packName(msg, h.Name, compression); err != nil {
+ return oldMsg, nil, fmt.Errorf("Name: %v", err)
+ }
+ msg = packUint16(msg, h.Type)
+ msg = packUint16(msg, h.Class)
+ msg = packUint32(msg, h.TTL)
+ lenBegin := len(msg)
+ msg = packUint16(msg, h.Length)
+ return msg, msg[lenBegin:len(msg)], nil
+}
+
+func (h *ResponseHeader) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if h.Name, newOff, err = unpackName(msg, newOff); err != nil {
+ return off, fmt.Errorf("Name: %v", err)
+ }
+ if h.Type, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("Type: %v", err)
+ }
+ if h.Class, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("Class: %v", err)
+ }
+ if h.TTL, newOff, err = unpackUint32(msg, newOff); err != nil {
+ return off, fmt.Errorf("TTL: %v", err)
+ }
+ if h.Length, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("Length: %v", err)
+ }
+ return newOff, nil
+}
+
+func skipResponse(msg []byte, off int) (int, error) {
+ newOff, err := skipName(msg, off)
+ if err != nil {
+ return off, fmt.Errorf("Name: %v", err)
+ }
+ if newOff, err = skipUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("Type: %v", err)
+ }
+ if newOff, err = skipUint16(msg, newOff); err != nil {
+ return off, fmt.Errorf("Class: %v", err)
+ }
+ if newOff, err = skipUint32(msg, newOff); err != nil {
+ return off, fmt.Errorf("TTL: %v", err)
+ }
+ length, newOff, err := unpackUint16(msg, newOff)
+ if err != nil {
+ return off, fmt.Errorf("Length: %v", err)
+ }
+ if newOff += int(length); newOff > len(msg) {
+ return off, errResponseLen
+ }
+ return newOff, nil
+}
+
+func packUint16(msg []byte, field uint16) []byte {
+ return append(msg, byte(field>>8), byte(field))
+}
+
+func unpackUint16(msg []byte, off int) (uint16, int, error) {
+ if off+2 > len(msg) {
+ return 0, off, errBaseLen
+ }
+ return uint16(msg[off])<<8 | uint16(msg[off+1]), off + 2, nil
+}
+
+func skipUint16(msg []byte, off int) (int, error) {
+ if off+2 > len(msg) {
+ return off, errBaseLen
+ }
+ return off + 2, nil
+}
+
+func packUint32(msg []byte, field uint32) []byte {
+ return append(
+ msg,
+ byte(field>>24),
+ byte(field>>16),
+ byte(field>>8),
+ byte(field),
+ )
+}
+
+func unpackUint32(msg []byte, off int) (uint32, int, error) {
+ if off+4 > len(msg) {
+ return 0, off, errBaseLen
+ }
+ v := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3])
+ return v, off + 4, nil
+}
+
+func skipUint32(msg []byte, off int) (int, error) {
+ if off+4 > len(msg) {
+ return off, errBaseLen
+ }
+ return off + 4, nil
+}
+
+func packText(msg []byte, field string) []byte {
+ for len(field) > 0 {
+ l := len(field)
+ if l > 255 {
+ l = 255
+ }
+ msg = append(msg, byte(l))
+ msg = append(msg, field[:l]...)
+ field = field[l:]
+ }
+ return msg
+}
+
+func unpackText(msg []byte, off int) (string, int, error) {
+ if off >= len(msg) {
+ return "", off, errBaseLen
+ }
+ beginOff := off + 1
+ endOff := beginOff + int(msg[off])
+ if endOff > len(msg) {
+ return "", off, errCalcLen
+ }
+ return string(msg[beginOff:endOff]), endOff, nil
+}
+
+func skipText(msg []byte, off int) (int, error) {
+ if off >= len(msg) {
+ return off, errBaseLen
+ }
+ endOff := off + 1 + int(msg[off])
+ if endOff > len(msg) {
+ return off, errCalcLen
+ }
+ return endOff, nil
+}
+
+func packBytes(msg []byte, field []byte) []byte {
+ return append(msg, field...)
+}
+
+func unpackBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ copy(field, msg[off:newOff])
+ return newOff, nil
+}
+
+func skipBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ return newOff, nil
+}
+
+// packName packs a domain name.
+//
+// Domain names are a sequence of counted strings split at the dots. They end
+// with a zero-length string. Compression can be used to reuse domain suffixes.
+//
+// The compression map will be updated with new domain suffixes.
+func packName(msg []byte, name string, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+
+ // Add a trailing dot to canonicalize name.
+ if n := len(name); n == 0 || name[n-1] != '.' {
+ name += "."
+ }
+
+ // Allow root domain.
+ if name == "." {
+ return append(msg, 0), nil
+ }
+
+ // Emit sequence of counted strings, chopping at dots.
+ for i, begin := 0, 0; i < len(name); i++ {
+ // Check for the end of the segment.
+ if name[i] == '.' {
+ // The two most significant bits have special meaning. It isn't
+ // allowed for segments to be long enough to need them.
+ if i-begin >= 1<<6 {
+ return oldMsg, errSegTooLong
+ }
+
+ // Segments must have a non-zero length.
+ if i-begin == 0 {
+ return oldMsg, errZeroSegLen
+ }
+
+ msg = append(msg, byte(i-begin))
+
+ for j := begin; j < i; j++ {
+ msg = append(msg, name[j])
+ }
+
+ begin = i + 1
+ continue
+ }
+
+ // We can only compress domain suffixes starting with a new segment. A
+ // pointer is two bytes with the two most significant bits set to 1 to
+ // indicate that it is a pointer.
+ if i == 0 || name[i-1] == '.' {
+ if ptr, ok := compression[name[i:]]; ok {
+ // Hit. Emit a pointer instead of the rest of the domain.
+ return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil
+ }
+
+ // Miss. Add the suffix to the compression table if the offset can
+ // be stored in the available 14 bytes.
+ if len(msg) <= int(^uint16(0)>>2) {
+ compression[name[i:]] = len(msg)
+ }
+ }
+ }
+ return append(msg, 0), nil
+}
+
+// unpackName unpacks a domain name.
+func unpackName(msg []byte, off int) (string, int, error) {
+ // currOff is the current working offset.
+ currOff := off
+
+ // newOff is the offset where the next record will start. Pointers lead to
+ // data that belongs to other names and thus doesn't count towards to the
+ // usage of this name.
+ newOff := off
+
+ // name is the domain name being unpacked.
+ var name string
+
+ // ptr is the number of pointers followed.
+ var ptr int
+Loop:
+ for {
+ if currOff >= len(msg) {
+ return "", off, errBaseLen
+ }
+ c := int(msg[currOff])
+ currOff++
+ switch c & 0xC0 {
+ case 0x00: // String segment
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break Loop
+ }
+ endOff := currOff + c
+ if endOff > len(msg) {
+ return "", off, errCalcLen
+ }
+ name += string(msg[currOff:endOff]) + "."
+ currOff = endOff
+ case 0xC0: // Pointer
+ if currOff >= len(msg) {
+ return "", off, errInvalidPtr
+ }
+ c1 := msg[currOff]
+ currOff++
+ if ptr == 0 {
+ newOff = currOff
+ }
+ // Don't follow too many pointers, maybe there's a loop.
+ if ptr++; ptr > 10 {
+ return "", off, errTooManyPtr
+ }
+ currOff = (c^0xC0)<<8 | int(c1)
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return "", off, errReserved
+ }
+ }
+ if len(name) == 0 {
+ name = "."
+ }
+ if ptr == 0 {
+ newOff = currOff
+ }
+ return name, newOff, nil
+}
+
+func skipName(msg []byte, off int) (int, error) {
+ // newOff is the offset where the next record will start. Pointers lead to
+ // data that belongs to other names and thus doesn't count towards to the
+ // usage of this name.
+ newOff := off
+
+ if newOff >= len(msg) {
+ return off, errBaseLen
+ }
+ c := int(msg[newOff])
+ newOff++
+ switch c & 0xC0 {
+ case 0x00:
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break
+ }
+ // literal string
+ newOff += c
+ if newOff > len(msg) {
+ return off, errCalcLen
+ }
+ case 0xC0:
+ // Pointer to somewhere else in msg.
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return off, errReserved
+ }
+
+ return newOff, nil
+}
+
+// A Question is a DNS query.
+type Question struct {
+ Name string
+ Type uint16
+ Class uint16
+}
+
+func (q *Question) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ msg, err := packName(msg, q.Name, compression)
+ if err != nil {
+ return msg, fmt.Errorf("Name: %v", err)
+ }
+ msg = packUint16(msg, q.Type)
+ return packUint16(msg, q.Class), nil
+}
+
+func unpackResponse(msg []byte, off int) (Response, int, error) {
+ var hdr ResponseHeader
+ off, err := hdr.unpack(msg, off)
+ if err != nil {
+ return nil, off, fmt.Errorf("ResponseHeader: %v", err)
+ }
+ var r Response
+ var name string
+ switch hdr.Type {
+ case TypeA:
+ r, err = unpackAResponse(hdr, msg, off)
+ name = "A"
+ case TypeNS:
+ r, err = unpackNSResponse(hdr, msg, off)
+ name = "NS"
+ case TypeCNAME:
+ r, err = unpackCNAMEResponse(hdr, msg, off)
+ name = "CNAME"
+ case TypeSOA:
+ r, err = unpackSOAResponse(hdr, msg, off)
+ name = "SOA"
+ case TypePTR:
+ r, err = unpackPTRResponse(hdr, msg, off)
+ name = "PTR"
+ case TypeMX:
+ r, err = unpackMXResponse(hdr, msg, off)
+ name = "MX"
+ case TypeTXT:
+ r, err = unpackTXTResponse(hdr, msg, off)
+ name = "TXT"
+ case TypeAAAA:
+ r, err = unpackAAAAResponse(hdr, msg, off)
+ name = "AAAA"
+ case TypeSRV:
+ r, err = unpackSRVResponse(hdr, msg, off)
+ name = "SRV"
+ }
+ if err != nil {
+ return nil, off, fmt.Errorf("%s record: %v", name, err)
+ }
+ if r != nil {
+ return r, off + int(hdr.Length), nil
+ }
+ return nil, off, fmt.Errorf("invalid response type: %d", hdr.Type)
+}
+
+// A CNAMEResponse is a CNAME Response record.
+type CNAMEResponse struct {
+ ResponseHeader
+
+ CNAME string
+}
+
+func (r *CNAMEResponse) realType() uint16 {
+ return TypeCNAME
+}
+
+func (r *CNAMEResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.CNAME, compression)
+}
+
+func unpackCNAMEResponse(hdr ResponseHeader, msg []byte, off int) (*CNAMEResponse, error) {
+ cname, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &CNAMEResponse{hdr, cname}, nil
+}
+
+// An MXResponse is an MX Response record.
+type MXResponse struct {
+ ResponseHeader
+
+ Pref uint16
+ MX string
+}
+
+func (r *MXResponse) realType() uint16 {
+ return TypeMX
+}
+
+func (r *MXResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Pref)
+ msg, err := packName(msg, r.MX, compression)
+ if err != nil {
+ return oldMsg, fmt.Errorf("MXResponse.MX: %v", err)
+ }
+ return msg, nil
+}
+
+func unpackMXResponse(hdr ResponseHeader, msg []byte, off int) (*MXResponse, error) {
+ pref, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Pref: %v", err)
+ }
+ mx, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("MX: %v", err)
+ }
+ return &MXResponse{hdr, pref, mx}, nil
+}
+
+// An NSResponse is an NS Response record.
+type NSResponse struct {
+ ResponseHeader
+
+ NS string
+}
+
+func (r *NSResponse) realType() uint16 {
+ return TypeNS
+}
+
+func (r *NSResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.NS, compression)
+}
+
+func unpackNSResponse(hdr ResponseHeader, msg []byte, off int) (*NSResponse, error) {
+ ns, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &NSResponse{hdr, ns}, nil
+}
+
+// A PTRResponse is a PTR Response record.
+type PTRResponse struct {
+ ResponseHeader
+
+ PTR string
+}
+
+func (r *PTRResponse) realType() uint16 {
+ return TypePTR
+}
+
+func (r *PTRResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.PTR, compression)
+}
+
+func unpackPTRResponse(hdr ResponseHeader, msg []byte, off int) (*PTRResponse, error) {
+ ptr, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &PTRResponse{hdr, ptr}, nil
+}
+
+// An SOAResponse is an SOA Response record.
+type SOAResponse struct {
+ ResponseHeader
+
+ NS string
+ MBox string
+ Serial uint32
+ Refresh uint32
+ Retry uint32
+ Expire uint32
+ MinTTL uint32
+}
+
+func (r *SOAResponse) realType() uint16 {
+ return TypeSOA
+}
+
+func (r *SOAResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg, err := packName(msg, r.NS, compression)
+ if err != nil {
+ return oldMsg, fmt.Errorf("SOAResponse.NS: %v", err)
+ }
+ msg, err = packName(msg, r.MBox, compression)
+ if err != nil {
+ return oldMsg, fmt.Errorf("SOAResponse.MBox: %v", err)
+ }
+ msg = packUint32(msg, r.Serial)
+ msg = packUint32(msg, r.Refresh)
+ msg = packUint32(msg, r.Retry)
+ msg = packUint32(msg, r.Expire)
+ return packUint32(msg, r.MinTTL), nil
+}
+
+func unpackSOAResponse(hdr ResponseHeader, msg []byte, off int) (*SOAResponse, error) {
+ ns, off, err := unpackName(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("NS: %v", err)
+ }
+ mbox, off, err := unpackName(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("MBox: %v", err)
+ }
+ serial, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Serial: %v", err)
+ }
+ refresh, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Refresh: %v", err)
+ }
+ retry, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Retry: %v", err)
+ }
+ expire, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Expire: %v", err)
+ }
+ minTTL, _, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("MinTTL: %v", err)
+ }
+ return &SOAResponse{hdr, ns, mbox, serial, refresh, retry, expire, minTTL}, nil
+}
+
+// A TXTResponse is a TXT Response record.
+type TXTResponse struct {
+ ResponseHeader
+
+ Txt string // Not a domain name.
+}
+
+func (r *TXTResponse) realType() uint16 {
+ return TypeTXT
+}
+
+func (r *TXTResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packText(msg, r.Txt), nil
+}
+
+func unpackTXTResponse(hdr ResponseHeader, msg []byte, off int) (*TXTResponse, error) {
+ var txt string
+ for n := uint16(0); n < hdr.Length; {
+ var t string
+ var err error
+ if t, off, err = unpackText(msg, off); err != nil {
+ return nil, fmt.Errorf("text: %v", err)
+ }
+ // Check if we got too many bytes.
+ if hdr.Length-n < uint16(len(t))+1 {
+ return nil, errCalcLen
+ }
+ n += uint16(len(t)) + 1
+ txt += t
+ }
+ return &TXTResponse{hdr, txt}, nil
+}
+
+// An SRVResponse is an SRV Response record.
+type SRVResponse struct {
+ ResponseHeader
+
+ Priority uint16
+ Weight uint16
+ Port uint16
+ Target string
+}
+
+func (r *SRVResponse) realType() uint16 {
+ return TypeSRV
+}
+
+func (r *SRVResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Priority)
+ msg = packUint16(msg, r.Weight)
+ msg = packUint16(msg, r.Port)
+ msg, err := packName(msg, r.Target, compression)
+ if err != nil {
+ return oldMsg, fmt.Errorf("SRVResponse.Target: %v", err)
+ }
+ return msg, nil
+}
+
+func unpackSRVResponse(hdr ResponseHeader, msg []byte, off int) (*SRVResponse, error) {
+ priority, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Priority: %v", err)
+ }
+ weight, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Weight: %v", err)
+ }
+ port, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Port: %v", err)
+ }
+ target, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, fmt.Errorf("Target: %v", err)
+ }
+ return &SRVResponse{hdr, priority, weight, port, target}, nil
+}
+
+// An AResponse is an A Response record.
+type AResponse struct {
+ ResponseHeader
+
+ A uint32
+}
+
+func (r *AResponse) realType() uint16 {
+ return TypeA
+}
+
+func (r *AResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packUint32(msg, r.A), nil
+}
+
+func unpackAResponse(hdr ResponseHeader, msg []byte, off int) (*AResponse, error) {
+ a, _, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &AResponse{hdr, a}, nil
+}
+
+// An AAAAResponse is an AAAA Response record.
+type AAAAResponse struct {
+ ResponseHeader
+
+ AAAA [16]byte
+}
+
+func (r *AAAAResponse) realType() uint16 {
+ return TypeAAAA
+}
+
+func (r *AAAAResponse) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packBytes(msg, r.AAAA[:]), nil
+}
+
+func unpackAAAAResponse(hdr ResponseHeader, msg []byte, off int) (*AAAAResponse, error) {
+ var aaaa [16]byte
+ if _, err := unpackBytes(msg, off, aaaa[:]); err != nil {
+ return nil, err
+ }
+ return &AAAAResponse{hdr, aaaa}, nil
+}
diff --git a/dns/message_test.go b/dns/message_test.go
new file mode 100644
index 0000000..33baf7b
--- /dev/null
+++ b/dns/message_test.go
@@ -0,0 +1,333 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dns
+
+import (
+ "encoding/binary"
+ "reflect"
+ "testing"
+)
+
+func TestQuestionPackUnpack(t *testing.T) {
+ want := Question{
+ Name: ".",
+ Type: TypeA,
+ Class: ClassINET,
+ }
+ buf, err := want.pack(make([]byte, 1, 50), map[string]int{})
+ if err != nil {
+ t.Fatal("Packing failed:", err)
+ }
+ var p Parser
+ p.msg = buf
+ p.header.questions = 1
+ p.question.off = 1
+ got, err := p.Question()
+ if err != nil {
+ t.Fatalf("Unpacking failed: %v\n%s", err, string(buf[1:]))
+ }
+ if p.question.off != len(buf) {
+ t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", p.question.off, len(buf))
+ }
+ if !reflect.DeepEqual(*got, want) {
+ t.Errorf("Got = %+v, want = %+v", *got, want)
+ }
+}
+
+func TestNamePackUnpack(t *testing.T) {
+ tests := []struct {
+ in string
+ want string
+ err error
+ }{
+ {"", ".", nil},
+ {".", ".", nil},
+ {"google..com", "", errZeroSegLen},
+ {"google.com", "google.com.", nil},
+ {"google..com.", "", errZeroSegLen},
+ {"google.com.", "google.com.", nil},
+ {".google.com.", "", errZeroSegLen},
+ {"www..google.com.", "", errZeroSegLen},
+ {"www.google.com.", "www.google.com.", nil},
+ }
+
+ for _, test := range tests {
+ buf, err := packName(make([]byte, 0, 30), test.in, map[string]int{})
+ if err != test.err {
+ t.Errorf("Packing of %s: got err = %v, want err = %v", test.in, err, test.err)
+ continue
+ }
+ if test.err != nil {
+ continue
+ }
+ got, n, err := unpackName(buf, 0)
+ if err != nil {
+ t.Errorf("Unpacking for %s failed: %v", test.in, err)
+ continue
+ }
+ if n != len(buf) {
+ t.Errorf(
+ "Unpacked different amount than packed for %s: got n = %d, want = %d",
+ test.in,
+ n,
+ len(buf),
+ )
+ }
+ if got != test.want {
+ t.Errorf("Unpacking packing of %s: got = %s, want = %s", test.in, got, test.want)
+ }
+ }
+}
+
+func TestDNSPackUnpack(t *testing.T) {
+ wants := []Message{
+ {
+ Questions: []*Question{
+ &Question{
+ Name: ".",
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Response{},
+ NameServers: []Response{},
+ Extras: []Response{},
+ },
+ {
+ MessageHeader: MessageHeader{Response: true, Authoritative: true},
+ Questions: []*Question{
+ &Question{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Response{
+ &AResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: binary.BigEndian.Uint32([]byte{127, 0, 0, 1}),
+ },
+ &AResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: binary.BigEndian.Uint32([]byte{127, 0, 0, 2}),
+ },
+ },
+ NameServers: []Response{
+ &NSResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ NS: "ns1.example.com.",
+ },
+ &NSResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ NS: "ns2.example.com.",
+ },
+ },
+ Extras: []Response{
+ &TXTResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: "So Long, and Thanks for All the Fish",
+ },
+ &TXTResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: "Hamster Huey and the Gooey Kablooie",
+ },
+ },
+ },
+ }
+ for i, want := range wants {
+ b, err := want.Pack()
+ if err != nil {
+ t.Fatalf("%d: packing failed: %v", i, err)
+ }
+ var got Message
+ err = got.Parse(b)
+ if err != nil {
+ t.Fatalf("%d: unpacking failed: %v", i, err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%d: got = %+v, want = %+v", i, &got, &want)
+ }
+ }
+}
+
+func TestVeryLongTxt(t *testing.T) {
+ want := &TXTResponse{
+ ResponseHeader: ResponseHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: loremIpsum,
+ }
+ buf, err := packResponse(make([]byte, 0, 8000), want, map[string]int{})
+ if err != nil {
+ t.Fatal("Packing failed:", err)
+ }
+ got, n, err := unpackResponse(buf, 0)
+ if err != nil {
+ t.Fatal("Unpacking failed:", err)
+ }
+ if n != len(buf) {
+ t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", n, len(buf))
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Got = %+v, want = %+v", got, want)
+ }
+}
+
+const loremIpsum = `
+Lorem ipsum dolor sit amet, nec enim antiopam id, an ullum choro
+nonumes qui, pro eu debet honestatis mediocritatem. No alia enim eos,
+magna signiferumque ex vis. Mei no aperiri dissentias, cu vel quas
+regione. Malorum quaeque vim ut, eum cu semper aliquid invidunt, ei
+nam ipsum assentior.
+
+Nostrum appellantur usu no, vis ex probatus adipiscing. Cu usu illum
+facilis eleifend. Iusto conceptam complectitur vim id. Tale omnesque
+no usu, ei oblique sadipscing vim. At nullam voluptua usu, mei laudem
+reformidans et. Qui ei eros porro reformidans, ius suas veritus
+torquatos ex. Mea te facer alterum consequat.
+
+Soleat torquatos democritum sed et, no mea congue appareat, facer
+aliquam nec in. Has te ipsum tritani. At justo dicta option nec, movet
+phaedrum ad nam. Ea detracto verterem liberavisse has, delectus
+suscipiantur in mei. Ex nam meliore complectitur. Ut nam omnis
+honestatis quaerendum, ea mea nihil affert detracto, ad vix rebum
+mollis.
+
+Ut epicurei praesent neglegentur pri, prima fuisset intellegebat ad
+vim. An habemus comprehensam usu, at enim dignissim pro. Eam reque
+vivendum adipisci ea. Vel ne odio choro minimum. Sea admodum
+dissentiet ex. Mundi tamquam evertitur ius cu. Homero postea iisque ut
+pro, vel ne saepe senserit consetetur.
+
+Nulla utamur facilisis ius ea, in viderer diceret pertinax eum. Mei no
+enim quodsi facilisi, ex sed aeterno appareat mediocritatem, eum
+sententiae deterruisset ut. At suas timeam euismod cum, offendit
+appareat interpretaris ne vix. Vel ea civibus albucius, ex vim quidam
+accusata intellegebat, noluisse instructior sea id. Nec te nonumes
+habemus appellantur, quis dignissim vituperata eu nam.
+
+At vix apeirian patrioque vituperatoribus, an usu agam assum. Debet
+iisque an mea. Per eu dicant ponderum accommodare. Pri alienum
+placerat senserit an, ne eum ferri abhorreant vituperatoribus. Ut mea
+eligendi disputationi. Ius no tation everti impedit, ei magna quidam
+mediocritatem pri.
+
+Legendos perpetua iracundia ne usu, no ius ullum epicurei intellegam,
+ad modus epicuri lucilius eam. In unum quaerendum usu. Ne diam paulo
+has, ea veri virtute sed. Alia honestatis conclusionemque mea eu, ut
+iudico albucius his.
+
+Usu essent probatus eu, sed omnis dolor delicatissimi ex. No qui augue
+dissentias dissentiet. Laudem recteque no usu, vel an velit noluisse,
+an sed utinam eirmod appetere. Ne mea fuisset inimicus ocurreret. At
+vis dicant abhorreant, utinam forensibus nec ne, mei te docendi
+consequat. Brute inermis persecuti cum id. Ut ipsum munere propriae
+usu, dicit graeco disputando id has.
+
+Eros dolore quaerendum nam ei. Timeam ornatus inciderint pro id. Nec
+torquatos sadipscing ei, ancillae molestie per in. Malis principes duo
+ea, usu liber postulant ei.
+
+Graece timeam voluptatibus eu eam. Alia probatus quo no, ea scripta
+feugiat duo. Congue option meliore ex qui, noster invenire appellantur
+ea vel. Eu exerci legendos vel. Consetetur repudiandae vim ut. Vix an
+probo minimum, et nam illud falli tempor.
+
+Cum dico signiferumque eu. Sed ut regione maiorum, id veritus insolens
+tacimates vix. Eu mel sint tamquam lucilius, duo no oporteat
+tacimates. Atqui augue concludaturque vix ei, id mel utroque menandri.
+
+Ad oratio blandit aliquando pro. Vis et dolorum rationibus
+philosophia, ad cum nulla molestie. Hinc fuisset adversarium eum et,
+ne qui nisl verear saperet, vel te quaestio forensibus. Per odio
+option delenit an. Alii placerat has no, in pri nihil platonem
+cotidieque. Est ut elit copiosae scaevola, debet tollit maluisset sea
+an.
+
+Te sea hinc debet pericula, liber ridens fabulas cu sed, quem mutat
+accusam mea et. Elitr labitur albucius et pri, an labore feugait mel.
+Velit zril melius usu ea. Ad stet putent interpretaris qui. Mel no
+error volumus scripserit. In pro paulo iudico, quo ei dolorem
+verterem, affert fabellas dissentiet ea vix.
+
+Vis quot deserunt te. Error aliquid detraxit eu usu, vis alia eruditi
+salutatus cu. Est nostrud bonorum an, ei usu alii salutatus. Vel at
+nisl primis, eum ex aperiri noluisse reformidans. Ad veri velit
+utroque vis, ex equidem detraxit temporibus has.
+
+Inermis appareat usu ne. Eros placerat periculis mea ad, in dictas
+pericula pro. Errem postulant at usu, ea nec amet ornatus mentitum. Ad
+mazim graeco eum, vel ex percipit volutpat iudicabit, sit ne delicata
+interesset. Mel sapientem prodesset abhorreant et, oblique suscipit
+eam id.
+
+An maluisset disputando mea, vidit mnesarchum pri et. Malis insolens
+inciderint no sea. Ea persius maluisset vix, ne vim appellantur
+instructior, consul quidam definiebas pri id. Cum integre feugiat
+pericula in, ex sed persius similique, mel ne natum dicit percipitur.
+
+Primis discere ne pri, errem putent definitionem at vis. Ei mel dolore
+neglegentur, mei tincidunt percipitur ei. Pro ad simul integre
+rationibus. Eu vel alii honestatis definitiones, mea no nonumy
+reprehendunt.
+
+Dicta appareat legendos est cu. Eu vel congue dicunt omittam, no vix
+adhuc minimum constituam, quot noluisse id mel. Eu quot sale mutat
+duo, ex nisl munere invenire duo. Ne nec ullum utamur. Pro alterum
+debitis nostrum no, ut vel aliquid vivendo.
+
+Aliquip fierent praesent quo ne, id sit audiam recusabo delicatissimi.
+Usu postulant incorrupte cu. At pro dicit tibique intellegam, cibo
+dolore impedit id eam, et aeque feugait assentior has. Quando sensibus
+nec ex. Possit sensibus pri ad, unum mutat periculis cu vix.
+
+Mundi tibique vix te, duo simul partiendo qualisque id, est at vidit
+sonet tempor. No per solet aeterno deseruisse. Petentium salutandi
+definiebas pri cu. Munere vivendum est in. Ei justo congue eligendi
+vis, modus offendit omittantur te mel.
+
+Integre voluptaria in qui, sit habemus tractatos constituam no. Utinam
+melius conceptam est ne, quo in minimum apeirian delicata, ut ius
+porro recusabo. Dicant expetenda vix no, ludus scripserit sed ex, eu
+his modo nostro. Ut etiam sonet his, quodsi inciderint philosophia te
+per. Nullam lobortis eu cum, vix an sonet efficiendi repudiandae. Vis
+ad idque fabellas intellegebat.
+
+Eum commodo senserit conclusionemque ex. Sed forensibus sadipscing ut,
+mei in facer delicata periculis, sea ne hinc putent cetero. Nec ne
+alia corpora invenire, alia prima soleat te cum. Eleifend posidonium
+nam at.
+
+Dolorum indoctum cu quo, ex dolor legendos recteque eam, cu pri zril
+discere. Nec civibus officiis dissentiunt ex, est te liber ludus
+elaboraret. Cum ea fabellas invenire. Ex vim nostrud eripuit
+comprehensam, nam te inermis delectus, saepe inermis senserit.
+`
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #2 to this change.
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #3 to this change.
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #4 to this change.
x/net/dns: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. * Support parsing and packing as many valid DNS packets as possible. Updates golang/go#16218 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/dns.go A dns/message.go A dns/message_test.go 3 files changed, 1,646 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #5 to this change.
x/net/dns: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/dns.go A dns/message.go A dns/message_test.go 3 files changed, 1,646 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Mikio Hara posted comments on this change.
Patch set 5:
looks nice, thanks.
(6 comments)
Patch Set #5, Line 7: x/net/dns
drop the repository name prefix x/net, just dns is fine.
this must be a line break; pls try godoc golang.org/x/net/dns
Patch Set #5, Line 5: See RFC 1035
might be better to note follwup RFCs to 1035
Patch Set #5, Line 5: is an experimental DNS library
Package dns provides ... or something something not experimental ;)
Patch Set #5, Line 8: "fmt"
i'd prefer to drop the use of fmt package for vendoring; see go/src/go/build/deps_test.go for further info
Pack and Parse, Pack and Unpack or Marshal and Parse. Its your call.
To view, visit this change. To unsubscribe, visit settings.
(1 comment)
Patch Set #5, Line 1267: A uint32
A [4]byte ?
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #6 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,659 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 6:
(7 comments)
Patch Set #5, Line 7: dns/messa
drop the repository name prefix x/net, just dns is fine.
I moved this code to a new message subpackage. Should the commit message be dns/message or just message?
this must be a line break; pls try godoc golang.org/x/net/dns
Done
Package dns provides ... or something something not experimental ;)
Done
might be better to note follwup RFCs to 1035
Do you have any suggestions? I think RFC 1035 is the main RFC that applies here.
i'd prefer to drop the use of fmt package for vendoring; see go/src/go/buil
Done
Pack and Parse, Pack and Unpack or Marshal and Parse. Its your call.
Done
I renamed this function to Unpack. Do you think it is ok to leave the Parser, or should I rename that too?
A [4]byte ?
Done
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #7 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,658 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #8 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,659 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #9 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for parsing and packing DNS packets, but it is not exported, doesn't follow Go style and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,659 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Matt Layher posted comments on this change.
Patch set 9:
I am in favor of adding this package, but it's worth noting that this issue is not yet resolved: https://github.com/golang/go/issues/17244.
If mikioh is okay with it though (being that he "owns" x/net), I don't see any problem with including this.
Ian Gudger posted comments on this change.
Patch set 9:
I am in favor of adding this package, but it's worth noting that this issue is not yet resolved: https://github.com/golang/go/issues/17244.
If mikioh is okay with it though (being that he "owns" x/net), I don't see any problem with including this.
I already got tentative approval from @bradfitz in the associated issue.
Friendly ping.
Mikio Hara posted comments on this change.
Patch set 9:
looks like no test cases for the Skip series methods of Parser. do you want to add such test cases in a followup cl?
(6 comments)
Patch Set #9, Line 10: packets
isn't it better to align s/packet/message/g ?
Do you have any suggestions? I think RFC 1035 is the main RFC that applies
RFC 2181 for the clarification of TTL and RFC 2308 for the clarification of SOA minimum field might be good candidates.
Done
my concern about symbol names is just conflict with future changes; for example, when we want to add Marshaler/Unmarshaler (or Encoder and Decoder) for stream processing over connection-oriented byte-stream protocols such as http (dns over http), tls, tcp. Pack, Unpack and Parser for handling a single DNS message look fine.
Patch Set #9, Line 18: // Wire constants.
what do you think about having Type, Class, OpCode and RCode types (and a few methods for textual processing if necessary) for people who have to work on dns author/recur servers and registries?
Patch Set #9, Line 42: TypeMAILB
i feel like it's better to drop obsoleted and experimental constants. see http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
Patch Set #9, Line 70: insufficent
s/insufficent/insufficient/g
To view, visit this change. To unsubscribe, visit settings.
Mikio Hara posted comments on this change.
Patch set 9:Run-TryBot +1Code-Review +1
LGTM, but i'd like to wait for someone who want to review API surfaces.
Gobot Gobot posted comments on this change.
Patch set 9:
TryBots beginning. Status page: http://farmer.golang.org/try?commit=f7c9a498
Gobot Gobot posted comments on this change.
Patch set 9:TryBot-Result +1
TryBots are happy.
Mikio Hara posted comments on this change.
Patch set 9:
a few nits.
(4 comments)
s/Msg/Message/
Patch Set #9, Line 98: packet
s/packet/message/
Patch Set #9, Line 131: packet
s/packet/message/
Patch Set #9, Line 148: packet
s/packet/message/
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #10 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,694 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 10:
(6 comments)
looks like no test cases for the Skip series methods of Parser. do you want to add such test cases in a followup cl?
Yes, I am planning to add them in a followup CL.
(9 comments)
isn't it better to align s/packet/message/g ?
PTAL
RFC 2181 for the clarification of TTL and RFC 2308 for the clarification of
Done
Patch Set #9, Line 18: // A Type is a typ
what do you think about having Type, Class, OpCode and RCode types (and a f
Done
i feel like it's better to drop obsoleted and experimental constants.
Done
s/Msg/Message/
Done
Patch Set #9, Line 70: revious rec
s/insufficent/insufficient/g
Done
s/packet/message/
Done
s/packet/message/
Done
s/packet/message/
Done
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #11 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,736 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Masa Sekimura posted comments on this change.
Patch set 11:
(1 comment)
Patch Set #11, Line 215: // A Response is a DNS response resource record.
Any reason not to use ResourceRecord or RR? Technically resource records could be in either a query or a response.
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #12 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,737 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #13 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,800 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 12:
(9 comments)
(6 comments) > looks like no test cases for the Skip series methods of Parser. do you want to add such test cases in a followup cl?
Yes, I am planning to add them in a followup CL.
I have added some tests.
(1 comment)
Patch Set #11, Line 215: // A Resource is a DNS resource record.
Any reason not to use ResourceRecord or RR? Technically resource records co
Done
Good point.
To view, visit this change. To unsubscribe, visit settings.
Matthew Dempsky posted comments on this change.
Patch set 12:
(4 comments)
Patch Set #12, Line 5: mostly
mostly?
Patch Set #12, Line 140: NameServers
Officially, the three response record sections are "answer", "authority", and "additional". I think we should use those names.
Patch Set #12, Line 266: tracker
Why is there more than one tracker? I would expect we only need one.
Patch Set #12, Line 272: Reset
What's the use of this method? Why not make (*Parser).Start implicitly reset the Parser?
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #14 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,829 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #15 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,826 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch Set #12, Line 5: mostly
mostly?
For example, the WKS Resource type isn't supported.
Patch Set #12, Line 140: NameServers
Officially, the three response record sections are "answer", "authority", a
Done
Patch Set #12, Line 266: tracker
Why is there more than one tracker? I would expect we only need one.
Done
Patch Set #12, Line 272: Reset
What's the use of this method? Why not make (*Parser).Start implicitly rese
Done
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #16 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,827 insertions(+), 0 deletions(-)
To view, visit this change. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 16:
Friendly ping...
To view, visit change 35237. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch set 16:
Sorry, we've been focusing on getting the Go 1.8 release out.
I also think we need to have a plan for who the co-owner of this is. I can review API at least, but I can't do a thorough review. Matthew, what's you time look like?
Matthew Dempsky posted comments on this change.
Patch set 16:
I can review API at least, but I can't do a thorough review. Matthew, what's you time look like?
I should have more time to review now.
FWIW, I was imagining an even simpler / lower-level interface than Parser (which could probably be used within Parser). E.g., djbdns uses just these three functions for decoding DNS packets: https://cr.yp.to/djbdns/dns_packet.html
Translating to Go, I'd expect something like:
type Packet []byte
func (p Packet) Copy(pos int, out []byte) int
func (p Packet) GetName(pos int) (Name, int)
func (p Packet) SkipName(pos int) int // A Name is a domain name in wire format.
type Name []bytedjbdns returns 0 to indicate an error, but I'd probably use -1 in Go or an error return parameter.
(3 comments)
Patch Set #12, Line 5: mostly
I would just say RFC 1035 compliant in that case. WKS, HINFO, MINFO, etc. have been deprecated by later RFCs.
Patch Set #16, Line 422: func (p *Parser) Answer() (Resource, error) {
Having separate Answer/Authority/Additional methods seems like a code smell to me. If Parser is already internally going to track which resource section it's in, why make the caller also responsible for that?
I'd suggest either Parser continues internally tracking the section, and just includes the section in the return values like so:
func (p *Parser) Resource() (Resource, Section, error)
Or Parser just blindly parses Resources, and the caller is solely responsible for tracking which section they're in:
func (p *Parser) Resource() (Resource, error)
Maybe just rename "a", "ns", and "e" to "r" for resource?
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 16:
(3 comments)
I can review API at least, but I can't do a thorough review. Matthew, what's you time look like?
I should have more time to review now.
FWIW, I was imagining an even simpler / lower-level interface than Parser (which could probably be used within Parser). E.g., djbdns uses just these three functions for decoding DNS packets: https://cr.yp.to/djbdns/dns_packet.html
Translating to Go, I'd expect something like:
type Packet []byte
func (p Packet) Copy(pos int, out []byte) int func (p Packet) GetName(pos int) (Name, int) func (p Packet) SkipName(pos int) int
// A Name is a domain name in wire format. type Name []byte
djbdns returns 0 to indicate an error, but I'd probably use -1 in Go or an error return parameter.
I reviewed the djbdns code and tried to take its approach. I started off with just the packType, unpackType and skipType functions which are roughly how djbdns works and are what I think you are suggesting.
I then thought about how one might actually incrementally parse a DNS message and why they might want to do it. I decided that whole headers and resource records was likely the smallest unit that would be useful to choose between parsing and skipping and left the lower level functions unexported.
I am happy to export the low level functionality, but I want to provide a high level interface for ease of use.
(1 comment)
Patch Set #16, Line 422: func (p *Parser) Answer() (Resource, error) {
I want to give the option to easily only parse one type. I am fine with exposing low-level functionality, but I want to include a high level abstraction as well.
To view, visit change 35237. To unsubscribe, visit settings.
Matthew Dempsky posted comments on this change.
Patch set 16:
I then thought about how one might actually incrementally parse a DNS message and why they might want to do it. I decided that whole headers and resource records was likely the smallest unit that would be useful to choose between parsing and skipping and left the lower level functions unexported.
In DNS client code, usually clients are hard coded to handle just a couple record types. For example, I would expect a typical use within package net to look something like:
var addrs []IPAddr
scanner := dns.NewScanner(msg)
for scanner.Answer() {
if scanner.Class() != dns.ClassINET || scanner.Name().Equals(qname) {
continue
} switch scanner.Type() {
case dns.TypeA:
var d dns.AData
if err := d.Unmarshal(&scanner); err != nil {
return nil, err
}
addrs = append(addrs, IPAddr{IP: IPv4(d.A[0], d.A[1], d.A[2], d.A[3])})
case dns.TypeAAAA:
var d dns.AAAAData
if err := rr.Unmarshal(&scanner); err != nil {
return nil, err
}
addrs = append(addrs, IPAddr{IP: d.A[:]})
}
} if err := scanner.Err(); err != nil {
return nil, err
}
return addrs, nilAside from adding elements to addrs, this shouldn't require any heap allocations.
For Name() to be alloc-free, it would return a type like
// A Name is a domain name in wire format. type Name []byte
// Equals compares n and x for domain name equality (i.e., ASCII case-insensitively). func (n Name) Equals(x Name) bool
Because domain names are limited to 255 bytes (or 256 bytes for MDNS), the scanner object could store a 256 byte buffer internally to hold the name of the current record.
A higher level interface would be to provide a method like:
func (s *Scanner) Data() (Data, error) {
var d Data
switch s.Type() {
case dns.TypeA:
d = new(dns.AData)
case dns.TypeAAAA:
d = new(dns.AAAAData)
case dns.TypeMX:
d = new(dns.MXData)
...
default:
return nil, errUnknownType // or decode generically
} if err := d.Unmarshal(s); err != nil {
return nil, err
}
return d, nil
}which would be a bit more user friendly, but necessitate heap allocations.
And then stacking on top of that, you can easily decode into a complete Message object.
WDYT?
Mikio Hara posted comments on this change.
Patch set 16:
scanner := dns.NewScanner(msg)
if we need to implement a type like Scanner, i think it should also handle a stream of messages for connection-oriented byte-stream dns transports. it perhaps might be better to take an io.Reader interface instead of a slice of byte, to be able to skip processing on corrupted messages.
not sure for unmarshaling, but for marshaling, having a type like "type WireFormat []byte" and its methods looks fine for avoiding unnecessary allocation on message (bulk) transmission.
as long as we leave space for addition of stream processing of dns messages on any dns transport, i'm fine with any conclusions.
Matthew Dempsky posted comments on this change.
Patch set 16:
if we need to implement a type like Scanner, i think it should also handle a stream of messages for connection-oriented byte-stream dns transports. it perhaps might be better to take an io.Reader interface instead of a slice of byte, to be able to skip processing on corrupted messages.
I'm not convinced the extra complexity is warranted. DNS packets are limited to 64kB and require random access to the first 16kB to handle name compression anyway.
Mikio Hara posted comments on this change.
Patch set 16:
I'm not convinced the extra complexity is warranted.
perhaps it's useful only for AXFR/zone transfer. each response message for client still requires ancillary information for its transport and it might be better to do such job in another package.
well, i think patch set #16 has a good shape (except a few nits for documentation), and am fine either way; doing s/Parser/Scanner/g or not.
Ian Gudger posted comments on this change.
Patch set 16:
Patch Set 16:
I am thinking about replacing the nil RR meaning done with a section done error.
I can add additional methods on the parser like this: func (*Parser) AnswerHeader() (ResourceHeader, error) which parse the header if not already parsed. I can store the parsed ResourceHeader in the Parser so it doesn't need to be parsed again if Answer() is called.
This won't prevent a string allocation for the name, but in my opinion it is a nicer interface.
Matthew Dempsky posted comments on this change.
Patch set 16:
perhaps it's useful only for AXFR/zone transfer.
Even with AXFR, the response is chunked into DNS messages that are each <=64KiB.
Mikio Hara posted comments on this change.
Patch set 16:
Even with AXFR, the response is chunked into DNS messages that are each <=64KiB.
yup, i now think that this package should focus on a single message marshaling and unmarshaling.
Ian Gudger uploaded patch set #17 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,872 insertions(+), 0 deletions(-)
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 17:
Patch Set 16:
PTAL
Matthew Dempsky posted comments on this change.
Patch set 17:
PTAL
LGTM protocol wise.
Can you add an Example (i.e., https://golang.org/pkg/testing/#hdr-Examples) for how to parse the A/AAAA answer records from a DNS message for a given qname (analogous to what I proposed earlier)? As long as bradfitz@ thinks it looks okay, I'm happy.
Ian Gudger uploaded patch set #18 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,980 insertions(+), 0 deletions(-)
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 18:
Patch Set 17:
PTAL
LGTM protocol wise.
Can you add an Example (i.e., https://golang.org/pkg/testing/#hdr-Examples) for how to parse the A/AAAA answer records from a DNS message for a given qname (analogous to what I proposed earlier)? As long as bradfitz@ thinks it looks okay, I'm happy.
I added a test. Is that OK?
Matthew Dempsky posted comments on this change.
Patch set 18:
I added a test. Is that OK?
I think an Example would be better. I think extracting A/AAAA records is a pretty representative example of how people will use Parser, so including an best-practices example would be good for documentation purposes. (Examples can also still behave as tests: https://blog.golang.org/examples)
Ian Gudger uploaded patch set #19 to this change.
dns/message: add support for parsing and packing of DNS packets The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,993 insertions(+), 0 deletions(-)
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 19:
Patch Set 18:
I added a test. Is that OK?
I think an Example would be better. I think extracting A/AAAA records is a pretty representative example of how people will use Parser, so including an best-practices example would be good for documentation purposes. (Examples can also still behave as tests: https://blog.golang.org/examples)
PTAL
Patch Set 19:
Patch Set 18:
I added a test. Is that OK?
I think an Example would be better. I think extracting A/AAAA records is a pretty representative example of how people will use Parser, so including an best-practices example would be good for documentation purposes. (Examples can also still behave as tests: https://blog.golang.org/examples)
PTAL
Friendly ping. Is my example ok?
Mikio Hara posted comments on this change.
Patch set 19:
(1 comment)
Patch Set #19, Line 7: packets
s/packets/messages/
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #20 to this change.
dns/message: add support for parsing and packing of DNS messages The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/message/message.go A dns/message/message_test.go 2 files changed, 1,993 insertions(+), 0 deletions(-)
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 20:
(1 comment)
Patch Set #19, Line 7: message
s/packets/messages/
Done
To view, visit change 35237. To unsubscribe, visit settings.
Mikio Hara posted comments on this change.
Patch set 20:Code-Review +1
lgtm; leave to someone in the go team or inner circle.
Matthew Dempsky posted comments on this change.
Patch set 20:
Brad: PTAL at the API and example.
(6 comments)
File dns/message/message_test.go:
for {
q, err := p.Question()
if err == ErrSectionDone {
fmt.Println("Searched all questions without finding question for", wantName)
break
}
if err != nil {
fmt.Println("Searching for question:", err)
return
}
if q.Name != wantName {
continue
}
fmt.Println("Found question for name", wantName)
if err := p.SkipAllQuestions(); err != nil {
fmt.Println("Skipping remaining questions:", err)
return
}
break
}
Can this all just be replaced by p.SkipAllQuestions()?
Patch Set #20, Line 342: fmt.Println("Searched all answers without finding answer for", wantName)
IMO, all of these fmt.Printlns clutter up the example without adding any extra value.
In the error cases, I recommend just using "panic(err)". Cases like this, you can include a comment if necessary, but I think ErrSectionDone is self-descriptive enough.
Patch Set #20, Line 349: if h.Name != wantName {
Does this handle case-insensitivity automatically?
Also, we should be checking the RR class.
Lastly, can we skip the p.Answer() call if Type is not A/AAAA so we don't unmarshal unnecessary record types?
Patch Set #20, Line 361: ar, ok := a.(*AResource)
Please handle both A and AAAA records.
Patch Set #20, Line 366: if ar.A != wantIP {
Accumulate the values into an []IPAddr and just print that at the end. There's no need for wantIP, since the Output check below will enforce that we extracted the correct IP anyway.
Patch Set #20, Line 370: break
remove the break. There can be multiple A/AAAA records for a single domain name. In general, code should loop and extract them all.
To view, visit change 35237. To unsubscribe, visit settings.
In DNS client code, usually clients are hard coded to handle just a couple record types. For example, I would expect a typical use within package net to look something like:
FWIW, I sketched out a proof-of-concept for the API I proposed: https://github.com/mdempsky/dns
The BenchmarkScanner loop is alloc-free.
Brad: Do you mind reviewing just the API for Go style? Mikio and I already reviewed the implementation for DNS-specific issues.
Brad Fitzpatrick posted comments on this change.
Patch set 20:Code-Review +1
API looks good.
Minor comments, but feel free to ignore.
(4 comments)
wrong year. mostly new code, no?
Patch Set #20, Line 10: package message
"message" is a little vague.
message.Parser message.Type
Seems like "dns" should be in there somewhere:
dnsmsg.Parser dnsmsg.Type dnsmessage.Parser dnsmessage.Type
golang.org/x/net/dns/dnsmessage ?
(precedent with net/http/httputil, io/ioutil)
Patch Set #20, Line 302: Start
Is there any precedent for the name "Start"?
We have Init in at least https://golang.org/pkg/container/heap/#Init
And Reset in https://golang.org/pkg/compress/gzip/#Reader.Reset and other places.
Start isn't terrible, but I'm just wondering if it's consistent with anything else.
no real reason for these to be pointer types.
value types would be the same number of allocations.
either way.
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger uploaded patch set #21 to this change.
dns/dnsmessage: add support for parsing and packing of DNS messages The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of
github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 --- A dns/dnsmessage/message.go A dns/dnsmessage/message_test.go 2 files changed, 1,993 insertions(+), 0 deletions(-)
To view, visit change 35237. To unsubscribe, visit settings.
Ian Gudger posted comments on this change.
Patch set 21:
(10 comments)
wrong year. mostly new code, no?
packName/unpackName include some code from net. I don't know how old it is, but the original file says Copyright 2009.
"message" is a little vague.
Done
Is there any precedent for the name "Start"?
I found a few in the standard library: exec.Cmd.Start textproto.Pipeline.StartRequest textproto.sequencer.Start httptest.Server.Start gc.Timings.Start
no real reason for these to be pointer types.
Ack
File dns/message/message_test.go:
Can this all just be replaced by p.SkipAllQuestions()?
I wanted to check that the question I was looking for is present.
IMO, all of these fmt.Printlns clutter up the example without adding any ex
Done
Does this handle case-insensitivity automatically?
Done
Please handle both A and AAAA records.
Done
Accumulate the values into an []IPAddr and just print that at the end. Ther
Done
remove the break. There can be multiple A/AAAA records for a single domain
Done
To view, visit change 35237. To unsubscribe, visit settings.
Friendly ping, is this ok to merge?
Mikio Hara posted comments on this change.
Patch set 21:Run-TryBot +1Code-Review +1
Gobot Gobot posted comments on this change.
Patch set 21:
TryBots beginning. Status page: http://farmer.golang.org/try?commit=5b2ef574
Gobot Gobot posted comments on this change.
Patch set 21:TryBot-Result +1
TryBots are happy.
Mikio Hara posted comments on this change.
Patch set 21:Code-Review +2
lgtm
Ian Gudger posted comments on this change.
Patch set 21:
lgtm
Does someone want to submit this? I don't have permission.
Matthew Dempsky merged this change.
dns/dnsmessage: add support for parsing and packing of DNS messages The Go standard library contains support for packing and unpacking of DNS messages, but it is not exported, doesn't follow Go style, and is not very well optimized. Low level DNS functionality is clearly useful to the Go community as evidenced by the success of github.com/miekg/dns. This implementation endeavors to avoid the limitations of both the standard library and github.com/miekg/dns
implementations and is an almost complete rewrite of the code currently found in on net/dnsmsg.go and net/dnsmsg_test.go. Goals: * Minimize heap allocations. * Allow parsing only what is needed. Avoid unnecessary parsing and heap allocations for parts of the message that you don't care about. Parsing should be allowed on as small of a granularity as is useful, but no smaller as to avoid complicating the interface. * Parse and pack each byte of the message at most one time. Updates golang/go#16218 Updates golang/go#10622 Change-Id: Ib754d0007609a617d88be867f21c2feb15b6fcd7 Reviewed-on: https://go-review.googlesource.com/35237 Run-TryBot: Mikio Hara <mikioh...@gmail.com> TryBot-Result: Gobot Gobot <go...@golang.org> Reviewed-by: Mikio Hara <mikioh...@gmail.com> --- A dns/dnsmessage/message.go A dns/dnsmessage/message_test.go 2 files changed, 1,993 insertions(+), 0 deletions(-)
diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go
new file mode 100644
index 0000000..da43b0b
--- /dev/null
+++ b/dns/dnsmessage/message.go
@@ -0,0 +1,1418 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package dnsmessage provides a mostly RFC 1035 compliant implementation of
+// DNS message packing and unpacking.
+//
+// This implementation is designed to minimize heap allocations and avoid
+// unnecessary packing and unpacking as much as possible.
+package dnsmessage
+
+import (
+ "errors"
+)
+
+// Packet formats
+
+// A Type is a type of DNS request and response.
+type Type uint16
+
+// A Class is a type of network.
+type Class uint16
+
+// An OpCode is a DNS operation code.
+type OpCode uint16
+
+// An RCode is a DNS response status code.
+type RCode uint16
+
+// Wire constants.
+const (
+ // ResourceHeader.Type and Question.Type
+ TypeA Type = 1
+ TypeNS Type = 2
+ TypeCNAME Type = 5
+ TypeSOA Type = 6
+ TypePTR Type = 12
+ TypeMX Type = 15
+ TypeTXT Type = 16
+ TypeAAAA Type = 28
+ TypeSRV Type = 33
+
+ // Question.Type
+ TypeWKS Type = 11
+ TypeHINFO Type = 13
+ TypeMINFO Type = 14
+ TypeAXFR Type = 252
+ TypeALL Type = 255
+
+ // ResourceHeader.Class and Question.Class
+ ClassINET Class = 1
+ ClassCSNET Class = 2
+ ClassCHAOS Class = 3
+ ClassHESIOD Class = 4
+
+ // Question.Class
+ ClassANY Class = 255
+
+ // Message.Rcode
+ RCodeSuccess RCode = 0
+ RCodeFormatError RCode = 1
+ RCodeServerFailure RCode = 2
+ RCodeNameError RCode = 3
+ RCodeNotImplemented RCode = 4
+ RCodeRefused RCode = 5
+)
+
+var (
+ // ErrNotStarted indicates that the prerequisite information isn't
+ // available yet because the previous records haven't been appropriately
+ // parsed or skipped.
+ ErrNotStarted = errors.New("parsing of this type isn't available yet")
+
+ // ErrSectionDone indicated that all records in the section have been
+ // parsed.
+ ErrSectionDone = errors.New("parsing of this section has completed")
+
+ errBaseLen = errors.New("insufficient data for base length type")
+ errCalcLen = errors.New("insufficient data for calculated length type")
+ errReserved = errors.New("segment prefix is reserved")
+ errTooManyPtr = errors.New("too many pointers (>10)")
+ errInvalidPtr = errors.New("invalid pointer")
+ errResourceLen = errors.New("insufficient data for resource body length")
+ errSegTooLong = errors.New("segment length too long")
+ errZeroSegLen = errors.New("zero length segment")
+ errResTooLong = errors.New("resource length too long")
+ errTooManyQuestions = errors.New("too many Questions to pack (>65535)")
+ errTooManyAnswers = errors.New("too many Answers to pack (>65535)")
+ errTooManyAuthorities = errors.New("too many Authorities to pack (>65535)")
+ errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
+)
+
+type nestedError struct {
+ // s is the current level's error message.
+ s string
+
+ // err is the nested error.
+ err error
+}
+
+// nestedError implements error.Error.
+func (e *nestedError) Error() string {
+ return e.s + ": " + e.err.Error()
+}
+
+// Header is a representation of a DNS message header.
+type Header struct {
+ ID uint16
+ Response bool
+ OpCode OpCode
+ Authoritative bool
+ Truncated bool
+ RecursionDesired bool
+ RecursionAvailable bool
+ RCode RCode
+}
+
+func (m *Header) pack() (id uint16, bits uint16) {
+ id = m.ID
+ bits = uint16(m.OpCode)<<11 | uint16(m.RCode)
+ if m.RecursionAvailable {
+ bits |= headerBitRA
+ }
+ if m.RecursionDesired {
+ bits |= headerBitRD
+ }
+ if m.Truncated {
+ bits |= headerBitTC
+ }
+ if m.Authoritative {
+ bits |= headerBitAA
+ }
+ if m.Response {
+ bits |= headerBitQR
+ }
+ return
+}
+
+// Message is a representation of a DNS message.
+type Message struct {
+ Header
+ Questions []Question
+ Answers []Resource
+ Authorities []Resource
+ Additionals []Resource
+}
+
+type section uint8
+
+const (
+ sectionHeader section = iota
+ sectionQuestions
+ sectionAnswers
+ sectionAuthorities
+ sectionAdditionals
+ sectionDone
+
+ headerBitQR = 1 << 15 // query/response (response=1)
+ headerBitAA = 1 << 10 // authoritative
+ headerBitTC = 1 << 9 // truncated
+ headerBitRD = 1 << 8 // recursion desired
+ headerBitRA = 1 << 7 // recursion available
+)
+
+var sectionNames = map[section]string{
+ sectionHeader: "header",
+ sectionQuestions: "Question",
+ sectionAnswers: "Answer",
+ sectionAuthorities: "Authority",
+ sectionAdditionals: "Additional",
+}
+
+// header is the wire format for a DNS message header.
+type header struct {
+ id uint16
+ bits uint16
+ questions uint16
+ answers uint16
+ authorities uint16
+ additionals uint16
+}
+
+func (h *header) count(sec section) uint16 {
+ switch sec {
+ case sectionQuestions:
+ return h.questions
+ case sectionAnswers:
+ return h.answers
+ case sectionAuthorities:
+ return h.authorities
+ case sectionAdditionals:
+ return h.additionals
+ }
+ return 0
+}
+
+func (h *header) pack(msg []byte) []byte {
+ msg = packUint16(msg, h.id)
+ msg = packUint16(msg, h.bits)
+ msg = packUint16(msg, h.questions)
+ msg = packUint16(msg, h.answers)
+ msg = packUint16(msg, h.authorities)
+ return packUint16(msg, h.additionals)
+}
+
+func (h *header) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if h.id, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"id", err}
+ }
+ if h.bits, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"bits", err}
+ }
+ if h.questions, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"questions", err}
+ }
+ if h.answers, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"answers", err}
+ }
+ if h.authorities, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"authorities", err}
+ }
+ if h.additionals, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"additionals", err}
+ }
+ return newOff, nil
+}
+
+func (h *header) header() Header {
+ return Header{
+ ID: h.id,
+ Response: (h.bits & headerBitQR) != 0,
+ OpCode: OpCode(h.bits>>11) & 0xF,
+ Authoritative: (h.bits & headerBitAA) != 0,
+ Truncated: (h.bits & headerBitTC) != 0,
+ RecursionDesired: (h.bits & headerBitRD) != 0,
+ RecursionAvailable: (h.bits & headerBitRA) != 0,
+ RCode: RCode(h.bits & 0xF),
+ }
+}
+
+// A Resource is a DNS resource record.
+type Resource interface {
+ // Header return's the Resource's ResourceHeader.
+ Header() *ResourceHeader
+
+ // pack packs a Resource except for its header.
+ pack(msg []byte, compression map[string]int) ([]byte, error)
+
+ // realType returns the actual type of the Resource. This is used to
+ // fill in the header Type field.
+ realType() Type
+}
+
+func packResource(msg []byte, resource Resource, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ resource.Header().Type = resource.realType()
+ msg, length, err := resource.Header().pack(msg, compression)
+ if err != nil {
+ return msg, &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ msg, err = resource.pack(msg, compression)
+ if err != nil {
+ return msg, &nestedError{"content", err}
+ }
+ conLen := len(msg) - preLen
+ if conLen > int(^uint16(0)) {
+ return oldMsg, errResTooLong
+ }
+ // Fill in the length now that we know how long the content is.
+ packUint16(length[:0], uint16(conLen))
+ resource.Header().Length = uint16(conLen)
+ return msg, nil
+}
+
+// A Parser allows incrementally parsing a DNS message.
+//
+// When parsing is started, the Header is parsed. Next, each Question can be
+// either parsed or skipped. Alternatively, all Questions can be skipped at
+// once. When all Questions have been parsed, attempting to parse Questions
+// will return (nil, nil) and attempting to skip Questions will return
+// (true, nil). After all Questions have been either parsed or skipped, all
+// Answers, Authorities and Additionals can be either parsed or skipped in the
+// same way, and each type of Resource must be fully parsed or skipped before
+// proceeding to the next type of Resource.
+//
+// Note that there is no requirement to fully skip or parse the message.
+type Parser struct {
+ msg []byte
+ header header
+
+ section section
+ off int
+ index int
+ resHeaderValid bool
+ resHeader ResourceHeader
+}
+
+// Start parses the header and enables the parsing of Questions.
+func (p *Parser) Start(msg []byte) (Header, error) {
+ if p.msg != nil {
+ *p = Parser{}
+ }
+ p.msg = msg
+ var err error
+ if p.off, err = p.header.unpack(msg, 0); err != nil {
+ return Header{}, &nestedError{"unpacking header", err}
+ }
+ p.section = sectionQuestions
+ return p.header.header(), nil
+}
+
+func (p *Parser) checkAdvance(sec section) error {
+ if p.section < sec {
+ return ErrNotStarted
+ }
+ if p.section > sec {
+ return ErrSectionDone
+ }
+ p.resHeaderValid = false
+ if p.index == int(p.header.count(sec)) {
+ p.index = 0
+ p.section++
+ return ErrSectionDone
+ }
+ return nil
+}
+
+func (p *Parser) resource(sec section) (Resource, error) {
+ var r Resource
+ hdr, err := p.resourceHeader(sec)
+ if err != nil {
+ return r, err
+ }
+ p.resHeaderValid = false
+ r, p.off, err = unpackResource(p.msg, p.off, hdr)
+ if err != nil {
+ return nil, &nestedError{"unpacking " + sectionNames[sec], err}
+ }
+ p.index++
+ return r, nil
+}
+
+func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) {
+ if p.resHeaderValid {
+ return p.resHeader, nil
+ }
+ if err := p.checkAdvance(sec); err != nil {
+ return ResourceHeader{}, err
+ }
+ var hdr ResourceHeader
+ off, err := hdr.unpack(p.msg, p.off)
+ if err != nil {
+ return ResourceHeader{}, err
+ }
+ p.resHeaderValid = true
+ p.resHeader = hdr
+ p.off = off
+ return hdr, nil
+}
+
+func (p *Parser) skipResource(sec section) error {
+ if p.resHeaderValid {
+ newOff := p.off + int(p.resHeader.Length)
+ if newOff > len(p.msg) {
+ return errResourceLen
+ }
+ p.off = newOff
+ p.resHeaderValid = false
+ p.index++
+ return nil
+ }
+ if err := p.checkAdvance(sec); err != nil {
+ return err
+ }
+ var err error
+ p.off, err = skipResource(p.msg, p.off)
+ if err != nil {
+ return &nestedError{"skipping: " + sectionNames[sec], err}
+ }
+ p.index++
+ return nil
+}
+
+// Question parses a single Question.
+func (p *Parser) Question() (Question, error) {
+ if err := p.checkAdvance(sectionQuestions); err != nil {
+ return Question{}, err
+ }
+ name, off, err := unpackName(p.msg, p.off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Name", err}
+ }
+ typ, off, err := unpackType(p.msg, off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Type", err}
+ }
+ class, off, err := unpackClass(p.msg, off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Class", err}
+ }
+ p.off = off
+ p.index++
+ return Question{name, typ, class}, nil
+}
+
+// AllQuestions parses all Questions.
+func (p *Parser) AllQuestions() ([]Question, error) {
+ qs := make([]Question, 0, p.header.questions)
+ for {
+ q, err := p.Question()
+ if err == ErrSectionDone {
+ return qs, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ qs = append(qs, q)
+ }
+}
+
+// SkipQuestion skips a single Question.
+func (p *Parser) SkipQuestion() error {
+ if err := p.checkAdvance(sectionQuestions); err != nil {
+ return err
+ }
+ off, err := skipName(p.msg, p.off)
+ if err != nil {
+ return &nestedError{"skipping Question Name", err}
+ }
+ if off, err = skipType(p.msg, off); err != nil {
+ return &nestedError{"skipping Question Type", err}
+ }
+ if off, err = skipClass(p.msg, off); err != nil {
+ return &nestedError{"skipping Question Class", err}
+ }
+ p.off = off
+ p.index++
+ return nil
+}
+
+// SkipAllQuestions skips all Questions.
+func (p *Parser) SkipAllQuestions() error {
+ for {
+ if err := p.SkipQuestion(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AnswerHeader parses a single Answer ResourceHeader.
+func (p *Parser) AnswerHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAnswers)
+}
+
+// Answer parses a single Answer Resource.
+func (p *Parser) Answer() (Resource, error) {
+ return p.resource(sectionAnswers)
+}
+
+// AllAnswers parses all Answer Resources.
+func (p *Parser) AllAnswers() ([]Resource, error) {
+ as := make([]Resource, 0, p.header.answers)
+ for {
+ a, err := p.Answer()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAnswer skips a single Answer Resource.
+func (p *Parser) SkipAnswer() error {
+ return p.skipResource(sectionAnswers)
+}
+
+// SkipAllAnswers skips all Answer Resources.
+func (p *Parser) SkipAllAnswers() error {
+ for {
+ if err := p.SkipAnswer(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AuthorityHeader parses a single Authority ResourceHeader.
+func (p *Parser) AuthorityHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAuthorities)
+}
+
+// Authority parses a single Authority Resource.
+func (p *Parser) Authority() (Resource, error) {
+ return p.resource(sectionAuthorities)
+}
+
+// AllAuthorities parses all Authority Resources.
+func (p *Parser) AllAuthorities() ([]Resource, error) {
+ as := make([]Resource, 0, p.header.authorities)
+ for {
+ a, err := p.Authority()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAuthority skips a single Authority Resource.
+func (p *Parser) SkipAuthority() error {
+ return p.skipResource(sectionAuthorities)
+}
+
+// SkipAllAuthorities skips all Authority Resources.
+func (p *Parser) SkipAllAuthorities() error {
+ for {
+ if err := p.SkipAuthority(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AdditionalHeader parses a single Additional ResourceHeader.
+func (p *Parser) AdditionalHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAdditionals)
+}
+
+// Additional parses a single Additional Resource.
+func (p *Parser) Additional() (Resource, error) {
+ return p.resource(sectionAdditionals)
+}
+
+// AllAdditionals parses all Additional Resources.
+func (p *Parser) AllAdditionals() ([]Resource, error) {
+ as := make([]Resource, 0, p.header.additionals)
+ for {
+ a, err := p.Additional()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAdditional skips a single Additional Resource.
+func (p *Parser) SkipAdditional() error {
+ return p.skipResource(sectionAdditionals)
+}
+
+// SkipAllAdditionals skips all Additional Resources.
+func (p *Parser) SkipAllAdditionals() error {
+ for {
+ if err := p.SkipAdditional(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// Unpack parses a full Message.
+func (m *Message) Unpack(msg []byte) error {
+ var p Parser
+ var err error
+ if m.Header, err = p.Start(msg); err != nil {
+ return err
+ }
+ if m.Questions, err = p.AllQuestions(); err != nil {
+ return err
+ }
+ if m.Answers, err = p.AllAnswers(); err != nil {
+ return err
+ }
+ if m.Authorities, err = p.AllAuthorities(); err != nil {
+ return err
+ }
+ if m.Additionals, err = p.AllAdditionals(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Pack packs a full Message.
+func (m *Message) Pack() ([]byte, error) {
+ // Validate the lengths. It is very unlikely that anyone will try to
+ // pack more than 65535 of any particular type, but it is possible and
+ // we should fail gracefully.
+ if len(m.Questions) > int(^uint16(0)) {
+ return nil, errTooManyQuestions
+ }
+ if len(m.Answers) > int(^uint16(0)) {
+ return nil, errTooManyAnswers
+ }
+ if len(m.Authorities) > int(^uint16(0)) {
+ return nil, errTooManyAuthorities
+ }
+ if len(m.Additionals) > int(^uint16(0)) {
+ return nil, errTooManyAdditionals
+ }
+
+ var h header
+ h.id, h.bits = m.Header.pack()
+
+ h.questions = uint16(len(m.Questions))
+ h.answers = uint16(len(m.Answers))
+ h.authorities = uint16(len(m.Authorities))
+ h.additionals = uint16(len(m.Additionals))
+
+ // The starting capacity doesn't matter too much, but most DNS responses
+ // Will be <= 512 bytes as it is the limit for DNS over UDP.
+ msg := make([]byte, 0, 512)
+
+ msg = h.pack(msg)
+
+ // RFC 1035 allows (but does not require) compression for packing. RFC
+ // 1035 requires unpacking implementations to support compression, so
+ // unconditionally enabling it is fine.
+ //
+ // DNS lookups are typically done over UDP, and RFC 1035 states that UDP
+ // DNS packets can be a maximum of 512 bytes long. Without compression,
+ // many DNS response packets are over this limit, so enabling
+ // compression will help ensure compliance.
+ compression := map[string]int{}
+
+ for _, q := range m.Questions {
+ var err error
+ msg, err = q.pack(msg, compression)
+ if err != nil {
+ return nil, &nestedError{"packing Question", err}
+ }
+ }
+ for _, a := range m.Answers {
+ var err error
+ msg, err = packResource(msg, a, compression)
+ if err != nil {
+ return nil, &nestedError{"packing Answer", err}
+ }
+ }
+ for _, a := range m.Authorities {
+ var err error
+ msg, err = packResource(msg, a, compression)
+ if err != nil {
+ return nil, &nestedError{"packing Authority", err}
+ }
+ }
+ for _, a := range m.Additionals {
+ var err error
+ msg, err = packResource(msg, a, compression)
+ if err != nil {
+ return nil, &nestedError{"packing Additional", err}
+ }
+ }
+
+ return msg, nil
+}
+
+// An ResourceHeader is the header of a DNS resource record. There are
+// many types of DNS resource records, but they all share the same header.
+type ResourceHeader struct {
+ // Name is the domain name for which this resource record pertains.
+ Name string
+
+ // Type is the type of DNS resource record.
+ //
+ // This field will be set automatically during packing.
+ Type Type
+
+ // Class is the class of network to which this DNS resource record
+ // pertains.
+ Class Class
+
+ // TTL is the length of time (measured in seconds) which this resource
+ // record is valid for (time to live). All Resources in a set should
+ // have the same TTL (RFC 2181 Section 5.2).
+ TTL uint32
+
+ // Length is the length of data in the resource record after the header.
+ //
+ // This field will be set automatically during packing.
+ Length uint16
+}
+
+// Header implements Resource.Header.
+func (h *ResourceHeader) Header() *ResourceHeader {
+ return h
+}
+
+// pack packs all of the fields in a ResourceHeader except for the length. The
+// length bytes are returned as a slice so they can be filled in after the rest
+// of the Resource has been packed.
+func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int) (msg []byte, length []byte, err error) {
+ msg = oldMsg
+ if msg, err = packName(msg, h.Name, compression); err != nil {
+ return oldMsg, nil, &nestedError{"Name", err}
+ }
+ msg = packType(msg, h.Type)
+ msg = packClass(msg, h.Class)
+ msg = packUint32(msg, h.TTL)
+ lenBegin := len(msg)
+ msg = packUint16(msg, h.Length)
+ return msg, msg[lenBegin:], nil
+}
+
+func (h *ResourceHeader) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if h.Name, newOff, err = unpackName(msg, newOff); err != nil {
+ return off, &nestedError{"Name", err}
+ }
+ if h.Type, newOff, err = unpackType(msg, newOff); err != nil {
+ return off, &nestedError{"Type", err}
+ }
+ if h.Class, newOff, err = unpackClass(msg, newOff); err != nil {
+ return off, &nestedError{"Class", err}
+ }
+ if h.TTL, newOff, err = unpackUint32(msg, newOff); err != nil {
+ return off, &nestedError{"TTL", err}
+ }
+ if h.Length, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"Length", err}
+ }
+ return newOff, nil
+}
+
+func skipResource(msg []byte, off int) (int, error) {
+ newOff, err := skipName(msg, off)
+ if err != nil {
+ return off, &nestedError{"Name", err}
+ }
+ if newOff, err = skipType(msg, newOff); err != nil {
+ return off, &nestedError{"Type", err}
+ }
+ if newOff, err = skipClass(msg, newOff); err != nil {
+ return off, &nestedError{"Class", err}
+ }
+ if newOff, err = skipUint32(msg, newOff); err != nil {
+ return off, &nestedError{"TTL", err}
+ }
+ length, newOff, err := unpackUint16(msg, newOff)
+ if err != nil {
+ return off, &nestedError{"Length", err}
+ }
+ if newOff += int(length); newOff > len(msg) {
+ return off, errResourceLen
+ }
+ return newOff, nil
+}
+
+func packUint16(msg []byte, field uint16) []byte {
+ return append(msg, byte(field>>8), byte(field))
+}
+
+func unpackUint16(msg []byte, off int) (uint16, int, error) {
+ if off+2 > len(msg) {
+ return 0, off, errBaseLen
+ }
+ return uint16(msg[off])<<8 | uint16(msg[off+1]), off + 2, nil
+}
+
+func skipUint16(msg []byte, off int) (int, error) {
+ if off+2 > len(msg) {
+ return off, errBaseLen
+ }
+ return off + 2, nil
+}
+
+func packType(msg []byte, field Type) []byte {
+ return packUint16(msg, uint16(field))
+}
+
+func unpackType(msg []byte, off int) (Type, int, error) {
+ t, o, err := unpackUint16(msg, off)
+ return Type(t), o, err
+}
+
+func skipType(msg []byte, off int) (int, error) {
+ return skipUint16(msg, off)
+}
+
+func packClass(msg []byte, field Class) []byte {
+ return packUint16(msg, uint16(field))
+}
+
+func unpackClass(msg []byte, off int) (Class, int, error) {
+ c, o, err := unpackUint16(msg, off)
+ return Class(c), o, err
+}
+
+func skipClass(msg []byte, off int) (int, error) {
+ return skipUint16(msg, off)
+}
+
+func packUint32(msg []byte, field uint32) []byte {
+ return append(
+ msg,
+ byte(field>>24),
+ byte(field>>16),
+ byte(field>>8),
+ byte(field),
+ )
+}
+
+func unpackUint32(msg []byte, off int) (uint32, int, error) {
+ if off+4 > len(msg) {
+ return 0, off, errBaseLen
+ }
+ v := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3])
+ return v, off + 4, nil
+}
+
+func skipUint32(msg []byte, off int) (int, error) {
+ if off+4 > len(msg) {
+ return off, errBaseLen
+ }
+ return off + 4, nil
+}
+
+func packText(msg []byte, field string) []byte {
+ for len(field) > 0 {
+ l := len(field)
+ if l > 255 {
+ l = 255
+ }
+ msg = append(msg, byte(l))
+ msg = append(msg, field[:l]...)
+ field = field[l:]
+ }
+ return msg
+}
+
+func unpackText(msg []byte, off int) (string, int, error) {
+ if off >= len(msg) {
+ return "", off, errBaseLen
+ }
+ beginOff := off + 1
+ endOff := beginOff + int(msg[off])
+ if endOff > len(msg) {
+ return "", off, errCalcLen
+ }
+ return string(msg[beginOff:endOff]), endOff, nil
+}
+
+func skipText(msg []byte, off int) (int, error) {
+ if off >= len(msg) {
+ return off, errBaseLen
+ }
+ endOff := off + 1 + int(msg[off])
+ if endOff > len(msg) {
+ return off, errCalcLen
+ }
+ return endOff, nil
+}
+
+func packBytes(msg []byte, field []byte) []byte {
+ return append(msg, field...)
+}
+
+func unpackBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ copy(field, msg[off:newOff])
+ return newOff, nil
+}
+
+func skipBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ return newOff, nil
+}
+
+// packName packs a domain name.
+//
+// Domain names are a sequence of counted strings split at the dots. They end
+// with a zero-length string. Compression can be used to reuse domain suffixes.
+//
+// The compression map will be updated with new domain suffixes. If compression
+// is nil, compression will not be used.
+func packName(msg []byte, name string, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+
+ // Add a trailing dot to canonicalize name.
+ if n := len(name); n == 0 || name[n-1] != '.' {
+ name += "."
+ }
+
+ // Allow root domain.
+ if name == "." {
+ return append(msg, 0), nil
+ }
+
+ // Emit sequence of counted strings, chopping at dots.
+ for i, begin := 0, 0; i < len(name); i++ {
+ // Check for the end of the segment.
+ if name[i] == '.' {
+ // The two most significant bits have special meaning.
+ // It isn't allowed for segments to be long enough to
+ // need them.
+ if i-begin >= 1<<6 {
+ return oldMsg, errSegTooLong
+ }
+
+ // Segments must have a non-zero length.
+ if i-begin == 0 {
+ return oldMsg, errZeroSegLen
+ }
+
+ msg = append(msg, byte(i-begin))
+
+ for j := begin; j < i; j++ {
+ msg = append(msg, name[j])
+ }
+
+ begin = i + 1
+ continue
+ }
+
+ // We can only compress domain suffixes starting with a new
+ // segment. A pointer is two bytes with the two most significant
+ // bits set to 1 to indicate that it is a pointer.
+ if (i == 0 || name[i-1] == '.') && compression != nil {
+ if ptr, ok := compression[name[i:]]; ok {
+ // Hit. Emit a pointer instead of the rest of
+ // the domain.
+ return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil
+ }
+
+ // Miss. Add the suffix to the compression table if the
+ // offset can be stored in the available 14 bytes.
+ if len(msg) <= int(^uint16(0)>>2) {
+ compression[name[i:]] = len(msg)
+ }
+ }
+ }
+ return append(msg, 0), nil
+}
+
+// unpackName unpacks a domain name.
+func unpackName(msg []byte, off int) (string, int, error) {
+ // currOff is the current working offset.
+ currOff := off
+
+ // newOff is the offset where the next record will start. Pointers lead
+ // to data that belongs to other names and thus doesn't count towards to
+ // the usage of this name.
+ newOff := off
+
+ // name is the domain name being unpacked.
+ name := make([]byte, 0, 255)
+
+ // ptr is the number of pointers followed.
+ var ptr int
+Loop:
+ for {
+ if currOff >= len(msg) {
+ return "", off, errBaseLen
+ }
+ c := int(msg[currOff])
+ currOff++
+ switch c & 0xC0 {
+ case 0x00: // String segment
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break Loop
+ }
+ endOff := currOff + c
+ if endOff > len(msg) {
+ return "", off, errCalcLen
+ }
+ name = append(name, msg[currOff:endOff]...)
+ name = append(name, '.')
+ currOff = endOff
+ case 0xC0: // Pointer
+ if currOff >= len(msg) {
+ return "", off, errInvalidPtr
+ }
+ c1 := msg[currOff]
+ currOff++
+ if ptr == 0 {
+ newOff = currOff
+ }
+ // Don't follow too many pointers, maybe there's a loop.
+ if ptr++; ptr > 10 {
+ return "", off, errTooManyPtr
+ }
+ currOff = (c^0xC0)<<8 | int(c1)
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return "", off, errReserved
+ }
+ }
+ if len(name) == 0 {
+ name = append(name, '.')
+ }
+ if ptr == 0 {
+ newOff = currOff
+ }
+ return string(name), newOff, nil
+}
+
+func skipName(msg []byte, off int) (int, error) {
+ // newOff is the offset where the next record will start. Pointers lead
+ // to data that belongs to other names and thus doesn't count towards to
+ // the usage of this name.
+ newOff := off
+
+Loop:
+ for {
+ if newOff >= len(msg) {
+ return off, errBaseLen
+ }
+ c := int(msg[newOff])
+ newOff++
+ switch c & 0xC0 {
+ case 0x00:
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break Loop
+ }
+ // literal string
+ newOff += c
+ if newOff > len(msg) {
+ return off, errCalcLen
+ }
+ case 0xC0:
+ // Pointer to somewhere else in msg.
+
+ // Pointers are two bytes.
+ newOff++
+
+ // Don't follow the pointer as the data here has ended.
+ break Loop
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return off, errReserved
+ }
+ }
+
+ return newOff, nil
+}
+
+// A Question is a DNS query.
+type Question struct {
+ Name string
+ Type Type
+ Class Class
+}
+
+func (q *Question) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ msg, err := packName(msg, q.Name, compression)
+ if err != nil {
+ return msg, &nestedError{"Name", err}
+ }
+ msg = packType(msg, q.Type)
+ return packClass(msg, q.Class), nil
+}
+
+func unpackResource(msg []byte, off int, hdr ResourceHeader) (Resource, int, error) {
+ var (
+ r Resource
+ err error
+ name string
+ )
+ switch hdr.Type {
+ case TypeA:
+ r, err = unpackAResource(hdr, msg, off)
+ name = "A"
+ case TypeNS:
+ r, err = unpackNSResource(hdr, msg, off)
+ name = "NS"
+ case TypeCNAME:
+ r, err = unpackCNAMEResource(hdr, msg, off)
+ name = "CNAME"
+ case TypeSOA:
+ r, err = unpackSOAResource(hdr, msg, off)
+ name = "SOA"
+ case TypePTR:
+ r, err = unpackPTRResource(hdr, msg, off)
+ name = "PTR"
+ case TypeMX:
+ r, err = unpackMXResource(hdr, msg, off)
+ name = "MX"
+ case TypeTXT:
+ r, err = unpackTXTResource(hdr, msg, off)
+ name = "TXT"
+ case TypeAAAA:
+ r, err = unpackAAAAResource(hdr, msg, off)
+ name = "AAAA"
+ case TypeSRV:
+ r, err = unpackSRVResource(hdr, msg, off)
+ name = "SRV"
+ }
+ if err != nil {
+ return nil, off, &nestedError{name + " record", err}
+ }
+ if r != nil {
+ return r, off + int(hdr.Length), nil
+ }
+ return nil, off, errors.New("invalid resource type: " + string(hdr.Type+'0'))
+}
+
+// A CNAMEResource is a CNAME Resource record.
+type CNAMEResource struct {
+ ResourceHeader
+
+ CNAME string
+}
+
+func (r *CNAMEResource) realType() Type {
+ return TypeCNAME
+}
+
+func (r *CNAMEResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.CNAME, compression)
+}
+
+func unpackCNAMEResource(hdr ResourceHeader, msg []byte, off int) (*CNAMEResource, error) {
+ cname, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &CNAMEResource{hdr, cname}, nil
+}
+
+// An MXResource is an MX Resource record.
+type MXResource struct {
+ ResourceHeader
+
+ Pref uint16
+ MX string
+}
+
+func (r *MXResource) realType() Type {
+ return TypeMX
+}
+
+func (r *MXResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Pref)
+ msg, err := packName(msg, r.MX, compression)
+ if err != nil {
+ return oldMsg, &nestedError{"MXResource.MX", err}
+ }
+ return msg, nil
+}
+
+func unpackMXResource(hdr ResourceHeader, msg []byte, off int) (*MXResource, error) {
+ pref, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Pref", err}
+ }
+ mx, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, &nestedError{"MX", err}
+ }
+ return &MXResource{hdr, pref, mx}, nil
+}
+
+// An NSResource is an NS Resource record.
+type NSResource struct {
+ ResourceHeader
+
+ NS string
+}
+
+func (r *NSResource) realType() Type {
+ return TypeNS
+}
+
+func (r *NSResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.NS, compression)
+}
+
+func unpackNSResource(hdr ResourceHeader, msg []byte, off int) (*NSResource, error) {
+ ns, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &NSResource{hdr, ns}, nil
+}
+
+// A PTRResource is a PTR Resource record.
+type PTRResource struct {
+ ResourceHeader
+
+ PTR string
+}
+
+func (r *PTRResource) realType() Type {
+ return TypePTR
+}
+
+func (r *PTRResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packName(msg, r.PTR, compression)
+}
+
+func unpackPTRResource(hdr ResourceHeader, msg []byte, off int) (*PTRResource, error) {
+ ptr, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, err
+ }
+ return &PTRResource{hdr, ptr}, nil
+}
+
+// An SOAResource is an SOA Resource record.
+type SOAResource struct {
+ ResourceHeader
+
+ NS string
+ MBox string
+ Serial uint32
+ Refresh uint32
+ Retry uint32
+ Expire uint32
+
+ // MinTTL the is the default TTL of Resources records which did not
+ // contain a TTL value and the TTL of negative responses. (RFC 2308
+ // Section 4)
+ MinTTL uint32
+}
+
+func (r *SOAResource) realType() Type {
+ return TypeSOA
+}
+
+func (r *SOAResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg, err := packName(msg, r.NS, compression)
+ if err != nil {
+ return oldMsg, &nestedError{"SOAResource.NS", err}
+ }
+ msg, err = packName(msg, r.MBox, compression)
+ if err != nil {
+ return oldMsg, &nestedError{"SOAResource.MBox", err}
+ }
+ msg = packUint32(msg, r.Serial)
+ msg = packUint32(msg, r.Refresh)
+ msg = packUint32(msg, r.Retry)
+ msg = packUint32(msg, r.Expire)
+ return packUint32(msg, r.MinTTL), nil
+}
+
+func unpackSOAResource(hdr ResourceHeader, msg []byte, off int) (*SOAResource, error) {
+ ns, off, err := unpackName(msg, off)
+ if err != nil {
+ return nil, &nestedError{"NS", err}
+ }
+ mbox, off, err := unpackName(msg, off)
+ if err != nil {
+ return nil, &nestedError{"MBox", err}
+ }
+ serial, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Serial", err}
+ }
+ refresh, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Refresh", err}
+ }
+ retry, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Retry", err}
+ }
+ expire, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Expire", err}
+ }
+ minTTL, _, err := unpackUint32(msg, off)
+ if err != nil {
+ return nil, &nestedError{"MinTTL", err}
+ }
+ return &SOAResource{hdr, ns, mbox, serial, refresh, retry, expire, minTTL}, nil
+}
+
+// A TXTResource is a TXT Resource record.
+type TXTResource struct {
+ ResourceHeader
+
+ Txt string // Not a domain name.
+}
+
+func (r *TXTResource) realType() Type {
+ return TypeTXT
+}
+
+func (r *TXTResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packText(msg, r.Txt), nil
+}
+
+func unpackTXTResource(hdr ResourceHeader, msg []byte, off int) (*TXTResource, error) {
+ var txt string
+ for n := uint16(0); n < hdr.Length; {
+ var t string
+ var err error
+ if t, off, err = unpackText(msg, off); err != nil {
+ return nil, &nestedError{"text", err}
+ }
+ // Check if we got too many bytes.
+ if hdr.Length-n < uint16(len(t))+1 {
+ return nil, errCalcLen
+ }
+ n += uint16(len(t)) + 1
+ txt += t
+ }
+ return &TXTResource{hdr, txt}, nil
+}
+
+// An SRVResource is an SRV Resource record.
+type SRVResource struct {
+ ResourceHeader
+
+ Priority uint16
+ Weight uint16
+ Port uint16
+ Target string // Not compressed as per RFC 2782.
+}
+
+func (r *SRVResource) realType() Type {
+ return TypeSRV
+}
+
+func (r *SRVResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Priority)
+ msg = packUint16(msg, r.Weight)
+ msg = packUint16(msg, r.Port)
+ msg, err := packName(msg, r.Target, nil)
+ if err != nil {
+ return oldMsg, &nestedError{"SRVResource.Target", err}
+ }
+ return msg, nil
+}
+
+func unpackSRVResource(hdr ResourceHeader, msg []byte, off int) (*SRVResource, error) {
+ priority, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Priority", err}
+ }
+ weight, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Weight", err}
+ }
+ port, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Port", err}
+ }
+ target, _, err := unpackName(msg, off)
+ if err != nil {
+ return nil, &nestedError{"Target", err}
+ }
+ return &SRVResource{hdr, priority, weight, port, target}, nil
+}
+
+// An AResource is an A Resource record.
+type AResource struct {
+ ResourceHeader
+
+ A [4]byte
+}
+
+func (r *AResource) realType() Type {
+ return TypeA
+}
+
+func (r *AResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packBytes(msg, r.A[:]), nil
+}
+
+func unpackAResource(hdr ResourceHeader, msg []byte, off int) (*AResource, error) {
+ var a [4]byte
+ if _, err := unpackBytes(msg, off, a[:]); err != nil {
+ return nil, err
+ }
+ return &AResource{hdr, a}, nil
+}
+
+// An AAAAResource is an AAAA Resource record.
+type AAAAResource struct {
+ ResourceHeader
+
+ AAAA [16]byte
+}
+
+func (r *AAAAResource) realType() Type {
+ return TypeAAAA
+}
+
+func (r *AAAAResource) pack(msg []byte, compression map[string]int) ([]byte, error) {
+ return packBytes(msg, r.AAAA[:]), nil
+}
+
+func unpackAAAAResource(hdr ResourceHeader, msg []byte, off int) (*AAAAResource, error) {
+ var aaaa [16]byte
+ if _, err := unpackBytes(msg, off, aaaa[:]); err != nil {
+ return nil, err
+ }
+ return &AAAAResource{hdr, aaaa}, nil
+}
diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go
new file mode 100644
index 0000000..46edd72
--- /dev/null
+++ b/dns/dnsmessage/message_test.go
@@ -0,0 +1,575 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dnsmessage
+
+import (
+ "fmt"
+ "net"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func (m *Message) String() string {
+ s := fmt.Sprintf("Message: %#v\n", &m.Header)
+ if len(m.Questions) > 0 {
+ s += "-- Questions\n"
+ for _, q := range m.Questions {
+ s += fmt.Sprintf("%#v\n", q)
+ }
+ }
+ if len(m.Answers) > 0 {
+ s += "-- Answers\n"
+ for _, a := range m.Answers {
+ s += fmt.Sprintf("%#v\n", a)
+ }
+ }
+ if len(m.Authorities) > 0 {
+ s += "-- Authorities\n"
+ for _, ns := range m.Authorities {
+ s += fmt.Sprintf("%#v\n", ns)
+ }
+ }
+ if len(m.Additionals) > 0 {
+ s += "-- Additionals\n"
+ for _, e := range m.Additionals {
+ s += fmt.Sprintf("%#v\n", e)
+ }
+ }
+ return s
+}
+
+func TestQuestionPackUnpack(t *testing.T) {
+ want := Question{
+ Name: ".",
+ Type: TypeA,
+ Class: ClassINET,
+ }
+ buf, err := want.pack(make([]byte, 1, 50), map[string]int{})
+ if err != nil {
+ t.Fatal("Packing failed:", err)
+ }
+ var p Parser
+ p.msg = buf
+ p.header.questions = 1
+ p.section = sectionQuestions
+ p.off = 1
+ got, err := p.Question()
+ if err != nil {
+ t.Fatalf("Unpacking failed: %v\n%s", err, string(buf[1:]))
+ }
+ if p.off != len(buf) {
+ t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", p.off, len(buf))
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Got = %+v, want = %+v", got, want)
+ }
+}
+
+func TestNamePackUnpack(t *testing.T) {
+ tests := []struct {
+ in string
+ want string
+ err error
+ }{
+ {"", ".", nil},
+ {".", ".", nil},
+ {"google..com", "", errZeroSegLen},
+ {"google.com", "google.com.", nil},
+ {"google..com.", "", errZeroSegLen},
+ {"google.com.", "google.com.", nil},
+ {".google.com.", "", errZeroSegLen},
+ {"www..google.com.", "", errZeroSegLen},
+ {"www.google.com.", "www.google.com.", nil},
+ }
+
+ for _, test := range tests {
+ buf, err := packName(make([]byte, 0, 30), test.in, map[string]int{})
+ if err != test.err {
+ t.Errorf("Packing of %s: got err = %v, want err = %v", test.in, err, test.err)
+ continue
+ }
+ if test.err != nil {
+ continue
+ }
+ got, n, err := unpackName(buf, 0)
+ if err != nil {
+ t.Errorf("Unpacking for %s failed: %v", test.in, err)
+ continue
+ }
+ if n != len(buf) {
+ t.Errorf(
+ "Unpacked different amount than packed for %s: got n = %d, want = %d",
+ test.in,
+ n,
+ len(buf),
+ )
+ }
+ if got != test.want {
+ t.Errorf("Unpacking packing of %s: got = %s, want = %s", test.in, got, test.want)
+ }
+ }
+}
+
+func TestDNSPackUnpack(t *testing.T) {
+ wants := []Message{
+ {
+ Questions: []Question{
+ {
+ Name: ".",
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{},
+ Authorities: []Resource{},
+ Additionals: []Resource{},
+ },
+ largeTestMsg(),
+ }
+ for i, want := range wants {
+ b, err := want.Pack()
+ if err != nil {
+ t.Fatalf("%d: packing failed: %v", i, err)
+ }
+ var got Message
+ err = got.Unpack(b)
+ if err != nil {
+ t.Fatalf("%d: unpacking failed: %v", i, err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%d: got = %+v, want = %+v", i, &got, &want)
+ }
+ }
+}
+
+func TestSkipAll(t *testing.T) {
+ msg := largeTestMsg()
+ buf, err := msg.Pack()
+ if err != nil {
+ t.Fatal("Packing large test message:", err)
+ }
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ t.Fatal(err)
+ }
+
+ tests := []struct {
+ name string
+ f func() error
+ }{
+ {"SkipAllQuestions", p.SkipAllQuestions},
+ {"SkipAllAnswers", p.SkipAllAnswers},
+ {"SkipAllAuthorities", p.SkipAllAuthorities},
+ {"SkipAllAdditionals", p.SkipAllAdditionals},
+ }
+ for _, test := range tests {
+ for i := 1; i <= 3; i++ {
+ if err := test.f(); err != nil {
+ t.Errorf("Call #%d to %s(): %v", i, test.name, err)
+ }
+ }
+ }
+}
+
+func TestSkipNotStarted(t *testing.T) {
+ var p Parser
+
+ tests := []struct {
+ name string
+ f func() error
+ }{
+ {"SkipAllQuestions", p.SkipAllQuestions},
+ {"SkipAllAnswers", p.SkipAllAnswers},
+ {"SkipAllAuthorities", p.SkipAllAuthorities},
+ {"SkipAllAdditionals", p.SkipAllAdditionals},
+ }
+ for _, test := range tests {
+ if err := test.f(); err != ErrNotStarted {
+ t.Errorf("Got %s() = %v, want = %v", test.name, err, ErrNotStarted)
+ }
+ }
+}
+
+func TestTooManyRecords(t *testing.T) {
+ const recs = int(^uint16(0)) + 1
+ tests := []struct {
+ name string
+ msg Message
+ want error
+ }{
+ {
+ "Questions",
+ Message{
+ Questions: make([]Question, recs),
+ },
+ errTooManyQuestions,
+ },
+ {
+ "Answers",
+ Message{
+ Answers: make([]Resource, recs),
+ },
+ errTooManyAnswers,
+ },
+ {
+ "Authorities",
+ Message{
+ Authorities: make([]Resource, recs),
+ },
+ errTooManyAuthorities,
+ },
+ {
+ "Additionals",
+ Message{
+ Additionals: make([]Resource, recs),
+ },
+ errTooManyAdditionals,
+ },
+ }
+
+ for _, test := range tests {
+ if _, got := test.msg.Pack(); got != test.want {
+ t.Errorf("Packing %d %s: got = %v, want = %v", recs, test.name, got, test.want)
+ }
+ }
+}
+
+func TestVeryLongTxt(t *testing.T) {
+ want := &TXTResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: loremIpsum,
+ }
+ buf, err := packResource(make([]byte, 0, 8000), want, map[string]int{})
+ if err != nil {
+ t.Fatal("Packing failed:", err)
+ }
+ var hdr ResourceHeader
+ off, err := hdr.unpack(buf, 0)
+ if err != nil {
+ t.Fatal("Unpacking ResourceHeader failed:", err)
+ }
+ got, n, err := unpackResource(buf, off, hdr)
+ if err != nil {
+ t.Fatal("Unpacking failed:", err)
+ }
+ if n != len(buf) {
+ t.Errorf("Unpacked different amount than packed: got n = %d, want = %d", n, len(buf))
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Got = %+v, want = %+v", got, want)
+ }
+}
+
+func ExampleHeaderSearch() {
+ msg := Message{
+ Header: Header{Response: true, Authoritative: true},
+ Questions: []Question{
+ {
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ {
+ Name: "bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{
+ &AResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: [4]byte{127, 0, 0, 1},
+ },
+ &AResource{
+ ResourceHeader: ResourceHeader{
+ Name: "bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: [4]byte{127, 0, 0, 2},
+ },
+ },
+ }
+
+ buf, err := msg.Pack()
+ if err != nil {
+ panic(err)
+ }
+
+ wantName := "bar.example.com."
+
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ panic(err)
+ }
+
+ for {
+ q, err := p.Question()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ panic(err)
+ }
+
+ if q.Name != wantName {
+ continue
+ }
+
+ fmt.Println("Found question for name", wantName)
+ if err := p.SkipAllQuestions(); err != nil {
+ panic(err)
+ }
+ break
+ }
+
+ var gotIPs []net.IP
+ for {
+ h, err := p.AnswerHeader()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ panic(err)
+ }
+
+ if (h.Type != TypeA && h.Type != TypeAAAA) || h.Class != ClassINET {
+ continue
+ }
+
+ if !strings.EqualFold(h.Name, wantName) {
+ if err := p.SkipAnswer(); err != nil {
+ panic(err)
+ }
+ continue
+ }
+ a, err := p.Answer()
+ if err != nil {
+ panic(err)
+ }
+
+ switch r := a.(type) {
+ default:
+ panic(fmt.Sprintf("unknown type: %T", r))
+ case *AResource:
+ gotIPs = append(gotIPs, r.A[:])
+ case *AAAAResource:
+ gotIPs = append(gotIPs, r.AAAA[:])
+ }
+ }
+
+ fmt.Printf("Found A/AAAA records for name %s: %v\n", wantName, gotIPs)
+
+ // Output:
+ // Found question for name bar.example.com.
+ // Found A/AAAA records for name bar.example.com.: [127.0.0.2]
+}
+
+func largeTestMsg() Message {
+ return Message{
+ Header: Header{Response: true, Authoritative: true},
+ Questions: []Question{
+ {
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{
+ &AResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: [4]byte{127, 0, 0, 1},
+ },
+ &AResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ A: [4]byte{127, 0, 0, 2},
+ },
+ },
+ Authorities: []Resource{
+ &NSResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ NS: "ns1.example.com.",
+ },
+ &NSResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ NS: "ns2.example.com.",
+ },
+ },
+ Additionals: []Resource{
+ &TXTResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: "So Long, and Thanks for All the Fish",
+ },
+ &TXTResource{
+ ResourceHeader: ResourceHeader{
+ Name: "foo.bar.example.com.",
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ Txt: "Hamster Huey and the Gooey Kablooie",
+ },
+ },
+ }
+}
+
+const loremIpsum = `
+Lorem ipsum dolor sit amet, nec enim antiopam id, an ullum choro
+nonumes qui, pro eu debet honestatis mediocritatem. No alia enim eos,
+magna signiferumque ex vis. Mei no aperiri dissentias, cu vel quas
+regione. Malorum quaeque vim ut, eum cu semper aliquid invidunt, ei
+nam ipsum assentior.
+
+Nostrum appellantur usu no, vis ex probatus adipiscing. Cu usu illum
+facilis eleifend. Iusto conceptam complectitur vim id. Tale omnesque
+no usu, ei oblique sadipscing vim. At nullam voluptua usu, mei laudem
+reformidans et. Qui ei eros porro reformidans, ius suas veritus
+torquatos ex. Mea te facer alterum consequat.
+
+Soleat torquatos democritum sed et, no mea congue appareat, facer
+aliquam nec in. Has te ipsum tritani. At justo dicta option nec, movet
+phaedrum ad nam. Ea detracto verterem liberavisse has, delectus
+suscipiantur in mei. Ex nam meliore complectitur. Ut nam omnis
+honestatis quaerendum, ea mea nihil affert detracto, ad vix rebum
+mollis.
+
+Ut epicurei praesent neglegentur pri, prima fuisset intellegebat ad
+vim. An habemus comprehensam usu, at enim dignissim pro. Eam reque
+vivendum adipisci ea. Vel ne odio choro minimum. Sea admodum
+dissentiet ex. Mundi tamquam evertitur ius cu. Homero postea iisque ut
+pro, vel ne saepe senserit consetetur.
+
+Nulla utamur facilisis ius ea, in viderer diceret pertinax eum. Mei no
+enim quodsi facilisi, ex sed aeterno appareat mediocritatem, eum
+sententiae deterruisset ut. At suas timeam euismod cum, offendit
+appareat interpretaris ne vix. Vel ea civibus albucius, ex vim quidam
+accusata intellegebat, noluisse instructior sea id. Nec te nonumes
+habemus appellantur, quis dignissim vituperata eu nam.
+
+At vix apeirian patrioque vituperatoribus, an usu agam assum. Debet
+iisque an mea. Per eu dicant ponderum accommodare. Pri alienum
+placerat senserit an, ne eum ferri abhorreant vituperatoribus. Ut mea
+eligendi disputationi. Ius no tation everti impedit, ei magna quidam
+mediocritatem pri.
+
+Legendos perpetua iracundia ne usu, no ius ullum epicurei intellegam,
+ad modus epicuri lucilius eam. In unum quaerendum usu. Ne diam paulo
+has, ea veri virtute sed. Alia honestatis conclusionemque mea eu, ut
+iudico albucius his.
+
+Usu essent probatus eu, sed omnis dolor delicatissimi ex. No qui augue
+dissentias dissentiet. Laudem recteque no usu, vel an velit noluisse,
+an sed utinam eirmod appetere. Ne mea fuisset inimicus ocurreret. At
+vis dicant abhorreant, utinam forensibus nec ne, mei te docendi
+consequat. Brute inermis persecuti cum id. Ut ipsum munere propriae
+usu, dicit graeco disputando id has.
+
+Eros dolore quaerendum nam ei. Timeam ornatus inciderint pro id. Nec
+torquatos sadipscing ei, ancillae molestie per in. Malis principes duo
+ea, usu liber postulant ei.
+
+Graece timeam voluptatibus eu eam. Alia probatus quo no, ea scripta
+feugiat duo. Congue option meliore ex qui, noster invenire appellantur
+ea vel. Eu exerci legendos vel. Consetetur repudiandae vim ut. Vix an
+probo minimum, et nam illud falli tempor.
+
+Cum dico signiferumque eu. Sed ut regione maiorum, id veritus insolens
+tacimates vix. Eu mel sint tamquam lucilius, duo no oporteat
+tacimates. Atqui augue concludaturque vix ei, id mel utroque menandri.
+
+Ad oratio blandit aliquando pro. Vis et dolorum rationibus
+philosophia, ad cum nulla molestie. Hinc fuisset adversarium eum et,
+ne qui nisl verear saperet, vel te quaestio forensibus. Per odio
+option delenit an. Alii placerat has no, in pri nihil platonem
+cotidieque. Est ut elit copiosae scaevola, debet tollit maluisset sea
+an.
+
+Te sea hinc debet pericula, liber ridens fabulas cu sed, quem mutat
+accusam mea et. Elitr labitur albucius et pri, an labore feugait mel.
+Velit zril melius usu ea. Ad stet putent interpretaris qui. Mel no
+error volumus scripserit. In pro paulo iudico, quo ei dolorem
+verterem, affert fabellas dissentiet ea vix.
+
+Vis quot deserunt te. Error aliquid detraxit eu usu, vis alia eruditi
+salutatus cu. Est nostrud bonorum an, ei usu alii salutatus. Vel at
+nisl primis, eum ex aperiri noluisse reformidans. Ad veri velit
+utroque vis, ex equidem detraxit temporibus has.
+
+Inermis appareat usu ne. Eros placerat periculis mea ad, in dictas
+pericula pro. Errem postulant at usu, ea nec amet ornatus mentitum. Ad
+mazim graeco eum, vel ex percipit volutpat iudicabit, sit ne delicata
+interesset. Mel sapientem prodesset abhorreant et, oblique suscipit
+eam id.
+
+An maluisset disputando mea, vidit mnesarchum pri et. Malis insolens
+inciderint no sea. Ea persius maluisset vix, ne vim appellantur
+instructior, consul quidam definiebas pri id. Cum integre feugiat
+pericula in, ex sed persius similique, mel ne natum dicit percipitur.
+
+Primis discere ne pri, errem putent definitionem at vis. Ei mel dolore
+neglegentur, mei tincidunt percipitur ei. Pro ad simul integre
+rationibus. Eu vel alii honestatis definitiones, mea no nonumy
+reprehendunt.
+
+Dicta appareat legendos est cu. Eu vel congue dicunt omittam, no vix
+adhuc minimum constituam, quot noluisse id mel. Eu quot sale mutat
+duo, ex nisl munere invenire duo. Ne nec ullum utamur. Pro alterum
+debitis nostrum no, ut vel aliquid vivendo.
+
+Aliquip fierent praesent quo ne, id sit audiam recusabo delicatissimi.
+Usu postulant incorrupte cu. At pro dicit tibique intellegam, cibo
+dolore impedit id eam, et aeque feugait assentior has. Quando sensibus
+nec ex. Possit sensibus pri ad, unum mutat periculis cu vix.
+
+Mundi tibique vix te, duo simul partiendo qualisque id, est at vidit
+sonet tempor. No per solet aeterno deseruisse. Petentium salutandi
+definiebas pri cu. Munere vivendum est in. Ei justo congue eligendi
+vis, modus offendit omittantur te mel.
+
+Integre voluptaria in qui, sit habemus tractatos constituam no. Utinam
+melius conceptam est ne, quo in minimum apeirian delicata, ut ius
+porro recusabo. Dicant expetenda vix no, ludus scripserit sed ex, eu
+his modo nostro. Ut etiam sonet his, quodsi inciderint philosophia te
+per. Nullam lobortis eu cum, vix an sonet efficiendi repudiandae. Vis
+ad idque fabellas intellegebat.
+
+Eum commodo senserit conclusionemque ex. Sed forensibus sadipscing ut,
+mei in facer delicata periculis, sea ne hinc putent cetero. Nec ne
+alia corpora invenire, alia prima soleat te cum. Eleifend posidonium
+nam at.
+
+Dolorum indoctum cu quo, ex dolor legendos recteque eam, cu pri zril
+discere. Nec civibus officiis dissentiunt ex, est te liber ludus
+elaboraret. Cum ea fabellas invenire. Ex vim nostrud eripuit
+comprehensam, nam te inermis delectus, saepe inermis senserit.
+`
To view, visit change 35237. To unsubscribe, visit settings.