aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/google/gopacket/layers/dns.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/gopacket/layers/dns.go')
-rw-r--r--vendor/github.com/google/gopacket/layers/dns.go894
1 files changed, 894 insertions, 0 deletions
diff --git a/vendor/github.com/google/gopacket/layers/dns.go b/vendor/github.com/google/gopacket/layers/dns.go
new file mode 100644
index 0000000..2368a28
--- /dev/null
+++ b/vendor/github.com/google/gopacket/layers/dns.go
@@ -0,0 +1,894 @@
+// Copyright 2014 Google, Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree.
+
+package layers
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/google/gopacket"
+)
+
+// DNSClass defines the class associated with a request/response. Different DNS
+// classes can be thought of as an array of parallel namespace trees.
+type DNSClass uint16
+
+// DNSClass known values.
+const (
+ DNSClassIN DNSClass = 1 // Internet
+ DNSClassCS DNSClass = 2 // the CSNET class (Obsolete)
+ DNSClassCH DNSClass = 3 // the CHAOS class
+ DNSClassHS DNSClass = 4 // Hesiod [Dyer 87]
+ DNSClassAny DNSClass = 255 // AnyClass
+)
+
+func (dc DNSClass) String() string {
+ switch dc {
+ default:
+ return "Unknown"
+ case DNSClassIN:
+ return "IN"
+ case DNSClassCS:
+ return "CS"
+ case DNSClassCH:
+ return "CH"
+ case DNSClassHS:
+ return "HS"
+ case DNSClassAny:
+ return "Any"
+ }
+}
+
+// DNSType defines the type of data being requested/returned in a
+// question/answer.
+type DNSType uint16
+
+// DNSType known values.
+const (
+ DNSTypeA DNSType = 1 // a host address
+ DNSTypeNS DNSType = 2 // an authoritative name server
+ DNSTypeMD DNSType = 3 // a mail destination (Obsolete - use MX)
+ DNSTypeMF DNSType = 4 // a mail forwarder (Obsolete - use MX)
+ DNSTypeCNAME DNSType = 5 // the canonical name for an alias
+ DNSTypeSOA DNSType = 6 // marks the start of a zone of authority
+ DNSTypeMB DNSType = 7 // a mailbox domain name (EXPERIMENTAL)
+ DNSTypeMG DNSType = 8 // a mail group member (EXPERIMENTAL)
+ DNSTypeMR DNSType = 9 // a mail rename domain name (EXPERIMENTAL)
+ DNSTypeNULL DNSType = 10 // a null RR (EXPERIMENTAL)
+ DNSTypeWKS DNSType = 11 // a well known service description
+ DNSTypePTR DNSType = 12 // a domain name pointer
+ DNSTypeHINFO DNSType = 13 // host information
+ DNSTypeMINFO DNSType = 14 // mailbox or mail list information
+ DNSTypeMX DNSType = 15 // mail exchange
+ DNSTypeTXT DNSType = 16 // text strings
+ DNSTypeAAAA DNSType = 28 // a IPv6 host address [RFC3596]
+ DNSTypeSRV DNSType = 33 // server discovery [RFC2782] [RFC6195]
+)
+
+func (dt DNSType) String() string {
+ switch dt {
+ default:
+ return "Unknown"
+ case DNSTypeA:
+ return "A"
+ case DNSTypeNS:
+ return "NS"
+ case DNSTypeMD:
+ return "MD"
+ case DNSTypeMF:
+ return "MF"
+ case DNSTypeCNAME:
+ return "CNAME"
+ case DNSTypeSOA:
+ return "SOA"
+ case DNSTypeMB:
+ return "MB"
+ case DNSTypeMG:
+ return "MG"
+ case DNSTypeMR:
+ return "MR"
+ case DNSTypeNULL:
+ return "NULL"
+ case DNSTypeWKS:
+ return "WKS"
+ case DNSTypePTR:
+ return "PTR"
+ case DNSTypeHINFO:
+ return "HINFO"
+ case DNSTypeMINFO:
+ return "MINFO"
+ case DNSTypeMX:
+ return "MX"
+ case DNSTypeTXT:
+ return "TXT"
+ case DNSTypeAAAA:
+ return "AAAA"
+ case DNSTypeSRV:
+ return "SRV"
+ }
+}
+
+// DNSResponseCode provides response codes for question answers.
+type DNSResponseCode uint8
+
+// DNSResponseCode known values.
+const (
+ DNSResponseCodeNoErr DNSResponseCode = 0 // No error
+ DNSResponseCodeFormErr DNSResponseCode = 1 // Format Error [RFC1035]
+ DNSResponseCodeServFail DNSResponseCode = 2 // Server Failure [RFC1035]
+ DNSResponseCodeNXDomain DNSResponseCode = 3 // Non-Existent Domain [RFC1035]
+ DNSResponseCodeNotImp DNSResponseCode = 4 // Not Implemented [RFC1035]
+ DNSResponseCodeRefused DNSResponseCode = 5 // Query Refused [RFC1035]
+ DNSResponseCodeYXDomain DNSResponseCode = 6 // Name Exists when it should not [RFC2136]
+ DNSResponseCodeYXRRSet DNSResponseCode = 7 // RR Set Exists when it should not [RFC2136]
+ DNSResponseCodeNXRRSet DNSResponseCode = 8 // RR Set that should exist does not [RFC2136]
+ DNSResponseCodeNotAuth DNSResponseCode = 9 // Server Not Authoritative for zone [RFC2136]
+ DNSResponseCodeNotZone DNSResponseCode = 10 // Name not contained in zone [RFC2136]
+ DNSResponseCodeBadVers DNSResponseCode = 16 // Bad OPT Version [RFC2671]
+ DNSResponseCodeBadSig DNSResponseCode = 16 // TSIG Signature Failure [RFC2845]
+ DNSResponseCodeBadKey DNSResponseCode = 17 // Key not recognized [RFC2845]
+ DNSResponseCodeBadTime DNSResponseCode = 18 // Signature out of time window [RFC2845]
+ DNSResponseCodeBadMode DNSResponseCode = 19 // Bad TKEY Mode [RFC2930]
+ DNSResponseCodeBadName DNSResponseCode = 20 // Duplicate key name [RFC2930]
+ DNSResponseCodeBadAlg DNSResponseCode = 21 // Algorithm not supported [RFC2930]
+ DNSResponseCodeBadTruc DNSResponseCode = 22 // Bad Truncation [RFC4635]
+)
+
+func (drc DNSResponseCode) String() string {
+ switch drc {
+ default:
+ return "Unknown"
+ case DNSResponseCodeNoErr:
+ return "No Error"
+ case DNSResponseCodeFormErr:
+ return "Format Error"
+ case DNSResponseCodeServFail:
+ return "Server Failure "
+ case DNSResponseCodeNXDomain:
+ return "Non-Existent Domain"
+ case DNSResponseCodeNotImp:
+ return "Not Implemented"
+ case DNSResponseCodeRefused:
+ return "Query Refused"
+ case DNSResponseCodeYXDomain:
+ return "Name Exists when it should not"
+ case DNSResponseCodeYXRRSet:
+ return "RR Set Exists when it should not"
+ case DNSResponseCodeNXRRSet:
+ return "RR Set that should exist does not"
+ case DNSResponseCodeNotAuth:
+ return "Server Not Authoritative for zone"
+ case DNSResponseCodeNotZone:
+ return "Name not contained in zone"
+ case DNSResponseCodeBadVers:
+ return "Bad OPT Version"
+ case DNSResponseCodeBadKey:
+ return "Key not recognized"
+ case DNSResponseCodeBadTime:
+ return "Signature out of time window"
+ case DNSResponseCodeBadMode:
+ return "Bad TKEY Mode"
+ case DNSResponseCodeBadName:
+ return "Duplicate key name"
+ case DNSResponseCodeBadAlg:
+ return "Algorithm not supported"
+ case DNSResponseCodeBadTruc:
+ return "Bad Truncation"
+ }
+}
+
+// DNSOpCode defines a set of different operation types.
+type DNSOpCode uint8
+
+// DNSOpCode known values.
+const (
+ DNSOpCodeQuery DNSOpCode = 0 // Query [RFC1035]
+ DNSOpCodeIQuery DNSOpCode = 1 // Inverse Query Obsolete [RFC3425]
+ DNSOpCodeStatus DNSOpCode = 2 // Status [RFC1035]
+ DNSOpCodeNotify DNSOpCode = 4 // Notify [RFC1996]
+ DNSOpCodeUpdate DNSOpCode = 5 // Update [RFC2136]
+)
+
+func (doc DNSOpCode) String() string {
+ switch doc {
+ default:
+ return "Unknown"
+ case DNSOpCodeQuery:
+ return "Query"
+ case DNSOpCodeIQuery:
+ return "Inverse Query"
+ case DNSOpCodeStatus:
+ return "Status"
+ case DNSOpCodeNotify:
+ return "Notify"
+ case DNSOpCodeUpdate:
+ return "Update"
+ }
+}
+
+// DNS is specified in RFC 1034 / RFC 1035
+// +---------------------+
+// | Header |
+// +---------------------+
+// | Question | the question for the name server
+// +---------------------+
+// | Answer | RRs answering the question
+// +---------------------+
+// | Authority | RRs pointing toward an authority
+// +---------------------+
+// | Additional | RRs holding additional information
+// +---------------------+
+//
+// DNS Header
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ID |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | QDCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ANCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | NSCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ARCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+// DNS contains data from a single Domain Name Service packet.
+type DNS struct {
+ BaseLayer
+
+ // Header fields
+ ID uint16
+ QR bool
+ OpCode DNSOpCode
+
+ AA bool // Authoritative answer
+ TC bool // Truncated
+ RD bool // Recursion desired
+ RA bool // Recursion available
+ Z uint8 // Resrved for future use
+
+ ResponseCode DNSResponseCode
+ QDCount uint16 // Number of questions to expect
+ ANCount uint16 // Number of answers to expect
+ NSCount uint16 // Number of authorities to expect
+ ARCount uint16 // Number of additional records to expect
+
+ // Entries
+ Questions []DNSQuestion
+ Answers []DNSResourceRecord
+ Authorities []DNSResourceRecord
+ Additionals []DNSResourceRecord
+
+ // buffer for doing name decoding. We use a single reusable buffer to avoid
+ // name decoding on a single object via multiple DecodeFromBytes calls
+ // requiring constant allocation of small byte slices.
+ buffer []byte
+}
+
+// LayerType returns gopacket.LayerTypeDNS.
+func (d *DNS) LayerType() gopacket.LayerType { return LayerTypeDNS }
+
+// decodeDNS decodes the byte slice into a DNS type. It also
+// setups the application Layer in PacketBuilder.
+func decodeDNS(data []byte, p gopacket.PacketBuilder) error {
+ d := &DNS{}
+ err := d.DecodeFromBytes(data, p)
+ if err != nil {
+ return err
+ }
+ p.AddLayer(d)
+ p.SetApplicationLayer(d)
+ return nil
+}
+
+// DecodeFromBytes decodes the slice into the DNS struct.
+func (d *DNS) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
+ d.buffer = d.buffer[:0]
+
+ if len(data) < 12 {
+ df.SetTruncated()
+ return errors.New("DNS packet too short")
+ }
+
+ // since there are no further layers, the baselayer's content is
+ // pointing to this layer
+ d.BaseLayer = BaseLayer{Contents: data[:len(data)]}
+ d.ID = binary.BigEndian.Uint16(data[:2])
+ d.QR = data[2]&0x80 != 0
+ d.OpCode = DNSOpCode(data[2]>>3) & 0x0F
+ d.AA = data[2]&0x04 != 0
+ d.TC = data[2]&0x02 != 0
+ d.RD = data[2]&0x01 != 0
+ d.RA = data[3]&0x80 != 0
+ d.Z = uint8(data[3]>>4) & 0x7
+ d.ResponseCode = DNSResponseCode(data[3] & 0xF)
+ d.QDCount = binary.BigEndian.Uint16(data[4:6])
+ d.ANCount = binary.BigEndian.Uint16(data[6:8])
+ d.NSCount = binary.BigEndian.Uint16(data[8:10])
+ d.ARCount = binary.BigEndian.Uint16(data[10:12])
+
+ d.Questions = d.Questions[:0]
+ d.Answers = d.Answers[:0]
+ d.Authorities = d.Authorities[:0]
+ d.Additionals = d.Additionals[:0]
+
+ offset := 12
+ var err error
+ for i := 0; i < int(d.QDCount); i++ {
+ var q DNSQuestion
+ if offset, err = q.decode(data, offset, df, &d.buffer); err != nil {
+ return err
+ }
+ d.Questions = append(d.Questions, q)
+ }
+
+ // For some horrible reason, if we do the obvious thing in this loop:
+ // var r DNSResourceRecord
+ // if blah := r.decode(blah); err != nil {
+ // return err
+ // }
+ // d.Foo = append(d.Foo, r)
+ // the Go compiler thinks that 'r' escapes to the heap, causing a malloc for
+ // every Answer, Authority, and Additional. To get around this, we do
+ // something really silly: we append an empty resource record to our slice,
+ // then use the last value in the slice to call decode. Since the value is
+ // already in the slice, there's no WAY it can escape... on the other hand our
+ // code is MUCH uglier :(
+ for i := 0; i < int(d.ANCount); i++ {
+ d.Answers = append(d.Answers, DNSResourceRecord{})
+ if offset, err = d.Answers[i].decode(data, offset, df, &d.buffer); err != nil {
+ d.Answers = d.Answers[:i] // strip off erroneous value
+ return err
+ }
+ }
+ for i := 0; i < int(d.NSCount); i++ {
+ d.Authorities = append(d.Authorities, DNSResourceRecord{})
+ if offset, err = d.Authorities[i].decode(data, offset, df, &d.buffer); err != nil {
+ d.Authorities = d.Authorities[:i] // strip off erroneous value
+ return err
+ }
+ }
+ for i := 0; i < int(d.ARCount); i++ {
+ d.Additionals = append(d.Additionals, DNSResourceRecord{})
+ if offset, err = d.Additionals[i].decode(data, offset, df, &d.buffer); err != nil {
+ d.Additionals = d.Additionals[:i] // strip off erroneous value
+ return err
+ }
+ }
+
+ if uint16(len(d.Questions)) != d.QDCount {
+ return errors.New("Invalid query decoding, not the right number of questions")
+ } else if uint16(len(d.Answers)) != d.ANCount {
+ return errors.New("Invalid query decoding, not the right number of answers")
+ } else if uint16(len(d.Authorities)) != d.NSCount {
+ return errors.New("Invalid query decoding, not the right number of authorities")
+ } else if uint16(len(d.Additionals)) != d.ARCount {
+ return errors.New("Invalid query decoding, not the right number of additionals info")
+ }
+ return nil
+}
+
+// CanDecode implements gopacket.DecodingLayer.
+func (d *DNS) CanDecode() gopacket.LayerClass {
+ return LayerTypeDNS
+}
+
+// NextLayerType implements gopacket.DecodingLayer.
+func (d *DNS) NextLayerType() gopacket.LayerType {
+ return gopacket.LayerTypePayload
+}
+
+// Payload returns nil.
+func (d *DNS) Payload() []byte {
+ return nil
+}
+
+func b2i(b bool) int {
+ if b {
+ return 1
+ }
+ return 0
+}
+
+func recSize(rr *DNSResourceRecord) int {
+ switch rr.Type {
+ case DNSTypeA:
+ return 4
+ case DNSTypeAAAA:
+ return 16
+ case DNSTypeNS:
+ return len(rr.NS) + 2
+ case DNSTypeCNAME:
+ return len(rr.CNAME) + 2
+ case DNSTypePTR:
+ return len(rr.PTR) + 2
+ case DNSTypeSOA:
+ return len(rr.SOA.MName) + 2 + len(rr.SOA.RName) + 2 + 20
+ case DNSTypeMX:
+ return 2 + len(rr.MX.Name) + 2
+ case DNSTypeTXT:
+ l := len(rr.TXTs)
+ for _, txt := range rr.TXTs {
+ l += len(txt)
+ }
+ return l
+ case DNSTypeSRV:
+ return 6 + len(rr.SRV.Name) + 2
+ }
+
+ return 0
+}
+
+func computeSize(recs []DNSResourceRecord) int {
+ sz := 0
+ for _, rr := range recs {
+ sz += len(rr.Name) + 14
+ sz += recSize(&rr)
+ }
+ return sz
+}
+
+// SerializeTo writes the serialized form of this layer into the
+// SerializationBuffer, implementing gopacket.SerializableLayer.
+func (d *DNS) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
+ dsz := 0
+ for _, q := range d.Questions {
+ dsz += len(q.Name) + 6
+ }
+ dsz += computeSize(d.Answers)
+ dsz += computeSize(d.Authorities)
+ dsz += computeSize(d.Additionals)
+
+ bytes, err := b.PrependBytes(12 + dsz)
+ if err != nil {
+ return err
+ }
+ binary.BigEndian.PutUint16(bytes, d.ID)
+ bytes[2] = byte((b2i(d.QR) << 7) | (int(d.OpCode) << 3) | (b2i(d.AA) << 2) | (b2i(d.TC) << 1) | b2i(d.RD))
+ bytes[3] = byte((b2i(d.RA) << 7) | (int(d.Z) << 4) | int(d.ResponseCode))
+
+ if opts.FixLengths {
+ d.QDCount = uint16(len(d.Questions))
+ d.ANCount = uint16(len(d.Answers))
+ d.NSCount = uint16(len(d.Authorities))
+ d.ARCount = uint16(len(d.Additionals))
+ }
+ binary.BigEndian.PutUint16(bytes[4:], d.QDCount)
+ binary.BigEndian.PutUint16(bytes[6:], d.ANCount)
+ binary.BigEndian.PutUint16(bytes[8:], d.NSCount)
+ binary.BigEndian.PutUint16(bytes[10:], d.ARCount)
+
+ off := 12
+ for _, qd := range d.Questions {
+ n := qd.encode(bytes, off)
+ off += n
+ }
+
+ for i := range d.Answers {
+ // done this way so we can modify DNSResourceRecord to fix
+ // lengths if requested
+ qa := &d.Answers[i]
+ n, err := qa.encode(bytes, off, opts)
+ if err != nil {
+ return err
+ }
+ off += n
+ }
+
+ for i := range d.Authorities {
+ qa := &d.Authorities[i]
+ n, err := qa.encode(bytes, off, opts)
+ if err != nil {
+ return err
+ }
+ off += n
+ }
+ for i := range d.Additionals {
+ qa := &d.Additionals[i]
+ n, err := qa.encode(bytes, off, opts)
+ if err != nil {
+ return err
+ }
+ off += n
+ }
+
+ return nil
+}
+
+var errMaxRecursion = errors.New("max DNS recursion level hit")
+
+const maxRecursionLevel = 255
+
+func decodeName(data []byte, offset int, buffer *[]byte, level int) ([]byte, int, error) {
+ if level > maxRecursionLevel {
+ return nil, 0, errMaxRecursion
+ } else if offset >= len(data) {
+ return nil, 0, errors.New("dns name offset too high")
+ } else if offset < 0 {
+ return nil, 0, errors.New("dns name offset is negative")
+ }
+ start := len(*buffer)
+ index := offset
+ if data[index] == 0x00 {
+ return nil, index + 1, nil
+ }
+loop:
+ for data[index] != 0x00 {
+ switch data[index] & 0xc0 {
+ default:
+ /* RFC 1035
+ A domain name represented as a sequence of labels, where
+ each label consists of a length octet followed by that
+ number of octets. The domain name terminates with the
+ zero length octet for the null label of the root. Note
+ that this field may be an odd number of octets; no
+ padding is used.
+ */
+ index2 := index + int(data[index]) + 1
+ if index2-offset > 255 {
+ return nil, 0, errors.New("dns name is too long")
+ } else if index2 < index+1 || index2 > len(data) {
+ return nil, 0, errors.New("dns name uncomputable: invalid index")
+ }
+ *buffer = append(*buffer, '.')
+ *buffer = append(*buffer, data[index+1:index2]...)
+ index = index2
+
+ case 0xc0:
+ /* RFC 1035
+ The pointer takes the form of a two octet sequence.
+
+ The first two bits are ones. This allows a pointer to
+ be distinguished from a label, since the label must
+ begin with two zero bits because labels are restricted
+ to 63 octets or less. (The 10 and 01 combinations are
+ reserved for future use.) The OFFSET field specifies
+ an offset from the start of the message (i.e., the
+ first octet of the ID field in the domain header). A
+ zero offset specifies the first byte of the ID field,
+ etc.
+
+ The compression scheme allows a domain name in a message to be
+ represented as either:
+ - a sequence of labels ending in a zero octet
+ - a pointer
+ - a sequence of labels ending with a pointer
+ */
+ if index+2 > len(data) {
+ return nil, 0, errors.New("dns offset pointer too high")
+ }
+ offsetp := int(binary.BigEndian.Uint16(data[index:index+2]) & 0x3fff)
+ if offsetp > len(data) {
+ return nil, 0, errors.New("dns offset pointer too high")
+ }
+ // This looks a little tricky, but actually isn't. Because of how
+ // decodeName is written, calling it appends the decoded name to the
+ // current buffer. We already have the start of the buffer, then, so
+ // once this call is done buffer[start:] will contain our full name.
+ _, _, err := decodeName(data, offsetp, buffer, level+1)
+ if err != nil {
+ return nil, 0, err
+ }
+ index++ // pointer is two bytes, so add an extra byte here.
+ break loop
+ /* EDNS, or other DNS option ? */
+ case 0x40: // RFC 2673
+ return nil, 0, fmt.Errorf("qname '0x40' - RFC 2673 unsupported yet (data=%x index=%d)",
+ data[index], index)
+
+ case 0x80:
+ return nil, 0, fmt.Errorf("qname '0x80' unsupported yet (data=%x index=%d)",
+ data[index], index)
+ }
+ if index >= len(data) {
+ return nil, 0, errors.New("dns index walked out of range")
+ }
+ }
+ if len(*buffer) <= start {
+ return nil, 0, errors.New("no dns data found for name")
+ }
+ return (*buffer)[start+1:], index + 1, nil
+}
+
+// DNSQuestion wraps a single request (question) within a DNS query.
+type DNSQuestion struct {
+ Name []byte
+ Type DNSType
+ Class DNSClass
+}
+
+func (q *DNSQuestion) decode(data []byte, offset int, df gopacket.DecodeFeedback, buffer *[]byte) (int, error) {
+ name, endq, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return 0, err
+ }
+
+ q.Name = name
+ q.Type = DNSType(binary.BigEndian.Uint16(data[endq : endq+2]))
+ q.Class = DNSClass(binary.BigEndian.Uint16(data[endq+2 : endq+4]))
+
+ return endq + 4, nil
+}
+
+func (q *DNSQuestion) encode(data []byte, offset int) int {
+ noff := encodeName(q.Name, data, offset)
+ binary.BigEndian.PutUint16(data[noff:], uint16(q.Type))
+ binary.BigEndian.PutUint16(data[noff+2:], uint16(q.Class))
+ return len(q.Name) + 6
+}
+
+// DNSResourceRecord
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | |
+// / /
+// / NAME /
+// | |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | TYPE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | CLASS |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | TTL |
+// | |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | RDLENGTH |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+// / RDATA /
+// / /
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+// DNSResourceRecord wraps the data from a single DNS resource within a
+// response.
+type DNSResourceRecord struct {
+ // Header
+ Name []byte
+ Type DNSType
+ Class DNSClass
+ TTL uint32
+
+ // RDATA Raw Values
+ DataLength uint16
+ Data []byte
+
+ // RDATA Decoded Values
+ IP net.IP
+ NS, CNAME, PTR []byte
+ TXTs [][]byte
+ SOA DNSSOA
+ SRV DNSSRV
+ MX DNSMX
+
+ // Undecoded TXT for backward compatibility
+ TXT []byte
+}
+
+// decode decodes the resource record, returning the total length of the record.
+func (rr *DNSResourceRecord) decode(data []byte, offset int, df gopacket.DecodeFeedback, buffer *[]byte) (int, error) {
+ name, endq, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return 0, err
+ }
+
+ rr.Name = name
+ rr.Type = DNSType(binary.BigEndian.Uint16(data[endq : endq+2]))
+ rr.Class = DNSClass(binary.BigEndian.Uint16(data[endq+2 : endq+4]))
+ rr.TTL = binary.BigEndian.Uint32(data[endq+4 : endq+8])
+ rr.DataLength = binary.BigEndian.Uint16(data[endq+8 : endq+10])
+ end := endq + 10 + int(rr.DataLength)
+ if end > len(data) {
+ return 0, fmt.Errorf("resource record length exceeds data")
+ }
+ rr.Data = data[endq+10 : end]
+
+ if err = rr.decodeRData(data, endq+10, buffer); err != nil {
+ return 0, err
+ }
+
+ return endq + 10 + int(rr.DataLength), nil
+}
+
+func encodeName(name []byte, data []byte, offset int) int {
+ l := 0
+ for i := range name {
+ if name[i] == '.' {
+ data[offset+i-l] = byte(l)
+ l = 0
+ } else {
+ // skip one to write the length
+ data[offset+i+1] = name[i]
+ l++
+ }
+ }
+ // length for final portion
+ data[offset+len(name)-l] = byte(l)
+ data[offset+len(name)+1] = 0x00 // terminal
+ return offset + len(name) + 2
+}
+
+func (rr *DNSResourceRecord) encode(data []byte, offset int, opts gopacket.SerializeOptions) (int, error) {
+
+ noff := encodeName(rr.Name, data, offset)
+
+ binary.BigEndian.PutUint16(data[noff:], uint16(rr.Type))
+ binary.BigEndian.PutUint16(data[noff+2:], uint16(rr.Class))
+ binary.BigEndian.PutUint32(data[noff+4:], uint32(rr.TTL))
+
+ switch rr.Type {
+ case DNSTypeA:
+ copy(data[noff+10:], rr.IP.To4())
+ case DNSTypeAAAA:
+ copy(data[noff+10:], rr.IP)
+ case DNSTypeNS:
+ encodeName(rr.NS, data, noff+10)
+ case DNSTypeCNAME:
+ encodeName(rr.CNAME, data, noff+10)
+ case DNSTypePTR:
+ encodeName(rr.PTR, data, noff+10)
+ case DNSTypeSOA:
+ noff2 := encodeName(rr.SOA.MName, data, noff+10)
+ noff2 = encodeName(rr.SOA.RName, data, noff2)
+ binary.BigEndian.PutUint32(data[noff2:], rr.SOA.Serial)
+ binary.BigEndian.PutUint32(data[noff2+4:], rr.SOA.Refresh)
+ binary.BigEndian.PutUint32(data[noff2+8:], rr.SOA.Retry)
+ binary.BigEndian.PutUint32(data[noff2+12:], rr.SOA.Expire)
+ binary.BigEndian.PutUint32(data[noff2+16:], rr.SOA.Minimum)
+ case DNSTypeMX:
+ binary.BigEndian.PutUint16(data[noff+10:], rr.MX.Preference)
+ encodeName(rr.MX.Name, data, noff+12)
+ case DNSTypeTXT:
+ noff2 := noff + 10
+ for _, txt := range rr.TXTs {
+ data[noff2] = byte(len(txt))
+ copy(data[noff2+1:], txt)
+ noff2 += 1 + len(txt)
+ }
+ case DNSTypeSRV:
+ binary.BigEndian.PutUint16(data[noff+10:], rr.SRV.Priority)
+ binary.BigEndian.PutUint16(data[noff+12:], rr.SRV.Weight)
+ binary.BigEndian.PutUint16(data[noff+14:], rr.SRV.Port)
+ encodeName(rr.SRV.Name, data, noff+16)
+ default:
+ return 0, fmt.Errorf("serializing resource record of type %v not supported", rr.Type)
+ }
+
+ // DataLength
+ dSz := recSize(rr)
+ binary.BigEndian.PutUint16(data[noff+8:], uint16(dSz))
+
+ if opts.FixLengths {
+ rr.DataLength = uint16(dSz)
+ }
+
+ return len(rr.Name) + 1 + 11 + dSz, nil
+}
+
+func (rr *DNSResourceRecord) String() string {
+
+ if rr.Class == DNSClassIN {
+ switch rr.Type {
+ case DNSTypeA, DNSTypeAAAA:
+ return rr.IP.String()
+ case DNSTypeNS:
+ return "NS " + string(rr.NS)
+ case DNSTypeCNAME:
+ return "CNAME " + string(rr.CNAME)
+ case DNSTypePTR:
+ return "PTR " + string(rr.PTR)
+ case DNSTypeTXT:
+ return "TXT " + string(rr.TXT)
+ }
+ }
+
+ return fmt.Sprintf("<%v, %v>", rr.Class, rr.Type)
+}
+
+func decodeCharacterStrings(data []byte) ([][]byte, error) {
+ strings := make([][]byte, 0, 1)
+ end := len(data)
+ for index, index2 := 0, 0; index != end; index = index2 {
+ index2 = index + 1 + int(data[index]) // index increases by 1..256 and does not overflow
+ if index2 > end {
+ return nil, errors.New("Insufficient data for a <character-string>")
+ }
+ strings = append(strings, data[index+1:index2])
+ }
+ return strings, nil
+}
+
+func (rr *DNSResourceRecord) decodeRData(data []byte, offset int, buffer *[]byte) error {
+ switch rr.Type {
+ case DNSTypeA:
+ rr.IP = rr.Data
+ case DNSTypeAAAA:
+ rr.IP = rr.Data
+ case DNSTypeTXT, DNSTypeHINFO:
+ rr.TXT = rr.Data
+ txts, err := decodeCharacterStrings(rr.Data)
+ if err != nil {
+ return err
+ }
+ rr.TXTs = txts
+ case DNSTypeNS:
+ name, _, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.NS = name
+ case DNSTypeCNAME:
+ name, _, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.CNAME = name
+ case DNSTypePTR:
+ name, _, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.PTR = name
+ case DNSTypeSOA:
+ name, endq, err := decodeName(data, offset, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.SOA.MName = name
+ name, endq, err = decodeName(data, endq, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.SOA.RName = name
+ rr.SOA.Serial = binary.BigEndian.Uint32(data[endq : endq+4])
+ rr.SOA.Refresh = binary.BigEndian.Uint32(data[endq+4 : endq+8])
+ rr.SOA.Retry = binary.BigEndian.Uint32(data[endq+8 : endq+12])
+ rr.SOA.Expire = binary.BigEndian.Uint32(data[endq+12 : endq+16])
+ rr.SOA.Minimum = binary.BigEndian.Uint32(data[endq+16 : endq+20])
+ case DNSTypeMX:
+ rr.MX.Preference = binary.BigEndian.Uint16(data[offset : offset+2])
+ name, _, err := decodeName(data, offset+2, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.MX.Name = name
+ case DNSTypeSRV:
+ rr.SRV.Priority = binary.BigEndian.Uint16(data[offset : offset+2])
+ rr.SRV.Weight = binary.BigEndian.Uint16(data[offset+2 : offset+4])
+ rr.SRV.Port = binary.BigEndian.Uint16(data[offset+4 : offset+6])
+ name, _, err := decodeName(data, offset+6, buffer, 1)
+ if err != nil {
+ return err
+ }
+ rr.SRV.Name = name
+ }
+ return nil
+}
+
+// DNSSOA is a Start of Authority record. Each domain requires a SOA record at
+// the cutover where a domain is delegated from its parent.
+type DNSSOA struct {
+ MName, RName []byte
+ Serial, Refresh, Retry, Expire, Minimum uint32
+}
+
+// DNSSRV is a Service record, defining a location (hostname/port) of a
+// server/service.
+type DNSSRV struct {
+ Priority, Weight, Port uint16
+ Name []byte
+}
+
+// DNSMX is a mail exchange record, defining a mail server for a recipient's
+// domain.
+type DNSMX struct {
+ Preference uint16
+ Name []byte
+}