mirror of
https://github.com/miekg/dns.git
synced 2025-08-11 20:16:58 +02:00
RFC3597 - unknown rr are implemented. Currently Go dns needs to now the type code, but must lack the actual implementation of that type. See IPSECKEY as an example.
1021 lines
28 KiB
Go
1021 lines
28 KiB
Go
// 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.
|
|
|
|
// This is not (yet) optimized for speed.
|
|
|
|
// DNS packet assembly, see RFC 1035. Converting from - Unpack() -
|
|
// and to - Pack() - wire format.
|
|
// All the packers and unpackers take a (msg []byte, off int)
|
|
// and return (off1 int, ok bool). If they return ok==false, they
|
|
// also return off1==len(msg), so that the next unpacker will
|
|
// also fail. This lets us avoid checks of ok until the end of a
|
|
// packing sequence.
|
|
|
|
package dns
|
|
|
|
import (
|
|
"os"
|
|
"reflect"
|
|
"net"
|
|
"rand"
|
|
"time"
|
|
"strconv"
|
|
"encoding/base64"
|
|
"encoding/base32"
|
|
"encoding/hex"
|
|
)
|
|
|
|
// The size of the standard buffer.
|
|
const DefaultMsgSize = 4096
|
|
|
|
// A manually-unpacked version of (id, bits).
|
|
// This is in its own struct for easy printing.
|
|
type MsgHdr struct {
|
|
Id uint16
|
|
Response bool
|
|
Opcode int
|
|
Authoritative bool
|
|
Truncated bool
|
|
RecursionDesired bool
|
|
RecursionAvailable bool
|
|
Zero bool
|
|
AuthenticatedData bool
|
|
CheckingDisabled bool
|
|
Rcode int
|
|
}
|
|
|
|
// The layout of a DNS message.
|
|
type Msg struct {
|
|
MsgHdr
|
|
Question []Question
|
|
Answer []RR
|
|
Ns []RR
|
|
Extra []RR
|
|
}
|
|
|
|
// Map of strings for each RR wire type.
|
|
var Rr_str = map[uint16]string{
|
|
TypeCNAME: "CNAME",
|
|
TypeHINFO: "HINFO",
|
|
TypeMB: "MB",
|
|
TypeMG: "MG",
|
|
TypeMINFO: "MINFO",
|
|
TypeMR: "MR",
|
|
TypeMX: "MX",
|
|
TypeNS: "NS",
|
|
TypePTR: "PTR",
|
|
TypeSOA: "SOA",
|
|
TypeTXT: "TXT",
|
|
TypeSRV: "SRV",
|
|
TypeNAPTR: "NAPTR",
|
|
TypeCERT: "CERT",
|
|
TypeDNAME: "DNAME",
|
|
TypeA: "A",
|
|
TypeAAAA: "AAAA",
|
|
TypeLOC: "LOC",
|
|
TypeOPT: "OPT",
|
|
TypeDS: "DS",
|
|
TypeIPSECKEY: "IPSECKEY",
|
|
TypeSSHFP: "SSHFP",
|
|
TypeRRSIG: "RRSIG",
|
|
TypeNSEC: "NSEC",
|
|
TypeDNSKEY: "DNSKEY",
|
|
TypeNSEC3: "NSEC3",
|
|
TypeNSEC3PARAM: "NSEC3PARAM",
|
|
TypeTALINK: "TALINK",
|
|
TypeSPF: "SPF",
|
|
TypeTKEY: "TKEY", // Meta RR
|
|
TypeTSIG: "TSIG", // Meta RR
|
|
TypeAXFR: "AXFR", // Meta RR
|
|
TypeIXFR: "IXFR", // Meta RR
|
|
TypeALL: "ANY", // Meta RR
|
|
TypeTA: "TA",
|
|
TypeDLV: "DLV",
|
|
}
|
|
|
|
// Reverse of Rr_str (needed for string parsing).
|
|
var Str_rr = reverse(Rr_str)
|
|
|
|
// Map of strings for each CLASS wire type.
|
|
var Class_str = map[uint16]string{
|
|
ClassINET: "IN",
|
|
ClassCSNET: "CS",
|
|
ClassCHAOS: "CH",
|
|
ClassHESIOD: "HS",
|
|
ClassANY: "ANY",
|
|
}
|
|
|
|
// Map of strings for opcodes.
|
|
var opcode_str = map[int]string{
|
|
OpcodeQuery: "QUERY",
|
|
OpcodeIQuery: "IQUERY",
|
|
OpcodeStatus: "STATUS",
|
|
OpcodeNotify: "NOTIFY",
|
|
OpcodeUpdate: "UPDATE",
|
|
}
|
|
|
|
// Map of strings for rcode
|
|
var rcode_str = map[int]string{
|
|
RcodeSuccess: "NOERROR",
|
|
RcodeFormatError: "FORMERR",
|
|
RcodeServerFailure: "SERVFAIL",
|
|
RcodeNameError: "NXDOMAIN",
|
|
RcodeNotImplemented: "NOTIMPL",
|
|
RcodeRefused: "REFUSED",
|
|
RcodeYXDomain: "YXDOMAIN", // From RFC 2136
|
|
RcodeYXRrset: "YXRRSET",
|
|
RcodeNXRrset: "NXRRSET",
|
|
RcodeNotAuth: "NOTAUTH",
|
|
RcodeNotZone: "NOTZONE",
|
|
}
|
|
|
|
// Rather than write the usual handful of routines to pack and
|
|
// unpack every message that can appear on the wire, we use
|
|
// reflection to write a generic pack/unpack for structs and then
|
|
// use it. Thus, if in the future we need to define new message
|
|
// structs, no new pack/unpack/printing code needs to be written.
|
|
|
|
// Pack a domain name s into msg[off:].
|
|
// Domain names are a sequence of counted strings
|
|
// split at the dots. They end with a zero-length string.
|
|
func packDomainName(s string, msg []byte, off int) (off1 int, ok bool) {
|
|
// Add trailing dot to canonicalize name.
|
|
if n := len(s); n == 0 || s[n-1] != '.' {
|
|
s += "."
|
|
}
|
|
|
|
// Each dot ends a segment of the name.
|
|
// We trade each dot byte for a length byte.
|
|
// There is also a trailing zero.
|
|
// Check that we have all the space we need.
|
|
tot := len(s) + 1
|
|
if off+tot > len(msg) {
|
|
return len(msg), false
|
|
}
|
|
|
|
// Emit sequence of counted strings, chopping at dots.
|
|
begin := 0
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '.' {
|
|
if i-begin >= 1<<6 { // top two bits of length must be clear
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(i - begin)
|
|
off++
|
|
for j := begin; j < i; j++ {
|
|
msg[off] = s[j]
|
|
off++
|
|
}
|
|
begin = i + 1
|
|
}
|
|
}
|
|
// Root label is special
|
|
if s == "." {
|
|
return off, true
|
|
}
|
|
msg[off] = 0
|
|
off++
|
|
return off, true
|
|
}
|
|
|
|
// Unpack a domain name.
|
|
// In addition to the simple sequences of counted strings above,
|
|
// domain names are allowed to refer to strings elsewhere in the
|
|
// packet, to avoid repeating common suffixes when returning
|
|
// many entries in a single domain. The pointers are marked
|
|
// by a length byte with the top two bits set. Ignoring those
|
|
// two bits, that byte and the next give a 14 bit offset from msg[0]
|
|
// where we should pick up the trail.
|
|
// Note that if we jump elsewhere in the packet,
|
|
// we return off1 == the offset after the first pointer we found,
|
|
// which is where the next record will start.
|
|
// In theory, the pointers are only allowed to jump backward.
|
|
// We let them jump anywhere and stop jumping after a while.
|
|
func unpackDomainName(msg []byte, off int) (s string, off1 int, ok bool) {
|
|
s = ""
|
|
ptr := 0 // number of pointers followed
|
|
Loop:
|
|
for {
|
|
if off >= len(msg) {
|
|
return "", len(msg), false
|
|
}
|
|
c := int(msg[off])
|
|
off++
|
|
switch c & 0xC0 {
|
|
case 0x00:
|
|
if c == 0x00 {
|
|
// end of name
|
|
break Loop
|
|
}
|
|
// literal string
|
|
if off+c > len(msg) {
|
|
return "", len(msg), false
|
|
}
|
|
s += string(msg[off:off+c]) + "."
|
|
off += c
|
|
case 0xC0:
|
|
// pointer to somewhere else in msg.
|
|
// remember location after first ptr,
|
|
// since that's how many bytes we consumed.
|
|
// also, don't follow too many pointers --
|
|
// maybe there's a loop.
|
|
if off >= len(msg) {
|
|
return "", len(msg), false
|
|
}
|
|
c1 := msg[off]
|
|
off++
|
|
if ptr == 0 {
|
|
off1 = off
|
|
}
|
|
if ptr++; ptr > 10 {
|
|
return "", len(msg), false
|
|
}
|
|
off = (c^0xC0)<<8 | int(c1)
|
|
default:
|
|
// 0x80 and 0x40 are reserved
|
|
return "", len(msg), false
|
|
}
|
|
}
|
|
if ptr == 0 {
|
|
off1 = off
|
|
}
|
|
return s, off1, true
|
|
}
|
|
|
|
// Pack a reflect.StructValue into msg. Struct members can only be uint8, uint16, uint32, string,
|
|
// slices and other (often anonymous) structs.
|
|
func packStructValue(val *reflect.StructValue, msg []byte, off int) (off1 int, ok bool) {
|
|
for i := 0; i < val.NumField(); i++ {
|
|
f := val.Type().(*reflect.StructType).Field(i)
|
|
switch fv := val.Field(i).(type) {
|
|
default:
|
|
BadType:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown packing type %v\n", f.Type)
|
|
return len(msg), false
|
|
case *reflect.SliceValue:
|
|
switch f.Tag {
|
|
default:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown packing slice tag %v\n", f.Tag)
|
|
return len(msg), false
|
|
case "OPT": // edns
|
|
for j := 0; j < val.Field(i).(*reflect.SliceValue).Len(); j++ {
|
|
element := val.Field(i).(*reflect.SliceValue).Elem(j)
|
|
code := uint16(element.(*reflect.StructValue).Field(0).(*reflect.UintValue).Get())
|
|
// for each code we should do something else
|
|
h, e := hex.DecodeString(string(element.(*reflect.StructValue).Field(1).(*reflect.StringValue).Get()))
|
|
if e != nil {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure packing OTP")
|
|
return len(msg), false
|
|
}
|
|
data := string(h)
|
|
// Option Code
|
|
msg[off] = byte(code >> 8)
|
|
msg[off+1] = byte(code)
|
|
// Length
|
|
msg[off+2] = byte(len(data) >> 8)
|
|
msg[off+3] = byte(len(data))
|
|
off += 4
|
|
copy(msg[off:off+len(data)], []byte(data))
|
|
off += len(data)
|
|
}
|
|
case "A":
|
|
// It must be a slice of 4, even if it is 16, we encode
|
|
// only the first 4
|
|
if off+net.IPv4len > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing A")
|
|
return len(msg), false
|
|
}
|
|
if fv.Len() == net.IPv6len {
|
|
msg[off] = byte(fv.Elem(12).(*reflect.UintValue).Get())
|
|
msg[off+1] = byte(fv.Elem(13).(*reflect.UintValue).Get())
|
|
msg[off+2] = byte(fv.Elem(14).(*reflect.UintValue).Get())
|
|
msg[off+3] = byte(fv.Elem(15).(*reflect.UintValue).Get())
|
|
} else {
|
|
msg[off] = byte(fv.Elem(0).(*reflect.UintValue).Get())
|
|
msg[off+1] = byte(fv.Elem(1).(*reflect.UintValue).Get())
|
|
msg[off+2] = byte(fv.Elem(2).(*reflect.UintValue).Get())
|
|
msg[off+3] = byte(fv.Elem(3).(*reflect.UintValue).Get())
|
|
}
|
|
off += net.IPv4len
|
|
case "AAAA":
|
|
if fv.Len() > net.IPv6len || off+fv.Len() > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing AAAA")
|
|
return len(msg), false
|
|
}
|
|
for j := 0; j < net.IPv6len; j++ {
|
|
msg[off] = byte(fv.Elem(j).(*reflect.UintValue).Get())
|
|
off++
|
|
}
|
|
case "NSEC": // NSEC/NSEC3
|
|
for j := 0; j < val.Field(i).(*reflect.SliceValue).Len(); j++ {
|
|
var _ = byte(fv.Elem(j).(*reflect.UintValue).Get())
|
|
}
|
|
// handle type bit maps
|
|
}
|
|
case *reflect.StructValue:
|
|
off, ok = packStructValue(fv, msg, off)
|
|
case *reflect.UintValue:
|
|
i := fv.Get()
|
|
switch fv.Type().Kind() {
|
|
default:
|
|
goto BadType
|
|
case reflect.Uint8:
|
|
if off+1 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing uint8")
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(i)
|
|
off++
|
|
case reflect.Uint16:
|
|
if off+2 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing uint16")
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(i >> 8)
|
|
msg[off+1] = byte(i)
|
|
off += 2
|
|
case reflect.Uint32:
|
|
if off+4 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing uint32")
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(i >> 24)
|
|
msg[off+1] = byte(i >> 16)
|
|
msg[off+2] = byte(i >> 8)
|
|
msg[off+3] = byte(i)
|
|
off += 4
|
|
case reflect.Uint64:
|
|
// Only used in TSIG, where it stops at 48 bits, so we discard the upper 16
|
|
if off+6 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing uint64")
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(i >> 40)
|
|
msg[off+1] = byte(i >> 32)
|
|
msg[off+2] = byte(i >> 24)
|
|
msg[off+3] = byte(i >> 16)
|
|
msg[off+4] = byte(i >> 8)
|
|
msg[off+5] = byte(i)
|
|
off += 6
|
|
}
|
|
case *reflect.StringValue:
|
|
// There are multiple string encodings.
|
|
// The tag distinguishes ordinary strings from domain names.
|
|
s := fv.Get()
|
|
switch f.Tag {
|
|
default:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown packing string tag %v", f.Tag)
|
|
return len(msg), false
|
|
case "base32":
|
|
b32, err := packBase32([]byte(s))
|
|
if err != nil {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing base32")
|
|
return len(msg), false
|
|
}
|
|
copy(msg[off:off+len(b32)], b32)
|
|
off += len(b32)
|
|
case "base64":
|
|
b64, err := packBase64([]byte(s))
|
|
if err != nil {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing base64")
|
|
return len(msg), false
|
|
}
|
|
copy(msg[off:off+len(b64)], b64)
|
|
off += len(b64)
|
|
/*
|
|
b64len := base64.StdEncoding.DecodedLen(len(s))
|
|
_, err := base64.StdEncoding.Decode(msg[off:off+b64len], []byte(s))
|
|
if err != nil {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing base64")
|
|
return len(msg), false
|
|
}
|
|
off += b64len
|
|
*/
|
|
case "domain-name":
|
|
off, ok = packDomainName(s, msg, off)
|
|
if !ok {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing domain-name")
|
|
return len(msg), false
|
|
}
|
|
case "size-hex":
|
|
case "hex":
|
|
// There is no length encoded here, for DS at least
|
|
h, e := hex.DecodeString(s)
|
|
if e != nil {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing domain-name")
|
|
return len(msg), false
|
|
}
|
|
copy(msg[off:off+hex.DecodedLen(len(s))], h)
|
|
off += hex.DecodedLen(len(s))
|
|
case "size":
|
|
// the size is already encoded in the RR, we can safely use the
|
|
// length of string. String is RAW (not encoded in hex, nor base64)
|
|
copy(msg[off:off+len(s)], s)
|
|
off += len(s)
|
|
case "":
|
|
// Counted string: 1 byte length.
|
|
if len(s) > 255 || off+1+len(s) > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow packing string")
|
|
return len(msg), false
|
|
}
|
|
msg[off] = byte(len(s))
|
|
off++
|
|
for i := 0; i < len(s); i++ {
|
|
msg[off+i] = s[i]
|
|
}
|
|
off += len(s)
|
|
}
|
|
}
|
|
}
|
|
return off, true
|
|
}
|
|
|
|
func structValue(any interface{}) *reflect.StructValue {
|
|
return reflect.NewValue(any).(*reflect.PtrValue).Elem().(*reflect.StructValue)
|
|
}
|
|
|
|
func packStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) {
|
|
off, ok = packStructValue(structValue(any), msg, off)
|
|
return off, ok
|
|
}
|
|
|
|
// Unpack a reflect.StructValue from msg.
|
|
// Same restrictions as packStructValue.
|
|
func unpackStructValue(val *reflect.StructValue, msg []byte, off int) (off1 int, ok bool) {
|
|
for i := 0; i < val.NumField(); i++ {
|
|
f := val.Type().(*reflect.StructType).Field(i)
|
|
switch fv := val.Field(i).(type) {
|
|
default:
|
|
BadType:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown unpacking type %v", f.Type)
|
|
return len(msg), false
|
|
case *reflect.SliceValue:
|
|
switch f.Tag {
|
|
default:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown unpacking slice tag %v", f.Tag)
|
|
return len(msg), false
|
|
case "A":
|
|
if off+net.IPv4len > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking A")
|
|
return len(msg), false
|
|
}
|
|
b := net.IPv4(msg[off], msg[off+1], msg[off+2], msg[off+3])
|
|
fv.Set(reflect.NewValue(b).(*reflect.SliceValue))
|
|
off += net.IPv4len
|
|
case "AAAA":
|
|
if off+net.IPv6len > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking AAAA")
|
|
return len(msg), false
|
|
}
|
|
p := make(net.IP, net.IPv6len)
|
|
copy(p, msg[off:off+net.IPv6len])
|
|
b := net.IP(p)
|
|
fv.Set(reflect.NewValue(b).(*reflect.SliceValue))
|
|
off += net.IPv6len
|
|
case "OPT": // EDNS
|
|
if off+2 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking OPT")
|
|
// No room for anything else
|
|
break
|
|
}
|
|
opt := make([]Option, 1)
|
|
opt[0].Code, off = unpackUint16(msg, off)
|
|
optlen, off1 := unpackUint16(msg, off)
|
|
if off1+int(optlen) > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking OPT")
|
|
return len(msg), false
|
|
}
|
|
opt[0].Data = hex.EncodeToString(msg[off1 : off1+int(optlen)])
|
|
fv.Set(reflect.NewValue(opt).(*reflect.SliceValue))
|
|
off = off1 + int(optlen)
|
|
case "NSEC": // NSEC/NSEC3
|
|
if off+1 > len(msg) {
|
|
// there is none
|
|
break
|
|
}
|
|
// Fix multple windows TODO(mg)
|
|
nsec := make([]uint16, 256) // use append TODO(mg)
|
|
ni := 0
|
|
window := int(msg[off])
|
|
blocks := int(msg[off+1])
|
|
if off+blocks > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking NSEC")
|
|
return len(msg), false
|
|
}
|
|
off += 2
|
|
for j := 0; j < blocks; j++ {
|
|
b := msg[off+j]
|
|
// Check the bits one by one, and set the type
|
|
if b&0x80 == 0x80 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 0)
|
|
ni++
|
|
}
|
|
if b&0x40 == 0x40 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 1)
|
|
ni++
|
|
}
|
|
if b&0x20 == 0x20 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 2)
|
|
ni++
|
|
}
|
|
if b&0x10 == 0x10 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 3)
|
|
ni++
|
|
}
|
|
if b&0x8 == 0x8 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 4)
|
|
ni++
|
|
}
|
|
if b&0x4 == 0x4 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 5)
|
|
ni++
|
|
}
|
|
if b&0x2 == 0x2 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 6)
|
|
ni++
|
|
}
|
|
if b&0x1 == 0x1 {
|
|
nsec[ni] = uint16(window*256 + j*8 + 7)
|
|
ni++
|
|
}
|
|
}
|
|
nsec = nsec[:ni]
|
|
fv.Set(reflect.NewValue(nsec).(*reflect.SliceValue))
|
|
off += blocks
|
|
}
|
|
case *reflect.StructValue:
|
|
off, ok = unpackStructValue(fv, msg, off)
|
|
case *reflect.UintValue:
|
|
switch fv.Type().Kind() {
|
|
default:
|
|
goto BadType
|
|
case reflect.Uint8:
|
|
if off+1 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking uint8")
|
|
return len(msg), false
|
|
}
|
|
i := uint8(msg[off])
|
|
fv.Set(uint64(i))
|
|
off++
|
|
case reflect.Uint16:
|
|
var i uint16
|
|
if off+2 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking uint16")
|
|
return len(msg), false
|
|
}
|
|
i, off = unpackUint16(msg, off)
|
|
fv.Set(uint64(i))
|
|
case reflect.Uint32:
|
|
if off+4 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking uint32")
|
|
return len(msg), false
|
|
}
|
|
i := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3])
|
|
fv.Set(uint64(i))
|
|
off += 4
|
|
case reflect.Uint64:
|
|
// This is *only* used in TSIG where the last 48 bits are occupied
|
|
// So for now, assume a uint48 (6 bytes)
|
|
if off+6 > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: overflow unpacking uint64")
|
|
return len(msg), false
|
|
}
|
|
i := uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
|
|
uint64(msg[off+4])<<8 | uint64(msg[off+5])
|
|
fv.Set(uint64(i))
|
|
off += 6
|
|
}
|
|
case *reflect.StringValue:
|
|
var s string
|
|
switch f.Tag {
|
|
default:
|
|
//fmt.Fprintf(os.Stderr, "dns: unknown unpacking string tag %v", f.Tag)
|
|
return len(msg), false
|
|
case "hex":
|
|
// Rest of the RR is hex encoded, network order an issue here?
|
|
rdlength := int(val.FieldByName("Hdr").(*reflect.StructValue).FieldByName("Rdlength").(*reflect.UintValue).Get())
|
|
var consumed int
|
|
switch val.Type().Name() {
|
|
case "RR_DS":
|
|
consumed = 4 // KeyTag(2) + Algorithm(1) + DigestType(1)
|
|
case "RR_SSHFP":
|
|
consumed = 2 // Algorithm(1) + Type(1)
|
|
case "RR_NSEC3PARAM":
|
|
consumed = 5 // Hash(1) + Flags(1) + Iterations(2) + SaltLength(1)
|
|
case "RR_RFC3597":
|
|
fallthrough; // Rest is the unknown data
|
|
default:
|
|
consumed = 0 // return len(msg), false?
|
|
}
|
|
s = hex.EncodeToString(msg[off : off+rdlength-consumed])
|
|
off += rdlength - consumed
|
|
case "base64":
|
|
// Rest of the RR is base64 encoded value
|
|
rdlength := int(val.FieldByName("Hdr").(*reflect.StructValue).FieldByName("Rdlength").(*reflect.UintValue).Get())
|
|
// Need to know how much of rdlength is already consumed, in this packet
|
|
var consumed int
|
|
switch val.Type().Name() {
|
|
case "RR_DNSKEY":
|
|
consumed = 4 // Flags(2) + Protocol(1) + Algorithm(1)
|
|
case "RR_RRSIG":
|
|
consumed = 18 // TypeCovered(2) + Algorithm(1) + Labels(1) +
|
|
// OrigTTL(4) + SigExpir(4) + SigIncep(4) + KeyTag(2) + len(signername)
|
|
// Should already be set in the sequence of parsing (comes before)
|
|
// Work because of rfc4034, section 3.17
|
|
consumed += len(val.FieldByName("SignerName").(*reflect.StringValue).Get()) + 1
|
|
default:
|
|
consumed = 0 // TODO
|
|
}
|
|
s = unpackBase64(msg[off : off+rdlength-consumed])
|
|
off += rdlength - consumed
|
|
case "domain-name":
|
|
s, off, ok = unpackDomainName(msg, off)
|
|
if !ok {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure unpacking domain-name")
|
|
return len(msg), false
|
|
}
|
|
case "size-base32":
|
|
var size int
|
|
switch val.Type().Name() {
|
|
case "RR_NSEC3":
|
|
switch f.Name {
|
|
case "NextDomain":
|
|
name := val.FieldByName("HashLength")
|
|
size = int(name.(*reflect.UintValue).Get())
|
|
}
|
|
}
|
|
if off+size > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure unpacking size-base32 string")
|
|
return len(msg), false
|
|
}
|
|
s = unpackBase32(msg[off : off+size])
|
|
off += size
|
|
case "size-hex":
|
|
// a "size" string, but a it must be encoded in hex in the string
|
|
var size int
|
|
switch val.Type().Name() {
|
|
case "RR_NSEC3":
|
|
switch f.Name {
|
|
case "Salt":
|
|
name := val.FieldByName("SaltLength")
|
|
size = int(name.(*reflect.UintValue).Get())
|
|
case "NextDomain":
|
|
name := val.FieldByName("HashLength")
|
|
size = int(name.(*reflect.UintValue).Get())
|
|
}
|
|
}
|
|
if off+size > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure unpacking hex-size string")
|
|
return len(msg), false
|
|
}
|
|
s = hex.EncodeToString(msg[off : off+size])
|
|
off += size
|
|
case "size":
|
|
// We should already know how many bytes we can expect
|
|
// TODO(mg) pack variant. Note that looks a bit like the EDNS0
|
|
// Option parsing, maybe it should be merged.
|
|
var size int
|
|
switch val.Type().Name() {
|
|
case "RR_TSIG":
|
|
switch f.Name {
|
|
case "MAC":
|
|
name := val.FieldByName("MACSize")
|
|
size = int(name.(*reflect.UintValue).Get())
|
|
case "OtherData":
|
|
name := val.FieldByName("OtherLen")
|
|
size = int(name.(*reflect.UintValue).Get())
|
|
}
|
|
}
|
|
if off+size > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure unpacking size string")
|
|
return len(msg), false
|
|
}
|
|
s = string(msg[off : off+size])
|
|
off += size
|
|
case "":
|
|
if off >= len(msg) || off+1+int(msg[off]) > len(msg) {
|
|
//fmt.Fprintf(os.Stderr, "dns: failure unpacking string")
|
|
return len(msg), false
|
|
}
|
|
n := int(msg[off])
|
|
off++
|
|
b := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
b[i] = msg[off+i]
|
|
}
|
|
off += n
|
|
s = string(b)
|
|
}
|
|
fv.Set(s)
|
|
}
|
|
}
|
|
return off, true
|
|
}
|
|
|
|
// Helper function for unpacking
|
|
func unpackUint16(msg []byte, off int) (v uint16, off1 int) {
|
|
v = uint16(msg[off])<<8 | uint16(msg[off+1])
|
|
off1 = off + 2
|
|
return
|
|
}
|
|
|
|
func unpackStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) {
|
|
off, ok = unpackStructValue(structValue(any), msg, off)
|
|
return off, ok
|
|
}
|
|
|
|
func unpackBase32(b []byte) string {
|
|
b32 := make([]byte, base32.HexEncoding.EncodedLen(len(b)))
|
|
base32.HexEncoding.Encode(b32, b)
|
|
return string(b32)
|
|
}
|
|
|
|
func unpackBase64(b []byte) string {
|
|
b64 := make([]byte, base64.StdEncoding.EncodedLen(len(b)))
|
|
base64.StdEncoding.Encode(b64, b)
|
|
return string(b64)
|
|
}
|
|
|
|
// Helper function for packing, mostly used in dnssec.go
|
|
func packBase64(s []byte) ([]byte, os.Error) {
|
|
b64len := base64.StdEncoding.DecodedLen(len(s))
|
|
buf := make([]byte, b64len)
|
|
n, err := base64.StdEncoding.Decode(buf, []byte(s))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buf = buf[:n]
|
|
return buf, nil
|
|
}
|
|
|
|
// Helper function for packing, mostly used in dnssec.go
|
|
func packBase32(s []byte) ([]byte, os.Error) {
|
|
b32len := base32.HexEncoding.DecodedLen(len(s))
|
|
buf := make([]byte, b32len)
|
|
n, err := base32.HexEncoding.Decode(buf, []byte(s))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buf = buf[:n]
|
|
return buf, nil
|
|
}
|
|
|
|
// Resource record packer.
|
|
func packRR(rr RR, msg []byte, off int) (off2 int, ok bool) {
|
|
if rr == nil {
|
|
return len(msg), false
|
|
}
|
|
|
|
var off1 int
|
|
// pack twice, once to find end of header
|
|
// and again to find end of packet.
|
|
// a bit inefficient but this doesn't need to be fast.
|
|
// off1 is end of header
|
|
// off2 is end of rr
|
|
off1, ok = packStruct(rr.Header(), msg, off)
|
|
off2, ok = packStruct(rr, msg, off)
|
|
if !ok {
|
|
return len(msg), false
|
|
}
|
|
|
|
// TODO make this quicker?
|
|
// pack a third time; redo header with correct data length
|
|
rr.Header().Rdlength = uint16(off2 - off1)
|
|
packStruct(rr.Header(), msg, off)
|
|
return off2, true
|
|
}
|
|
|
|
// Resource record unpacker.
|
|
func unpackRR(msg []byte, off int) (rr RR, off1 int, ok bool) {
|
|
// unpack just the header, to find the rr type and length
|
|
var h RR_Header
|
|
off0 := off
|
|
if off, ok = unpackStruct(&h, msg, off); !ok {
|
|
return nil, len(msg), false
|
|
}
|
|
end := off + int(h.Rdlength)
|
|
|
|
// make an rr of that type and re-unpack.
|
|
// again inefficient but doesn't need to be fast.
|
|
mk, known := rr_mk[int(h.Rrtype)]
|
|
if !known {
|
|
rr = new(RR_RFC3597)
|
|
} else {
|
|
rr = mk()
|
|
}
|
|
off, ok = unpackStruct(rr, msg, off0)
|
|
if off != end {
|
|
return &h, end, true
|
|
}
|
|
return rr, off, ok
|
|
}
|
|
|
|
// Reverse a map
|
|
func reverse(m map[uint16]string) map[string]uint16 {
|
|
n := make(map[string]uint16)
|
|
for u, s := range m {
|
|
n[s] = u
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Convert a MsgHdr to a string, mimic the way Dig displays headers:
|
|
//;; opcode: QUERY, status: NOERROR, id: 48404
|
|
//;; flags: qr aa rd ra;
|
|
func (h *MsgHdr) String() string {
|
|
if h == nil {
|
|
return "<nil> MsgHdr"
|
|
}
|
|
|
|
s := ";; opcode: " + opcode_str[h.Opcode]
|
|
s += ", status: " + rcode_str[h.Rcode]
|
|
s += ", id: " + strconv.Itoa(int(h.Id)) + "\n"
|
|
|
|
s += ";; flags:"
|
|
if h.Response {
|
|
s += " qr"
|
|
}
|
|
if h.Authoritative {
|
|
s += " aa"
|
|
}
|
|
if h.Truncated {
|
|
s += " tc"
|
|
}
|
|
if h.RecursionDesired {
|
|
s += " rd"
|
|
}
|
|
if h.RecursionAvailable {
|
|
s += " ra"
|
|
}
|
|
if h.Zero { // Hmm
|
|
s += " z"
|
|
}
|
|
if h.AuthenticatedData {
|
|
s += " ad"
|
|
}
|
|
if h.CheckingDisabled {
|
|
s += " cd"
|
|
}
|
|
|
|
s += ";"
|
|
return s
|
|
}
|
|
|
|
// Pack a msg: convert it to wire format.
|
|
func (dns *Msg) Pack() (msg []byte, ok bool) {
|
|
var dh Header
|
|
|
|
// Convert convenient Msg into wire-like Header.
|
|
dh.Id = dns.Id
|
|
dh.Bits = uint16(dns.Opcode)<<11 | uint16(dns.Rcode)
|
|
if dns.Response {
|
|
dh.Bits |= _QR
|
|
}
|
|
if dns.Authoritative {
|
|
dh.Bits |= _AA
|
|
}
|
|
if dns.Truncated {
|
|
dh.Bits |= _TC
|
|
}
|
|
if dns.RecursionDesired {
|
|
dh.Bits |= _RD
|
|
}
|
|
if dns.RecursionAvailable {
|
|
dh.Bits |= _RA
|
|
}
|
|
if dns.Zero {
|
|
dh.Bits |= _Z
|
|
}
|
|
if dns.AuthenticatedData {
|
|
dh.Bits |= _AD
|
|
}
|
|
if dns.CheckingDisabled {
|
|
dh.Bits |= _CD
|
|
}
|
|
|
|
// Prepare variable sized arrays.
|
|
question := dns.Question
|
|
answer := dns.Answer
|
|
ns := dns.Ns
|
|
extra := dns.Extra
|
|
|
|
dh.Qdcount = uint16(len(question))
|
|
dh.Ancount = uint16(len(answer))
|
|
dh.Nscount = uint16(len(ns))
|
|
dh.Arcount = uint16(len(extra))
|
|
|
|
// Could work harder to calculate message size,
|
|
// but this is far more than we need and not
|
|
// big enough to hurt the allocator.
|
|
msg = make([]byte, DefaultMsgSize) // TODO, calculate REAL size
|
|
|
|
// Pack it in: header and then the pieces.
|
|
off := 0
|
|
off, ok = packStruct(&dh, msg, off)
|
|
for i := 0; i < len(question); i++ {
|
|
off, ok = packStruct(&question[i], msg, off)
|
|
}
|
|
for i := 0; i < len(answer); i++ {
|
|
off, ok = packRR(answer[i], msg, off)
|
|
}
|
|
for i := 0; i < len(ns); i++ {
|
|
off, ok = packRR(ns[i], msg, off)
|
|
}
|
|
for i := 0; i < len(extra); i++ {
|
|
off, ok = packRR(extra[i], msg, off)
|
|
}
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return msg[:off], true
|
|
}
|
|
|
|
// Unpack a binary message to a Msg structure.
|
|
func (dns *Msg) Unpack(msg []byte) bool {
|
|
// Header.
|
|
var dh Header
|
|
off := 0
|
|
var ok bool
|
|
if off, ok = unpackStruct(&dh, msg, off); !ok {
|
|
return false
|
|
}
|
|
dns.Id = dh.Id
|
|
dns.Response = (dh.Bits & _QR) != 0
|
|
dns.Opcode = int(dh.Bits>>11) & 0xF
|
|
dns.Authoritative = (dh.Bits & _AA) != 0
|
|
dns.Truncated = (dh.Bits & _TC) != 0
|
|
dns.RecursionDesired = (dh.Bits & _RD) != 0
|
|
dns.RecursionAvailable = (dh.Bits & _RA) != 0
|
|
dns.Rcode = int(dh.Bits & 0xF)
|
|
|
|
// Arrays.
|
|
dns.Question = make([]Question, dh.Qdcount)
|
|
dns.Answer = make([]RR, dh.Ancount)
|
|
dns.Ns = make([]RR, dh.Nscount)
|
|
dns.Extra = make([]RR, dh.Arcount)
|
|
|
|
for i := 0; i < len(dns.Question); i++ {
|
|
off, ok = unpackStruct(&dns.Question[i], msg, off)
|
|
}
|
|
for i := 0; i < len(dns.Answer); i++ {
|
|
dns.Answer[i], off, ok = unpackRR(msg, off)
|
|
}
|
|
for i := 0; i < len(dns.Ns); i++ {
|
|
dns.Ns[i], off, ok = unpackRR(msg, off)
|
|
}
|
|
for i := 0; i < len(dns.Extra); i++ {
|
|
dns.Extra[i], off, ok = unpackRR(msg, off)
|
|
}
|
|
if !ok {
|
|
return false
|
|
}
|
|
if off != len(msg) {
|
|
// TODO(mg) remove eventually
|
|
println("extra bytes in dns packet", off, "<", len(msg))
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Convert a complete message to a string with dig-like output.
|
|
func (dns *Msg) String() string {
|
|
if dns == nil {
|
|
return "<nil> MsgHdr"
|
|
}
|
|
s := dns.MsgHdr.String() + " "
|
|
s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
|
|
s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
|
|
s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
|
|
s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
|
|
if len(dns.Question) > 0 {
|
|
s += "\n;; QUESTION SECTION:\n"
|
|
for i := 0; i < len(dns.Question); i++ {
|
|
s += dns.Question[i].String() + "\n"
|
|
}
|
|
}
|
|
if len(dns.Answer) > 0 {
|
|
s += "\n;; ANSWER SECTION:\n"
|
|
for i := 0; i < len(dns.Answer); i++ {
|
|
s += dns.Answer[i].String() + "\n"
|
|
}
|
|
}
|
|
if len(dns.Ns) > 0 {
|
|
s += "\n;; AUTHORITY SECTION:\n"
|
|
for i := 0; i < len(dns.Ns); i++ {
|
|
s += dns.Ns[i].String() + "\n"
|
|
}
|
|
}
|
|
if len(dns.Extra) > 0 {
|
|
s += "\n;; ADDITIONAL SECTION:\n"
|
|
for i := 0; i < len(dns.Extra); i++ {
|
|
s += dns.Extra[i].String() + "\n"
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Set an Msg Id to a random value.
|
|
func (m *Msg) SetId() {
|
|
m.Id = uint16(rand.Int()) ^ uint16(time.Nanoseconds())
|
|
}
|