Skip to content

Commit

Permalink
Merge pull request google#198 from tzneal/master
Browse files Browse the repository at this point in the history
Add serialization support for the DNS layer
  • Loading branch information
gconnell committed May 26, 2016
2 parents b09bf40 + d8f5a1d commit dc8da3f
Show file tree
Hide file tree
Showing 5 changed files with 512 additions and 68 deletions.
68 changes: 3 additions & 65 deletions layers/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
"bytes"
"encoding/hex"
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/bytediff"
"net"
"reflect"
"strings"
"testing"

"github.com/google/gopacket"
"github.com/google/gopacket/bytediff"
)

var testSimpleTCPPacket []byte = []byte{
Expand Down Expand Up @@ -1207,66 +1208,3 @@ func TestPacketIPv4Fragmented(t *testing.T) {
checkLayers(p, []gopacket.LayerType{LayerTypeEthernet, LayerTypeIPv4, gopacket.LayerTypeFragment}, t)
testSerializationWithOpts(t, p, testPacketIPv4Fragmented, gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true})
}

// testPacketDNSRegression is the packet:
// 11:08:05.708342 IP 109.194.160.4.57766 > 95.211.92.14.53: 63000% [1au] A? picslife.ru. (40)
// 0x0000: 0022 19b6 7e22 000f 35bb 0b40 0800 4500 ."..~"[email protected].
// 0x0010: 0044 89c4 0000 3811 2f3d 6dc2 a004 5fd3 .D....8./=m..._.
// 0x0020: 5c0e e1a6 0035 0030 a597 f618 0010 0001 \....5.0........
// 0x0030: 0000 0000 0001 0870 6963 736c 6966 6502 .......picslife.
// 0x0040: 7275 0000 0100 0100 0029 1000 0000 8000 ru.......)......
// 0x0050: 0000 ..
var testPacketDNSRegression = []byte{
0x00, 0x22, 0x19, 0xb6, 0x7e, 0x22, 0x00, 0x0f, 0x35, 0xbb, 0x0b, 0x40, 0x08, 0x00, 0x45, 0x00,
0x00, 0x44, 0x89, 0xc4, 0x00, 0x00, 0x38, 0x11, 0x2f, 0x3d, 0x6d, 0xc2, 0xa0, 0x04, 0x5f, 0xd3,
0x5c, 0x0e, 0xe1, 0xa6, 0x00, 0x35, 0x00, 0x30, 0xa5, 0x97, 0xf6, 0x18, 0x00, 0x10, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x70, 0x69, 0x63, 0x73, 0x6c, 0x69, 0x66, 0x65, 0x02,
0x72, 0x75, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00,
}

func TestPacketDNSRegression(t *testing.T) {
p := gopacket.NewPacket(testPacketDNSRegression, LinkTypeEthernet, testDecodeOptions)
if p.ErrorLayer() != nil {
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
}
checkLayers(p, []gopacket.LayerType{LayerTypeEthernet, LayerTypeIPv4, LayerTypeUDP, LayerTypeDNS}, t)
}
func BenchmarkDecodePacketDNSRegression(b *testing.B) {
for i := 0; i < b.N; i++ {
gopacket.NewPacket(testPacketDNSRegression, LinkTypeEthernet, gopacket.NoCopy)
}
}

// response to `dig TXT google.com` over IPv4 link:
var testParseDNSTypeTXTValue = `v=spf1 include:_spf.google.com ~all`
var testParseDNSTypeTXT = []byte{
0x02, 0x00, 0x00, 0x00, // PF_INET
0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x39, 0x11, 0x64, 0x98, 0xd0, 0x43, 0xde, 0xde,
0x0a, 0xba, 0x23, 0x06, 0x00, 0x35, 0x81, 0xb2, 0x00, 0x5f, 0xdc, 0xb5, 0x98, 0x71, 0x81, 0x80,
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x10, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
0x0e, 0x10, 0x00, 0x24, 0x23, 0x76, 0x3d, 0x73, 0x70, 0x66, 0x31, 0x20, 0x69, 0x6e, 0x63, 0x6c,
0x75, 0x64, 0x65, 0x3a, 0x5f, 0x73, 0x70, 0x66, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x63, 0x6f, 0x6d, 0x20, 0x7e, 0x61, 0x6c, 0x6c, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
}

func TestParseDNSTypeTXT(t *testing.T) {
p := gopacket.NewPacket(testParseDNSTypeTXT, LinkTypeNull, testDecodeOptions)
if p.ErrorLayer() != nil {
t.Error("Failed to decode packet:", p.ErrorLayer().Error())
}
checkLayers(p, []gopacket.LayerType{LayerTypeLoopback, LayerTypeIPv4, LayerTypeUDP, LayerTypeDNS}, t)
answers := p.Layer(LayerTypeDNS).(*DNS).Answers
if len(answers) != 1 {
t.Error("Failed to parse 1 DNS answer")
}
if len(answers[0].TXTs) != 1 {
t.Error("Failed to parse 1 TXT record")
}
txt := string(answers[0].TXTs[0])
if txt != testParseDNSTypeTXTValue {
t.Errorf("Incorrect TXT value, expected %q, got %q", testParseDNSTypeTXTValue, txt)
}
}
154 changes: 153 additions & 1 deletion layers/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/google/gopacket"
"net"

"github.com/google/gopacket"
)

type DNSClass uint16
Expand Down Expand Up @@ -50,6 +51,7 @@ const (
type DNSResponseCode uint8

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]
Expand All @@ -74,6 +76,8 @@ func (drc DNSResponseCode) String() string {
switch drc {
default:
return "Unknown"
case DNSResponseCodeNoErr:
return "No Error"
case DNSResponseCodeFormErr:
return "Format Error"
case DNSResponseCodeServFail:
Expand Down Expand Up @@ -298,6 +302,97 @@ func (d *DNS) Payload() []byte {
return nil
}

func b2i(b bool) int {
if b {
return 1
}
return 0
}

func computeSize(recs []DNSResourceRecord) int {
sz := 0
for _, rr := range recs {
sz += len(rr.Name) + 14
switch rr.Type {
case DNSTypeA:
sz += 4
case DNSTypeAAAA:
sz += 16
case DNSTypeCNAME:
sz += len(rr.CNAME) + 1
}
}
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)
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.Answers[i]
n, err := qa.encode(bytes, off, opts)
if err != nil {
return err
}
off += n
}
for i := range d.Additionals {
qa := &d.Answers[i]
n, err := qa.encode(bytes, off, opts)
if err != nil {
return err
}
off += n
}

return nil
}

var maxRecursion = errors.New("max DNS recursion level hit")

const maxRecursionLevel = 255
Expand Down Expand Up @@ -396,6 +491,13 @@ func (q *DNSQuestion) decode(data []byte, offset int, df gopacket.DecodeFeedback
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
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Expand Down Expand Up @@ -461,6 +563,56 @@ func (rr *DNSResourceRecord) decode(data []byte, offset int, df gopacket.DecodeF
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))

var dSz int
switch rr.Type {
case DNSTypeA:
dSz = 4
copy(data[noff+10:], rr.IP)
case DNSTypeAAAA:
dSz = 16
copy(data[noff+10:], rr.IP)
case DNSTypeCNAME:
dSz = len(rr.CNAME) + 1
encodeName(rr.CNAME, data, noff+10)
default:
return 0, fmt.Errorf("serializing resource record of type %v not supported", rr.Type)
}
// DataLength
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) && ((rr.Type == DNSTypeA) || (rr.Type == DNSTypeAAAA)) {
return net.IP(rr.Data).String()
Expand Down
Loading

0 comments on commit dc8da3f

Please sign in to comment.