diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 1b12e16d1b7..61d8ded62e4 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -185,6 +185,7 @@ https://github.com/elastic/beats/compare/v1.2.0...5.0.0-alpha1[View commits] - Change the DNS library used throughout the dns package to github.com/miekg/dns. {pull}803[803] - Add support for NFS v3 and v4. {pull}1231[1231] +- Add support for EDNS and DNSSEC. {pull}1292[1292] *Topbeat* diff --git a/glide.yaml b/glide.yaml index 1064994ab3d..704ec043d74 100644 --- a/glide.yaml +++ b/glide.yaml @@ -42,7 +42,7 @@ import: - package: github.com/go-ole/go-ole version: v1.2.0 - package: github.com/miekg/dns - version: 85b661b2a6fc95a5a83e66d7730c4bc0b6e9c99e + version: c9d1302d540edfb97d9ecbfe90b4fb515088630b - package: github.com/Shopify/sarama version: v1.8.0 - package: github.com/klauspost/crc32 diff --git a/packetbeat/docs/fields.asciidoc b/packetbeat/docs/fields.asciidoc index ef1f3af4fb5..4e271c767f7 100644 --- a/packetbeat/docs/fields.asciidoc +++ b/packetbeat/docs/fields.asciidoc @@ -638,6 +638,30 @@ The time interval in seconds that this resource record may be cached before it s The data describing the resource. The meaning of this data depends on the type and class of the resource record. +==== dns.opt.version + +example: 0 + +The EDNS version. + +==== dns.opt.do + +type: bool + +If set, the transaction uses DNSSEC. + +==== dns.opt.ext_rcode + +example: BADVERS + +Extended response code field. + +==== dns.opt.udp_size + +type: int + +Requestor's UDP payload size (in bytes). + === amqp Fields AMQP specific event fields. diff --git a/packetbeat/etc/fields.yml b/packetbeat/etc/fields.yml index 05f2b71d69e..261bc4c9666 100644 --- a/packetbeat/etc/fields.yml +++ b/packetbeat/etc/fields.yml @@ -644,6 +644,22 @@ trans_event: The data describing the resource. The meaning of this data depends on the type and class of the resource record. + - name: opt.version + description: The EDNS version. + example: "0" + + - name: opt.do + type: bool + description: If set, the transaction uses DNSSEC. + + - name: opt.ext_rcode + description: Extended response code field. + example: "BADVERS" + + - name: opt.udp_size + type: int + description: Requestor's UDP payload size (in bytes). + - name: amqp type: group description: AMQP specific event fields. diff --git a/packetbeat/protos/dns/README.md b/packetbeat/protos/dns/README.md index 9cc27c567b8..55f37571fff 100644 --- a/packetbeat/protos/dns/README.md +++ b/packetbeat/protos/dns/README.md @@ -35,8 +35,6 @@ When response error Notes are linked to the previous request, the transaction is **General** * Publish an event with Notes when a Query or a lone Response cannot be decoded. -* Add EDNS and DNSSEC support (consider using miekg/dns instead - of gopacket). * Consider adding ICMP support to - correlate ICMP type 3, code 4 (datagram too big) with DNS messages, - correlate ICMP type 3, code 13 (administratively prohibited) or diff --git a/packetbeat/protos/dns/dns.go b/packetbeat/protos/dns/dns.go index b50beb3ea21..c7358737588 100644 --- a/packetbeat/protos/dns/dns.go +++ b/packetbeat/protos/dns/dns.go @@ -8,8 +8,10 @@ package dns import ( + "bytes" "fmt" "net" + "sort" "strconv" "strings" "time" @@ -24,6 +26,10 @@ import ( "golang.org/x/net/publicsuffix" ) +var ( + debugf = logp.MakeDebug("dns") +) + const MaxDnsTupleRawSize = 16 + 16 + 2 + 2 + 4 + 1 // Constants used to associate the DNS QR flag with a meaningful value. @@ -275,35 +281,64 @@ func (dns *Dns) ConnectionTimeout() time.Duration { } func (dns *Dns) receivedDnsRequest(tuple *DnsTuple, msg *DnsMessage) { - logp.Debug("dns", "Processing query. %s", tuple.String()) + debugf("Processing query. %s", tuple.String()) trans := dns.deleteTransaction(tuple.Hashable()) if trans != nil { // This happens if a client puts multiple requests in flight // with the same ID. trans.Notes = append(trans.Notes, DuplicateQueryMsg.Error()) - logp.Debug("dns", "%s %s", DuplicateQueryMsg.Error(), tuple.String()) + debugf("%s %s", DuplicateQueryMsg.Error(), tuple.String()) dns.publishTransaction(trans) dns.deleteTransaction(trans.tuple.Hashable()) } trans = newTransaction(msg.Ts, *tuple, *msg.CmdlineTuple) + + if tuple.Transport == TransportUdp && (msg.Data.IsEdns0() != nil) && msg.Length > MaxDnsPacketSize { + trans.Notes = append(trans.Notes, UdpPacketTooLarge.Error()) + debugf("%s", UdpPacketTooLarge.Error()) + } + dns.transactions.Put(tuple.Hashable(), trans) trans.Request = msg } func (dns *Dns) receivedDnsResponse(tuple *DnsTuple, msg *DnsMessage) { - logp.Debug("dns", "Processing response. %s", tuple.String()) + debugf("Processing response. %s", tuple.String()) trans := dns.getTransaction(tuple.RevHashable()) if trans == nil { trans = newTransaction(msg.Ts, tuple.Reverse(), common.CmdlineTuple{ Src: msg.CmdlineTuple.Dst, Dst: msg.CmdlineTuple.Src}) trans.Notes = append(trans.Notes, OrphanedResponse.Error()) - logp.Debug("dns", "%s %s", OrphanedResponse.Error(), tuple.String()) + debugf("%s %s", OrphanedResponse.Error(), tuple.String()) } trans.Response = msg + + if tuple.Transport == TransportUdp { + respIsEdns := msg.Data.IsEdns0() != nil + if !respIsEdns && msg.Length > MaxDnsPacketSize { + trans.Notes = append(trans.Notes, UdpPacketTooLarge.ResponseError()) + debugf("%s", UdpPacketTooLarge.ResponseError()) + } + + request := trans.Request + if request != nil { + reqIsEdns := request.Data.IsEdns0() != nil + + switch { + case reqIsEdns && !respIsEdns: + trans.Notes = append(trans.Notes, RespEdnsNoSupport.Error()) + debugf("%s %s", RespEdnsNoSupport.Error(), tuple.String()) + case !reqIsEdns && respIsEdns: + trans.Notes = append(trans.Notes, RespEdnsUnexpected.Error()) + debugf("%s %s", RespEdnsUnexpected.Error(), tuple.String()) + } + } + } + dns.publishTransaction(trans) dns.deleteTransaction(trans.tuple.Hashable()) } @@ -313,7 +348,7 @@ func (dns *Dns) publishTransaction(t *DnsTransaction) { return } - logp.Debug("dns", "Publishing transaction. %s", t.tuple.String()) + debugf("Publishing transaction. %s", t.tuple.String()) event := common.MapStr{} event["@timestamp"] = common.Time(t.ts) @@ -385,7 +420,7 @@ func (dns *Dns) publishTransaction(t *DnsTransaction) { func (dns *Dns) expireTransaction(t *DnsTransaction) { t.Notes = append(t.Notes, NoResponse.Error()) - logp.Debug("dns", "%s %s", NoResponse.Error(), t.tuple.String()) + debugf("%s %s", NoResponse.Error(), t.tuple.String()) dns.publishTransaction(t) } @@ -419,167 +454,249 @@ func addDnsToMapStr(m common.MapStr, dns *mkdns.Msg, authority bool, additional } } + rrOPT := dns.IsEdns0() + if rrOPT != nil { + m["opt"] = optToMapStr(rrOPT) + } + m["answers_count"] = len(dns.Answer) if len(dns.Answer) > 0 { - m["answers"] = rrToMapStr(dns.Answer) + m["answers"] = rrsToMapStrs(dns.Answer) } m["authorities_count"] = len(dns.Ns) if authority && len(dns.Ns) > 0 { - m["authorities"] = rrToMapStr(dns.Ns) + m["authorities"] = rrsToMapStrs(dns.Ns) } - m["additionals_count"] = len(dns.Extra) + if rrOPT != nil { + m["additionals_count"] = len(dns.Extra) - 1 + } else { + m["additionals_count"] = len(dns.Extra) + } if additional && len(dns.Extra) > 0 { - m["additionals"] = rrToMapStr(dns.Extra) + rrsMapStrs := rrsToMapStrs(dns.Extra) + // We do not want OPT RR to appear in the 'additional' section, + // that's why rrsMapStrs could be empty even though len(dns.Extra) > 0 + if len(rrsMapStrs) > 0 { + m["additionals"] = rrsMapStrs + } + } + +} + +func optToMapStr(rrOPT *mkdns.OPT) common.MapStr { + optMapStr := common.MapStr{ + "do": rrOPT.Do(), // true if DNSSEC + "version": strconv.FormatUint(uint64(rrOPT.Version()), 10), + "udp_size": rrOPT.UDPSize(), + "ext_rcode": dnsResponseCodeToString(rrOPT.ExtendedRcode()), + } + for _, o := range rrOPT.Option { + switch o.(type) { + case *mkdns.EDNS0_DAU: + optMapStr["dau"] = o.String() + case *mkdns.EDNS0_DHU: + optMapStr["dhu"] = o.String() + case *mkdns.EDNS0_EXPIRE: + optMapStr["local"] = o.String() + case *mkdns.EDNS0_LLQ: + optMapStr["llq"] = o.String() + case *mkdns.EDNS0_LOCAL: + optMapStr["local"] = o.String() + case *mkdns.EDNS0_N3U: + optMapStr["n3u"] = o.String() + case *mkdns.EDNS0_NSID: + optMapStr["nsid"] = o.String() + case *mkdns.EDNS0_SUBNET: + var draft string + if o.(*mkdns.EDNS0_SUBNET).DraftOption { + draft = " draft" + } + optMapStr["subnet"] = o.String() + draft + case *mkdns.EDNS0_UL: + optMapStr["ul"] = o.String() + } } + return optMapStr } -// rrToMapStr converts an array of DNSResourceRecord's to an array of MapStr's. -func rrToMapStr(records []mkdns.RR) []common.MapStr { +// rrsToMapStr converts an array of RR's to an array of MapStr's. +func rrsToMapStrs(records []mkdns.RR) []common.MapStr { mapStrArray := make([]common.MapStr, len(records)) for i, rr := range records { rrHeader := rr.Header() - rrType := rrHeader.Rrtype - mapStr := common.MapStr{ - "name": rrHeader.Name, - "type": dnsTypeToString(rrType), - "class": dnsClassToString(rrHeader.Class), - "ttl": strconv.FormatInt(int64(rrHeader.Ttl), 10), + mapStr := rrToMapStr(rr) + if len(mapStr) == 0 { // OPT pseudo-RR returns an empty MapStr + resizeStrArray := make([]common.MapStr, len(mapStrArray)-1) + copy(resizeStrArray, mapStrArray) + mapStrArray = resizeStrArray + continue } + mapStr["name"] = rrHeader.Name + mapStr["type"] = dnsTypeToString(rrHeader.Rrtype) + mapStr["class"] = dnsClassToString(rrHeader.Class) + mapStr["ttl"] = strconv.FormatInt(int64(rrHeader.Ttl), 10) mapStrArray[i] = mapStr - - switch x := rr.(type) { - default: - // We don't have special handling for this type - logp.Debug("dns", "No special handling for RR type %s", dnsTypeToString(rrType)) - unsupportedRR := new(mkdns.RFC3597) - err := unsupportedRR.ToRFC3597(x) - if err == nil { - rData, err := hexStringToString(unsupportedRR.Rdata) - mapStr["data"] = rData - if err != nil { - logp.Debug("dns", "%s", err.Error()) - } - } else { - logp.Debug("dns", "Rdata for the unhandled RR type %s could not be fetched", dnsTypeToString(rrType)) - } - case *mkdns.A: - mapStr["data"] = x.A.String() - case *mkdns.AAAA: - mapStr["data"] = x.AAAA.String() - case *mkdns.CNAME: - mapStr["data"] = x.Target - case *mkdns.MX: - mapStr["preference"] = x.Preference - mapStr["data"] = x.Mx - case *mkdns.NS: - mapStr["data"] = x.Ns - case *mkdns.PTR: - mapStr["data"] = x.Ptr - case *mkdns.RFC3597: - // Miekg/dns lib doesn't handle this type - //TODO: write a test for this. - logp.Debug("dns", "Unknown RR type %s", dnsTypeToString(rrType)) - rData, err := hexStringToString(x.Rdata) - mapStr["data"] = rData - if err != nil { - logp.Debug("dns", "%s", err.Error()) - } - case *mkdns.SOA: - mapStr["rname"] = x.Mbox - mapStr["serial"] = x.Serial - mapStr["refresh"] = x.Refresh - mapStr["retry"] = x.Retry - mapStr["expire"] = x.Expire - mapStr["minimum"] = x.Minttl - mapStr["data"] = x.Ns - case *mkdns.SRV: - mapStr["priority"] = x.Priority - mapStr["weight"] = x.Weight - mapStr["port"] = x.Port - mapStr["data"] = x.Target - case *mkdns.TXT: - mapStr["data"] = strings.Join(x.Txt, " ") - } } - return mapStrArray } -// dnsQuestionToString converts a Question to a string. -func dnsQuestionToString(q mkdns.Question) string { - name := q.Name +// Convert all RDATA fields of a RR to a single string +// fields are ordered alphabetically with 'data' as the last element +// +// TODO An improvement would be to replace 'data' by the real field name +// It would require some changes in unit tests +func rrToString(rr mkdns.RR) string { + var st string + var keys []string + + mapStr := rrToMapStr(rr) + data, ok := mapStr["data"] + delete(mapStr, "data") + + for k, _ := range mapStr { + keys = append(keys, k) + } + sort.Strings(keys) + + var b bytes.Buffer + for _, k := range keys { + v := mapStr[k] + switch x := v.(type) { + case int: + fmt.Fprintf(&b, "%s %d, ", k, x) + case string: + fmt.Fprintf(&b, "%s %s, ", k, x) + } + } + if !ok { + st = strings.TrimSuffix(b.String(), ", ") + return st + } - return fmt.Sprintf("class %s, type %s, %s", dnsClassToString(q.Qclass), - dnsTypeToString(q.Qtype), name) + switch x := data.(type) { + case int: + fmt.Fprintf(&b, "%d", x) + case string: + fmt.Fprintf(&b, "%s", x) + } + return b.String() } -// dnsResourceRecordToString converts a RR to a string. -func dnsResourceRecordToString(rr mkdns.RR) string { - rrHeader := rr.Header() - rrType := rrHeader.Rrtype +func rrToMapStr(rr mkdns.RR) common.MapStr { + mapStr := common.MapStr{} + rrType := rr.Header().Rrtype - var data string switch x := rr.(type) { default: // We don't have special handling for this type - logp.Debug("dns", "No special handling for RR type %s", dnsTypeToString(rrType)) + debugf("No special handling for RR type %s", dnsTypeToString(rrType)) unsupportedRR := new(mkdns.RFC3597) err := unsupportedRR.ToRFC3597(x) if err == nil { rData, err := hexStringToString(unsupportedRR.Rdata) - data = rData + mapStr["data"] = rData if err != nil { - logp.Debug("dns", "%s", err.Error()) + debugf("%s", err.Error()) } } else { - logp.Debug("dns", "Rdata for the unhandled RR type %s could not be fetched", dnsTypeToString(rrType)) + debugf("Rdata for the unhandled RR type %s could not be fetched", dnsTypeToString(rrType)) } case *mkdns.A: - data = x.A.String() + mapStr["data"] = x.A.String() case *mkdns.AAAA: - data = x.AAAA.String() + mapStr["data"] = x.AAAA.String() case *mkdns.CNAME: - data = x.Target + mapStr["data"] = x.Target + case *mkdns.DNSKEY: + mapStr["flags"] = strconv.Itoa(int(x.Flags)) + mapStr["protocol"] = strconv.Itoa(int(x.Protocol)) + mapStr["algorithm"] = dnsAlgorithmToString(x.Algorithm) + mapStr["data"] = x.PublicKey + case *mkdns.DS: + mapStr["key_tag"] = strconv.Itoa(int(x.KeyTag)) + mapStr["algorithm"] = dnsAlgorithmToString(x.Algorithm) + mapStr["digest_type"] = dnsHashToString(x.DigestType) + mapStr["data"] = strings.ToUpper(x.Digest) case *mkdns.MX: - data = fmt.Sprintf("preference %d, %s", x.Preference, x.Mx) + mapStr["preference"] = x.Preference + mapStr["data"] = x.Mx case *mkdns.NS: - data = x.Ns + mapStr["data"] = x.Ns + case *mkdns.NSEC: + mapStr["type_bits"] = dnsTypeBitsMapToString(x.TypeBitMap) + mapStr["data"] = x.NextDomain + case *mkdns.NSEC3: + mapStr["hash"] = dnsHashToString(x.Hash) + mapStr["flags"] = strconv.Itoa(int(x.Flags)) + mapStr["iterations"] = strconv.Itoa(int(x.Iterations)) + mapStr["salt"] = dnsSaltToString(x.Salt) + mapStr["type_bits"] = dnsTypeBitsMapToString(x.TypeBitMap) + mapStr["data"] = x.NextDomain + case *mkdns.NSEC3PARAM: + mapStr["hash"] = dnsHashToString(x.Hash) + mapStr["flags"] = strconv.Itoa(int(x.Flags)) + mapStr["iterations"] = strconv.Itoa(int(x.Iterations)) + mapStr["data"] = dnsSaltToString(x.Salt) + case *mkdns.OPT: // EDNS [RFC6891] + // OPT pseudo-RR is managed in addDnsToMapStr function + return nil case *mkdns.PTR: - data = x.Ptr + mapStr["data"] = x.Ptr case *mkdns.RFC3597: // Miekg/dns lib doesn't handle this type - logp.Debug("dns", "Unknown RR type %s", dnsTypeToString(rrType)) + debugf("Unknown RR type %s", dnsTypeToString(rrType)) rData, err := hexStringToString(x.Rdata) - data = rData + mapStr["data"] = rData if err != nil { - logp.Debug("dns", "%s", err.Error()) + debugf("%s", err.Error()) } + case *mkdns.RRSIG: + mapStr["type_covered"] = dnsTypeToString(x.TypeCovered) + mapStr["algorithm"] = dnsAlgorithmToString(x.Algorithm) + mapStr["labels"] = strconv.Itoa(int(x.Labels)) + mapStr["original_ttl"] = strconv.FormatInt(int64(x.OrigTtl), 10) + mapStr["expiration"] = mkdns.TimeToString(x.Expiration) + mapStr["inception"] = mkdns.TimeToString(x.Inception) + mapStr["key_tag"] = strconv.Itoa(int(x.KeyTag)) + mapStr["signer_name"] = x.SignerName + mapStr["data"] = x.Signature case *mkdns.SOA: - data = fmt.Sprintf("mname %s, rname %s, serial %d, refresh %d, "+ - "retry %d, expire %d, minimum %d", x.Ns, x.Mbox, - x.Serial, x.Refresh, x.Retry, x.Expire, - x.Minttl) + mapStr["rname"] = x.Mbox + mapStr["serial"] = x.Serial + mapStr["refresh"] = x.Refresh + mapStr["retry"] = x.Retry + mapStr["expire"] = x.Expire + mapStr["minimum"] = x.Minttl + mapStr["data"] = x.Ns case *mkdns.SRV: - data = fmt.Sprintf("priority %d, weight %d, port %d, %s", x.Priority, - x.Weight, x.Port, x.Target) + mapStr["priority"] = x.Priority + mapStr["weight"] = x.Weight + mapStr["port"] = x.Port + mapStr["data"] = x.Target case *mkdns.TXT: - data = strings.Join(x.Txt, " ") + mapStr["data"] = strings.Join(x.Txt, " ") } - return fmt.Sprintf("%s: ttl %d, class %s, type %s, %s", rrHeader.Name, - int(rrHeader.Ttl), dnsClassToString(rrHeader.Class), - dnsTypeToString(rrType), data) + return mapStr +} + +// dnsQuestionToString converts a Question to a string. +func dnsQuestionToString(q mkdns.Question) string { + name := q.Name + + return fmt.Sprintf("class %s, type %s, %s", dnsClassToString(q.Qclass), + dnsTypeToString(q.Qtype), name) } -// dnsResourceRecordsToString converts an array of DNSResourceRecord's to a +// rrsToString converts an array of RR's to a // string. -func dnsResourceRecordsToString(r []mkdns.RR) string { +func rrsToString(r []mkdns.RR) string { var rrStrs []string for _, rr := range r { - rrStrs = append(rrStrs, dnsResourceRecordToString(rr)) + rrStrs = append(rrStrs, rrToString(rr)) } return strings.Join(rrStrs, "; ") } @@ -629,17 +746,17 @@ func dnsToString(dns *mkdns.Msg) string { if len(dns.Answer) > 0 { a = append(a, fmt.Sprintf("ANSWER %s", - dnsResourceRecordsToString(dns.Answer))) + rrsToString(dns.Answer))) } if len(dns.Ns) > 0 { a = append(a, fmt.Sprintf("AUTHORITY %s", - dnsResourceRecordsToString(dns.Ns))) + rrsToString(dns.Ns))) } if len(dns.Extra) > 0 { a = append(a, fmt.Sprintf("ADDITIONAL %s", - dnsResourceRecordsToString(dns.Extra))) + rrsToString(dns.Extra))) } return strings.Join(a, "; ") diff --git a/packetbeat/protos/dns/dns_tcp.go b/packetbeat/protos/dns/dns_tcp.go index 7bfddd6f67d..74df307477c 100644 --- a/packetbeat/protos/dns/dns_tcp.go +++ b/packetbeat/protos/dns/dns_tcp.go @@ -40,7 +40,7 @@ type dnsConnectionData struct { func (dns *Dns) Parse(pkt *protos.Packet, tcpTuple *common.TcpTuple, dir uint8, private protos.ProtocolData) protos.ProtocolData { defer logp.Recover("Dns ParseTcp") - logp.Debug("dns", "Parsing packet addressed with %s of length %d.", + debugf("Parsing packet addressed with %s of length %d.", pkt.Tuple.String(), len(pkt.Payload)) conn := ensureDnsConnection(private) @@ -85,7 +85,7 @@ func (dns *Dns) doParse(conn *dnsConnectionData, pkt *protos.Packet, tcpTuple *c stream.rawData = append(stream.rawData, payload...) if len(stream.rawData) > tcp.TCP_MAX_DATA_IN_STREAM { - logp.Debug("dns", "Stream data too large, dropping DNS stream") + debugf("Stream data too large, dropping DNS stream") conn.Data[dir] = nil return conn } @@ -95,7 +95,7 @@ func (dns *Dns) doParse(conn *dnsConnectionData, pkt *protos.Packet, tcpTuple *c if err != nil { if err == IncompleteMsg { - logp.Debug("dns", "Waiting for more raw data") + debugf("Waiting for more raw data") return conn } @@ -103,7 +103,7 @@ func (dns *Dns) doParse(conn *dnsConnectionData, pkt *protos.Packet, tcpTuple *c dns.publishResponseError(conn, err) } - logp.Debug("dns", "%s addresses %s, length %d", err.Error(), + debugf("%s addresses %s, length %d", err.Error(), tcpTuple.String(), len(stream.rawData)) // This means that malformed requests or responses are being sent... @@ -177,7 +177,7 @@ func (dns *Dns) ReceivedFin(tcpTuple *common.TcpTuple, dir uint8, private protos dns.publishResponseError(conn, err) } - logp.Debug("dns", "%s addresses %s, length %d", err.Error(), + debugf("%s addresses %s, length %d", err.Error(), tcpTuple.String(), len(stream.rawData)) return conn @@ -208,9 +208,9 @@ func (dns *Dns) GapInStream(tcpTuple *common.TcpTuple, dir uint8, nbytes int, pr dns.publishResponseError(conn, err) } - logp.Debug("dns", "%s addresses %s, length %d", err.Error(), + debugf("%s addresses %s, length %d", err.Error(), tcpTuple.String(), len(stream.rawData)) - logp.Debug("dns", "Dropping the stream %s", tcpTuple.String()) + debugf("Dropping the stream %s", tcpTuple.String()) // drop the stream because it is binary Data and it would be unexpected to have a decodable message later return private, true diff --git a/packetbeat/protos/dns/dns_test.go b/packetbeat/protos/dns/dns_test.go index 43c92ca5a0c..039338975ad 100644 --- a/packetbeat/protos/dns/dns_test.go +++ b/packetbeat/protos/dns/dns_test.go @@ -228,6 +228,10 @@ func assertFlags(t testing.TB, m common.MapStr, flags []string) { t.Fatalf("Unknown flag '%s' specified in test.", expected) case "aa": key = "dns.flags.authoritative" + case "ad": + key = "dns.flags.authentic_data" + case "cd": + key = "dns.flags.checking_disabled" case "ra": key = "dns.flags.recursion_available" case "rd": diff --git a/packetbeat/protos/dns/dns_udp.go b/packetbeat/protos/dns/dns_udp.go index 13d8ffaff06..72a2f6db41d 100644 --- a/packetbeat/protos/dns/dns_udp.go +++ b/packetbeat/protos/dns/dns_udp.go @@ -7,18 +7,22 @@ import ( "github.com/elastic/beats/packetbeat/protos" ) +// Only EDNS packets should have their size beyond this value +const MaxDnsPacketSize = (1 << 9) // 512 (bytes) + func (dns *Dns) ParseUdp(pkt *protos.Packet) { defer logp.Recover("Dns ParseUdp") + packetSize := len(pkt.Payload) - logp.Debug("dns", "Parsing packet addressed with %s of length %d.", - pkt.Tuple.String(), len(pkt.Payload)) + debugf("Parsing packet addressed with %s of length %d.", + pkt.Tuple.String(), packetSize) dnsPkt, err := decodeDnsData(TransportUdp, pkt.Payload) if err != nil { // This means that malformed requests or responses are being sent or // that someone is attempting to the DNS port for non-DNS traffic. Both // are issues that a monitoring system should report. - logp.Debug("dns", "%s", err.Error()) + debugf("%s", err.Error()) return } @@ -28,7 +32,7 @@ func (dns *Dns) ParseUdp(pkt *protos.Packet) { Tuple: pkt.Tuple, CmdlineTuple: procs.ProcWatcher.FindProcessesTuple(&pkt.Tuple), Data: dnsPkt, - Length: len(pkt.Payload), + Length: packetSize, } if dnsMsg.Data.Response { diff --git a/packetbeat/protos/dns/dns_udp_test.go b/packetbeat/protos/dns/dns_udp_test.go index 890fc71b470..9d513087066 100644 --- a/packetbeat/protos/dns/dns_udp_test.go +++ b/packetbeat/protos/dns/dns_udp_test.go @@ -10,7 +10,6 @@ // TODO: // * Add test validation for responsetime to make sure unit conversion // is being done correctly. -// * Add validation of special fields provided in MX, SOA, NS queries. // * Add test case to verify that Include_authorities and Include_additionals // are working. // * Add test case for Send_request and validate the stringified DNS message. @@ -29,9 +28,8 @@ import ( "github.com/elastic/beats/libbeat/common" - "github.com/stretchr/testify/assert" - mkdns "github.com/miekg/dns" + "github.com/stretchr/testify/assert" ) // Verify that the interface for UDP has been satisfied. @@ -46,6 +44,7 @@ var ( zoneIxfr, githubPtr, sophosTxt, + ednsSecA, } elasticA = DnsTestMessage{ @@ -194,6 +193,48 @@ var ( 0x6f, 0x73, 0x78, 0x6c, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x10, 0x00, 0x01, }, } + + ednsSecA = DnsTestMessage{ + id: 20498, + opcode: "QUERY", + flags: []string{"rd", "ad", "ra"}, + rcode: "NOERROR", + q_class: "IN", + q_type: "A", + q_name: "www.ietf.org.", + q_etld: "ietf.org.", + answers: []string{"64.170.98.30", "iDA8bJnrAEz3jgYnyFRm567a76qlv1V0CqxOSd/o9nvnN0GlZLaVoDmuXpaIaoypbGxwzwgK/LY6CV2k6SWKwicBmpENL26hwyjkFzPDW8kX3ibFhtfsOb8pYe7nBj326actp/7iG+DRuDmPnkYBja+wDYk61doTtkqZg57fn3iS97tjNPCC9C9knRAuDYUG+dVxalazSwYrpvY97dUC1H2spD0g4UdDyCbGA46mouZ4GPzNMewgf948qxrnU8pWPk3nQW5TgLVkGoWgco2owfLElBqf6rJ4LOswuhaw8IpTtmw3FsixxTLQvKOE5nftd1nMhQQd9CaHjoKNAUEz5Q=="}, + request: []byte{ + 0x50, 0x12, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, 0x77, 0x77, + 0x04, 0x69, 0x65, 0x74, 0x66, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + }, + response: []byte{ + 0x50, 0x12, 0x81, 0xa0, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, 0x77, 0x77, + 0x04, 0x69, 0x65, 0x74, 0x66, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x04, 0xd8, 0x00, 0x04, 0x40, 0xaa, 0x62, 0x1e, 0xc0, 0x0c, + 0x00, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x04, 0xd8, 0x01, 0x1c, 0x00, 0x01, 0x05, 0x03, 0x00, 0x00, + 0x07, 0x08, 0x52, 0xd0, 0x71, 0xc7, 0x50, 0xef, 0x30, 0xaf, 0x9e, 0x04, 0x04, 0x69, 0x65, 0x74, + 0x66, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x88, 0x30, 0x3c, 0x6c, 0x99, 0xeb, 0x00, 0x4c, 0xf7, 0x8e, + 0x06, 0x27, 0xc8, 0x54, 0x66, 0xe7, 0xae, 0xda, 0xef, 0xaa, 0xa5, 0xbf, 0x55, 0x74, 0x0a, 0xac, + 0x4e, 0x49, 0xdf, 0xe8, 0xf6, 0x7b, 0xe7, 0x37, 0x41, 0xa5, 0x64, 0xb6, 0x95, 0xa0, 0x39, 0xae, + 0x5e, 0x96, 0x88, 0x6a, 0x8c, 0xa9, 0x6c, 0x6c, 0x70, 0xcf, 0x08, 0x0a, 0xfc, 0xb6, 0x3a, 0x09, + 0x5d, 0xa4, 0xe9, 0x25, 0x8a, 0xc2, 0x27, 0x01, 0x9a, 0x91, 0x0d, 0x2f, 0x6e, 0xa1, 0xc3, 0x28, + 0xe4, 0x17, 0x33, 0xc3, 0x5b, 0xc9, 0x17, 0xde, 0x26, 0xc5, 0x86, 0xd7, 0xec, 0x39, 0xbf, 0x29, + 0x61, 0xee, 0xe7, 0x06, 0x3d, 0xf6, 0xe9, 0xa7, 0x2d, 0xa7, 0xfe, 0xe2, 0x1b, 0xe0, 0xd1, 0xb8, + 0x39, 0x8f, 0x9e, 0x46, 0x01, 0x8d, 0xaf, 0xb0, 0x0d, 0x89, 0x3a, 0xd5, 0xda, 0x13, 0xb6, 0x4a, + 0x99, 0x83, 0x9e, 0xdf, 0x9f, 0x78, 0x92, 0xf7, 0xbb, 0x63, 0x34, 0xf0, 0x82, 0xf4, 0x2f, 0x64, + 0x9d, 0x10, 0x2e, 0x0d, 0x85, 0x06, 0xf9, 0xd5, 0x71, 0x6a, 0x56, 0xb3, 0x4b, 0x06, 0x2b, 0xa6, + 0xf6, 0x3d, 0xed, 0xd5, 0x02, 0xd4, 0x7d, 0xac, 0xa4, 0x3d, 0x20, 0xe1, 0x47, 0x43, 0xc8, 0x26, + 0xc6, 0x03, 0x8e, 0xa6, 0xa2, 0xe6, 0x78, 0x18, 0xfc, 0xcd, 0x31, 0xec, 0x20, 0x7f, 0xde, 0x3c, + 0xab, 0x1a, 0xe7, 0x53, 0xca, 0x56, 0x3e, 0x4d, 0xe7, 0x41, 0x6e, 0x53, 0x80, 0xb5, 0x64, 0x1a, + 0x85, 0xa0, 0x72, 0x8d, 0xa8, 0xc1, 0xf2, 0xc4, 0x94, 0x1a, 0x9f, 0xea, 0xb2, 0x78, 0x2c, 0xeb, + 0x30, 0xba, 0x16, 0xb0, 0xf0, 0x8a, 0x53, 0xb6, 0x6c, 0x37, 0x16, 0xc8, 0xb1, 0xc5, 0x32, 0xd0, + 0xbc, 0xa3, 0x84, 0xe6, 0x77, 0xed, 0x77, 0x59, 0xcc, 0x85, 0x04, 0x1d, 0xf4, 0x26, 0x87, 0x8e, + 0x82, 0x8d, 0x01, 0x41, 0x33, 0xe5, 0x00, 0x00, 0x29, 0x0f, 0xa0, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, + }, + } ) // Verify that an empty packet is safely handled (no panics). @@ -332,6 +373,72 @@ func TestPublishTransaction_emptyDnsResponse(t *testing.T) { assert.Equal(t, common.ERROR_STATUS, mapValue(t, m, "status")) } +func TestPublishTransaction_edns(t *testing.T) { + dns := newDns(testing.Verbose()) + q := ednsSecA + packet := newPacket(forward, q.request) + dns.ParseUdp(packet) + assert.Equal(t, 1, dns.transactions.Size(), "There should be one transaction.") + packet = newPacket(reverse, q.response) + dns.ParseUdp(packet) + assert.Empty(t, dns.transactions.Size(), "There should be no transactions.") + + m := expectResult(t, dns) + assert.Equal(t, "udp", mapValue(t, m, "transport")) + assert.Equal(t, len(q.request), mapValue(t, m, "bytes_in")) + assert.Equal(t, len(q.response), mapValue(t, m, "bytes_out")) + assert.NotNil(t, mapValue(t, m, "responsetime")) + assert.Equal(t, common.OK_STATUS, mapValue(t, m, "status")) + assert.Nil(t, mapValue(t, m, "notes")) + assertMapStrData(t, m, q) +} + +// Verify that a non-edns answer to a edns query publishes Notes. +func TestPublishTransaction_respEdnsNoSupport(t *testing.T) { + dns := newDns(testing.Verbose()) + q := ednsSecA + q.response = q.response[:len(q.response)-11] // Remove OPT RR + + packet := newPacket(forward, q.request) + dns.ParseUdp(packet) + assert.Equal(t, 1, dns.transactions.Size(), "There should be one transaction.") + packet = newPacket(reverse, q.response) + dns.ParseUdp(packet) + assert.Empty(t, dns.transactions.Size(), "There should be no transactions.") + + m := expectResult(t, dns) + assert.Equal(t, "udp", mapValue(t, m, "transport")) + assert.Equal(t, len(q.request), mapValue(t, m, "bytes_in")) + assert.Equal(t, len(q.response), mapValue(t, m, "bytes_out")) + assert.NotNil(t, mapValue(t, m, "responsetime")) + assert.Equal(t, common.OK_STATUS, mapValue(t, m, "status")) + assert.Equal(t, RespEdnsNoSupport.Error(), mapValue(t, m, "notes")) + assertRequest(t, m, q) +} + +// Verify that a edns response to a non-edns query publishes Notes. +func TestPublishTransaction_respEdnsUnexpected(t *testing.T) { + dns := newDns(testing.Verbose()) + q := ednsSecA + q.request = q.request[:len(q.request)-11] // Remove OPT RR + + packet := newPacket(forward, q.request) + dns.ParseUdp(packet) + assert.Equal(t, 1, dns.transactions.Size(), "There should be one transaction.") + packet = newPacket(reverse, q.response) + dns.ParseUdp(packet) + assert.Empty(t, dns.transactions.Size(), "There should be no transactions.") + + m := expectResult(t, dns) + assert.Equal(t, "udp", mapValue(t, m, "transport")) + assert.Equal(t, len(q.request), mapValue(t, m, "bytes_in")) + assert.Equal(t, len(q.response), mapValue(t, m, "bytes_out")) + assert.NotNil(t, mapValue(t, m, "responsetime")) + assert.Equal(t, common.OK_STATUS, mapValue(t, m, "status")) + assert.Equal(t, RespEdnsUnexpected.Error(), mapValue(t, m, "notes")) + assertMapStrData(t, m, q) +} + // Benchmarks UDP parsing for the given test message. func benchmarkUdp(b *testing.B, q DnsTestMessage) { dns := newDns(false) diff --git a/packetbeat/protos/dns/errors.go b/packetbeat/protos/dns/errors.go index 735c99abafe..117034d980f 100644 --- a/packetbeat/protos/dns/errors.go +++ b/packetbeat/protos/dns/errors.go @@ -1,5 +1,9 @@ package dns +import ( + "fmt" +) + // All dns protocol errors are defined here. type Error interface { @@ -22,18 +26,25 @@ func (e *DNSError) ResponseError() string { return "Response: " + e.Error() } -// Messages +// Common var ( - NonDnsMsg = &DNSError{Err: "Message's data could not be decoded as DNS"} - ZeroLengthMsg = &DNSError{Err: "Message's length was set to zero"} - UnexpectedLengthMsg = &DNSError{Err: "Unexpected message data length"} - DuplicateQueryMsg = &DNSError{Err: "Another query with the same DNS ID from this client " + + NonDnsMsg = &DNSError{Err: "Message's data could not be decoded as DNS"} + DuplicateQueryMsg = &DNSError{Err: "Another query with the same DNS ID from this client " + "was received so this query was closed without receiving a response"} - IncompleteMsg = &DNSError{Err: "Message's data is incomplete"} - NoResponse = &DNSError{Err: "No response to this query was received"} + NoResponse = &DNSError{Err: "No response to this query was received"} + OrphanedResponse = &DNSError{Err: "Response: received without an associated Query"} ) -// TCP responses +// EDNS var ( - OrphanedResponse = &DNSError{Err: "Response: received without an associated Query"} + UdpPacketTooLarge = &DNSError{Err: fmt.Sprintf("Non-EDNS packet has size greater than %d", MaxDnsPacketSize)} + RespEdnsNoSupport = &DNSError{Err: "Responder does not support EDNS"} + RespEdnsUnexpected = &DNSError{Err: "Unexpected EDNS answer"} +) + +// TCP +var ( + ZeroLengthMsg = &DNSError{Err: "Message's length was set to zero"} + UnexpectedLengthMsg = &DNSError{Err: "Unexpected message data length"} + IncompleteMsg = &DNSError{Err: "Message's data is incomplete"} ) diff --git a/packetbeat/protos/dns/names.go b/packetbeat/protos/dns/names.go index aa2f517570a..b3e41935422 100644 --- a/packetbeat/protos/dns/names.go +++ b/packetbeat/protos/dns/names.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "strconv" + "strings" mkdns "github.com/miekg/dns" ) @@ -54,6 +55,49 @@ func dnsClassToString(c uint16) string { return s } +// dnsAlgorithmToString converts an algorithm value to a string. If the algorithm's +// string representation is unknown then the numeric value will be returned +// as a string. +func dnsAlgorithmToString(a uint8) string { + s, exists := mkdns.AlgorithmToString[uint8(a)] + if !exists { + return strconv.Itoa(int(a)) + } + return s +} + +// dnsHashToString converts a hash value to a string. If the hash's +// string representation is unknown then the numeric value will be returned +// as a string. +func dnsHashToString(h uint8) string { + s, exists := mkdns.HashToString[uint8(h)] + if !exists { + return strconv.Itoa(int(h)) + } + return s +} + +// dnsTypeBitsMapToString converts a map of type bits to a string. If the type's +// string representation is unknown then the numeric value will be returned +// as a string. +func dnsTypeBitsMapToString(t []uint16) string { + var s string + for i := 0; i < len(t); i++ { + s += dnsTypeToString(t[i]) + " " + } + return strings.TrimSuffix(s, " ") +} + +// saltToString converts a NSECX salt to uppercase and +// returns "-" when it is empty +// func copied from miekg/dns because unexported +func dnsSaltToString(s string) string { + if len(s) == 0 { + return "-" + } + return strings.ToUpper(s) +} + // hexStringToString converts an hexadeciaml string to string. Bytes // below 32 or above 126 are represented as an escaped base10 integer (\DDD). // Back slashes and quotes are escaped. Tabs, carriage returns, and line feeds diff --git a/packetbeat/protos/dns/names_test.go b/packetbeat/protos/dns/names_test.go new file mode 100644 index 00000000000..b5745d87391 --- /dev/null +++ b/packetbeat/protos/dns/names_test.go @@ -0,0 +1,124 @@ +// +build !integration + +// Unit tests and benchmarks for the dns package. +// This file contains tests for queries' RR type +// +// TODO: +// * Add validation of special fields provided in MX, SOA, NS... +// * Use struct DnsTestMsg fields question, answers, authorities,... for struct DnsTestMessage + +package dns + +import ( + "testing" + + "github.com/elastic/beats/libbeat/common" + + "github.com/stretchr/testify/assert" +) + +type DnsTestMsg struct { + rawData []byte + question common.MapStr + answers []common.MapStr + authorities []common.MapStr + additionals []common.MapStr + opt common.MapStr +} + +// DNS messages for testing. +var ( + // An array of all test messages. + dnsTestRRs = []DnsTestMsg{ + unhandledRR, + unknownRR, + opt, + } + + unhandledRR = DnsTestMsg{ // RR specified in a RFC but not implemented in the package dns + rawData: []byte{ + 0x21, 0x51, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x6c, 0x61, + 0x73, 0x74, 0x69, 0x63, 0x02, 0x63, 0x6f, 0x00, 0x00, 0x1e, 0x00, 0x01, + }, + question: common.MapStr{ + "type": "NXT", + "name": "elastic.co.", + }, + } + + unknownRR = DnsTestMsg{ // RR unspecified in any known RFC + rawData: []byte{ + 0x21, 0x51, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x65, 0x6c, 0x61, + 0x73, 0x74, 0x69, 0x63, 0x02, 0x63, 0x6f, 0x00, 0xff, 0x00, 0x00, 0x01, + }, + question: common.MapStr{ + "type": "65280", + "name": "elastic.co.", + }, + } + + opt = DnsTestMsg{ + rawData: []byte{ + 0x50, 0x12, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, 0x77, 0x77, + 0x04, 0x69, 0x65, 0x74, 0x66, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + }, + question: common.MapStr{ + "type": "A", + "name": "www.ietf.org.", + }, + opt: common.MapStr{ + "version": "0", + "do": true, + }, + } +) + +// oracleRRs and rrs should be sorted in the same order +func assertRRs(t testing.TB, oracleRRs []common.MapStr, rrs []common.MapStr) { + assert.Equal(t, len(oracleRRs), len(rrs)) + for i, oracleRR := range oracleRRs { + rr := rrs[i] + for k, v := range oracleRR { + assert.NotNil(t, rr[k]) + assert.Equal(t, v, rr[k]) + } + } +} + +func assertDnsMessage(t testing.TB, q DnsTestMsg) { + dns, err := decodeDnsData(TransportUdp, q.rawData) + if err != nil { + t.Error("failed to decode dns data") + } + + mapStr := common.MapStr{} + addDnsToMapStr(mapStr, dns, true, true) + if q.question != nil { + for k, v := range q.question { + assert.NotNil(t, mapStr["question"].(common.MapStr)[k]) + assert.Equal(t, v, mapStr["question"].(common.MapStr)[k]) + } + } + if len(q.answers) > 0 { + assertRRs(t, q.answers, mapStr["answer"].([]common.MapStr)) + } + if len(q.authorities) > 0 { + assertRRs(t, q.authorities, mapStr["authorities"].([]common.MapStr)) + } + if len(q.additionals) > 0 { + assertRRs(t, q.additionals, mapStr["additionals"].([]common.MapStr)) + } + if q.opt != nil { + for k, v := range q.opt { + assert.NotNil(t, mapStr["opt"].(common.MapStr)[k]) + assert.Equal(t, v, mapStr["opt"].(common.MapStr)[k]) + } + } +} + +func TestAllRR(t *testing.T) { + for _, q := range dnsTestRRs { + assertDnsMessage(t, q) + } +} diff --git a/packetbeat/tests/system/pcaps/dns_udp_edns_ds.pcap b/packetbeat/tests/system/pcaps/dns_udp_edns_ds.pcap new file mode 100644 index 00000000000..7e16154ea33 Binary files /dev/null and b/packetbeat/tests/system/pcaps/dns_udp_edns_ds.pcap differ diff --git a/packetbeat/tests/system/test_0032_dns.py b/packetbeat/tests/system/test_0032_dns.py index 25f09be2267..d31c86d38a2 100644 --- a/packetbeat/tests/system/test_0032_dns.py +++ b/packetbeat/tests/system/test_0032_dns.py @@ -203,3 +203,29 @@ def test_tcp_axfr(self): assert o["status"] == "OK" assert len(o["dns.answers"]) == 4 assert all("etas.com" in x["name"] for x in o["dns.answers"]) + + def test_edns_dnssec(self): + """ + Should correctly interpret a UDP edns with a DNSSEC RR + """ + self.render_config_template( + dns_ports=[53], + ) + self.run_packetbeat(pcap="dns_udp_edns_ds.pcap") + + objs = self.read_output() + assert len(objs) == 1 + o = objs[0] + + assert o["type"] == "dns" + assert o["transport"] == "udp" + assert o["method"] == "QUERY" + assert o["query"] == "class IN, type DS, ietf.org." + assert o["dns.question.type"] == "DS" + assert o["status"] == "OK" + assert o["dns.opt.do"] == True + assert o["dns.opt.version"] == "0" + assert o["dns.opt.udp_size"] == 4000 + assert o["dns.opt.ext_rcode"] == "Unknown 15" + assert len(o["dns.answers"]) == 3 + assert all("ietf.org" in x["name"] for x in o["dns.answers"]) diff --git a/vendor/github.com/miekg/dns/.gitignore b/vendor/github.com/miekg/dns/.gitignore deleted file mode 100644 index 776cd950c25..00000000000 --- a/vendor/github.com/miekg/dns/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.6 -tags -test.out -a.out diff --git a/vendor/github.com/miekg/dns/.travis.yml b/vendor/github.com/miekg/dns/.travis.yml index 0012f6ae4cf..1f056ab7ccc 100644 --- a/vendor/github.com/miekg/dns/.travis.yml +++ b/vendor/github.com/miekg/dns/.travis.yml @@ -1,7 +1,7 @@ language: go sudo: false go: - - 1.4 - 1.5 + - 1.6 script: - go test -race -v -bench=. diff --git a/vendor/github.com/miekg/dns/README.md b/vendor/github.com/miekg/dns/README.md index 1a9a8b7c29e..3439f8c5bbc 100644 --- a/vendor/github.com/miekg/dns/README.md +++ b/vendor/github.com/miekg/dns/README.md @@ -12,7 +12,7 @@ can build servers and resolvers with it. We try to keep the "master" branch as sane as possible and at the bleeding edge of standards, avoiding breaking changes wherever reasonable. We support the last -two versions of Go, currently: 1.4 and 1.5. +two versions of Go, currently: 1.5 and 1.6. # Goals @@ -33,6 +33,7 @@ A not-so-up-to-date-list-that-may-be-actually-current: * https://github.com/fcambus/rrda * https://github.com/kenshinx/godns * https://github.com/skynetservices/skydns +* https://github.com/hashicorp/consul * https://github.com/DevelopersPL/godnsagent * https://github.com/duedil-ltd/discodns * https://github.com/StalkR/dns-reverse-proxy @@ -42,10 +43,11 @@ A not-so-up-to-date-list-that-may-be-actually-current: * https://play.google.com/store/apps/details?id=com.turbobytes.dig * https://github.com/fcambus/statzone * https://github.com/benschw/dns-clb-go -* https://github.com/corny/dnscheck for http://public-dns.tk/ +* https://github.com/corny/dnscheck for http://public-dns.info/ * https://namesmith.io * https://github.com/miekg/unbound * https://github.com/miekg/exdns +* https://dnslookup.org Send pull request if you want to be listed here. diff --git a/vendor/github.com/miekg/dns/client.go b/vendor/github.com/miekg/dns/client.go index 881e9057340..c1a4a430fa4 100644 --- a/vendor/github.com/miekg/dns/client.go +++ b/vendor/github.com/miekg/dns/client.go @@ -28,9 +28,10 @@ type Client struct { Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) UDPSize uint16 // minimum receive buffer for UDP messages TLSConfig *tls.Config // TLS connection configuration - DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds + Timeout time.Duration // a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout and WriteTimeout when non-zero + DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - overridden by Timeout when that value is non-zero + ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero + WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass group singleflight @@ -39,14 +40,7 @@ type Client struct { // Exchange performs a synchronous UDP query. It sends the message m to the address // contained in a and waits for an reply. Exchange does not retry a failed query, nor // will it fall back to TCP in case of truncation. -// If you need to send a DNS message on an already existing connection, you can use the -// following: -// -// co := &dns.Conn{Conn: c} // c is your net.Conn -// co.WriteMsg(m) -// in, err := co.ReadMsg() -// co.Close() -// +// See client.Exchange for more information on setting larger buffer sizes. func Exchange(m *Msg, a string) (r *Msg, err error) { var co *Conn co, err = DialTimeout("udp", a, dnsTimeout) @@ -106,6 +100,10 @@ func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { // // Exchange does not retry a failed query, nor will it fall back to TCP in // case of truncation. +// It is up to the caller to create a message that allows for larger responses to be +// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger +// buffer, see SetEdns0. Messsages without an OPT RR will fallback to the historic limit +// of 512 bytes. func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { if !c.SingleInflight { return c.exchange(m, a) @@ -132,6 +130,9 @@ func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err erro } func (c *Client) dialTimeout() time.Duration { + if c.Timeout != 0 { + return c.Timeout + } if c.DialTimeout != 0 { return c.DialTimeout } @@ -173,6 +174,11 @@ func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err erro } } + var deadline time.Time + if c.Timeout != 0 { + deadline = time.Now().Add(c.Timeout) + } + if tls { co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, c.dialTimeout()) } else { @@ -195,12 +201,12 @@ func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err erro } co.TsigSecret = c.TsigSecret - co.SetWriteDeadline(time.Now().Add(c.writeTimeout())) + co.SetWriteDeadline(deadlineOrTimeout(deadline, c.writeTimeout())) if err = co.WriteMsg(m); err != nil { return nil, 0, err } - co.SetReadDeadline(time.Now().Add(c.readTimeout())) + co.SetReadDeadline(deadlineOrTimeout(deadline, c.readTimeout())) r, err = co.ReadMsg() if err == nil && r.Id != m.Id { err = ErrId @@ -258,7 +264,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { } p = make([]byte, l) n, err = tcpRead(r, p) - + co.rtt = time.Since(co.t) default: if co.UDPSize > MinMsgSize { p = make([]byte, co.UDPSize) @@ -266,6 +272,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { p = make([]byte, MinMsgSize) } n, err = co.Read(p) + co.rtt = time.Since(co.t) } if err != nil { @@ -342,8 +349,6 @@ func (co *Conn) Read(p []byte) (n int, err error) { if err != nil { return n, err } - - co.rtt = time.Since(co.t) return n, err } @@ -438,3 +443,10 @@ func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout } return conn, nil } + +func deadlineOrTimeout(deadline time.Time, timeout time.Duration) time.Time { + if deadline.IsZero() { + return time.Now().Add(timeout) + } + return deadline +} diff --git a/vendor/github.com/miekg/dns/client_test.go b/vendor/github.com/miekg/dns/client_test.go index 09ec7037339..f3d229c4c14 100644 --- a/vendor/github.com/miekg/dns/client_test.go +++ b/vendor/github.com/miekg/dns/client_test.go @@ -419,3 +419,51 @@ func TestTruncatedMsg(t *testing.T) { t.Fail() } } + +func TestTimeout(t *testing.T) { + // Set up a dummy UDP server that won't respond + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("unable to resolve local udp address: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer conn.Close() + addrstr := conn.LocalAddr().String() + + // Message to send + m := new(Msg) + m.SetQuestion("miek.nl.", TypeTXT) + + // Use a channel + timeout to ensure we don't get stuck if the + // Client Timeout is not working properly + done := make(chan struct{}) + + timeout := time.Millisecond + allowable := timeout + (10 * time.Millisecond) + abortAfter := timeout + (100 * time.Millisecond) + + start := time.Now() + + go func() { + c := &Client{Timeout: timeout} + _, _, err := c.Exchange(m, addrstr) + if err == nil { + t.Error("no timeout using Client") + } + done <- struct{}{} + }() + + select { + case <-done: + case <-time.After(abortAfter): + } + + length := time.Since(start) + + if length > allowable { + t.Errorf("exchange took longer (%v) than specified Timeout (%v)", length, timeout) + } +} diff --git a/vendor/github.com/miekg/dns/defaults.go b/vendor/github.com/miekg/dns/defaults.go index 63165b4fa9c..cf456165f43 100644 --- a/vendor/github.com/miekg/dns/defaults.go +++ b/vendor/github.com/miekg/dns/defaults.go @@ -142,9 +142,13 @@ func (dns *Msg) IsTsig() *TSIG { // record in the additional section will do. It returns the OPT record // found or nil. func (dns *Msg) IsEdns0() *OPT { - for _, r := range dns.Extra { - if r.Header().Rrtype == TypeOPT { - return r.(*OPT) + // EDNS0 is at the end of the additional section, start there. + // We might want to change this to *only* look at the last two + // records. So we see TSIG and/or OPT - this a slightly bigger + // change though. + for i := len(dns.Extra) - 1; i >= 0; i-- { + if dns.Extra[i].Header().Rrtype == TypeOPT { + return dns.Extra[i].(*OPT) } } return nil @@ -163,8 +167,8 @@ func IsDomainName(s string) (labels int, ok bool) { return labels, err == nil } -// IsSubDomain checks if child is indeed a child of the parent. Both child and -// parent are *not* downcased before doing the comparison. +// IsSubDomain checks if child is indeed a child of the parent. If child and parent +// are the same domain true is returned as well. func IsSubDomain(parent, child string) bool { // Entire child is contained in parent return CompareDomainName(parent, child) == CountLabel(parent) diff --git a/vendor/github.com/miekg/dns/dns_test.go b/vendor/github.com/miekg/dns/dns_test.go index 58fab4ca7b9..20b33f0cbd8 100644 --- a/vendor/github.com/miekg/dns/dns_test.go +++ b/vendor/github.com/miekg/dns/dns_test.go @@ -99,8 +99,9 @@ func TestPackUnpack3(t *testing.T) { func TestBailiwick(t *testing.T) { yes := map[string]string{ - "miek.nl": "ns.miek.nl", - ".": "miek.nl", + "miek1.nl": "miek1.nl", + "miek.nl": "ns.miek.nl", + ".": "miek.nl", } for parent, child := range yes { if !IsSubDomain(parent, child) { diff --git a/vendor/github.com/miekg/dns/dnsutil/util.go b/vendor/github.com/miekg/dns/dnsutil/util.go new file mode 100644 index 00000000000..9ed03f2969c --- /dev/null +++ b/vendor/github.com/miekg/dns/dnsutil/util.go @@ -0,0 +1,79 @@ +// Package dnsutil contains higher-level methods useful with the dns +// package. While package dns implements the DNS protocols itself, +// these functions are related but not directly required for protocol +// processing. They are often useful in preparing input/output of the +// functions in package dns. +package dnsutil + +import ( + "strings" + + "github.com/miekg/dns" +) + +// AddDomain adds origin to s if s is not already a FQDN. +// Note that the result may not be a FQDN. If origin does not end +// with a ".", the result won't either. +// This implements the zonefile convention (specified in RFC 1035, +// Section "5.1. Format") that "@" represents the +// apex (bare) domain. i.e. AddOrigin("@", "foo.com.") returns "foo.com.". +func AddOrigin(s, origin string) string { + // ("foo.", "origin.") -> "foo." (already a FQDN) + // ("foo", "origin.") -> "foo.origin." + // ("foo"), "origin" -> "foo.origin" + // ("@", "origin.") -> "origin." (@ represents the apex (bare) domain) + // ("", "origin.") -> "origin." (not obvious) + // ("foo", "") -> "foo" (not obvious) + + if dns.IsFqdn(s) { + return s // s is already a FQDN, no need to mess with it. + } + if len(origin) == 0 { + return s // Nothing to append. + } + if s == "@" || len(s) == 0 { + return origin // Expand apex. + } + + if origin == "." { + return s + origin // AddOrigin(s, ".") is an expensive way to add a ".". + } + + return s + "." + origin // The simple case. +} + +// TrimDomainName trims origin from s if s is a subdomain. +// This function will never return "", but returns "@" instead (@ represents the apex (bare) domain). +func TrimDomainName(s, origin string) string { + // An apex (bare) domain is always returned as "@". + // If the return value ends in a ".", the domain was not the suffix. + // origin can end in "." or not. Either way the results should be the same. + + if len(s) == 0 { + return "@" // Return the apex (@) rather than "". + } + // Someone is using TrimDomainName(s, ".") to remove a dot if it exists. + if origin == "." { + return strings.TrimSuffix(s, origin) + } + + // Dude, you aren't even if the right subdomain! + if !dns.IsSubDomain(origin, s) { + return s + } + + slabels := dns.Split(s) + olabels := dns.Split(origin) + m := dns.CompareDomainName(s, origin) + if len(olabels) == m { + if len(olabels) == len(slabels) { + return "@" // origin == s + } + if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) { + return "@" // TrimDomainName(".foo.", "foo.") + } + } + + // Return the first (len-m) labels: + return s[:slabels[len(slabels)-m]-1] +} diff --git a/vendor/github.com/miekg/dns/dnsutil/util_test.go b/vendor/github.com/miekg/dns/dnsutil/util_test.go new file mode 100644 index 00000000000..0f1ecec8e08 --- /dev/null +++ b/vendor/github.com/miekg/dns/dnsutil/util_test.go @@ -0,0 +1,130 @@ +package dnsutil + +import "testing" + +func TestAddOrigin(t *testing.T) { + var tests = []struct{ e1, e2, expected string }{ + {"@", "example.com", "example.com"}, + {"foo", "example.com", "foo.example.com"}, + {"foo.", "example.com", "foo."}, + {"@", "example.com.", "example.com."}, + {"foo", "example.com.", "foo.example.com."}, + {"foo.", "example.com.", "foo."}, + // Oddball tests: + // In general origin should not be "" or "." but at least + // these tests verify we don't crash and will keep results + // from changing unexpectedly. + {"*.", "", "*."}, + {"@", "", "@"}, + {"foobar", "", "foobar"}, + {"foobar.", "", "foobar."}, + {"*.", ".", "*."}, + {"@", ".", "."}, + {"foobar", ".", "foobar."}, + {"foobar.", ".", "foobar."}, + } + for _, test := range tests { + actual := AddOrigin(test.e1, test.e2) + if test.expected != actual { + t.Errorf("AddOrigin(%#v, %#v) expected %#v, go %#v\n", test.e1, test.e2, test.expected, actual) + } + } +} + +func TestTrimDomainName(t *testing.T) { + + // Basic tests. + // Try trimming "example.com" and "example.com." from typical use cases. + var tests_examplecom = []struct{ experiment, expected string }{ + {"foo.example.com", "foo"}, + {"foo.example.com.", "foo"}, + {".foo.example.com", ".foo"}, + {".foo.example.com.", ".foo"}, + {"*.example.com", "*"}, + {"example.com", "@"}, + {"example.com.", "@"}, + {"com.", "com."}, + {"foo.", "foo."}, + {"serverfault.com.", "serverfault.com."}, + {"serverfault.com", "serverfault.com"}, + {".foo.ronco.com", ".foo.ronco.com"}, + {".foo.ronco.com.", ".foo.ronco.com."}, + } + for _, dom := range []string{"example.com", "example.com."} { + for i, test := range tests_examplecom { + actual := TrimDomainName(test.experiment, dom) + if test.expected != actual { + t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.experiment, dom, test.expected, actual) + } + } + } + + // Paranoid tests. + // These test shouldn't be needed but I was weary of off-by-one errors. + // In theory, these can't happen because there are no single-letter TLDs, + // but it is good to exercize the code this way. + var tests = []struct{ experiment, expected string }{ + {"", "@"}, + {".", "."}, + {"a.b.c.d.e.f.", "a.b.c.d.e"}, + {"b.c.d.e.f.", "b.c.d.e"}, + {"c.d.e.f.", "c.d.e"}, + {"d.e.f.", "d.e"}, + {"e.f.", "e"}, + {"f.", "@"}, + {".a.b.c.d.e.f.", ".a.b.c.d.e"}, + {".b.c.d.e.f.", ".b.c.d.e"}, + {".c.d.e.f.", ".c.d.e"}, + {".d.e.f.", ".d.e"}, + {".e.f.", ".e"}, + {".f.", "@"}, + {"a.b.c.d.e.f", "a.b.c.d.e"}, + {"a.b.c.d.e.", "a.b.c.d.e."}, + {"a.b.c.d.e", "a.b.c.d.e"}, + {"a.b.c.d.", "a.b.c.d."}, + {"a.b.c.d", "a.b.c.d"}, + {"a.b.c.", "a.b.c."}, + {"a.b.c", "a.b.c"}, + {"a.b.", "a.b."}, + {"a.b", "a.b"}, + {"a.", "a."}, + {"a", "a"}, + {".a.b.c.d.e.f", ".a.b.c.d.e"}, + {".a.b.c.d.e.", ".a.b.c.d.e."}, + {".a.b.c.d.e", ".a.b.c.d.e"}, + {".a.b.c.d.", ".a.b.c.d."}, + {".a.b.c.d", ".a.b.c.d"}, + {".a.b.c.", ".a.b.c."}, + {".a.b.c", ".a.b.c"}, + {".a.b.", ".a.b."}, + {".a.b", ".a.b"}, + {".a.", ".a."}, + {".a", ".a"}, + } + for _, dom := range []string{"f", "f."} { + for i, test := range tests { + actual := TrimDomainName(test.experiment, dom) + if test.expected != actual { + t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.experiment, dom, test.expected, actual) + } + } + } + + // Test cases for bugs found in the wild. + // These test cases provide both origin, s, and the expected result. + // If you find a bug in the while, this is probably the easiest place + // to add it as a test case. + var tests_wild = []struct{ e1, e2, expected string }{ + {"mathoverflow.net.", ".", "mathoverflow.net"}, + {"mathoverflow.net", ".", "mathoverflow.net"}, + {"", ".", "@"}, + {"@", ".", "@"}, + } + for i, test := range tests_wild { + actual := TrimDomainName(test.e1, test.e2) + if test.expected != actual { + t.Errorf("%d TrimDomainName(%#v, %#v): expected (%v) got (%v)\n", i, test.e1, test.e2, test.expected, actual) + } + } + +} diff --git a/vendor/github.com/miekg/dns/doc.go b/vendor/github.com/miekg/dns/doc.go index e20f8a4453d..f3555e43399 100644 --- a/vendor/github.com/miekg/dns/doc.go +++ b/vendor/github.com/miekg/dns/doc.go @@ -101,7 +101,7 @@ uses public key cryptography to sign resource records. The public keys are stored in DNSKEY records and the signatures in RRSIG records. Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit -to an request. +to a request. m := new(dns.Msg) m.SetEdns0(4096, true) diff --git a/vendor/github.com/miekg/dns/edns.go b/vendor/github.com/miekg/dns/edns.go index 0c47f6ea5a4..867c810b3a2 100644 --- a/vendor/github.com/miekg/dns/edns.go +++ b/vendor/github.com/miekg/dns/edns.go @@ -96,13 +96,16 @@ func (rr *OPT) SetVersion(v uint8) { } // ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL). -func (rr *OPT) ExtendedRcode() uint8 { - return uint8((rr.Hdr.Ttl & 0xFF000000) >> 24) +func (rr *OPT) ExtendedRcode() int { + return int((rr.Hdr.Ttl&0xFF000000)>>24) + 15 } // SetExtendedRcode sets the EDNS extended RCODE field. func (rr *OPT) SetExtendedRcode(v uint8) { - rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24) + if v < RcodeBadVers { // Smaller than 16.. Use the 4 bits you have! + return + } + rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v-15) << 24) } // UDPSize returns the UDP buffer size. diff --git a/vendor/github.com/miekg/dns/edns_test.go b/vendor/github.com/miekg/dns/edns_test.go index 8ee82ab4264..5fd75abb455 100644 --- a/vendor/github.com/miekg/dns/edns_test.go +++ b/vendor/github.com/miekg/dns/edns_test.go @@ -8,41 +8,25 @@ func TestOPTTtl(t *testing.T) { e.Hdr.Rrtype = TypeOPT if e.Do() { - t.Fail() + t.Errorf("DO bit should be zero") } e.SetDo() if !e.Do() { - t.Fail() + t.Errorf("DO bit should be non-zero") } - oldTtl := e.Hdr.Ttl - if e.Version() != 0 { - t.Fail() + t.Errorf("version should be non-zero") } e.SetVersion(42) if e.Version() != 42 { - t.Fail() - } - - e.SetVersion(0) - if e.Hdr.Ttl != oldTtl { - t.Fail() - } - - if e.ExtendedRcode() != 0 { - t.Fail() + t.Errorf("set 42, expected %d, got %d", 42, e.Version()) } e.SetExtendedRcode(42) if e.ExtendedRcode() != 42 { - t.Fail() - } - - e.SetExtendedRcode(0) - if e.Hdr.Ttl != oldTtl { - t.Fail() + t.Errorf("set 42, expected %d, got %d", 42-15, e.ExtendedRcode()) } } diff --git a/vendor/github.com/miekg/dns/issue_test.go b/vendor/github.com/miekg/dns/issue_test.go new file mode 100644 index 00000000000..3025fc98cb1 --- /dev/null +++ b/vendor/github.com/miekg/dns/issue_test.go @@ -0,0 +1,23 @@ +package dns + +// Tests that solve that an specific issue. + +import "testing" + +func TestTCPRtt(t *testing.T) { + m := new(Msg) + m.RecursionDesired = true + m.SetQuestion("example.org.", TypeA) + + c := &Client{} + for _, proto := range []string{"udp", "tcp"} { + c.Net = proto + _, rtt, err := c.Exchange(m, "8.8.4.4:53") + if err != nil { + t.Fatal(err) + } + if rtt == 0 { + t.Fatalf("expecting non zero rtt %s, got zero", c.Net) + } + } +} diff --git a/vendor/github.com/miekg/dns/labels.go b/vendor/github.com/miekg/dns/labels.go index 3944dd0632f..fca5c7dd2d1 100644 --- a/vendor/github.com/miekg/dns/labels.go +++ b/vendor/github.com/miekg/dns/labels.go @@ -4,9 +4,11 @@ package dns // SplitDomainName splits a name string into it's labels. // www.miek.nl. returns []string{"www", "miek", "nl"} +// .www.miek.nl. returns []string{"", "www", "miek", "nl"}, // The root label (.) returns nil. Note that using // strings.Split(s) will work in most cases, but does not handle // escaped dots (\.) for instance. +// s must be a syntactically valid domain name, see IsDomainName. func SplitDomainName(s string) (labels []string) { if len(s) == 0 { return nil @@ -45,6 +47,8 @@ func SplitDomainName(s string) (labels []string) { // // www.miek.nl. and miek.nl. have two labels in common: miek and nl // www.miek.nl. and www.bla.nl. have one label in common: nl +// +// s1 and s2 must be syntactically valid domain names. func CompareDomainName(s1, s2 string) (n int) { s1 = Fqdn(s1) s2 = Fqdn(s2) @@ -85,6 +89,7 @@ func CompareDomainName(s1, s2 string) (n int) { } // CountLabel counts the the number of labels in the string s. +// s must be a syntactically valid domain name. func CountLabel(s string) (labels int) { if s == "." { return @@ -103,6 +108,7 @@ func CountLabel(s string) (labels int) { // Split splits a name s into its label indexes. // www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. // The root name (.) returns nil. Also see SplitDomainName. +// s must be a syntactically valid domain name. func Split(s string) []int { if s == "." { return nil diff --git a/vendor/github.com/miekg/dns/labels_test.go b/vendor/github.com/miekg/dns/labels_test.go index 2a3f3d05d9f..50f31163730 100644 --- a/vendor/github.com/miekg/dns/labels_test.go +++ b/vendor/github.com/miekg/dns/labels_test.go @@ -1,8 +1,6 @@ package dns -import ( - "testing" -) +import "testing" func TestCompareDomainName(t *testing.T) { s1 := "www.miek.nl." @@ -61,9 +59,9 @@ func TestSplit(t *testing.T) { func TestSplit2(t *testing.T) { splitter := map[string][]int{ - "www.miek.nl.": []int{0, 4, 9}, - "www.miek.nl": []int{0, 4, 9}, - "nl": []int{0}, + "www.miek.nl.": {0, 4, 9}, + "www.miek.nl": {0, 4, 9}, + "nl": {0}, } for s, i := range splitter { x := Split(s) @@ -125,13 +123,14 @@ func TestCountLabel(t *testing.T) { func TestSplitDomainName(t *testing.T) { labels := map[string][]string{ - "miek.nl": []string{"miek", "nl"}, + "miek.nl": {"miek", "nl"}, ".": nil, - "www.miek.nl.": []string{"www", "miek", "nl"}, - "www.miek.nl": []string{"www", "miek", "nl"}, - "www..miek.nl": []string{"www", "", "miek", "nl"}, - `www\.miek.nl`: []string{`www\.miek`, "nl"}, - `www\\.miek.nl`: []string{`www\\`, "miek", "nl"}, + "www.miek.nl.": {"www", "miek", "nl"}, + "www.miek.nl": {"www", "miek", "nl"}, + "www..miek.nl": {"www", "", "miek", "nl"}, + `www\.miek.nl`: {`www\.miek`, "nl"}, + `www\\.miek.nl`: {`www\\`, "miek", "nl"}, + ".www.miek.nl.": {"", "www", "miek", "nl"}, } domainLoop: for domain, splits := range labels { @@ -155,13 +154,13 @@ func TestIsDomainName(t *testing.T) { lab int } names := map[string]*ret{ - "..": &ret{false, 1}, - "@.": &ret{true, 1}, - "www.example.com": &ret{true, 3}, - "www.e%ample.com": &ret{true, 3}, - "www.example.com.": &ret{true, 3}, - "mi\\k.nl.": &ret{true, 2}, - "mi\\k.nl": &ret{true, 2}, + "..": {false, 1}, + "@.": {true, 1}, + "www.example.com": {true, 3}, + "www.e%ample.com": {true, 3}, + "www.example.com.": {true, 3}, + "mi\\k.nl.": {true, 2}, + "mi\\k.nl": {true, 2}, } for d, ok := range names { l, k := IsDomainName(d) diff --git a/vendor/github.com/miekg/dns/msg.go b/vendor/github.com/miekg/dns/msg.go index 692ad64cf04..7274800fc01 100644 --- a/vendor/github.com/miekg/dns/msg.go +++ b/vendor/github.com/miekg/dns/msg.go @@ -1896,6 +1896,11 @@ func Copy(r RR) RR { return r1 } +// Len returns the length (in octets) of the uncompressed RR in wire format. +func Len(r RR) int { + return r.len() +} + // Copy returns a new *Msg which is a deep-copy of dns. func (dns *Msg) Copy() *Msg { return dns.CopyTo(new(Msg)) diff --git a/vendor/github.com/miekg/dns/parse_test.go b/vendor/github.com/miekg/dns/parse_test.go index 0cbcd3ebfcd..a700f644412 100644 --- a/vendor/github.com/miekg/dns/parse_test.go +++ b/vendor/github.com/miekg/dns/parse_test.go @@ -768,7 +768,7 @@ func TestRfc1982(t *testing.T) { } func TestEmpty(t *testing.T) { - for _ = range ParseZone(strings.NewReader(""), "", "") { + for range ParseZone(strings.NewReader(""), "", "") { t.Errorf("should be empty") } } @@ -1208,11 +1208,11 @@ func TestNewPrivateKey(t *testing.T) { t.Skip("skipping test in short mode.") } algorithms := []algorithm{ - algorithm{ECDSAP256SHA256, 256}, - algorithm{ECDSAP384SHA384, 384}, - algorithm{RSASHA1, 1024}, - algorithm{RSASHA256, 2048}, - algorithm{DSA, 1024}, + {ECDSAP256SHA256, 256}, + {ECDSAP384SHA384, 384}, + {RSASHA1, 1024}, + {RSASHA256, 2048}, + {DSA, 1024}, } for _, algo := range algorithms { diff --git a/vendor/github.com/miekg/dns/sanitize_test.go b/vendor/github.com/miekg/dns/sanitize_test.go index 7933572791c..22d8e87987a 100644 --- a/vendor/github.com/miekg/dns/sanitize_test.go +++ b/vendor/github.com/miekg/dns/sanitize_test.go @@ -9,24 +9,24 @@ func TestDedup(t *testing.T) { newRR(t, "mIek.nl. IN A 127.0.0.1"), newRR(t, "mieK.nl. IN A 127.0.0.1"), newRR(t, "miek.Nl. IN A 127.0.0.1"), - }: []string{"mIek.nl.\t3600\tIN\tA\t127.0.0.1"}, + }: {"mIek.nl.\t3600\tIN\tA\t127.0.0.1"}, [...]RR{ newRR(t, "miEk.nl. 2000 IN A 127.0.0.1"), newRR(t, "mieK.Nl. 1000 IN A 127.0.0.1"), newRR(t, "Miek.nL. 500 IN A 127.0.0.1"), - }: []string{"miEk.nl.\t500\tIN\tA\t127.0.0.1"}, + }: {"miEk.nl.\t500\tIN\tA\t127.0.0.1"}, [...]RR{ newRR(t, "miek.nl. IN A 127.0.0.1"), newRR(t, "miek.nl. CH A 127.0.0.1"), newRR(t, "miek.nl. IN A 127.0.0.1"), - }: []string{"miek.nl.\t3600\tIN\tA\t127.0.0.1", + }: {"miek.nl.\t3600\tIN\tA\t127.0.0.1", "miek.nl.\t3600\tCH\tA\t127.0.0.1", }, [...]RR{ newRR(t, "miek.nl. CH A 127.0.0.1"), newRR(t, "miek.nl. IN A 127.0.0.1"), newRR(t, "miek.de. IN A 127.0.0.1"), - }: []string{"miek.nl.\t3600\tCH\tA\t127.0.0.1", + }: {"miek.nl.\t3600\tCH\tA\t127.0.0.1", "miek.nl.\t3600\tIN\tA\t127.0.0.1", "miek.de.\t3600\tIN\tA\t127.0.0.1", }, @@ -34,7 +34,7 @@ func TestDedup(t *testing.T) { newRR(t, "miek.de. IN A 127.0.0.1"), newRR(t, "miek.nl. 200 IN A 127.0.0.1"), newRR(t, "miek.nl. 300 IN A 127.0.0.1"), - }: []string{"miek.de.\t3600\tIN\tA\t127.0.0.1", + }: {"miek.de.\t3600\tIN\tA\t127.0.0.1", "miek.nl.\t200\tIN\tA\t127.0.0.1", }, } @@ -57,7 +57,7 @@ func BenchmarkDedup(b *testing.B) { } m := make(map[string]RR) for i := 0; i < b.N; i++ { - Dedup(rrs,m ) + Dedup(rrs, m) } } diff --git a/vendor/github.com/miekg/dns/tlsa.go b/vendor/github.com/miekg/dns/tlsa.go index f027787df30..0a550dc6cbf 100644 --- a/vendor/github.com/miekg/dns/tlsa.go +++ b/vendor/github.com/miekg/dns/tlsa.go @@ -82,5 +82,5 @@ func TLSAName(name, service, network string) (string, error) { if e != nil { return "", e } - return "_" + strconv.Itoa(p) + "_" + network + "." + name, nil + return "_" + strconv.Itoa(p) + "._" + network + "." + name, nil } diff --git a/vendor/github.com/miekg/dns/tsig.go b/vendor/github.com/miekg/dns/tsig.go index 32616d2d44c..c3374e19407 100644 --- a/vendor/github.com/miekg/dns/tsig.go +++ b/vendor/github.com/miekg/dns/tsig.go @@ -112,7 +112,7 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s t := new(TSIG) var h hash.Hash - switch rr.Algorithm { + switch strings.ToLower(rr.Algorithm) { case HmacMD5: h = hmac.New(md5.New, []byte(rawsecret)) case HmacSHA1: @@ -178,7 +178,7 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { } var h hash.Hash - switch tsig.Algorithm { + switch strings.ToLower(tsig.Algorithm) { case HmacMD5: h = hmac.New(md5.New, rawsecret) case HmacSHA1: diff --git a/vendor/github.com/miekg/dns/tsig_test.go b/vendor/github.com/miekg/dns/tsig_test.go new file mode 100644 index 00000000000..48b9988b662 --- /dev/null +++ b/vendor/github.com/miekg/dns/tsig_test.go @@ -0,0 +1,37 @@ +package dns + +import ( + "testing" + "time" +) + +func newTsig(algo string) *Msg { + m := new(Msg) + m.SetQuestion("example.org.", TypeA) + m.SetTsig("example.", algo, 300, time.Now().Unix()) + return m +} + +func TestTsig(t *testing.T) { + m := newTsig(HmacMD5) + buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) + if err != nil { + t.Fatal(err) + } + err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) + if err != nil { + t.Fatal(err) + } +} + +func TestTsigCase(t *testing.T) { + m := newTsig("HmAc-mD5.sig-ALg.rEg.int.") // HmacMD5 + buf, _, err := TsigGenerate(m, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) + if err != nil { + t.Fatal(err) + } + err = TsigVerify(buf, "pRZgBrBvI4NAHZYhxmhs/Q==", "", false) + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/github.com/miekg/dns/types_generate.go b/vendor/github.com/miekg/dns/types_generate.go index 53690141a10..63bfda0e7ed 100644 --- a/vendor/github.com/miekg/dns/types_generate.go +++ b/vendor/github.com/miekg/dns/types_generate.go @@ -20,11 +20,11 @@ import ( ) var skipLen = map[string]struct{}{ - "NSEC": struct{}{}, - "NSEC3": struct{}{}, - "OPT": struct{}{}, - "WKS": struct{}{}, - "IPSECKEY": struct{}{}, + "NSEC": {}, + "NSEC3": {}, + "OPT": {}, + "WKS": {}, + "IPSECKEY": {}, } var packageHdr = ` @@ -229,7 +229,10 @@ func main() { if sl, ok := st.Field(i).Type().(*types.Slice); ok { t := sl.Underlying().String() t = strings.TrimPrefix(t, "[]") - t = strings.TrimPrefix(t, "github.com/miekg/dns.") + if strings.Contains(t, ".") { + splits := strings.Split(t, ".") + t = splits[len(splits)-1] + } fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", f, t, f, f, f) fields = append(fields, f) diff --git a/vendor/github.com/miekg/dns/udp.go b/vendor/github.com/miekg/dns/udp.go index fc86563744e..c79c6c88371 100644 --- a/vendor/github.com/miekg/dns/udp.go +++ b/vendor/github.com/miekg/dns/udp.go @@ -1,4 +1,4 @@ -// +build !windows +// +build !windows,!plan9 package dns diff --git a/vendor/github.com/miekg/dns/udp_other.go b/vendor/github.com/miekg/dns/udp_other.go index c38dd3e7f0e..d40732441b0 100644 --- a/vendor/github.com/miekg/dns/udp_other.go +++ b/vendor/github.com/miekg/dns/udp_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!plan9 package dns diff --git a/vendor/github.com/miekg/dns/udp_plan9.go b/vendor/github.com/miekg/dns/udp_plan9.go new file mode 100644 index 00000000000..b794deeba0f --- /dev/null +++ b/vendor/github.com/miekg/dns/udp_plan9.go @@ -0,0 +1,34 @@ +package dns + +import ( + "net" +) + +func setUDPSocketOptions(conn *net.UDPConn) error { return nil } + +// SessionUDP holds the remote address and the associated +// out-of-band data. +type SessionUDP struct { + raddr *net.UDPAddr + context []byte +} + +// RemoteAddr returns the remote network address. +func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } + +// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a +// net.UDPAddr. +func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { + oob := make([]byte, 40) + n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) + if err != nil { + return n, nil, err + } + return n, &SessionUDP{raddr, oob[:oobn]}, err +} + +// WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. +func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { + n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) + return n, err +} diff --git a/vendor/github.com/miekg/dns/update.go b/vendor/github.com/miekg/dns/update.go index 3539987ccb0..e90c5c968ec 100644 --- a/vendor/github.com/miekg/dns/update.go +++ b/vendor/github.com/miekg/dns/update.go @@ -3,18 +3,22 @@ package dns // NameUsed sets the RRs in the prereq section to // "Name is in use" RRs. RFC 2136 section 2.4.4. func (u *Msg) NameUsed(rr []RR) { - u.Answer = make([]RR, len(rr)) - for i, r := range rr { - u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) } } // NameNotUsed sets the RRs in the prereq section to // "Name is in not use" RRs. RFC 2136 section 2.4.5. func (u *Msg) NameNotUsed(rr []RR) { - u.Answer = make([]RR, len(rr)) - for i, r := range rr { - u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}} + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}}) } } @@ -24,34 +28,34 @@ func (u *Msg) Used(rr []RR) { if len(u.Question) == 0 { panic("dns: empty question section") } - u.Answer = make([]RR, len(rr)) - for i, r := range rr { - u.Answer[i] = r - u.Answer[i].Header().Class = u.Question[0].Qclass + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + r.Header().Class = u.Question[0].Qclass + u.Answer = append(u.Answer, r) } } // RRsetUsed sets the RRs in the prereq section to // "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. func (u *Msg) RRsetUsed(rr []RR) { - u.Answer = make([]RR, len(rr)) - for i, r := range rr { - u.Answer[i] = r - u.Answer[i].Header().Class = ClassANY - u.Answer[i].Header().Ttl = 0 - u.Answer[i].Header().Rdlength = 0 + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) } } // RRsetNotUsed sets the RRs in the prereq section to // "RRset does not exist" RRs. RFC 2136 section 2.4.3. func (u *Msg) RRsetNotUsed(rr []RR) { - u.Answer = make([]RR, len(rr)) - for i, r := range rr { - u.Answer[i] = r - u.Answer[i].Header().Class = ClassNONE - u.Answer[i].Header().Rdlength = 0 - u.Answer[i].Header().Ttl = 0 + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassNONE}}) } } @@ -60,35 +64,43 @@ func (u *Msg) Insert(rr []RR) { if len(u.Question) == 0 { panic("dns: empty question section") } - u.Ns = make([]RR, len(rr)) - for i, r := range rr { - u.Ns[i] = r - u.Ns[i].Header().Class = u.Question[0].Qclass + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + r.Header().Class = u.Question[0].Qclass + u.Ns = append(u.Ns, r) } } // RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. func (u *Msg) RemoveRRset(rr []RR) { - u.Ns = make([]RR, len(rr)) - for i, r := range rr { - u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}} + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}}) } } // RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 func (u *Msg) RemoveName(rr []RR) { - u.Ns = make([]RR, len(rr)) - for i, r := range rr { - u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) } } -// Remove creates a dynamic update packet deletes RR from the RRSset, see RFC 2136 section 2.5.4 +// Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4 func (u *Msg) Remove(rr []RR) { - u.Ns = make([]RR, len(rr)) - for i, r := range rr { - u.Ns[i] = r - u.Ns[i].Header().Class = ClassNONE - u.Ns[i].Header().Ttl = 0 + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + r.Header().Class = ClassNONE + r.Header().Ttl = 0 + u.Ns = append(u.Ns, r) } } diff --git a/vendor/github.com/miekg/dns/update_test.go b/vendor/github.com/miekg/dns/update_test.go index 1c840309758..43ef61628e9 100644 --- a/vendor/github.com/miekg/dns/update_test.go +++ b/vendor/github.com/miekg/dns/update_test.go @@ -83,3 +83,63 @@ func TestRemoveRRset(t *testing.T) { t.Errorf("actual msg:\n%v", tmp) } } + +func TestPreReqAndRemovals(t *testing.T) { + // Build a list of multiple prereqs and then somes removes followed by an insert. + // We should be able to add multiple prereqs and updates. + m := new(Msg) + m.SetUpdate("example.org.") + m.Id = 1234 + + // Use a full set of RRs each time, so we are sure the rdata is stripped. + rr_name1, _ := NewRR("name_used. 3600 IN A 127.0.0.1") + rr_name2, _ := NewRR("name_not_used. 3600 IN A 127.0.0.1") + rr_remove1, _ := NewRR("remove1. 3600 IN A 127.0.0.1") + rr_remove2, _ := NewRR("remove2. 3600 IN A 127.0.0.1") + rr_remove3, _ := NewRR("remove3. 3600 IN A 127.0.0.1") + rr_insert, _ := NewRR("insert. 3600 IN A 127.0.0.1") + rr_rrset1, _ := NewRR("rrset_used1. 3600 IN A 127.0.0.1") + rr_rrset2, _ := NewRR("rrset_used2. 3600 IN A 127.0.0.1") + rr_rrset3, _ := NewRR("rrset_not_used. 3600 IN A 127.0.0.1") + + // Handle the prereqs. + m.NameUsed([]RR{rr_name1}) + m.NameNotUsed([]RR{rr_name2}) + m.RRsetUsed([]RR{rr_rrset1}) + m.Used([]RR{rr_rrset2}) + m.RRsetNotUsed([]RR{rr_rrset3}) + + // and now the updates. + m.RemoveName([]RR{rr_remove1}) + m.RemoveRRset([]RR{rr_remove2}) + m.Remove([]RR{rr_remove3}) + m.Insert([]RR{rr_insert}) + + // This test function isn't a Example function because we print these RR with tabs at the + // end and the Example function trim these, thus they never match. + // TODO(miek): don't print these tabs and make this into an Example function. + expect := `;; opcode: UPDATE, status: NOERROR, id: 1234 +;; flags:; QUERY: 1, ANSWER: 5, AUTHORITY: 4, ADDITIONAL: 0 + +;; QUESTION SECTION: +;example.org. IN SOA + +;; ANSWER SECTION: +name_used. 0 ANY ANY +name_not_used. 0 NONE ANY +rrset_used1. 0 ANY A +rrset_used2. 3600 IN A 127.0.0.1 +rrset_not_used. 0 NONE A + +;; AUTHORITY SECTION: +remove1. 0 ANY ANY +remove2. 0 ANY A +remove3. 0 NONE A 127.0.0.1 +insert. 3600 IN A 127.0.0.1 +` + + if m.String() != expect { + t.Errorf("expected msg:\n%s", expect) + t.Errorf("actual msg:\n%v", m.String()) + } +} diff --git a/vendor/github.com/miekg/dns/xfr.go b/vendor/github.com/miekg/dns/xfr.go index 7d3a67b8e45..7346deffbb7 100644 --- a/vendor/github.com/miekg/dns/xfr.go +++ b/vendor/github.com/miekg/dns/xfr.go @@ -162,8 +162,8 @@ func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { // // ch := make(chan *dns.Envelope) // tr := new(dns.Transfer) -// tr.Out(w, r, ch) -// c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} +// go tr.Out(w, r, ch) +// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} // close(ch) // w.Hijack() // // w.Close() // Client closes connection diff --git a/vendor/github.com/miekg/dns/zscan_rr.go b/vendor/github.com/miekg/dns/zscan_rr.go index a2db008fa98..ead9614d13b 100644 --- a/vendor/github.com/miekg/dns/zscan_rr.go +++ b/vendor/github.com/miekg/dns/zscan_rr.go @@ -2203,68 +2203,68 @@ func setCAA(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { } var typeToparserFunc = map[uint16]parserFunc{ - TypeAAAA: parserFunc{setAAAA, false}, - TypeAFSDB: parserFunc{setAFSDB, false}, - TypeA: parserFunc{setA, false}, - TypeCAA: parserFunc{setCAA, true}, - TypeCDS: parserFunc{setCDS, true}, - TypeCDNSKEY: parserFunc{setCDNSKEY, true}, - TypeCERT: parserFunc{setCERT, true}, - TypeCNAME: parserFunc{setCNAME, false}, - TypeDHCID: parserFunc{setDHCID, true}, - TypeDLV: parserFunc{setDLV, true}, - TypeDNAME: parserFunc{setDNAME, false}, - TypeKEY: parserFunc{setKEY, true}, - TypeDNSKEY: parserFunc{setDNSKEY, true}, - TypeDS: parserFunc{setDS, true}, - TypeEID: parserFunc{setEID, true}, - TypeEUI48: parserFunc{setEUI48, false}, - TypeEUI64: parserFunc{setEUI64, false}, - TypeGID: parserFunc{setGID, false}, - TypeGPOS: parserFunc{setGPOS, false}, - TypeHINFO: parserFunc{setHINFO, true}, - TypeHIP: parserFunc{setHIP, true}, - TypeIPSECKEY: parserFunc{setIPSECKEY, true}, - TypeKX: parserFunc{setKX, false}, - TypeL32: parserFunc{setL32, false}, - TypeL64: parserFunc{setL64, false}, - TypeLOC: parserFunc{setLOC, true}, - TypeLP: parserFunc{setLP, false}, - TypeMB: parserFunc{setMB, false}, - TypeMD: parserFunc{setMD, false}, - TypeMF: parserFunc{setMF, false}, - TypeMG: parserFunc{setMG, false}, - TypeMINFO: parserFunc{setMINFO, false}, - TypeMR: parserFunc{setMR, false}, - TypeMX: parserFunc{setMX, false}, - TypeNAPTR: parserFunc{setNAPTR, false}, - TypeNID: parserFunc{setNID, false}, - TypeNIMLOC: parserFunc{setNIMLOC, true}, - TypeNINFO: parserFunc{setNINFO, true}, - TypeNSAPPTR: parserFunc{setNSAPPTR, false}, - TypeNSEC3PARAM: parserFunc{setNSEC3PARAM, false}, - TypeNSEC3: parserFunc{setNSEC3, true}, - TypeNSEC: parserFunc{setNSEC, true}, - TypeNS: parserFunc{setNS, false}, - TypeOPENPGPKEY: parserFunc{setOPENPGPKEY, true}, - TypePTR: parserFunc{setPTR, false}, - TypePX: parserFunc{setPX, false}, - TypeSIG: parserFunc{setSIG, true}, - TypeRKEY: parserFunc{setRKEY, true}, - TypeRP: parserFunc{setRP, false}, - TypeRRSIG: parserFunc{setRRSIG, true}, - TypeRT: parserFunc{setRT, false}, - TypeSOA: parserFunc{setSOA, false}, - TypeSPF: parserFunc{setSPF, true}, - TypeSRV: parserFunc{setSRV, false}, - TypeSSHFP: parserFunc{setSSHFP, true}, - TypeTALINK: parserFunc{setTALINK, false}, - TypeTA: parserFunc{setTA, true}, - TypeTLSA: parserFunc{setTLSA, true}, - TypeTXT: parserFunc{setTXT, true}, - TypeUID: parserFunc{setUID, false}, - TypeUINFO: parserFunc{setUINFO, true}, - TypeURI: parserFunc{setURI, true}, - TypeWKS: parserFunc{setWKS, true}, - TypeX25: parserFunc{setX25, false}, + TypeAAAA: {setAAAA, false}, + TypeAFSDB: {setAFSDB, false}, + TypeA: {setA, false}, + TypeCAA: {setCAA, true}, + TypeCDS: {setCDS, true}, + TypeCDNSKEY: {setCDNSKEY, true}, + TypeCERT: {setCERT, true}, + TypeCNAME: {setCNAME, false}, + TypeDHCID: {setDHCID, true}, + TypeDLV: {setDLV, true}, + TypeDNAME: {setDNAME, false}, + TypeKEY: {setKEY, true}, + TypeDNSKEY: {setDNSKEY, true}, + TypeDS: {setDS, true}, + TypeEID: {setEID, true}, + TypeEUI48: {setEUI48, false}, + TypeEUI64: {setEUI64, false}, + TypeGID: {setGID, false}, + TypeGPOS: {setGPOS, false}, + TypeHINFO: {setHINFO, true}, + TypeHIP: {setHIP, true}, + TypeIPSECKEY: {setIPSECKEY, true}, + TypeKX: {setKX, false}, + TypeL32: {setL32, false}, + TypeL64: {setL64, false}, + TypeLOC: {setLOC, true}, + TypeLP: {setLP, false}, + TypeMB: {setMB, false}, + TypeMD: {setMD, false}, + TypeMF: {setMF, false}, + TypeMG: {setMG, false}, + TypeMINFO: {setMINFO, false}, + TypeMR: {setMR, false}, + TypeMX: {setMX, false}, + TypeNAPTR: {setNAPTR, false}, + TypeNID: {setNID, false}, + TypeNIMLOC: {setNIMLOC, true}, + TypeNINFO: {setNINFO, true}, + TypeNSAPPTR: {setNSAPPTR, false}, + TypeNSEC3PARAM: {setNSEC3PARAM, false}, + TypeNSEC3: {setNSEC3, true}, + TypeNSEC: {setNSEC, true}, + TypeNS: {setNS, false}, + TypeOPENPGPKEY: {setOPENPGPKEY, true}, + TypePTR: {setPTR, false}, + TypePX: {setPX, false}, + TypeSIG: {setSIG, true}, + TypeRKEY: {setRKEY, true}, + TypeRP: {setRP, false}, + TypeRRSIG: {setRRSIG, true}, + TypeRT: {setRT, false}, + TypeSOA: {setSOA, false}, + TypeSPF: {setSPF, true}, + TypeSRV: {setSRV, false}, + TypeSSHFP: {setSSHFP, true}, + TypeTALINK: {setTALINK, false}, + TypeTA: {setTA, true}, + TypeTLSA: {setTLSA, true}, + TypeTXT: {setTXT, true}, + TypeUID: {setUID, false}, + TypeUINFO: {setUINFO, true}, + TypeURI: {setURI, true}, + TypeWKS: {setWKS, true}, + TypeX25: {setX25, false}, }