diff options
Diffstat (limited to 'src/pkg/net/dnsmsg.go')
| -rw-r--r-- | src/pkg/net/dnsmsg.go | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/src/pkg/net/dnsmsg.go b/src/pkg/net/dnsmsg.go new file mode 100644 index 000000000..d7a467fc6 --- /dev/null +++ b/src/pkg/net/dnsmsg.go @@ -0,0 +1,679 @@ +// 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. + +// DNS packet assembly. +// +// This is intended to support name resolution during net.Dial. +// It doesn't have to be blazing fast. +// +// 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. +// +// The first half of this file defines the DNS message formats. +// The second half implements the conversion to and from wire format. +// A few of the structure elements have string tags to aid the +// generic pack/unpack routines. +// +// TODO(rsc) There are enough names defined in this file that they're all +// prefixed with _DNS_. Perhaps put this in its own package later. + +package net + +import ( + "fmt"; + "os"; + "reflect"; +) + +// Packet formats + +// Wire constants. +const ( + // valid _DNS_RR_Header.rrtype and _DNS_Question.qtype + _DNS_TypeA = 1; + _DNS_TypeNS = 2; + _DNS_TypeMD = 3; + _DNS_TypeMF = 4; + _DNS_TypeCNAME = 5; + _DNS_TypeSOA = 6; + _DNS_TypeMB = 7; + _DNS_TypeMG = 8; + _DNS_TypeMR = 9; + _DNS_TypeNULL = 10; + _DNS_TypeWKS = 11; + _DNS_TypePTR = 12; + _DNS_TypeHINFO = 13; + _DNS_TypeMINFO = 14; + _DNS_TypeMX = 15; + _DNS_TypeTXT = 16; + + // valid _DNS_Question.qtype only + _DNS_TypeAXFR = 252; + _DNS_TypeMAILB = 253; + _DNS_TypeMAILA = 254; + _DNS_TypeALL = 255; + + // valid _DNS_Question.qclass + _DNS_ClassINET = 1; + _DNS_ClassCSNET = 2; + _DNS_ClassCHAOS = 3; + _DNS_ClassHESIOD = 4; + _DNS_ClassANY = 255; + + // _DNS_Msg.rcode + _DNS_RcodeSuccess = 0; + _DNS_RcodeFormatError = 1; + _DNS_RcodeServerFailure = 2; + _DNS_RcodeNameError = 3; + _DNS_RcodeNotImplemented = 4; + _DNS_RcodeRefused = 5; +) + +// The wire format for the DNS packet header. +type __DNS_Header struct { + id uint16; + bits uint16; + qdcount, ancount, nscount, arcount uint16; +} + +const ( + // __DNS_Header.bits + _QR = 1<<15; // query/response (response=1) + _AA = 1<<10; // authoritative + _TC = 1<<9; // truncated + _RD = 1<<8; // recursion desired + _RA = 1<<7; // recursion available +) + +// DNS queries. +type _DNS_Question struct { + name string "domain-name"; // "domain-name" specifies encoding; see packers below + qtype uint16; + qclass uint16; +} + +// DNS responses (resource records). +// There are many types of messages, +// but they all share the same header. +type _DNS_RR_Header struct { + name string "domain-name"; + rrtype uint16; + class uint16; + ttl uint32; + rdlength uint16; // length of data after header +} + +func (h *_DNS_RR_Header) Header() *_DNS_RR_Header { + return h +} + +type _DNS_RR interface { + Header() *_DNS_RR_Header +} + + +// Specific DNS RR formats for each query type. + +type _DNS_RR_CNAME struct { + _DNS_RR_Header; + cname string "domain-name"; +} + +type _DNS_RR_HINFO struct { + _DNS_RR_Header; + cpu string; + os string; +} + +type _DNS_RR_MB struct { + _DNS_RR_Header; + mb string "domain-name"; +} + +type _DNS_RR_MG struct { + _DNS_RR_Header; + mg string "domain-name"; +} + +type _DNS_RR_MINFO struct { + _DNS_RR_Header; + rmail string "domain-name"; + email string "domain-name"; +} + +type _DNS_RR_MR struct { + _DNS_RR_Header; + mr string "domain-name"; +} + +type _DNS_RR_MX struct { + _DNS_RR_Header; + pref uint16; + mx string "domain-name"; +} + +type _DNS_RR_NS struct { + _DNS_RR_Header; + ns string "domain-name"; +} + +type _DNS_RR_PTR struct { + _DNS_RR_Header; + ptr string "domain-name"; +} + +type _DNS_RR_SOA struct { + _DNS_RR_Header; + ns string "domain-name"; + mbox string "domain-name"; + serial uint32; + refresh uint32; + retry uint32; + expire uint32; + minttl uint32; +} + +type _DNS_RR_TXT struct { + _DNS_RR_Header; + txt string; // not domain name +} + +type _DNS_RR_A struct { + _DNS_RR_Header; + a uint32 "ipv4"; +} + + +// Packing and unpacking. +// +// 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. + +// Map of constructors for each RR wire type. +var rr_mk = map[int] func()_DNS_RR { + _DNS_TypeCNAME: func() _DNS_RR { return new(_DNS_RR_CNAME) }, + _DNS_TypeHINFO: func() _DNS_RR { return new(_DNS_RR_HINFO) }, + _DNS_TypeMB: func() _DNS_RR { return new(_DNS_RR_MB) }, + _DNS_TypeMG: func() _DNS_RR { return new(_DNS_RR_MG) }, + _DNS_TypeMINFO: func() _DNS_RR { return new(_DNS_RR_MINFO) }, + _DNS_TypeMR: func() _DNS_RR { return new(_DNS_RR_MR) }, + _DNS_TypeMX: func() _DNS_RR { return new(_DNS_RR_MX) }, + _DNS_TypeNS: func() _DNS_RR { return new(_DNS_RR_NS) }, + _DNS_TypePTR: func() _DNS_RR { return new(_DNS_RR_PTR) }, + _DNS_TypeSOA: func() _DNS_RR { return new(_DNS_RR_SOA) }, + _DNS_TypeTXT: func() _DNS_RR { return new(_DNS_RR_TXT) }, + _DNS_TypeA: func() _DNS_RR { return new(_DNS_RR_A) }, +} + +// 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; + } + } + 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 uint16, uint32, string, +// and other (often anonymous) structs. +func packStructValue(val reflect.StructValue, msg []byte, off int) (off1 int, ok bool) { + for i := 0; i < val.Len(); i++ { + fld := val.Field(i); + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + switch fld.Kind() { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown packing type %v", fld.Type()); + return len(msg), false; + case reflect.StructKind: + off, ok = packStructValue(fld.(reflect.StructValue), msg, off); + case reflect.Uint16Kind: + i := fld.(reflect.Uint16Value).Get(); + if off+2 > len(msg) { + return len(msg), false + } + msg[off] = byte(i>>8); + msg[off+1] = byte(i); + off += 2; + case reflect.Uint32Kind: + i := fld.(reflect.Uint32Value).Get(); + if off+4 > len(msg) { + return len(msg), false + } + msg[off] = byte(i>>24); + msg[off+1] = byte(i>>16); + msg[off+2] = byte(i>>8); + msg[off+4] = byte(i); + off += 4; + case reflect.StringKind: + // There are multiple string encodings. + // The tag distinguishes ordinary strings from domain names. + s := fld.(reflect.StringValue).Get(); + switch tag { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown string tag %v", tag); + return len(msg), false; + case "domain-name": + off, ok = packDomainName(s, msg, off); + if !ok { + return len(msg), false + } + case "": + // Counted string: 1 byte length. + if len(s) > 255 || off + 1 + len(s) > len(msg) { + 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 packStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + off, ok = packStructValue(val, 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.Len(); i++ { + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + fld := val.Field(i); + switch fld.Kind() { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown packing type %v", fld.Type()); + return len(msg), false; + case reflect.StructKind: + off, ok = unpackStructValue(fld.(reflect.StructValue), msg, off); + case reflect.Uint16Kind: + if off+2 > len(msg) { + return len(msg), false + } + i := uint16(msg[off])<<8 | uint16(msg[off+1]); + fld.(reflect.Uint16Value).Set(i); + off += 2; + case reflect.Uint32Kind: + if off+4 > len(msg) { + return len(msg), false + } + i := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3]); + fld.(reflect.Uint32Value).Set(i); + off += 4; + case reflect.StringKind: + var s string; + switch tag { + default: + fmt.Fprintf(os.Stderr, "net: dns: unknown string tag %v", tag); + return len(msg), false; + case "domain-name": + s, off, ok = unpackDomainName(msg, off); + if !ok { + return len(msg), false + } + case "": + if off >= len(msg) || off+1+int(msg[off]) > len(msg) { + 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); + } + fld.(reflect.StringValue).Set(s); + } + } + return off, true +} + +func unpackStruct(any interface{}, msg []byte, off int) (off1 int, ok bool) { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + off, ok = unpackStructValue(val, msg, off); + return off, ok +} + +// Generic struct printer. +// Doesn't care about the string tag "domain-name", +// but does look for an "ipv4" tag on uint32 variables, +// printing them as IP addresses. +func printStructValue(val reflect.StructValue) string { + s := "{"; + for i := 0; i < val.Len(); i++ { + if i > 0 { + s += ", "; + } + name, typ, tag, xxx := val.Type().(reflect.StructType).Field(i); + fld := val.Field(i); + if name != "" && name != "?" { // BUG? Shouldn't the reflect library hide "?" ? + s += name + "="; + } + kind := fld.Kind(); + switch { + case kind == reflect.StructKind: + s += printStructValue(fld.(reflect.StructValue)); + case kind == reflect.Uint32Kind && tag == "ipv4": + i := fld.(reflect.Uint32Value).Get(); + s += fmt.Sprintf("%d.%d.%d.%d", (i>>24)&0xFF, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF); + default: + s += fmt.Sprint(fld.Interface()) + } + } + s += "}"; + return s; +} + +func printStruct(any interface{}) string { + val := reflect.NewValue(any).(reflect.PtrValue).Sub().(reflect.StructValue); + s := printStructValue(val); + return s +} + +// Resource record packer. +func packRR(rr _DNS_RR, msg []byte, off int) (off2 int, ok bool) { + 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 + } + // 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 _DNS_RR, off1 int, ok bool) { + // unpack just the header, to find the rr type and length + var h _DNS_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 { + return &h, end, true + } + rr = mk(); + off, ok = unpackStruct(rr, msg, off0); + if off != end { + return &h, end, true + } + return rr, off, ok +} + +// Usable representation of a DNS packet. + +// A manually-unpacked version of (id, bits). +// This is in its own struct for easy printing. +type __DNS_Msg_Top struct { + id uint16; + response bool; + opcode int; + authoritative bool; + truncated bool; + recursion_desired bool; + recursion_available bool; + rcode int; +} + +type _DNS_Msg struct { + __DNS_Msg_Top; + question []_DNS_Question; + answer []_DNS_RR; + ns []_DNS_RR; + extra []_DNS_RR; +} + + +func (dns *_DNS_Msg) Pack() (msg []byte, ok bool) { + var dh __DNS_Header; + + // Convert convenient _DNS_Msg into wire-like __DNS_Header. + dh.id = dns.id; + dh.bits = uint16(dns.opcode)<<11 | uint16(dns.rcode); + if dns.recursion_available { + dh.bits |= _RA; + } + if dns.recursion_desired { + dh.bits |= _RD; + } + if dns.truncated { + dh.bits |= _TC; + } + if dns.authoritative { + dh.bits |= _AA; + } + if dns.response { + dh.bits |= _QR; + } + + // 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, 2000); + + // 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 = packStruct(answer[i], msg, off); + } + for i := 0; i < len(ns); i++ { + off, ok = packStruct(ns[i], msg, off); + } + for i := 0; i < len(extra); i++ { + off, ok = packStruct(extra[i], msg, off); + } + if !ok { + return nil, false + } + return msg[0:off], true +} + +func (dns *_DNS_Msg) Unpack(msg []byte) bool { + // Header. + var dh __DNS_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.recursion_desired = (dh.bits & _RD) != 0; + dns.recursion_available = (dh.bits & _RA) != 0; + dns.rcode = int(dh.bits & 0xF); + + // Arrays. + dns.question = make([]_DNS_Question, dh.qdcount); + dns.answer = make([]_DNS_RR, dh.ancount); + dns.ns = make([]_DNS_RR, dh.nscount); + dns.extra = make([]_DNS_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) { +// println("extra bytes in dns packet", off, "<", len(msg)); +// } + return true +} + +func (dns *_DNS_Msg) String() string { + s := "DNS: "+printStruct(&dns.__DNS_Msg_Top)+"\n"; + if len(dns.question) > 0 { + s += "-- Questions\n"; + for i := 0; i < len(dns.question); i++ { + s += printStruct(&dns.question[i])+"\n"; + } + } + if len(dns.answer) > 0 { + s += "-- Answers\n"; + for i := 0; i < len(dns.answer); i++ { + s += printStruct(dns.answer[i])+"\n"; + } + } + if len(dns.ns) > 0 { + s += "-- Name servers\n"; + for i := 0; i < len(dns.ns); i++ { + s += printStruct(dns.ns[i])+"\n"; + } + } + if len(dns.extra) > 0 { + s += "-- Extra\n"; + for i := 0; i < len(dns.extra); i++ { + s += printStruct(dns.extra[i])+"\n"; + } + } + return s; +} |
