| @ -0,0 +1,4 @@ | |||
| *.6 | |||
| tags | |||
| test.out | |||
| a.out | |||
| @ -0,0 +1,7 @@ | |||
| language: go | |||
| sudo: false | |||
| go: | |||
| - 1.4 | |||
| - 1.5 | |||
| script: | |||
| - go test -race -v -bench=. | |||
| @ -0,0 +1 @@ | |||
| Miek Gieben <miek@miek.nl> | |||
| @ -0,0 +1,9 @@ | |||
| Alex A. Skinner | |||
| Andrew Tunnell-Jones | |||
| Ask Bjørn Hansen | |||
| Dave Cheney | |||
| Dusty Wilson | |||
| Marek Majkowski | |||
| Peter van Dijk | |||
| Omri Bahumi | |||
| Alex Sergeyev | |||
| @ -0,0 +1,9 @@ | |||
| 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. | |||
| Extensions of the original work are copyright (c) 2011 Miek Gieben | |||
| Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is | |||
| governed by a BSD-style license that can be found in the LICENSE file. | |||
| Copyright 2014 CloudFlare. All rights reserved. Use of this source code is | |||
| governed by a BSD-style license that can be found in the LICENSE file. | |||
| @ -0,0 +1,32 @@ | |||
| Extensions of the original work are copyright (c) 2011 Miek Gieben | |||
| As this is fork of the official Go code the same license applies: | |||
| Copyright (c) 2009 The Go Authors. All rights reserved. | |||
| Redistribution and use in source and binary forms, with or without | |||
| modification, are permitted provided that the following conditions are | |||
| met: | |||
| * Redistributions of source code must retain the above copyright | |||
| notice, this list of conditions and the following disclaimer. | |||
| * Redistributions in binary form must reproduce the above | |||
| copyright notice, this list of conditions and the following disclaimer | |||
| in the documentation and/or other materials provided with the | |||
| distribution. | |||
| * Neither the name of Google Inc. nor the names of its | |||
| contributors may be used to endorse or promote products derived from | |||
| this software without specific prior written permission. | |||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| @ -0,0 +1,155 @@ | |||
| [](https://travis-ci.org/miekg/dns) | |||
| # Alternative (more granular) approach to a DNS library | |||
| > Less is more. | |||
| Complete and usable DNS library. All widely used Resource Records are | |||
| supported, including the DNSSEC types. It follows a lean and mean philosophy. | |||
| If there is stuff you should know as a DNS programmer there isn't a convenience | |||
| function for it. Server side and client side programming is supported, i.e. you | |||
| 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. | |||
| # Goals | |||
| * KISS; | |||
| * Fast; | |||
| * Small API, if its easy to code in Go, don't make a function for it. | |||
| # Users | |||
| A not-so-up-to-date-list-that-may-be-actually-current: | |||
| * https://cloudflare.com | |||
| * https://github.com/abh/geodns | |||
| * http://www.statdns.com/ | |||
| * http://www.dnsinspect.com/ | |||
| * https://github.com/chuangbo/jianbing-dictionary-dns | |||
| * http://www.dns-lg.com/ | |||
| * https://github.com/fcambus/rrda | |||
| * https://github.com/kenshinx/godns | |||
| * https://github.com/skynetservices/skydns | |||
| * https://github.com/DevelopersPL/godnsagent | |||
| * https://github.com/duedil-ltd/discodns | |||
| * https://github.com/StalkR/dns-reverse-proxy | |||
| * https://github.com/tianon/rawdns | |||
| * https://mesosphere.github.io/mesos-dns/ | |||
| * https://pulse.turbobytes.com/ | |||
| * 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://namesmith.io | |||
| * https://github.com/miekg/unbound | |||
| * https://github.com/miekg/exdns | |||
| Send pull request if you want to be listed here. | |||
| # Features | |||
| * UDP/TCP queries, IPv4 and IPv6; | |||
| * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported; | |||
| * Fast: | |||
| * Reply speed around ~ 80K qps (faster hardware results in more qps); | |||
| * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds; | |||
| * Server side programming (mimicking the net/http package); | |||
| * Client side programming; | |||
| * DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA; | |||
| * EDNS0, NSID; | |||
| * AXFR/IXFR; | |||
| * TSIG, SIG(0); | |||
| * DNS over TLS: optional encrypted connection between client and server; | |||
| * DNS name compression; | |||
| * Depends only on the standard library. | |||
| Have fun! | |||
| Miek Gieben - 2010-2012 - <miek@miek.nl> | |||
| # Building | |||
| Building is done with the `go` tool. If you have setup your GOPATH | |||
| correctly, the following should work: | |||
| go get github.com/miekg/dns | |||
| go build github.com/miekg/dns | |||
| ## Examples | |||
| A short "how to use the API" is at the beginning of doc.go (this also will show | |||
| when you call `godoc github.com/miekg/dns`). | |||
| Example programs can be found in the `github.com/miekg/exdns` repository. | |||
| ## Supported RFCs | |||
| *all of them* | |||
| * 103{4,5} - DNS standard | |||
| * 1348 - NSAP record (removed the record) | |||
| * 1982 - Serial Arithmetic | |||
| * 1876 - LOC record | |||
| * 1995 - IXFR | |||
| * 1996 - DNS notify | |||
| * 2136 - DNS Update (dynamic updates) | |||
| * 2181 - RRset definition - there is no RRset type though, just []RR | |||
| * 2537 - RSAMD5 DNS keys | |||
| * 2065 - DNSSEC (updated in later RFCs) | |||
| * 2671 - EDNS record | |||
| * 2782 - SRV record | |||
| * 2845 - TSIG record | |||
| * 2915 - NAPTR record | |||
| * 2929 - DNS IANA Considerations | |||
| * 3110 - RSASHA1 DNS keys | |||
| * 3225 - DO bit (DNSSEC OK) | |||
| * 340{1,2,3} - NAPTR record | |||
| * 3445 - Limiting the scope of (DNS)KEY | |||
| * 3597 - Unknown RRs | |||
| * 4025 - IPSECKEY | |||
| * 403{3,4,5} - DNSSEC + validation functions | |||
| * 4255 - SSHFP record | |||
| * 4343 - Case insensitivity | |||
| * 4408 - SPF record | |||
| * 4509 - SHA256 Hash in DS | |||
| * 4592 - Wildcards in the DNS | |||
| * 4635 - HMAC SHA TSIG | |||
| * 4701 - DHCID | |||
| * 4892 - id.server | |||
| * 5001 - NSID | |||
| * 5155 - NSEC3 record | |||
| * 5205 - HIP record | |||
| * 5702 - SHA2 in the DNS | |||
| * 5936 - AXFR | |||
| * 5966 - TCP implementation recommendations | |||
| * 6605 - ECDSA | |||
| * 6725 - IANA Registry Update | |||
| * 6742 - ILNP DNS | |||
| * 6840 - Clarifications and Implementation Notes for DNS Security | |||
| * 6844 - CAA record | |||
| * 6891 - EDNS0 update | |||
| * 6895 - DNS IANA considerations | |||
| * 6975 - Algorithm Understanding in DNSSEC | |||
| * 7043 - EUI48/EUI64 records | |||
| * 7314 - DNS (EDNS) EXPIRE Option | |||
| * 7553 - URI record | |||
| * xxxx - EDNS0 DNS Update Lease (draft) | |||
| * yyyy - DNS over TLS: Initiation and Performance Considerations (draft) | |||
| ## Loosely based upon | |||
| * `ldns` | |||
| * `NSD` | |||
| * `Net::DNS` | |||
| * `GRONG` | |||
| ## TODO | |||
| * privatekey.Precompute() when signing? | |||
| * Last remaining RRs: APL, ATMA, A6, NSAP and NXT. | |||
| * Missing in parsing: ISDN, UNSPEC, NSAP and ATMA. | |||
| * NSEC(3) cover/match/closest enclose. | |||
| * Replies with TC bit are not parsed to the end. | |||
| @ -0,0 +1,440 @@ | |||
| package dns | |||
| // A client implementation. | |||
| import ( | |||
| "bytes" | |||
| "crypto/tls" | |||
| "io" | |||
| "net" | |||
| "time" | |||
| ) | |||
| const dnsTimeout time.Duration = 2 * time.Second | |||
| const tcpIdleTimeout time.Duration = 8 * time.Second | |||
| // A Conn represents a connection to a DNS server. | |||
| type Conn struct { | |||
| net.Conn // a net.Conn holding the connection | |||
| UDPSize uint16 // minimum receive buffer for UDP messages | |||
| TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified | |||
| rtt time.Duration | |||
| t time.Time | |||
| tsigRequestMAC string | |||
| } | |||
| // A Client defines parameters for a DNS client. | |||
| 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 | |||
| TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified | |||
| SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass | |||
| group singleflight | |||
| } | |||
| // 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() | |||
| // | |||
| func Exchange(m *Msg, a string) (r *Msg, err error) { | |||
| var co *Conn | |||
| co, err = DialTimeout("udp", a, dnsTimeout) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer co.Close() | |||
| opt := m.IsEdns0() | |||
| // If EDNS0 is used use that for size. | |||
| if opt != nil && opt.UDPSize() >= MinMsgSize { | |||
| co.UDPSize = opt.UDPSize() | |||
| } | |||
| co.SetWriteDeadline(time.Now().Add(dnsTimeout)) | |||
| if err = co.WriteMsg(m); err != nil { | |||
| return nil, err | |||
| } | |||
| co.SetReadDeadline(time.Now().Add(dnsTimeout)) | |||
| r, err = co.ReadMsg() | |||
| if err == nil && r.Id != m.Id { | |||
| err = ErrId | |||
| } | |||
| return r, err | |||
| } | |||
| // ExchangeConn performs a synchronous query. It sends the message m via the connection | |||
| // c and waits for a reply. The connection c is not closed by ExchangeConn. | |||
| // This function is going away, but can easily be mimicked: | |||
| // | |||
| // co := &dns.Conn{Conn: c} // c is your net.Conn | |||
| // co.WriteMsg(m) | |||
| // in, _ := co.ReadMsg() | |||
| // co.Close() | |||
| // | |||
| func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { | |||
| println("dns: this function is deprecated") | |||
| co := new(Conn) | |||
| co.Conn = c | |||
| if err = co.WriteMsg(m); err != nil { | |||
| return nil, err | |||
| } | |||
| r, err = co.ReadMsg() | |||
| if err == nil && r.Id != m.Id { | |||
| err = ErrId | |||
| } | |||
| return r, err | |||
| } | |||
| // Exchange performs an synchronous query. It sends the message m to the address | |||
| // contained in a and waits for an reply. Basic use pattern with a *dns.Client: | |||
| // | |||
| // c := new(dns.Client) | |||
| // in, rtt, err := c.Exchange(message, "127.0.0.1:53") | |||
| // | |||
| // Exchange does not retry a failed query, nor will it fall back to TCP in | |||
| // case of truncation. | |||
| func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { | |||
| if !c.SingleInflight { | |||
| return c.exchange(m, a) | |||
| } | |||
| // This adds a bunch of garbage, TODO(miek). | |||
| t := "nop" | |||
| if t1, ok := TypeToString[m.Question[0].Qtype]; ok { | |||
| t = t1 | |||
| } | |||
| cl := "nop" | |||
| if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { | |||
| cl = cl1 | |||
| } | |||
| r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { | |||
| return c.exchange(m, a) | |||
| }) | |||
| if err != nil { | |||
| return r, rtt, err | |||
| } | |||
| if shared { | |||
| return r.Copy(), rtt, nil | |||
| } | |||
| return r, rtt, nil | |||
| } | |||
| func (c *Client) dialTimeout() time.Duration { | |||
| if c.DialTimeout != 0 { | |||
| return c.DialTimeout | |||
| } | |||
| return dnsTimeout | |||
| } | |||
| func (c *Client) readTimeout() time.Duration { | |||
| if c.ReadTimeout != 0 { | |||
| return c.ReadTimeout | |||
| } | |||
| return dnsTimeout | |||
| } | |||
| func (c *Client) writeTimeout() time.Duration { | |||
| if c.WriteTimeout != 0 { | |||
| return c.WriteTimeout | |||
| } | |||
| return dnsTimeout | |||
| } | |||
| func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { | |||
| var co *Conn | |||
| network := "udp" | |||
| tls := false | |||
| switch c.Net { | |||
| case "tcp-tls": | |||
| network = "tcp" | |||
| tls = true | |||
| case "tcp4-tls": | |||
| network = "tcp4" | |||
| tls = true | |||
| case "tcp6-tls": | |||
| network = "tcp6" | |||
| tls = true | |||
| default: | |||
| if c.Net != "" { | |||
| network = c.Net | |||
| } | |||
| } | |||
| if tls { | |||
| co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, c.dialTimeout()) | |||
| } else { | |||
| co, err = DialTimeout(network, a, c.dialTimeout()) | |||
| } | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| defer co.Close() | |||
| opt := m.IsEdns0() | |||
| // If EDNS0 is used use that for size. | |||
| if opt != nil && opt.UDPSize() >= MinMsgSize { | |||
| co.UDPSize = opt.UDPSize() | |||
| } | |||
| // Otherwise use the client's configured UDP size. | |||
| if opt == nil && c.UDPSize >= MinMsgSize { | |||
| co.UDPSize = c.UDPSize | |||
| } | |||
| co.TsigSecret = c.TsigSecret | |||
| co.SetWriteDeadline(time.Now().Add(c.writeTimeout())) | |||
| if err = co.WriteMsg(m); err != nil { | |||
| return nil, 0, err | |||
| } | |||
| co.SetReadDeadline(time.Now().Add(c.readTimeout())) | |||
| r, err = co.ReadMsg() | |||
| if err == nil && r.Id != m.Id { | |||
| err = ErrId | |||
| } | |||
| return r, co.rtt, err | |||
| } | |||
| // ReadMsg reads a message from the connection co. | |||
| // If the received message contains a TSIG record the transaction | |||
| // signature is verified. | |||
| func (co *Conn) ReadMsg() (*Msg, error) { | |||
| p, err := co.ReadMsgHeader(nil) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| m := new(Msg) | |||
| if err := m.Unpack(p); err != nil { | |||
| // If ErrTruncated was returned, we still want to allow the user to use | |||
| // the message, but naively they can just check err if they don't want | |||
| // to use a truncated message | |||
| if err == ErrTruncated { | |||
| return m, err | |||
| } | |||
| return nil, err | |||
| } | |||
| if t := m.IsTsig(); t != nil { | |||
| if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { | |||
| return m, ErrSecret | |||
| } | |||
| // Need to work on the original message p, as that was used to calculate the tsig. | |||
| err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) | |||
| } | |||
| return m, err | |||
| } | |||
| // ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil). | |||
| // Returns message as a byte slice to be parsed with Msg.Unpack later on. | |||
| // Note that error handling on the message body is not possible as only the header is parsed. | |||
| func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { | |||
| var ( | |||
| p []byte | |||
| n int | |||
| err error | |||
| ) | |||
| switch t := co.Conn.(type) { | |||
| case *net.TCPConn, *tls.Conn: | |||
| r := t.(io.Reader) | |||
| // First two bytes specify the length of the entire message. | |||
| l, err := tcpMsgLen(r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| p = make([]byte, l) | |||
| n, err = tcpRead(r, p) | |||
| default: | |||
| if co.UDPSize > MinMsgSize { | |||
| p = make([]byte, co.UDPSize) | |||
| } else { | |||
| p = make([]byte, MinMsgSize) | |||
| } | |||
| n, err = co.Read(p) | |||
| } | |||
| if err != nil { | |||
| return nil, err | |||
| } else if n < headerSize { | |||
| return nil, ErrShortRead | |||
| } | |||
| p = p[:n] | |||
| if hdr != nil { | |||
| if _, err = UnpackStruct(hdr, p, 0); err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| return p, err | |||
| } | |||
| // tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length. | |||
| func tcpMsgLen(t io.Reader) (int, error) { | |||
| p := []byte{0, 0} | |||
| n, err := t.Read(p) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if n != 2 { | |||
| return 0, ErrShortRead | |||
| } | |||
| l, _ := unpackUint16(p, 0) | |||
| if l == 0 { | |||
| return 0, ErrShortRead | |||
| } | |||
| return int(l), nil | |||
| } | |||
| // tcpRead calls TCPConn.Read enough times to fill allocated buffer. | |||
| func tcpRead(t io.Reader, p []byte) (int, error) { | |||
| n, err := t.Read(p) | |||
| if err != nil { | |||
| return n, err | |||
| } | |||
| for n < len(p) { | |||
| j, err := t.Read(p[n:]) | |||
| if err != nil { | |||
| return n, err | |||
| } | |||
| n += j | |||
| } | |||
| return n, err | |||
| } | |||
| // Read implements the net.Conn read method. | |||
| func (co *Conn) Read(p []byte) (n int, err error) { | |||
| if co.Conn == nil { | |||
| return 0, ErrConnEmpty | |||
| } | |||
| if len(p) < 2 { | |||
| return 0, io.ErrShortBuffer | |||
| } | |||
| switch t := co.Conn.(type) { | |||
| case *net.TCPConn, *tls.Conn: | |||
| r := t.(io.Reader) | |||
| l, err := tcpMsgLen(r) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if l > len(p) { | |||
| return int(l), io.ErrShortBuffer | |||
| } | |||
| return tcpRead(r, p[:l]) | |||
| } | |||
| // UDP connection | |||
| n, err = co.Conn.Read(p) | |||
| if err != nil { | |||
| return n, err | |||
| } | |||
| co.rtt = time.Since(co.t) | |||
| return n, err | |||
| } | |||
| // WriteMsg sends a message throught the connection co. | |||
| // If the message m contains a TSIG record the transaction | |||
| // signature is calculated. | |||
| func (co *Conn) WriteMsg(m *Msg) (err error) { | |||
| var out []byte | |||
| if t := m.IsTsig(); t != nil { | |||
| mac := "" | |||
| if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { | |||
| return ErrSecret | |||
| } | |||
| out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) | |||
| // Set for the next read, allthough only used in zone transfers | |||
| co.tsigRequestMAC = mac | |||
| } else { | |||
| out, err = m.Pack() | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| co.t = time.Now() | |||
| if _, err = co.Write(out); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // Write implements the net.Conn Write method. | |||
| func (co *Conn) Write(p []byte) (n int, err error) { | |||
| switch t := co.Conn.(type) { | |||
| case *net.TCPConn, *tls.Conn: | |||
| w := t.(io.Writer) | |||
| lp := len(p) | |||
| if lp < 2 { | |||
| return 0, io.ErrShortBuffer | |||
| } | |||
| if lp > MaxMsgSize { | |||
| return 0, &Error{err: "message too large"} | |||
| } | |||
| l := make([]byte, 2, lp+2) | |||
| l[0], l[1] = packUint16(uint16(lp)) | |||
| p = append(l, p...) | |||
| n, err := io.Copy(w, bytes.NewReader(p)) | |||
| return int(n), err | |||
| } | |||
| n, err = co.Conn.(*net.UDPConn).Write(p) | |||
| return n, err | |||
| } | |||
| // Dial connects to the address on the named network. | |||
| func Dial(network, address string) (conn *Conn, err error) { | |||
| conn = new(Conn) | |||
| conn.Conn, err = net.Dial(network, address) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return conn, nil | |||
| } | |||
| // DialTimeout acts like Dial but takes a timeout. | |||
| func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { | |||
| conn = new(Conn) | |||
| conn.Conn, err = net.DialTimeout(network, address, timeout) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return conn, nil | |||
| } | |||
| // DialWithTLS connects to the address on the named network with TLS. | |||
| func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) { | |||
| conn = new(Conn) | |||
| conn.Conn, err = tls.Dial(network, address, tlsConfig) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return conn, nil | |||
| } | |||
| // DialTimeoutWithTLS acts like DialWithTLS but takes a timeout. | |||
| func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) { | |||
| var dialer net.Dialer | |||
| dialer.Timeout = timeout | |||
| conn = new(Conn) | |||
| conn.Conn, err = tls.DialWithDialer(&dialer, network, address, tlsConfig) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return conn, nil | |||
| } | |||
| @ -0,0 +1,421 @@ | |||
| package dns | |||
| import ( | |||
| "crypto/tls" | |||
| "fmt" | |||
| "net" | |||
| "strconv" | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func TestClientSync(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeSOA) | |||
| c := new(Client) | |||
| r, _, err := c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| if r != nil && r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| // And now with plain Exchange(). | |||
| r, err = Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| if r == nil || r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| } | |||
| func TestClientTLSSync(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock) | |||
| if err != nil { | |||
| t.Fatalf("unable to build certificate: %v", err) | |||
| } | |||
| config := tls.Config{ | |||
| Certificates: []tls.Certificate{cert}, | |||
| } | |||
| s, addrstr, err := RunLocalTLSServer("127.0.0.1:0", &config) | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeSOA) | |||
| c := new(Client) | |||
| c.Net = "tcp-tls" | |||
| c.TLSConfig = &tls.Config{ | |||
| InsecureSkipVerify: true, | |||
| } | |||
| r, _, err := c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| if r != nil && r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| } | |||
| func TestClientSyncBadId(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServerBadId) | |||
| defer HandleRemove("miek.nl.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeSOA) | |||
| c := new(Client) | |||
| if _, _, err := c.Exchange(m, addrstr); err != ErrId { | |||
| t.Errorf("did not find a bad Id") | |||
| } | |||
| // And now with plain Exchange(). | |||
| if _, err := Exchange(m, addrstr); err != ErrId { | |||
| t.Errorf("did not find a bad Id") | |||
| } | |||
| } | |||
| func TestClientEDNS0(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeDNSKEY) | |||
| m.SetEdns0(2048, true) | |||
| c := new(Client) | |||
| r, _, err := c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| if r != nil && r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| } | |||
| // Validates the transmission and parsing of local EDNS0 options. | |||
| func TestClientEDNS0Local(t *testing.T) { | |||
| optStr1 := "1979:0x0707" | |||
| optStr2 := strconv.Itoa(EDNS0LOCALSTART) + ":0x0601" | |||
| handler := func(w ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Extra = make([]RR, 1, 2) | |||
| m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello local edns"}} | |||
| // If the local options are what we expect, then reflect them back. | |||
| ec1 := req.Extra[0].(*OPT).Option[0].(*EDNS0_LOCAL).String() | |||
| ec2 := req.Extra[0].(*OPT).Option[1].(*EDNS0_LOCAL).String() | |||
| if ec1 == optStr1 && ec2 == optStr2 { | |||
| m.Extra = append(m.Extra, req.Extra[0]) | |||
| } | |||
| w.WriteMsg(m) | |||
| } | |||
| HandleFunc("miek.nl.", handler) | |||
| defer HandleRemove("miek.nl.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %s", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeTXT) | |||
| // Add two local edns options to the query. | |||
| ec1 := &EDNS0_LOCAL{Code: 1979, Data: []byte{7, 7}} | |||
| ec2 := &EDNS0_LOCAL{Code: EDNS0LOCALSTART, Data: []byte{6, 1}} | |||
| o := &OPT{Hdr: RR_Header{Name: ".", Rrtype: TypeOPT}, Option: []EDNS0{ec1, ec2}} | |||
| m.Extra = append(m.Extra, o) | |||
| c := new(Client) | |||
| r, _, e := c.Exchange(m, addrstr) | |||
| if e != nil { | |||
| t.Logf("failed to exchange: %s", e.Error()) | |||
| t.Fail() | |||
| } | |||
| if r != nil && r.Rcode != RcodeSuccess { | |||
| t.Log("failed to get a valid answer") | |||
| t.Fail() | |||
| t.Logf("%v\n", r) | |||
| } | |||
| txt := r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello local edns" { | |||
| t.Log("Unexpected result for miek.nl", txt, "!= Hello local edns") | |||
| t.Fail() | |||
| } | |||
| // Validate the local options in the reply. | |||
| got := r.Extra[1].(*OPT).Option[0].(*EDNS0_LOCAL).String() | |||
| if got != optStr1 { | |||
| t.Logf("failed to get local edns0 answer; got %s, expected %s", got, optStr1) | |||
| t.Fail() | |||
| t.Logf("%v\n", r) | |||
| } | |||
| got = r.Extra[1].(*OPT).Option[1].(*EDNS0_LOCAL).String() | |||
| if got != optStr2 { | |||
| t.Logf("failed to get local edns0 answer; got %s, expected %s", got, optStr2) | |||
| t.Fail() | |||
| t.Logf("%v\n", r) | |||
| } | |||
| } | |||
| // ExampleTsigSecret_updateLeaseTSIG shows how to update a lease signed with TSIG | |||
| func ExampleTsigSecret_updateLeaseTSIG() { | |||
| m := new(Msg) | |||
| m.SetUpdate("t.local.ip6.io.") | |||
| rr, _ := NewRR("t.local.ip6.io. 30 A 127.0.0.1") | |||
| rrs := make([]RR, 1) | |||
| rrs[0] = rr | |||
| m.Insert(rrs) | |||
| leaseRr := new(OPT) | |||
| leaseRr.Hdr.Name = "." | |||
| leaseRr.Hdr.Rrtype = TypeOPT | |||
| e := new(EDNS0_UL) | |||
| e.Code = EDNS0UL | |||
| e.Lease = 120 | |||
| leaseRr.Option = append(leaseRr.Option, e) | |||
| m.Extra = append(m.Extra, leaseRr) | |||
| c := new(Client) | |||
| m.SetTsig("polvi.", HmacMD5, 300, time.Now().Unix()) | |||
| c.TsigSecret = map[string]string{"polvi.": "pRZgBrBvI4NAHZYhxmhs/Q=="} | |||
| _, _, err := c.Exchange(m, "127.0.0.1:53") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| } | |||
| func TestClientConn(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| // This uses TCP just to make it slightly different than TestClientSync | |||
| s, addrstr, err := RunLocalTCPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeSOA) | |||
| cn, err := Dial("tcp", addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to dial %s: %v", addrstr, err) | |||
| } | |||
| err = cn.WriteMsg(m) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| r, err := cn.ReadMsg() | |||
| if r == nil || r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| err = cn.WriteMsg(m) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| h := new(Header) | |||
| buf, err := cn.ReadMsgHeader(h) | |||
| if buf == nil { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| if int(h.Bits&0xF) != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer in ReadMsgHeader\n%v", r) | |||
| } | |||
| if h.Ancount != 0 || h.Qdcount != 1 || h.Nscount != 0 || h.Arcount != 1 { | |||
| t.Errorf("expected to have question and additional in response; got something else: %+v", h) | |||
| } | |||
| if err = r.Unpack(buf); err != nil { | |||
| t.Errorf("unable to unpack message fully: %v", err) | |||
| } | |||
| } | |||
| func TestTruncatedMsg(t *testing.T) { | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeSRV) | |||
| cnt := 10 | |||
| for i := 0; i < cnt; i++ { | |||
| r := &SRV{ | |||
| Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeSRV, Class: ClassINET, Ttl: 0}, | |||
| Port: uint16(i + 8000), | |||
| Target: "target.miek.nl.", | |||
| } | |||
| m.Answer = append(m.Answer, r) | |||
| re := &A{ | |||
| Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeA, Class: ClassINET, Ttl: 0}, | |||
| A: net.ParseIP(fmt.Sprintf("127.0.0.%d", i)).To4(), | |||
| } | |||
| m.Extra = append(m.Extra, re) | |||
| } | |||
| buf, err := m.Pack() | |||
| if err != nil { | |||
| t.Errorf("failed to pack: %v", err) | |||
| } | |||
| r := new(Msg) | |||
| if err = r.Unpack(buf); err != nil { | |||
| t.Errorf("unable to unpack message: %v", err) | |||
| } | |||
| if len(r.Answer) != cnt { | |||
| t.Logf("answer count after regular unpack doesn't match: %d", len(r.Answer)) | |||
| t.Fail() | |||
| } | |||
| if len(r.Extra) != cnt { | |||
| t.Logf("extra count after regular unpack doesn't match: %d", len(r.Extra)) | |||
| t.Fail() | |||
| } | |||
| m.Truncated = true | |||
| buf, err = m.Pack() | |||
| if err != nil { | |||
| t.Errorf("failed to pack truncated: %v", err) | |||
| } | |||
| r = new(Msg) | |||
| if err = r.Unpack(buf); err != nil && err != ErrTruncated { | |||
| t.Errorf("unable to unpack truncated message: %v", err) | |||
| } | |||
| if !r.Truncated { | |||
| t.Log("truncated message wasn't unpacked as truncated") | |||
| t.Fail() | |||
| } | |||
| if len(r.Answer) != cnt { | |||
| t.Logf("answer count after truncated unpack doesn't match: %d", len(r.Answer)) | |||
| t.Fail() | |||
| } | |||
| if len(r.Extra) != cnt { | |||
| t.Logf("extra count after truncated unpack doesn't match: %d", len(r.Extra)) | |||
| t.Fail() | |||
| } | |||
| // Now we want to remove almost all of the extra records | |||
| // We're going to loop over the extra to get the count of the size of all | |||
| // of them | |||
| off := 0 | |||
| buf1 := make([]byte, m.Len()) | |||
| for i := 0; i < len(m.Extra); i++ { | |||
| off, err = PackRR(m.Extra[i], buf1, off, nil, m.Compress) | |||
| if err != nil { | |||
| t.Errorf("failed to pack extra: %v", err) | |||
| } | |||
| } | |||
| // Remove all of the extra bytes but 10 bytes from the end of buf | |||
| off -= 10 | |||
| buf1 = buf[:len(buf)-off] | |||
| r = new(Msg) | |||
| if err = r.Unpack(buf1); err != nil && err != ErrTruncated { | |||
| t.Errorf("unable to unpack cutoff message: %v", err) | |||
| } | |||
| if !r.Truncated { | |||
| t.Log("truncated cutoff message wasn't unpacked as truncated") | |||
| t.Fail() | |||
| } | |||
| if len(r.Answer) != cnt { | |||
| t.Logf("answer count after cutoff unpack doesn't match: %d", len(r.Answer)) | |||
| t.Fail() | |||
| } | |||
| if len(r.Extra) != 0 { | |||
| t.Logf("extra count after cutoff unpack is not zero: %d", len(r.Extra)) | |||
| t.Fail() | |||
| } | |||
| // Now we want to remove almost all of the answer records too | |||
| buf1 = make([]byte, m.Len()) | |||
| as := 0 | |||
| for i := 0; i < len(m.Extra); i++ { | |||
| off1 := off | |||
| off, err = PackRR(m.Extra[i], buf1, off, nil, m.Compress) | |||
| as = off - off1 | |||
| if err != nil { | |||
| t.Errorf("failed to pack extra: %v", err) | |||
| } | |||
| } | |||
| // Keep exactly one answer left | |||
| // This should still cause Answer to be nil | |||
| off -= as | |||
| buf1 = buf[:len(buf)-off] | |||
| r = new(Msg) | |||
| if err = r.Unpack(buf1); err != nil && err != ErrTruncated { | |||
| t.Errorf("unable to unpack cutoff message: %v", err) | |||
| } | |||
| if !r.Truncated { | |||
| t.Log("truncated cutoff message wasn't unpacked as truncated") | |||
| t.Fail() | |||
| } | |||
| if len(r.Answer) != 0 { | |||
| t.Logf("answer count after second cutoff unpack is not zero: %d", len(r.Answer)) | |||
| t.Fail() | |||
| } | |||
| // Now leave only 1 byte of the question | |||
| // Since the header is always 12 bytes, we just need to keep 13 | |||
| buf1 = buf[:13] | |||
| r = new(Msg) | |||
| err = r.Unpack(buf1) | |||
| if err == nil || err == ErrTruncated { | |||
| t.Logf("error should not be ErrTruncated from question cutoff unpack: %v", err) | |||
| t.Fail() | |||
| } | |||
| // Finally, if we only have the header, we should still return an error | |||
| buf1 = buf[:12] | |||
| r = new(Msg) | |||
| if err = r.Unpack(buf1); err == nil || err != ErrTruncated { | |||
| t.Logf("error not ErrTruncated from header-only unpack: %v", err) | |||
| t.Fail() | |||
| } | |||
| } | |||
| @ -0,0 +1,99 @@ | |||
| package dns | |||
| import ( | |||
| "bufio" | |||
| "os" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| // ClientConfig wraps the contents of the /etc/resolv.conf file. | |||
| type ClientConfig struct { | |||
| Servers []string // servers to use | |||
| Search []string // suffixes to append to local name | |||
| Port string // what port to use | |||
| Ndots int // number of dots in name to trigger absolute lookup | |||
| Timeout int // seconds before giving up on packet | |||
| Attempts int // lost packets before giving up on server, not used in the package dns | |||
| } | |||
| // ClientConfigFromFile parses a resolv.conf(5) like file and returns | |||
| // a *ClientConfig. | |||
| func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { | |||
| file, err := os.Open(resolvconf) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer file.Close() | |||
| c := new(ClientConfig) | |||
| scanner := bufio.NewScanner(file) | |||
| c.Servers = make([]string, 0) | |||
| c.Search = make([]string, 0) | |||
| c.Port = "53" | |||
| c.Ndots = 1 | |||
| c.Timeout = 5 | |||
| c.Attempts = 2 | |||
| for scanner.Scan() { | |||
| if err := scanner.Err(); err != nil { | |||
| return nil, err | |||
| } | |||
| line := scanner.Text() | |||
| f := strings.Fields(line) | |||
| if len(f) < 1 { | |||
| continue | |||
| } | |||
| switch f[0] { | |||
| case "nameserver": // add one name server | |||
| if len(f) > 1 { | |||
| // One more check: make sure server name is | |||
| // just an IP address. Otherwise we need DNS | |||
| // to look it up. | |||
| name := f[1] | |||
| c.Servers = append(c.Servers, name) | |||
| } | |||
| case "domain": // set search path to just this domain | |||
| if len(f) > 1 { | |||
| c.Search = make([]string, 1) | |||
| c.Search[0] = f[1] | |||
| } else { | |||
| c.Search = make([]string, 0) | |||
| } | |||
| case "search": // set search path to given servers | |||
| c.Search = make([]string, len(f)-1) | |||
| for i := 0; i < len(c.Search); i++ { | |||
| c.Search[i] = f[i+1] | |||
| } | |||
| case "options": // magic options | |||
| for i := 1; i < len(f); i++ { | |||
| s := f[i] | |||
| switch { | |||
| case len(s) >= 6 && s[:6] == "ndots:": | |||
| n, _ := strconv.Atoi(s[6:]) | |||
| if n < 1 { | |||
| n = 1 | |||
| } | |||
| c.Ndots = n | |||
| case len(s) >= 8 && s[:8] == "timeout:": | |||
| n, _ := strconv.Atoi(s[8:]) | |||
| if n < 1 { | |||
| n = 1 | |||
| } | |||
| c.Timeout = n | |||
| case len(s) >= 8 && s[:9] == "attempts:": | |||
| n, _ := strconv.Atoi(s[9:]) | |||
| if n < 1 { | |||
| n = 1 | |||
| } | |||
| c.Attempts = n | |||
| case s == "rotate": | |||
| /* not imp */ | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return c, nil | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| package dns | |||
| import ( | |||
| "io/ioutil" | |||
| "os" | |||
| "path/filepath" | |||
| "testing" | |||
| ) | |||
| const normal string = ` | |||
| # Comment | |||
| domain somedomain.com | |||
| nameserver 10.28.10.2 | |||
| nameserver 11.28.10.1 | |||
| ` | |||
| const missingNewline string = ` | |||
| domain somedomain.com | |||
| nameserver 10.28.10.2 | |||
| nameserver 11.28.10.1` // <- NOTE: NO newline. | |||
| func testConfig(t *testing.T, data string) { | |||
| tempDir, err := ioutil.TempDir("", "") | |||
| if err != nil { | |||
| t.Fatalf("tempDir: %v", err) | |||
| } | |||
| defer os.RemoveAll(tempDir) | |||
| path := filepath.Join(tempDir, "resolv.conf") | |||
| if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { | |||
| t.Fatalf("writeFile: %v", err) | |||
| } | |||
| cc, err := ClientConfigFromFile(path) | |||
| if err != nil { | |||
| t.Errorf("error parsing resolv.conf: %v", err) | |||
| } | |||
| if l := len(cc.Servers); l != 2 { | |||
| t.Errorf("incorrect number of nameservers detected: %d", l) | |||
| } | |||
| if l := len(cc.Search); l != 1 { | |||
| t.Errorf("domain directive not parsed correctly: %v", cc.Search) | |||
| } else { | |||
| if cc.Search[0] != "somedomain.com" { | |||
| t.Errorf("domain is unexpected: %v", cc.Search[0]) | |||
| } | |||
| } | |||
| } | |||
| func TestNameserver(t *testing.T) { testConfig(t, normal) } | |||
| func TestMissingFinalNewLine(t *testing.T) { testConfig(t, missingNewline) } | |||
| @ -0,0 +1,278 @@ | |||
| package dns | |||
| import ( | |||
| "errors" | |||
| "net" | |||
| "strconv" | |||
| ) | |||
| const hexDigit = "0123456789abcdef" | |||
| // Everything is assumed in ClassINET. | |||
| // SetReply creates a reply message from a request message. | |||
| func (dns *Msg) SetReply(request *Msg) *Msg { | |||
| dns.Id = request.Id | |||
| dns.RecursionDesired = request.RecursionDesired // Copy rd bit | |||
| dns.Response = true | |||
| dns.Opcode = OpcodeQuery | |||
| dns.Rcode = RcodeSuccess | |||
| if len(request.Question) > 0 { | |||
| dns.Question = make([]Question, 1) | |||
| dns.Question[0] = request.Question[0] | |||
| } | |||
| return dns | |||
| } | |||
| // SetQuestion creates a question message, it sets the Question | |||
| // section, generates an Id and sets the RecursionDesired (RD) | |||
| // bit to true. | |||
| func (dns *Msg) SetQuestion(z string, t uint16) *Msg { | |||
| dns.Id = Id() | |||
| dns.RecursionDesired = true | |||
| dns.Question = make([]Question, 1) | |||
| dns.Question[0] = Question{z, t, ClassINET} | |||
| return dns | |||
| } | |||
| // SetNotify creates a notify message, it sets the Question | |||
| // section, generates an Id and sets the Authoritative (AA) | |||
| // bit to true. | |||
| func (dns *Msg) SetNotify(z string) *Msg { | |||
| dns.Opcode = OpcodeNotify | |||
| dns.Authoritative = true | |||
| dns.Id = Id() | |||
| dns.Question = make([]Question, 1) | |||
| dns.Question[0] = Question{z, TypeSOA, ClassINET} | |||
| return dns | |||
| } | |||
| // SetRcode creates an error message suitable for the request. | |||
| func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { | |||
| dns.SetReply(request) | |||
| dns.Rcode = rcode | |||
| return dns | |||
| } | |||
| // SetRcodeFormatError creates a message with FormError set. | |||
| func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { | |||
| dns.Rcode = RcodeFormatError | |||
| dns.Opcode = OpcodeQuery | |||
| dns.Response = true | |||
| dns.Authoritative = false | |||
| dns.Id = request.Id | |||
| return dns | |||
| } | |||
| // SetUpdate makes the message a dynamic update message. It | |||
| // sets the ZONE section to: z, TypeSOA, ClassINET. | |||
| func (dns *Msg) SetUpdate(z string) *Msg { | |||
| dns.Id = Id() | |||
| dns.Response = false | |||
| dns.Opcode = OpcodeUpdate | |||
| dns.Compress = false // BIND9 cannot handle compression | |||
| dns.Question = make([]Question, 1) | |||
| dns.Question[0] = Question{z, TypeSOA, ClassINET} | |||
| return dns | |||
| } | |||
| // SetIxfr creates message for requesting an IXFR. | |||
| func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { | |||
| dns.Id = Id() | |||
| dns.Question = make([]Question, 1) | |||
| dns.Ns = make([]RR, 1) | |||
| s := new(SOA) | |||
| s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} | |||
| s.Serial = serial | |||
| s.Ns = ns | |||
| s.Mbox = mbox | |||
| dns.Question[0] = Question{z, TypeIXFR, ClassINET} | |||
| dns.Ns[0] = s | |||
| return dns | |||
| } | |||
| // SetAxfr creates message for requesting an AXFR. | |||
| func (dns *Msg) SetAxfr(z string) *Msg { | |||
| dns.Id = Id() | |||
| dns.Question = make([]Question, 1) | |||
| dns.Question[0] = Question{z, TypeAXFR, ClassINET} | |||
| return dns | |||
| } | |||
| // SetTsig appends a TSIG RR to the message. | |||
| // This is only a skeleton TSIG RR that is added as the last RR in the | |||
| // additional section. The Tsig is calculated when the message is being send. | |||
| func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { | |||
| t := new(TSIG) | |||
| t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} | |||
| t.Algorithm = algo | |||
| t.Fudge = 300 | |||
| t.TimeSigned = uint64(timesigned) | |||
| t.OrigId = dns.Id | |||
| dns.Extra = append(dns.Extra, t) | |||
| return dns | |||
| } | |||
| // SetEdns0 appends a EDNS0 OPT RR to the message. | |||
| // TSIG should always the last RR in a message. | |||
| func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { | |||
| e := new(OPT) | |||
| e.Hdr.Name = "." | |||
| e.Hdr.Rrtype = TypeOPT | |||
| e.SetUDPSize(udpsize) | |||
| if do { | |||
| e.SetDo() | |||
| } | |||
| dns.Extra = append(dns.Extra, e) | |||
| return dns | |||
| } | |||
| // IsTsig checks if the message has a TSIG record as the last record | |||
| // in the additional section. It returns the TSIG record found or nil. | |||
| func (dns *Msg) IsTsig() *TSIG { | |||
| if len(dns.Extra) > 0 { | |||
| if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { | |||
| return dns.Extra[len(dns.Extra)-1].(*TSIG) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 | |||
| // 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) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // IsDomainName checks if s is a valid domain name, it returns the number of | |||
| // labels and true, when a domain name is valid. Note that non fully qualified | |||
| // domain name is considered valid, in this case the last label is counted in | |||
| // the number of labels. When false is returned the number of labels is not | |||
| // defined. Also note that this function is extremely liberal; almost any | |||
| // string is a valid domain name as the DNS is 8 bit protocol. It checks if each | |||
| // label fits in 63 characters, but there is no length check for the entire | |||
| // string s. I.e. a domain name longer than 255 characters is considered valid. | |||
| func IsDomainName(s string) (labels int, ok bool) { | |||
| _, labels, err := packDomainName(s, nil, 0, nil, false) | |||
| 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. | |||
| func IsSubDomain(parent, child string) bool { | |||
| // Entire child is contained in parent | |||
| return CompareDomainName(parent, child) == CountLabel(parent) | |||
| } | |||
| // IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. | |||
| // The checking is performed on the binary payload. | |||
| func IsMsg(buf []byte) error { | |||
| // Header | |||
| if len(buf) < 12 { | |||
| return errors.New("dns: bad message header") | |||
| } | |||
| // Header: Opcode | |||
| // TODO(miek): more checks here, e.g. check all header bits. | |||
| return nil | |||
| } | |||
| // IsFqdn checks if a domain name is fully qualified. | |||
| func IsFqdn(s string) bool { | |||
| l := len(s) | |||
| if l == 0 { | |||
| return false | |||
| } | |||
| return s[l-1] == '.' | |||
| } | |||
| // IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. | |||
| // This means the RRs need to have the same type, name, and class. Returns true | |||
| // if the RR set is valid, otherwise false. | |||
| func IsRRset(rrset []RR) bool { | |||
| if len(rrset) == 0 { | |||
| return false | |||
| } | |||
| if len(rrset) == 1 { | |||
| return true | |||
| } | |||
| rrHeader := rrset[0].Header() | |||
| rrType := rrHeader.Rrtype | |||
| rrClass := rrHeader.Class | |||
| rrName := rrHeader.Name | |||
| for _, rr := range rrset[1:] { | |||
| curRRHeader := rr.Header() | |||
| if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { | |||
| // Mismatch between the records, so this is not a valid rrset for | |||
| //signing/verifying | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| // Fqdn return the fully qualified domain name from s. | |||
| // If s is already fully qualified, it behaves as the identity function. | |||
| func Fqdn(s string) string { | |||
| if IsFqdn(s) { | |||
| return s | |||
| } | |||
| return s + "." | |||
| } | |||
| // Copied from the official Go code. | |||
| // ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP | |||
| // address suitable for reverse DNS (PTR) record lookups or an error if it fails | |||
| // to parse the IP address. | |||
| func ReverseAddr(addr string) (arpa string, err error) { | |||
| ip := net.ParseIP(addr) | |||
| if ip == nil { | |||
| return "", &Error{err: "unrecognized address: " + addr} | |||
| } | |||
| if ip.To4() != nil { | |||
| return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + | |||
| strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil | |||
| } | |||
| // Must be IPv6 | |||
| buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) | |||
| // Add it, in reverse, to the buffer | |||
| for i := len(ip) - 1; i >= 0; i-- { | |||
| v := ip[i] | |||
| buf = append(buf, hexDigit[v&0xF]) | |||
| buf = append(buf, '.') | |||
| buf = append(buf, hexDigit[v>>4]) | |||
| buf = append(buf, '.') | |||
| } | |||
| // Append "ip6.arpa." and return (buf already has the final .) | |||
| buf = append(buf, "ip6.arpa."...) | |||
| return string(buf), nil | |||
| } | |||
| // String returns the string representation for the type t. | |||
| func (t Type) String() string { | |||
| if t1, ok := TypeToString[uint16(t)]; ok { | |||
| return t1 | |||
| } | |||
| return "TYPE" + strconv.Itoa(int(t)) | |||
| } | |||
| // String returns the string representation for the class c. | |||
| func (c Class) String() string { | |||
| if c1, ok := ClassToString[uint16(c)]; ok { | |||
| return c1 | |||
| } | |||
| return "CLASS" + strconv.Itoa(int(c)) | |||
| } | |||
| // String returns the string representation for the name n. | |||
| func (n Name) String() string { | |||
| return sprintName(string(n)) | |||
| } | |||
| @ -0,0 +1,100 @@ | |||
| package dns | |||
| import "strconv" | |||
| const ( | |||
| year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. | |||
| // DefaultMsgSize is the standard default for messages larger than 512 bytes. | |||
| DefaultMsgSize = 4096 | |||
| // MinMsgSize is the minimal size of a DNS packet. | |||
| MinMsgSize = 512 | |||
| // MaxMsgSize is the largest possible DNS packet. | |||
| MaxMsgSize = 65535 | |||
| defaultTtl = 3600 // Default internal TTL. | |||
| ) | |||
| // Error represents a DNS error | |||
| type Error struct{ err string } | |||
| func (e *Error) Error() string { | |||
| if e == nil { | |||
| return "dns: <nil>" | |||
| } | |||
| return "dns: " + e.err | |||
| } | |||
| // An RR represents a resource record. | |||
| type RR interface { | |||
| // Header returns the header of an resource record. The header contains | |||
| // everything up to the rdata. | |||
| Header() *RR_Header | |||
| // String returns the text representation of the resource record. | |||
| String() string | |||
| // copy returns a copy of the RR | |||
| copy() RR | |||
| // len returns the length (in octets) of the uncompressed RR in wire format. | |||
| len() int | |||
| } | |||
| // RR_Header is the header all DNS resource records share. | |||
| type RR_Header struct { | |||
| Name string `dns:"cdomain-name"` | |||
| Rrtype uint16 | |||
| Class uint16 | |||
| Ttl uint32 | |||
| Rdlength uint16 // length of data after header | |||
| } | |||
| // Header returns itself. This is here to make RR_Header implement the RR interface. | |||
| func (h *RR_Header) Header() *RR_Header { return h } | |||
| // Just to imlement the RR interface. | |||
| func (h *RR_Header) copy() RR { return nil } | |||
| func (h *RR_Header) copyHeader() *RR_Header { | |||
| r := new(RR_Header) | |||
| r.Name = h.Name | |||
| r.Rrtype = h.Rrtype | |||
| r.Class = h.Class | |||
| r.Ttl = h.Ttl | |||
| r.Rdlength = h.Rdlength | |||
| return r | |||
| } | |||
| func (h *RR_Header) String() string { | |||
| var s string | |||
| if h.Rrtype == TypeOPT { | |||
| s = ";" | |||
| // and maybe other things | |||
| } | |||
| s += sprintName(h.Name) + "\t" | |||
| s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" | |||
| s += Class(h.Class).String() + "\t" | |||
| s += Type(h.Rrtype).String() + "\t" | |||
| return s | |||
| } | |||
| func (h *RR_Header) len() int { | |||
| l := len(h.Name) + 1 | |||
| l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) | |||
| return l | |||
| } | |||
| // ToRFC3597 converts a known RR to the unknown RR representation | |||
| // from RFC 3597. | |||
| func (rr *RFC3597) ToRFC3597(r RR) error { | |||
| buf := make([]byte, r.len()*2) | |||
| off, err := PackStruct(r, buf, 0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| buf = buf[:off] | |||
| rawSetRdlength(buf, 0, off) | |||
| _, err = UnpackStruct(rr, buf, 0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| @ -0,0 +1,578 @@ | |||
| package dns | |||
| import ( | |||
| "encoding/hex" | |||
| "net" | |||
| "testing" | |||
| ) | |||
| func TestPackUnpack(t *testing.T) { | |||
| out := new(Msg) | |||
| out.Answer = make([]RR, 1) | |||
| key := new(DNSKEY) | |||
| key = &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1} | |||
| key.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 3600} | |||
| key.PublicKey = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" | |||
| out.Answer[0] = key | |||
| msg, err := out.Pack() | |||
| if err != nil { | |||
| t.Error("failed to pack msg with DNSKEY") | |||
| } | |||
| in := new(Msg) | |||
| if in.Unpack(msg) != nil { | |||
| t.Error("failed to unpack msg with DNSKEY") | |||
| } | |||
| sig := new(RRSIG) | |||
| sig = &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2, | |||
| OrigTtl: 3600, Expiration: 4000, Inception: 4000, KeyTag: 34641, SignerName: "miek.nl.", | |||
| Signature: "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"} | |||
| sig.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeRRSIG, Class: ClassINET, Ttl: 3600} | |||
| out.Answer[0] = sig | |||
| msg, err = out.Pack() | |||
| if err != nil { | |||
| t.Error("failed to pack msg with RRSIG") | |||
| } | |||
| if in.Unpack(msg) != nil { | |||
| t.Error("failed to unpack msg with RRSIG") | |||
| } | |||
| } | |||
| func TestPackUnpack2(t *testing.T) { | |||
| m := new(Msg) | |||
| m.Extra = make([]RR, 1) | |||
| m.Answer = make([]RR, 1) | |||
| dom := "miek.nl." | |||
| rr := new(A) | |||
| rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0} | |||
| rr.A = net.IPv4(127, 0, 0, 1) | |||
| x := new(TXT) | |||
| x.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} | |||
| x.Txt = []string{"heelalaollo"} | |||
| m.Extra[0] = x | |||
| m.Answer[0] = rr | |||
| _, err := m.Pack() | |||
| if err != nil { | |||
| t.Error("Packing failed: ", err) | |||
| return | |||
| } | |||
| } | |||
| func TestPackUnpack3(t *testing.T) { | |||
| m := new(Msg) | |||
| m.Extra = make([]RR, 2) | |||
| m.Answer = make([]RR, 1) | |||
| dom := "miek.nl." | |||
| rr := new(A) | |||
| rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0} | |||
| rr.A = net.IPv4(127, 0, 0, 1) | |||
| x1 := new(TXT) | |||
| x1.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} | |||
| x1.Txt = []string{} | |||
| x2 := new(TXT) | |||
| x2.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0} | |||
| x2.Txt = []string{"heelalaollo"} | |||
| m.Extra[0] = x1 | |||
| m.Extra[1] = x2 | |||
| m.Answer[0] = rr | |||
| b, err := m.Pack() | |||
| if err != nil { | |||
| t.Error("packing failed: ", err) | |||
| return | |||
| } | |||
| var unpackMsg Msg | |||
| err = unpackMsg.Unpack(b) | |||
| if err != nil { | |||
| t.Error("unpacking failed") | |||
| return | |||
| } | |||
| } | |||
| func TestBailiwick(t *testing.T) { | |||
| yes := map[string]string{ | |||
| "miek.nl": "ns.miek.nl", | |||
| ".": "miek.nl", | |||
| } | |||
| for parent, child := range yes { | |||
| if !IsSubDomain(parent, child) { | |||
| t.Errorf("%s should be child of %s", child, parent) | |||
| t.Errorf("comparelabels %d", CompareDomainName(parent, child)) | |||
| t.Errorf("lenlabels %d %d", CountLabel(parent), CountLabel(child)) | |||
| } | |||
| } | |||
| no := map[string]string{ | |||
| "www.miek.nl": "ns.miek.nl", | |||
| "m\\.iek.nl": "ns.miek.nl", | |||
| "w\\.iek.nl": "w.iek.nl", | |||
| "p\\\\.iek.nl": "ns.p.iek.nl", // p\\.iek.nl , literal \ in domain name | |||
| "miek.nl": ".", | |||
| } | |||
| for parent, child := range no { | |||
| if IsSubDomain(parent, child) { | |||
| t.Errorf("%s should not be child of %s", child, parent) | |||
| t.Errorf("comparelabels %d", CompareDomainName(parent, child)) | |||
| t.Errorf("lenlabels %d %d", CountLabel(parent), CountLabel(child)) | |||
| } | |||
| } | |||
| } | |||
| func TestPack(t *testing.T) { | |||
| rr := []string{"US. 86400 IN NSEC 0-.us. NS SOA RRSIG NSEC DNSKEY TYPE65534"} | |||
| m := new(Msg) | |||
| var err error | |||
| m.Answer = make([]RR, 1) | |||
| for _, r := range rr { | |||
| m.Answer[0], err = NewRR(r) | |||
| if err != nil { | |||
| t.Errorf("failed to create RR: %v", err) | |||
| continue | |||
| } | |||
| if _, err := m.Pack(); err != nil { | |||
| t.Errorf("packing failed: %v", err) | |||
| } | |||
| } | |||
| x := new(Msg) | |||
| ns, _ := NewRR("pool.ntp.org. 390 IN NS a.ntpns.org") | |||
| ns.(*NS).Ns = "a.ntpns.org" | |||
| x.Ns = append(m.Ns, ns) | |||
| x.Ns = append(m.Ns, ns) | |||
| x.Ns = append(m.Ns, ns) | |||
| // This crashes due to the fact the a.ntpns.org isn't a FQDN | |||
| // How to recover() from a remove panic()? | |||
| if _, err := x.Pack(); err == nil { | |||
| t.Error("packing should fail") | |||
| } | |||
| x.Answer = make([]RR, 1) | |||
| x.Answer[0], err = NewRR(rr[0]) | |||
| if _, err := x.Pack(); err == nil { | |||
| t.Error("packing should fail") | |||
| } | |||
| x.Question = make([]Question, 1) | |||
| x.Question[0] = Question{";sd#eddddséâèµââ â¥âxzztsestxssweewwsssstx@s@Zåµe@cn.pool.ntp.org.", TypeA, ClassINET} | |||
| if _, err := x.Pack(); err == nil { | |||
| t.Error("packing should fail") | |||
| } | |||
| } | |||
| func TestPackNAPTR(t *testing.T) { | |||
| for _, n := range []string{ | |||
| `apple.com. IN NAPTR 100 50 "se" "SIP+D2U" "" _sip._udp.apple.com.`, | |||
| `apple.com. IN NAPTR 90 50 "se" "SIP+D2T" "" _sip._tcp.apple.com.`, | |||
| `apple.com. IN NAPTR 50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.`, | |||
| } { | |||
| rr, _ := NewRR(n) | |||
| msg := make([]byte, rr.len()) | |||
| if off, err := PackRR(rr, msg, 0, nil, false); err != nil { | |||
| t.Errorf("packing failed: %v", err) | |||
| t.Errorf("length %d, need more than %d", rr.len(), off) | |||
| } else { | |||
| t.Logf("buf size needed: %d", off) | |||
| } | |||
| } | |||
| } | |||
| func TestCompressLength(t *testing.T) { | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl", TypeMX) | |||
| ul := m.Len() | |||
| m.Compress = true | |||
| if ul != m.Len() { | |||
| t.Fatalf("should be equal") | |||
| } | |||
| } | |||
| // Does the predicted length match final packed length? | |||
| func TestMsgCompressLength(t *testing.T) { | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| msg.Compress = true | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrA, _ := NewRR(name1 + " 3600 IN A 192.0.2.1") | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| tests := []*Msg{ | |||
| makeMsg(name1, []RR{rrA}, nil, nil), | |||
| makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)} | |||
| for _, msg := range tests { | |||
| predicted := msg.Len() | |||
| buf, err := msg.Pack() | |||
| if err != nil { | |||
| t.Error(err) | |||
| } | |||
| if predicted < len(buf) { | |||
| t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", | |||
| msg.Question[0].Name, len(msg.Answer), predicted, len(buf)) | |||
| } | |||
| } | |||
| } | |||
| func TestMsgLength(t *testing.T) { | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrA, _ := NewRR(name1 + " 3600 IN A 192.0.2.1") | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| tests := []*Msg{ | |||
| makeMsg(name1, []RR{rrA}, nil, nil), | |||
| makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)} | |||
| for _, msg := range tests { | |||
| predicted := msg.Len() | |||
| buf, err := msg.Pack() | |||
| if err != nil { | |||
| t.Error(err) | |||
| } | |||
| if predicted < len(buf) { | |||
| t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d", | |||
| msg.Question[0].Name, predicted, len(buf)) | |||
| } | |||
| } | |||
| } | |||
| func TestMsgLength2(t *testing.T) { | |||
| // Serialized replies | |||
| var testMessages = []string{ | |||
| // google.com. IN A? | |||
| "064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc0d800010001000000050004d8ef200ac0ea00010001000000050004d8ef220ac0fc00010001000000050004d8ef240ac10e00010001000000050004d8ef260a0000290500000000050000", | |||
| // amazon.com. IN A? (reply has no EDNS0 record) | |||
| // TODO(miek): this one is off-by-one, need to find out why | |||
| //"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c00020001000000050006036e7333c0cfc00c00020001000000050006036e7334c0cfc00c000200010000000500110570646e733108756c747261646e73c0dac00c000200010000000500080570646e7332c127c00c000200010000000500080570646e7333c06ec0cb00010001000000050004d04e461fc0eb00010001000000050004cc0dfa1fc0fd00010001000000050004d04e471fc10f00010001000000050004cc0dfb1fc12100010001000000050004cc4a6c01c121001c000100000005001020010502f3ff00000000000000000001c13e00010001000000050004cc4a6d01c13e001c0001000000050010261000a1101400000000000000000001", | |||
| // yahoo.com. IN A? | |||
| "fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f00010001000000050004cb54dd35c0b100010001000000050004628a0b9dc0c30001000100000005000477a0f77cc05700010001000000050004ca2bdfaac06900010001000000050004caa568160000290500000000050000", | |||
| // microsoft.com. IN A? | |||
| "f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000000010001c07700010001000000050004d5c7b435c077001c00010000000500102a010111202000000000000000010001c08900010001000000050004cf2e4bfec089001c00010000000500102404f800200300000000000000010001c09b000100010000000500044137e28cc09b001c00010000000500102a010111200f000100000000000100010000290500000000050000", | |||
| // google.com. IN MX? | |||
| "724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c01000000000000001bc04200010001000000050004adc2461bc05700010001000000050004adc2451bc06c000100010000000500044a7d8f1bc081000100010000000500044a7d191bc0ca00010001000000050004d8ef200ac09400010001000000050004d8ef220ac0a600010001000000050004d8ef240ac0b800010001000000050004d8ef260a0000290500000000050000", | |||
| // reddit.com. IN A? | |||
| "12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690001000100000005000460073240c07c000100010000000500046007fb81c090000100010000000500047c283484c090001c00010000000500102a0226f0006700000000000000000064c0a400010001000000050004c16c5b01c0a4001c000100000005001026001401000200000000000000000001c0b800010001000000050004c16c5bc3c0b8001c0001000000050010260014010002000000000000000000c30000290500000000050000", | |||
| } | |||
| for i, hexData := range testMessages { | |||
| // we won't fail the decoding of the hex | |||
| input, _ := hex.DecodeString(hexData) | |||
| m := new(Msg) | |||
| m.Unpack(input) | |||
| //println(m.String()) | |||
| m.Compress = true | |||
| lenComp := m.Len() | |||
| b, _ := m.Pack() | |||
| pacComp := len(b) | |||
| m.Compress = false | |||
| lenUnComp := m.Len() | |||
| b, _ = m.Pack() | |||
| pacUnComp := len(b) | |||
| if pacComp+1 != lenComp { | |||
| t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i) | |||
| } | |||
| if pacUnComp+1 != lenUnComp { | |||
| t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i) | |||
| } | |||
| } | |||
| } | |||
| func TestMsgLengthCompressionMalformed(t *testing.T) { | |||
| // SOA with empty hostmaster, which is illegal | |||
| soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345}, | |||
| Ns: ".", | |||
| Mbox: "", | |||
| Serial: 0, | |||
| Refresh: 28800, | |||
| Retry: 7200, | |||
| Expire: 604800, | |||
| Minttl: 60} | |||
| m := new(Msg) | |||
| m.Compress = true | |||
| m.Ns = []RR{soa} | |||
| m.Len() // Should not crash. | |||
| } | |||
| func BenchmarkMsgLength(b *testing.B) { | |||
| b.StopTimer() | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| msg.Compress = true | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) | |||
| b.StartTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| msg.Len() | |||
| } | |||
| } | |||
| func BenchmarkMsgLengthPack(b *testing.B) { | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| msg.Compress = true | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _, _ = msg.Pack() | |||
| } | |||
| } | |||
| func BenchmarkMsgPackBuffer(b *testing.B) { | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| msg.Compress = true | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) | |||
| buf := make([]byte, 512) | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _, _ = msg.PackBuffer(buf) | |||
| } | |||
| } | |||
| func BenchmarkMsgUnpack(b *testing.B) { | |||
| makeMsg := func(question string, ans, ns, e []RR) *Msg { | |||
| msg := new(Msg) | |||
| msg.SetQuestion(Fqdn(question), TypeANY) | |||
| msg.Answer = append(msg.Answer, ans...) | |||
| msg.Ns = append(msg.Ns, ns...) | |||
| msg.Extra = append(msg.Extra, e...) | |||
| msg.Compress = true | |||
| return msg | |||
| } | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| rrMx, _ := NewRR(name1 + " 3600 IN MX 10 " + name1) | |||
| msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil) | |||
| msgBuf, _ := msg.Pack() | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _ = msg.Unpack(msgBuf) | |||
| } | |||
| } | |||
| func BenchmarkPackDomainName(b *testing.B) { | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| buf := make([]byte, len(name1)+1) | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _, _ = PackDomainName(name1, buf, 0, nil, false) | |||
| } | |||
| } | |||
| func BenchmarkUnpackDomainName(b *testing.B) { | |||
| name1 := "12345678901234567890123456789012345.12345678.123." | |||
| buf := make([]byte, len(name1)+1) | |||
| _, _ = PackDomainName(name1, buf, 0, nil, false) | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _, _, _ = UnpackDomainName(buf, 0) | |||
| } | |||
| } | |||
| func BenchmarkUnpackDomainNameUnprintable(b *testing.B) { | |||
| name1 := "\x02\x02\x02\x025\x02\x02\x02\x02.12345678.123." | |||
| buf := make([]byte, len(name1)+1) | |||
| _, _ = PackDomainName(name1, buf, 0, nil, false) | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| _, _, _ = UnpackDomainName(buf, 0) | |||
| } | |||
| } | |||
| func TestToRFC3597(t *testing.T) { | |||
| a, _ := NewRR("miek.nl. IN A 10.0.1.1") | |||
| x := new(RFC3597) | |||
| x.ToRFC3597(a) | |||
| if x.String() != `miek.nl. 3600 CLASS1 TYPE1 \# 4 0a000101` { | |||
| t.Error("string mismatch") | |||
| } | |||
| } | |||
| func TestNoRdataPack(t *testing.T) { | |||
| data := make([]byte, 1024) | |||
| for typ, fn := range TypeToRR { | |||
| r := fn() | |||
| *r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 3600} | |||
| _, err := PackRR(r, data, 0, nil, false) | |||
| if err != nil { | |||
| t.Errorf("failed to pack RR with zero rdata: %s: %v", TypeToString[typ], err) | |||
| } | |||
| } | |||
| } | |||
| // TODO(miek): fix dns buffer too small errors this throws | |||
| func TestNoRdataUnpack(t *testing.T) { | |||
| data := make([]byte, 1024) | |||
| for typ, fn := range TypeToRR { | |||
| if typ == TypeSOA || typ == TypeTSIG || typ == TypeWKS { | |||
| // SOA, TSIG will not be seen (like this) in dyn. updates? | |||
| // WKS is an bug, but...deprecated record. | |||
| continue | |||
| } | |||
| r := fn() | |||
| *r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 3600} | |||
| off, err := PackRR(r, data, 0, nil, false) | |||
| if err != nil { | |||
| // Should always works, TestNoDataPack should have caught this | |||
| t.Errorf("failed to pack RR: %v", err) | |||
| continue | |||
| } | |||
| rr, _, err := UnpackRR(data[:off], 0) | |||
| if err != nil { | |||
| t.Errorf("failed to unpack RR with zero rdata: %s: %v", TypeToString[typ], err) | |||
| } | |||
| t.Log(rr) | |||
| } | |||
| } | |||
| func TestRdataOverflow(t *testing.T) { | |||
| rr := new(RFC3597) | |||
| rr.Hdr.Name = "." | |||
| rr.Hdr.Class = ClassINET | |||
| rr.Hdr.Rrtype = 65280 | |||
| rr.Rdata = hex.EncodeToString(make([]byte, 0xFFFF)) | |||
| buf := make([]byte, 0xFFFF*2) | |||
| if _, err := PackRR(rr, buf, 0, nil, false); err != nil { | |||
| t.Fatalf("maximum size rrdata pack failed: %v", err) | |||
| } | |||
| rr.Rdata += "00" | |||
| if _, err := PackRR(rr, buf, 0, nil, false); err != ErrRdata { | |||
| t.Fatalf("oversize rrdata pack didn't return ErrRdata - instead: %v", err) | |||
| } | |||
| } | |||
| func TestCopy(t *testing.T) { | |||
| rr, _ := NewRR("miek.nl. 2311 IN A 127.0.0.1") // Weird TTL to avoid catching TTL | |||
| rr1 := Copy(rr) | |||
| if rr.String() != rr1.String() { | |||
| t.Fatalf("Copy() failed %s != %s", rr.String(), rr1.String()) | |||
| } | |||
| } | |||
| func TestMsgCopy(t *testing.T) { | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeA) | |||
| rr, _ := NewRR("miek.nl. 2311 IN A 127.0.0.1") | |||
| m.Answer = []RR{rr} | |||
| rr, _ = NewRR("miek.nl. 2311 IN NS 127.0.0.1") | |||
| m.Ns = []RR{rr} | |||
| m1 := m.Copy() | |||
| if m.String() != m1.String() { | |||
| t.Fatalf("Msg.Copy() failed %s != %s", m.String(), m1.String()) | |||
| } | |||
| m1.Answer[0], _ = NewRR("somethingelse.nl. 2311 IN A 127.0.0.1") | |||
| if m.String() == m1.String() { | |||
| t.Fatalf("Msg.Copy() failed; change to copy changed template %s", m.String()) | |||
| } | |||
| rr, _ = NewRR("miek.nl. 2311 IN A 127.0.0.2") | |||
| m1.Answer = append(m1.Answer, rr) | |||
| if m1.Ns[0].String() == m1.Answer[1].String() { | |||
| t.Fatalf("Msg.Copy() failed; append changed underlying array %s", m1.Ns[0].String()) | |||
| } | |||
| } | |||
| func BenchmarkCopy(b *testing.B) { | |||
| b.ReportAllocs() | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeA) | |||
| rr, _ := NewRR("miek.nl. 2311 IN A 127.0.0.1") | |||
| m.Answer = []RR{rr} | |||
| rr, _ = NewRR("miek.nl. 2311 IN NS 127.0.0.1") | |||
| m.Ns = []RR{rr} | |||
| rr, _ = NewRR("miek.nl. 2311 IN A 127.0.0.1") | |||
| m.Extra = []RR{rr} | |||
| b.ResetTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| m.Copy() | |||
| } | |||
| } | |||
| func TestPackIPSECKEY(t *testing.T) { | |||
| tests := []string{ | |||
| "38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )", | |||
| "38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )", | |||
| "38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )", | |||
| "38.1.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )", | |||
| "0.d.4.0.3.0.e.f.f.f.3.f.0.1.2.0 7200 IN IPSECKEY ( 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )", | |||
| } | |||
| buf := make([]byte, 1024) | |||
| for _, t1 := range tests { | |||
| rr, _ := NewRR(t1) | |||
| off, err := PackRR(rr, buf, 0, nil, false) | |||
| if err != nil { | |||
| t.Errorf("failed to pack IPSECKEY %v: %s", err, t1) | |||
| continue | |||
| } | |||
| rr, _, err = UnpackRR(buf[:off], 0) | |||
| if err != nil { | |||
| t.Errorf("failed to unpack IPSECKEY %v: %s", err, t1) | |||
| } | |||
| t.Log(rr) | |||
| } | |||
| } | |||
| func TestMsgPackBuffer(t *testing.T) { | |||
| var testMessages = []string{ | |||
| // news.ycombinator.com.in.escapemg.com. IN A, response | |||
| "586285830001000000010000046e6577730b79636f6d62696e61746f7203636f6d02696e086573636170656d6703636f6d0000010001c0210006000100000e10002c036e7332c02103646e730b67726f6f7665736861726bc02d77ed50e600002a3000000e1000093a8000000e10", | |||
| // news.ycombinator.com.in.escapemg.com. IN A, question | |||
| "586201000001000000000000046e6577730b79636f6d62696e61746f7203636f6d02696e086573636170656d6703636f6d0000010001", | |||
| "398781020001000000000000046e6577730b79636f6d62696e61746f7203636f6d0000010001", | |||
| } | |||
| for i, hexData := range testMessages { | |||
| // we won't fail the decoding of the hex | |||
| input, _ := hex.DecodeString(hexData) | |||
| m := new(Msg) | |||
| if err := m.Unpack(input); err != nil { | |||
| t.Errorf("packet %d failed to unpack", i) | |||
| continue | |||
| } | |||
| t.Logf("packet %d %s", i, m.String()) | |||
| } | |||
| } | |||
| @ -0,0 +1,664 @@ | |||
| package dns | |||
| import ( | |||
| "bytes" | |||
| "crypto" | |||
| "crypto/dsa" | |||
| "crypto/ecdsa" | |||
| "crypto/elliptic" | |||
| _ "crypto/md5" | |||
| "crypto/rand" | |||
| "crypto/rsa" | |||
| _ "crypto/sha1" | |||
| _ "crypto/sha256" | |||
| _ "crypto/sha512" | |||
| "encoding/asn1" | |||
| "encoding/hex" | |||
| "math/big" | |||
| "sort" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // DNSSEC encryption algorithm codes. | |||
| const ( | |||
| _ uint8 = iota | |||
| RSAMD5 | |||
| DH | |||
| DSA | |||
| _ // Skip 4, RFC 6725, section 2.1 | |||
| RSASHA1 | |||
| DSANSEC3SHA1 | |||
| RSASHA1NSEC3SHA1 | |||
| RSASHA256 | |||
| _ // Skip 9, RFC 6725, section 2.1 | |||
| RSASHA512 | |||
| _ // Skip 11, RFC 6725, section 2.1 | |||
| ECCGOST | |||
| ECDSAP256SHA256 | |||
| ECDSAP384SHA384 | |||
| INDIRECT uint8 = 252 | |||
| PRIVATEDNS uint8 = 253 // Private (experimental keys) | |||
| PRIVATEOID uint8 = 254 | |||
| ) | |||
| // Map for algorithm names. | |||
| var AlgorithmToString = map[uint8]string{ | |||
| RSAMD5: "RSAMD5", | |||
| DH: "DH", | |||
| DSA: "DSA", | |||
| RSASHA1: "RSASHA1", | |||
| DSANSEC3SHA1: "DSA-NSEC3-SHA1", | |||
| RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1", | |||
| RSASHA256: "RSASHA256", | |||
| RSASHA512: "RSASHA512", | |||
| ECCGOST: "ECC-GOST", | |||
| ECDSAP256SHA256: "ECDSAP256SHA256", | |||
| ECDSAP384SHA384: "ECDSAP384SHA384", | |||
| INDIRECT: "INDIRECT", | |||
| PRIVATEDNS: "PRIVATEDNS", | |||
| PRIVATEOID: "PRIVATEOID", | |||
| } | |||
| // Map of algorithm strings. | |||
| var StringToAlgorithm = reverseInt8(AlgorithmToString) | |||
| // Map of algorithm crypto hashes. | |||
| var AlgorithmToHash = map[uint8]crypto.Hash{ | |||
| RSAMD5: crypto.MD5, // Deprecated in RFC 6725 | |||
| RSASHA1: crypto.SHA1, | |||
| RSASHA1NSEC3SHA1: crypto.SHA1, | |||
| RSASHA256: crypto.SHA256, | |||
| ECDSAP256SHA256: crypto.SHA256, | |||
| ECDSAP384SHA384: crypto.SHA384, | |||
| RSASHA512: crypto.SHA512, | |||
| } | |||
| // DNSSEC hashing algorithm codes. | |||
| const ( | |||
| _ uint8 = iota | |||
| SHA1 // RFC 4034 | |||
| SHA256 // RFC 4509 | |||
| GOST94 // RFC 5933 | |||
| SHA384 // Experimental | |||
| SHA512 // Experimental | |||
| ) | |||
| // Map for hash names. | |||
| var HashToString = map[uint8]string{ | |||
| SHA1: "SHA1", | |||
| SHA256: "SHA256", | |||
| GOST94: "GOST94", | |||
| SHA384: "SHA384", | |||
| SHA512: "SHA512", | |||
| } | |||
| // Map of hash strings. | |||
| var StringToHash = reverseInt8(HashToString) | |||
| // DNSKEY flag values. | |||
| const ( | |||
| SEP = 1 | |||
| REVOKE = 1 << 7 | |||
| ZONE = 1 << 8 | |||
| ) | |||
| // The RRSIG needs to be converted to wireformat with some of | |||
| // the rdata (the signature) missing. Use this struct to ease | |||
| // the conversion (and re-use the pack/unpack functions). | |||
| type rrsigWireFmt struct { | |||
| TypeCovered uint16 | |||
| Algorithm uint8 | |||
| Labels uint8 | |||
| OrigTtl uint32 | |||
| Expiration uint32 | |||
| Inception uint32 | |||
| KeyTag uint16 | |||
| SignerName string `dns:"domain-name"` | |||
| /* No Signature */ | |||
| } | |||
| // Used for converting DNSKEY's rdata to wirefmt. | |||
| type dnskeyWireFmt struct { | |||
| Flags uint16 | |||
| Protocol uint8 | |||
| Algorithm uint8 | |||
| PublicKey string `dns:"base64"` | |||
| /* Nothing is left out */ | |||
| } | |||
| func divRoundUp(a, b int) int { | |||
| return (a + b - 1) / b | |||
| } | |||
| // KeyTag calculates the keytag (or key-id) of the DNSKEY. | |||
| func (k *DNSKEY) KeyTag() uint16 { | |||
| if k == nil { | |||
| return 0 | |||
| } | |||
| var keytag int | |||
| switch k.Algorithm { | |||
| case RSAMD5: | |||
| // Look at the bottom two bytes of the modules, which the last | |||
| // item in the pubkey. We could do this faster by looking directly | |||
| // at the base64 values. But I'm lazy. | |||
| modulus, _ := fromBase64([]byte(k.PublicKey)) | |||
| if len(modulus) > 1 { | |||
| x, _ := unpackUint16(modulus, len(modulus)-2) | |||
| keytag = int(x) | |||
| } | |||
| default: | |||
| keywire := new(dnskeyWireFmt) | |||
| keywire.Flags = k.Flags | |||
| keywire.Protocol = k.Protocol | |||
| keywire.Algorithm = k.Algorithm | |||
| keywire.PublicKey = k.PublicKey | |||
| wire := make([]byte, DefaultMsgSize) | |||
| n, err := PackStruct(keywire, wire, 0) | |||
| if err != nil { | |||
| return 0 | |||
| } | |||
| wire = wire[:n] | |||
| for i, v := range wire { | |||
| if i&1 != 0 { | |||
| keytag += int(v) // must be larger than uint32 | |||
| } else { | |||
| keytag += int(v) << 8 | |||
| } | |||
| } | |||
| keytag += (keytag >> 16) & 0xFFFF | |||
| keytag &= 0xFFFF | |||
| } | |||
| return uint16(keytag) | |||
| } | |||
| // ToDS converts a DNSKEY record to a DS record. | |||
| func (k *DNSKEY) ToDS(h uint8) *DS { | |||
| if k == nil { | |||
| return nil | |||
| } | |||
| ds := new(DS) | |||
| ds.Hdr.Name = k.Hdr.Name | |||
| ds.Hdr.Class = k.Hdr.Class | |||
| ds.Hdr.Rrtype = TypeDS | |||
| ds.Hdr.Ttl = k.Hdr.Ttl | |||
| ds.Algorithm = k.Algorithm | |||
| ds.DigestType = h | |||
| ds.KeyTag = k.KeyTag() | |||
| keywire := new(dnskeyWireFmt) | |||
| keywire.Flags = k.Flags | |||
| keywire.Protocol = k.Protocol | |||
| keywire.Algorithm = k.Algorithm | |||
| keywire.PublicKey = k.PublicKey | |||
| wire := make([]byte, DefaultMsgSize) | |||
| n, err := PackStruct(keywire, wire, 0) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| wire = wire[:n] | |||
| owner := make([]byte, 255) | |||
| off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false) | |||
| if err1 != nil { | |||
| return nil | |||
| } | |||
| owner = owner[:off] | |||
| // RFC4034: | |||
| // digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); | |||
| // "|" denotes concatenation | |||
| // DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. | |||
| // digest buffer | |||
| digest := append(owner, wire...) // another copy | |||
| var hash crypto.Hash | |||
| switch h { | |||
| case SHA1: | |||
| hash = crypto.SHA1 | |||
| case SHA256: | |||
| hash = crypto.SHA256 | |||
| case SHA384: | |||
| hash = crypto.SHA384 | |||
| case SHA512: | |||
| hash = crypto.SHA512 | |||
| default: | |||
| return nil | |||
| } | |||
| s := hash.New() | |||
| s.Write(digest) | |||
| ds.Digest = hex.EncodeToString(s.Sum(nil)) | |||
| return ds | |||
| } | |||
| // ToCDNSKEY converts a DNSKEY record to a CDNSKEY record. | |||
| func (k *DNSKEY) ToCDNSKEY() *CDNSKEY { | |||
| c := &CDNSKEY{DNSKEY: *k} | |||
| c.Hdr = *k.Hdr.copyHeader() | |||
| c.Hdr.Rrtype = TypeCDNSKEY | |||
| return c | |||
| } | |||
| // ToCDS converts a DS record to a CDS record. | |||
| func (d *DS) ToCDS() *CDS { | |||
| c := &CDS{DS: *d} | |||
| c.Hdr = *d.Hdr.copyHeader() | |||
| c.Hdr.Rrtype = TypeCDS | |||
| return c | |||
| } | |||
| // Sign signs an RRSet. The signature needs to be filled in with the values: | |||
| // Inception, Expiration, KeyTag, SignerName and Algorithm. The rest is copied | |||
| // from the RRset. Sign returns a non-nill error when the signing went OK. | |||
| // There is no check if RRSet is a proper (RFC 2181) RRSet. If OrigTTL is non | |||
| // zero, it is used as-is, otherwise the TTL of the RRset is used as the | |||
| // OrigTTL. | |||
| func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error { | |||
| if k == nil { | |||
| return ErrPrivKey | |||
| } | |||
| // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set | |||
| if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { | |||
| return ErrKey | |||
| } | |||
| rr.Hdr.Rrtype = TypeRRSIG | |||
| rr.Hdr.Name = rrset[0].Header().Name | |||
| rr.Hdr.Class = rrset[0].Header().Class | |||
| if rr.OrigTtl == 0 { // If set don't override | |||
| rr.OrigTtl = rrset[0].Header().Ttl | |||
| } | |||
| rr.TypeCovered = rrset[0].Header().Rrtype | |||
| rr.Labels = uint8(CountLabel(rrset[0].Header().Name)) | |||
| if strings.HasPrefix(rrset[0].Header().Name, "*") { | |||
| rr.Labels-- // wildcard, remove from label count | |||
| } | |||
| sigwire := new(rrsigWireFmt) | |||
| sigwire.TypeCovered = rr.TypeCovered | |||
| sigwire.Algorithm = rr.Algorithm | |||
| sigwire.Labels = rr.Labels | |||
| sigwire.OrigTtl = rr.OrigTtl | |||
| sigwire.Expiration = rr.Expiration | |||
| sigwire.Inception = rr.Inception | |||
| sigwire.KeyTag = rr.KeyTag | |||
| // For signing, lowercase this name | |||
| sigwire.SignerName = strings.ToLower(rr.SignerName) | |||
| // Create the desired binary blob | |||
| signdata := make([]byte, DefaultMsgSize) | |||
| n, err := PackStruct(sigwire, signdata, 0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| signdata = signdata[:n] | |||
| wire, err := rawSignatureData(rrset, rr) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| signdata = append(signdata, wire...) | |||
| hash, ok := AlgorithmToHash[rr.Algorithm] | |||
| if !ok { | |||
| return ErrAlg | |||
| } | |||
| h := hash.New() | |||
| h.Write(signdata) | |||
| signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| rr.Signature = toBase64(signature) | |||
| return nil | |||
| } | |||
| func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) { | |||
| signature, err := k.Sign(rand.Reader, hashed, hash) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| switch alg { | |||
| case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: | |||
| return signature, nil | |||
| case ECDSAP256SHA256, ECDSAP384SHA384: | |||
| ecdsaSignature := &struct { | |||
| R, S *big.Int | |||
| }{} | |||
| if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil { | |||
| return nil, err | |||
| } | |||
| var intlen int | |||
| switch alg { | |||
| case ECDSAP256SHA256: | |||
| intlen = 32 | |||
| case ECDSAP384SHA384: | |||
| intlen = 48 | |||
| } | |||
| signature := intToBytes(ecdsaSignature.R, intlen) | |||
| signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...) | |||
| return signature, nil | |||
| // There is no defined interface for what a DSA backed crypto.Signer returns | |||
| case DSA, DSANSEC3SHA1: | |||
| // t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8) | |||
| // signature := []byte{byte(t)} | |||
| // signature = append(signature, intToBytes(r1, 20)...) | |||
| // signature = append(signature, intToBytes(s1, 20)...) | |||
| // rr.Signature = signature | |||
| } | |||
| return nil, ErrAlg | |||
| } | |||
| // Verify validates an RRSet with the signature and key. This is only the | |||
| // cryptographic test, the signature validity period must be checked separately. | |||
| // This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. | |||
| func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { | |||
| // First the easy checks | |||
| if !IsRRset(rrset) { | |||
| return ErrRRset | |||
| } | |||
| if rr.KeyTag != k.KeyTag() { | |||
| return ErrKey | |||
| } | |||
| if rr.Hdr.Class != k.Hdr.Class { | |||
| return ErrKey | |||
| } | |||
| if rr.Algorithm != k.Algorithm { | |||
| return ErrKey | |||
| } | |||
| if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) { | |||
| return ErrKey | |||
| } | |||
| if k.Protocol != 3 { | |||
| return ErrKey | |||
| } | |||
| // IsRRset checked that we have at least one RR and that the RRs in | |||
| // the set have consistent type, class, and name. Also check that type and | |||
| // class matches the RRSIG record. | |||
| if rrset[0].Header().Class != rr.Hdr.Class { | |||
| return ErrRRset | |||
| } | |||
| if rrset[0].Header().Rrtype != rr.TypeCovered { | |||
| return ErrRRset | |||
| } | |||
| // RFC 4035 5.3.2. Reconstructing the Signed Data | |||
| // Copy the sig, except the rrsig data | |||
| sigwire := new(rrsigWireFmt) | |||
| sigwire.TypeCovered = rr.TypeCovered | |||
| sigwire.Algorithm = rr.Algorithm | |||
| sigwire.Labels = rr.Labels | |||
| sigwire.OrigTtl = rr.OrigTtl | |||
| sigwire.Expiration = rr.Expiration | |||
| sigwire.Inception = rr.Inception | |||
| sigwire.KeyTag = rr.KeyTag | |||
| sigwire.SignerName = strings.ToLower(rr.SignerName) | |||
| // Create the desired binary blob | |||
| signeddata := make([]byte, DefaultMsgSize) | |||
| n, err := PackStruct(sigwire, signeddata, 0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| signeddata = signeddata[:n] | |||
| wire, err := rawSignatureData(rrset, rr) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| signeddata = append(signeddata, wire...) | |||
| sigbuf := rr.sigBuf() // Get the binary signature data | |||
| if rr.Algorithm == PRIVATEDNS { // PRIVATEOID | |||
| // TODO(miek) | |||
| // remove the domain name and assume its ours? | |||
| } | |||
| hash, ok := AlgorithmToHash[rr.Algorithm] | |||
| if !ok { | |||
| return ErrAlg | |||
| } | |||
| switch rr.Algorithm { | |||
| case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5: | |||
| // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? | |||
| pubkey := k.publicKeyRSA() // Get the key | |||
| if pubkey == nil { | |||
| return ErrKey | |||
| } | |||
| h := hash.New() | |||
| h.Write(signeddata) | |||
| return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf) | |||
| case ECDSAP256SHA256, ECDSAP384SHA384: | |||
| pubkey := k.publicKeyECDSA() | |||
| if pubkey == nil { | |||
| return ErrKey | |||
| } | |||
| // Split sigbuf into the r and s coordinates | |||
| r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2]) | |||
| s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:]) | |||
| h := hash.New() | |||
| h.Write(signeddata) | |||
| if ecdsa.Verify(pubkey, h.Sum(nil), r, s) { | |||
| return nil | |||
| } | |||
| return ErrSig | |||
| default: | |||
| return ErrAlg | |||
| } | |||
| } | |||
| // ValidityPeriod uses RFC1982 serial arithmetic to calculate | |||
| // if a signature period is valid. If t is the zero time, the | |||
| // current time is taken other t is. Returns true if the signature | |||
| // is valid at the given time, otherwise returns false. | |||
| func (rr *RRSIG) ValidityPeriod(t time.Time) bool { | |||
| var utc int64 | |||
| if t.IsZero() { | |||
| utc = time.Now().UTC().Unix() | |||
| } else { | |||
| utc = t.UTC().Unix() | |||
| } | |||
| modi := (int64(rr.Inception) - utc) / year68 | |||
| mode := (int64(rr.Expiration) - utc) / year68 | |||
| ti := int64(rr.Inception) + (modi * year68) | |||
| te := int64(rr.Expiration) + (mode * year68) | |||
| return ti <= utc && utc <= te | |||
| } | |||
| // Return the signatures base64 encodedig sigdata as a byte slice. | |||
| func (rr *RRSIG) sigBuf() []byte { | |||
| sigbuf, err := fromBase64([]byte(rr.Signature)) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| return sigbuf | |||
| } | |||
| // publicKeyRSA returns the RSA public key from a DNSKEY record. | |||
| func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey { | |||
| keybuf, err := fromBase64([]byte(k.PublicKey)) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| // RFC 2537/3110, section 2. RSA Public KEY Resource Records | |||
| // Length is in the 0th byte, unless its zero, then it | |||
| // it in bytes 1 and 2 and its a 16 bit number | |||
| explen := uint16(keybuf[0]) | |||
| keyoff := 1 | |||
| if explen == 0 { | |||
| explen = uint16(keybuf[1])<<8 | uint16(keybuf[2]) | |||
| keyoff = 3 | |||
| } | |||
| pubkey := new(rsa.PublicKey) | |||
| pubkey.N = big.NewInt(0) | |||
| shift := uint64((explen - 1) * 8) | |||
| expo := uint64(0) | |||
| for i := int(explen - 1); i > 0; i-- { | |||
| expo += uint64(keybuf[keyoff+i]) << shift | |||
| shift -= 8 | |||
| } | |||
| // Remainder | |||
| expo += uint64(keybuf[keyoff]) | |||
| if expo > 2<<31 { | |||
| // Larger expo than supported. | |||
| // println("dns: F5 primes (or larger) are not supported") | |||
| return nil | |||
| } | |||
| pubkey.E = int(expo) | |||
| pubkey.N.SetBytes(keybuf[keyoff+int(explen):]) | |||
| return pubkey | |||
| } | |||
| // publicKeyECDSA returns the Curve public key from the DNSKEY record. | |||
| func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey { | |||
| keybuf, err := fromBase64([]byte(k.PublicKey)) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| pubkey := new(ecdsa.PublicKey) | |||
| switch k.Algorithm { | |||
| case ECDSAP256SHA256: | |||
| pubkey.Curve = elliptic.P256() | |||
| if len(keybuf) != 64 { | |||
| // wrongly encoded key | |||
| return nil | |||
| } | |||
| case ECDSAP384SHA384: | |||
| pubkey.Curve = elliptic.P384() | |||
| if len(keybuf) != 96 { | |||
| // Wrongly encoded key | |||
| return nil | |||
| } | |||
| } | |||
| pubkey.X = big.NewInt(0) | |||
| pubkey.X.SetBytes(keybuf[:len(keybuf)/2]) | |||
| pubkey.Y = big.NewInt(0) | |||
| pubkey.Y.SetBytes(keybuf[len(keybuf)/2:]) | |||
| return pubkey | |||
| } | |||
| func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey { | |||
| keybuf, err := fromBase64([]byte(k.PublicKey)) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| if len(keybuf) < 22 { | |||
| return nil | |||
| } | |||
| t, keybuf := int(keybuf[0]), keybuf[1:] | |||
| size := 64 + t*8 | |||
| q, keybuf := keybuf[:20], keybuf[20:] | |||
| if len(keybuf) != 3*size { | |||
| return nil | |||
| } | |||
| p, keybuf := keybuf[:size], keybuf[size:] | |||
| g, y := keybuf[:size], keybuf[size:] | |||
| pubkey := new(dsa.PublicKey) | |||
| pubkey.Parameters.Q = big.NewInt(0).SetBytes(q) | |||
| pubkey.Parameters.P = big.NewInt(0).SetBytes(p) | |||
| pubkey.Parameters.G = big.NewInt(0).SetBytes(g) | |||
| pubkey.Y = big.NewInt(0).SetBytes(y) | |||
| return pubkey | |||
| } | |||
| type wireSlice [][]byte | |||
| func (p wireSlice) Len() int { return len(p) } | |||
| func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } | |||
| func (p wireSlice) Less(i, j int) bool { | |||
| _, ioff, _ := UnpackDomainName(p[i], 0) | |||
| _, joff, _ := UnpackDomainName(p[j], 0) | |||
| return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0 | |||
| } | |||
| // Return the raw signature data. | |||
| func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) { | |||
| wires := make(wireSlice, len(rrset)) | |||
| for i, r := range rrset { | |||
| r1 := r.copy() | |||
| r1.Header().Ttl = s.OrigTtl | |||
| labels := SplitDomainName(r1.Header().Name) | |||
| // 6.2. Canonical RR Form. (4) - wildcards | |||
| if len(labels) > int(s.Labels) { | |||
| // Wildcard | |||
| r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." | |||
| } | |||
| // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase | |||
| r1.Header().Name = strings.ToLower(r1.Header().Name) | |||
| // 6.2. Canonical RR Form. (3) - domain rdata to lowercase. | |||
| // NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, | |||
| // HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, | |||
| // SRV, DNAME, A6 | |||
| // | |||
| // RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC): | |||
| // Section 6.2 of [RFC4034] also erroneously lists HINFO as a record | |||
| // that needs conversion to lowercase, and twice at that. Since HINFO | |||
| // records contain no domain names, they are not subject to case | |||
| // conversion. | |||
| switch x := r1.(type) { | |||
| case *NS: | |||
| x.Ns = strings.ToLower(x.Ns) | |||
| case *CNAME: | |||
| x.Target = strings.ToLower(x.Target) | |||
| case *SOA: | |||
| x.Ns = strings.ToLower(x.Ns) | |||
| x.Mbox = strings.ToLower(x.Mbox) | |||
| case *MB: | |||
| x.Mb = strings.ToLower(x.Mb) | |||
| case *MG: | |||
| x.Mg = strings.ToLower(x.Mg) | |||
| case *MR: | |||
| x.Mr = strings.ToLower(x.Mr) | |||
| case *PTR: | |||
| x.Ptr = strings.ToLower(x.Ptr) | |||
| case *MINFO: | |||
| x.Rmail = strings.ToLower(x.Rmail) | |||
| x.Email = strings.ToLower(x.Email) | |||
| case *MX: | |||
| x.Mx = strings.ToLower(x.Mx) | |||
| case *NAPTR: | |||
| x.Replacement = strings.ToLower(x.Replacement) | |||
| case *KX: | |||
| x.Exchanger = strings.ToLower(x.Exchanger) | |||
| case *SRV: | |||
| x.Target = strings.ToLower(x.Target) | |||
| case *DNAME: | |||
| x.Target = strings.ToLower(x.Target) | |||
| } | |||
| // 6.2. Canonical RR Form. (5) - origTTL | |||
| wire := make([]byte, r1.len()+1) // +1 to be safe(r) | |||
| off, err1 := PackRR(r1, wire, 0, nil, false) | |||
| if err1 != nil { | |||
| return nil, err1 | |||
| } | |||
| wire = wire[:off] | |||
| wires[i] = wire | |||
| } | |||
| sort.Sort(wires) | |||
| for i, wire := range wires { | |||
| if i > 0 && bytes.Equal(wire, wires[i-1]) { | |||
| continue | |||
| } | |||
| buf = append(buf, wire...) | |||
| } | |||
| return buf, nil | |||
| } | |||
| @ -0,0 +1,156 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "crypto/dsa" | |||
| "crypto/ecdsa" | |||
| "crypto/elliptic" | |||
| "crypto/rand" | |||
| "crypto/rsa" | |||
| "math/big" | |||
| ) | |||
| // Generate generates a DNSKEY of the given bit size. | |||
| // The public part is put inside the DNSKEY record. | |||
| // The Algorithm in the key must be set as this will define | |||
| // what kind of DNSKEY will be generated. | |||
| // The ECDSA algorithms imply a fixed keysize, in that case | |||
| // bits should be set to the size of the algorithm. | |||
| func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { | |||
| switch k.Algorithm { | |||
| case DSA, DSANSEC3SHA1: | |||
| if bits != 1024 { | |||
| return nil, ErrKeySize | |||
| } | |||
| case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: | |||
| if bits < 512 || bits > 4096 { | |||
| return nil, ErrKeySize | |||
| } | |||
| case RSASHA512: | |||
| if bits < 1024 || bits > 4096 { | |||
| return nil, ErrKeySize | |||
| } | |||
| case ECDSAP256SHA256: | |||
| if bits != 256 { | |||
| return nil, ErrKeySize | |||
| } | |||
| case ECDSAP384SHA384: | |||
| if bits != 384 { | |||
| return nil, ErrKeySize | |||
| } | |||
| } | |||
| switch k.Algorithm { | |||
| case DSA, DSANSEC3SHA1: | |||
| params := new(dsa.Parameters) | |||
| if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { | |||
| return nil, err | |||
| } | |||
| priv := new(dsa.PrivateKey) | |||
| priv.PublicKey.Parameters = *params | |||
| err := dsa.GenerateKey(priv, rand.Reader) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) | |||
| return priv, nil | |||
| case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: | |||
| priv, err := rsa.GenerateKey(rand.Reader, bits) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) | |||
| return priv, nil | |||
| case ECDSAP256SHA256, ECDSAP384SHA384: | |||
| var c elliptic.Curve | |||
| switch k.Algorithm { | |||
| case ECDSAP256SHA256: | |||
| c = elliptic.P256() | |||
| case ECDSAP384SHA384: | |||
| c = elliptic.P384() | |||
| } | |||
| priv, err := ecdsa.GenerateKey(c, rand.Reader) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) | |||
| return priv, nil | |||
| default: | |||
| return nil, ErrAlg | |||
| } | |||
| } | |||
| // Set the public key (the value E and N) | |||
| func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { | |||
| if _E == 0 || _N == nil { | |||
| return false | |||
| } | |||
| buf := exponentToBuf(_E) | |||
| buf = append(buf, _N.Bytes()...) | |||
| k.PublicKey = toBase64(buf) | |||
| return true | |||
| } | |||
| // Set the public key for Elliptic Curves | |||
| func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { | |||
| if _X == nil || _Y == nil { | |||
| return false | |||
| } | |||
| var intlen int | |||
| switch k.Algorithm { | |||
| case ECDSAP256SHA256: | |||
| intlen = 32 | |||
| case ECDSAP384SHA384: | |||
| intlen = 48 | |||
| } | |||
| k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) | |||
| return true | |||
| } | |||
| // Set the public key for DSA | |||
| func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { | |||
| if _Q == nil || _P == nil || _G == nil || _Y == nil { | |||
| return false | |||
| } | |||
| buf := dsaToBuf(_Q, _P, _G, _Y) | |||
| k.PublicKey = toBase64(buf) | |||
| return true | |||
| } | |||
| // Set the public key (the values E and N) for RSA | |||
| // RFC 3110: Section 2. RSA Public KEY Resource Records | |||
| func exponentToBuf(_E int) []byte { | |||
| var buf []byte | |||
| i := big.NewInt(int64(_E)) | |||
| if len(i.Bytes()) < 256 { | |||
| buf = make([]byte, 1) | |||
| buf[0] = uint8(len(i.Bytes())) | |||
| } else { | |||
| buf = make([]byte, 3) | |||
| buf[0] = 0 | |||
| buf[1] = uint8(len(i.Bytes()) >> 8) | |||
| buf[2] = uint8(len(i.Bytes())) | |||
| } | |||
| buf = append(buf, i.Bytes()...) | |||
| return buf | |||
| } | |||
| // Set the public key for X and Y for Curve. The two | |||
| // values are just concatenated. | |||
| func curveToBuf(_X, _Y *big.Int, intlen int) []byte { | |||
| buf := intToBytes(_X, intlen) | |||
| buf = append(buf, intToBytes(_Y, intlen)...) | |||
| return buf | |||
| } | |||
| // Set the public key for X and Y for Curve. The two | |||
| // values are just concatenated. | |||
| func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { | |||
| t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) | |||
| buf := []byte{byte(t)} | |||
| buf = append(buf, intToBytes(_Q, 20)...) | |||
| buf = append(buf, intToBytes(_P, 64+t*8)...) | |||
| buf = append(buf, intToBytes(_G, 64+t*8)...) | |||
| buf = append(buf, intToBytes(_Y, 64+t*8)...) | |||
| return buf | |||
| } | |||
| @ -0,0 +1,249 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "crypto/dsa" | |||
| "crypto/ecdsa" | |||
| "crypto/rsa" | |||
| "io" | |||
| "math/big" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| // NewPrivateKey returns a PrivateKey by parsing the string s. | |||
| // s should be in the same form of the BIND private key files. | |||
| func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { | |||
| if s[len(s)-1] != '\n' { // We need a closing newline | |||
| return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") | |||
| } | |||
| return k.ReadPrivateKey(strings.NewReader(s), "") | |||
| } | |||
| // ReadPrivateKey reads a private key from the io.Reader q. The string file is | |||
| // only used in error reporting. | |||
| // The public key must be known, because some cryptographic algorithms embed | |||
| // the public inside the privatekey. | |||
| func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { | |||
| m, e := parseKey(q, file) | |||
| if m == nil { | |||
| return nil, e | |||
| } | |||
| if _, ok := m["private-key-format"]; !ok { | |||
| return nil, ErrPrivKey | |||
| } | |||
| if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { | |||
| return nil, ErrPrivKey | |||
| } | |||
| // TODO(mg): check if the pubkey matches the private key | |||
| algo, err := strconv.Atoi(strings.SplitN(m["algorithm"], " ", 2)[0]) | |||
| if err != nil { | |||
| return nil, ErrPrivKey | |||
| } | |||
| switch uint8(algo) { | |||
| case DSA: | |||
| priv, e := readPrivateKeyDSA(m) | |||
| if e != nil { | |||
| return nil, e | |||
| } | |||
| pub := k.publicKeyDSA() | |||
| if pub == nil { | |||
| return nil, ErrKey | |||
| } | |||
| priv.PublicKey = *pub | |||
| return priv, e | |||
| case RSAMD5: | |||
| fallthrough | |||
| case RSASHA1: | |||
| fallthrough | |||
| case RSASHA1NSEC3SHA1: | |||
| fallthrough | |||
| case RSASHA256: | |||
| fallthrough | |||
| case RSASHA512: | |||
| priv, e := readPrivateKeyRSA(m) | |||
| if e != nil { | |||
| return nil, e | |||
| } | |||
| pub := k.publicKeyRSA() | |||
| if pub == nil { | |||
| return nil, ErrKey | |||
| } | |||
| priv.PublicKey = *pub | |||
| return priv, e | |||
| case ECCGOST: | |||
| return nil, ErrPrivKey | |||
| case ECDSAP256SHA256: | |||
| fallthrough | |||
| case ECDSAP384SHA384: | |||
| priv, e := readPrivateKeyECDSA(m) | |||
| if e != nil { | |||
| return nil, e | |||
| } | |||
| pub := k.publicKeyECDSA() | |||
| if pub == nil { | |||
| return nil, ErrKey | |||
| } | |||
| priv.PublicKey = *pub | |||
| return priv, e | |||
| default: | |||
| return nil, ErrPrivKey | |||
| } | |||
| } | |||
| // Read a private key (file) string and create a public key. Return the private key. | |||
| func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { | |||
| p := new(rsa.PrivateKey) | |||
| p.Primes = []*big.Int{nil, nil} | |||
| for k, v := range m { | |||
| switch k { | |||
| case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": | |||
| v1, err := fromBase64([]byte(v)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| switch k { | |||
| case "modulus": | |||
| p.PublicKey.N = big.NewInt(0) | |||
| p.PublicKey.N.SetBytes(v1) | |||
| case "publicexponent": | |||
| i := big.NewInt(0) | |||
| i.SetBytes(v1) | |||
| p.PublicKey.E = int(i.Int64()) // int64 should be large enough | |||
| case "privateexponent": | |||
| p.D = big.NewInt(0) | |||
| p.D.SetBytes(v1) | |||
| case "prime1": | |||
| p.Primes[0] = big.NewInt(0) | |||
| p.Primes[0].SetBytes(v1) | |||
| case "prime2": | |||
| p.Primes[1] = big.NewInt(0) | |||
| p.Primes[1].SetBytes(v1) | |||
| } | |||
| case "exponent1", "exponent2", "coefficient": | |||
| // not used in Go (yet) | |||
| case "created", "publish", "activate": | |||
| // not used in Go (yet) | |||
| } | |||
| } | |||
| return p, nil | |||
| } | |||
| func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) { | |||
| p := new(dsa.PrivateKey) | |||
| p.X = big.NewInt(0) | |||
| for k, v := range m { | |||
| switch k { | |||
| case "private_value(x)": | |||
| v1, err := fromBase64([]byte(v)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| p.X.SetBytes(v1) | |||
| case "created", "publish", "activate": | |||
| /* not used in Go (yet) */ | |||
| } | |||
| } | |||
| return p, nil | |||
| } | |||
| func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { | |||
| p := new(ecdsa.PrivateKey) | |||
| p.D = big.NewInt(0) | |||
| // TODO: validate that the required flags are present | |||
| for k, v := range m { | |||
| switch k { | |||
| case "privatekey": | |||
| v1, err := fromBase64([]byte(v)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| p.D.SetBytes(v1) | |||
| case "created", "publish", "activate": | |||
| /* not used in Go (yet) */ | |||
| } | |||
| } | |||
| return p, nil | |||
| } | |||
| // parseKey reads a private key from r. It returns a map[string]string, | |||
| // with the key-value pairs, or an error when the file is not correct. | |||
| func parseKey(r io.Reader, file string) (map[string]string, error) { | |||
| s := scanInit(r) | |||
| m := make(map[string]string) | |||
| c := make(chan lex) | |||
| k := "" | |||
| // Start the lexer | |||
| go klexer(s, c) | |||
| for l := range c { | |||
| // It should alternate | |||
| switch l.value { | |||
| case zKey: | |||
| k = l.token | |||
| case zValue: | |||
| if k == "" { | |||
| return nil, &ParseError{file, "no private key seen", l} | |||
| } | |||
| //println("Setting", strings.ToLower(k), "to", l.token, "b") | |||
| m[strings.ToLower(k)] = l.token | |||
| k = "" | |||
| } | |||
| } | |||
| return m, nil | |||
| } | |||
| // klexer scans the sourcefile and returns tokens on the channel c. | |||
| func klexer(s *scan, c chan lex) { | |||
| var l lex | |||
| str := "" // Hold the current read text | |||
| commt := false | |||
| key := true | |||
| x, err := s.tokenText() | |||
| defer close(c) | |||
| for err == nil { | |||
| l.column = s.position.Column | |||
| l.line = s.position.Line | |||
| switch x { | |||
| case ':': | |||
| if commt { | |||
| break | |||
| } | |||
| l.token = str | |||
| if key { | |||
| l.value = zKey | |||
| c <- l | |||
| // Next token is a space, eat it | |||
| s.tokenText() | |||
| key = false | |||
| str = "" | |||
| } else { | |||
| l.value = zValue | |||
| } | |||
| case ';': | |||
| commt = true | |||
| case '\n': | |||
| if commt { | |||
| // Reset a comment | |||
| commt = false | |||
| } | |||
| l.value = zValue | |||
| l.token = str | |||
| c <- l | |||
| str = "" | |||
| commt = false | |||
| key = true | |||
| default: | |||
| if commt { | |||
| break | |||
| } | |||
| str += string(x) | |||
| } | |||
| x, err = s.tokenText() | |||
| } | |||
| if len(str) > 0 { | |||
| // Send remainder | |||
| l.token = str | |||
| l.value = zValue | |||
| c <- l | |||
| } | |||
| } | |||
| @ -0,0 +1,85 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "crypto/dsa" | |||
| "crypto/ecdsa" | |||
| "crypto/rsa" | |||
| "math/big" | |||
| "strconv" | |||
| ) | |||
| const format = "Private-key-format: v1.3\n" | |||
| // PrivateKeyString converts a PrivateKey to a string. This string has the same | |||
| // format as the private-key-file of BIND9 (Private-key-format: v1.3). | |||
| // It needs some info from the key (the algorithm), so its a method of the DNSKEY | |||
| // It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey | |||
| func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { | |||
| algorithm := strconv.Itoa(int(r.Algorithm)) | |||
| algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" | |||
| switch p := p.(type) { | |||
| case *rsa.PrivateKey: | |||
| modulus := toBase64(p.PublicKey.N.Bytes()) | |||
| e := big.NewInt(int64(p.PublicKey.E)) | |||
| publicExponent := toBase64(e.Bytes()) | |||
| privateExponent := toBase64(p.D.Bytes()) | |||
| prime1 := toBase64(p.Primes[0].Bytes()) | |||
| prime2 := toBase64(p.Primes[1].Bytes()) | |||
| // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm | |||
| // and from: http://code.google.com/p/go/issues/detail?id=987 | |||
| one := big.NewInt(1) | |||
| p1 := big.NewInt(0).Sub(p.Primes[0], one) | |||
| q1 := big.NewInt(0).Sub(p.Primes[1], one) | |||
| exp1 := big.NewInt(0).Mod(p.D, p1) | |||
| exp2 := big.NewInt(0).Mod(p.D, q1) | |||
| coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0]) | |||
| exponent1 := toBase64(exp1.Bytes()) | |||
| exponent2 := toBase64(exp2.Bytes()) | |||
| coefficient := toBase64(coeff.Bytes()) | |||
| return format + | |||
| "Algorithm: " + algorithm + "\n" + | |||
| "Modulus: " + modulus + "\n" + | |||
| "PublicExponent: " + publicExponent + "\n" + | |||
| "PrivateExponent: " + privateExponent + "\n" + | |||
| "Prime1: " + prime1 + "\n" + | |||
| "Prime2: " + prime2 + "\n" + | |||
| "Exponent1: " + exponent1 + "\n" + | |||
| "Exponent2: " + exponent2 + "\n" + | |||
| "Coefficient: " + coefficient + "\n" | |||
| case *ecdsa.PrivateKey: | |||
| var intlen int | |||
| switch r.Algorithm { | |||
| case ECDSAP256SHA256: | |||
| intlen = 32 | |||
| case ECDSAP384SHA384: | |||
| intlen = 48 | |||
| } | |||
| private := toBase64(intToBytes(p.D, intlen)) | |||
| return format + | |||
| "Algorithm: " + algorithm + "\n" + | |||
| "PrivateKey: " + private + "\n" | |||
| case *dsa.PrivateKey: | |||
| T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) | |||
| prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) | |||
| subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) | |||
| base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) | |||
| priv := toBase64(intToBytes(p.X, 20)) | |||
| pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) | |||
| return format + | |||
| "Algorithm: " + algorithm + "\n" + | |||
| "Prime(p): " + prime + "\n" + | |||
| "Subprime(q): " + subprime + "\n" + | |||
| "Base(g): " + base + "\n" + | |||
| "Private_value(x): " + priv + "\n" + | |||
| "Public_value(y): " + pub + "\n" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| @ -0,0 +1,733 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "crypto/ecdsa" | |||
| "crypto/rsa" | |||
| "reflect" | |||
| "strings" | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func getKey() *DNSKEY { | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" | |||
| return key | |||
| } | |||
| func getSoa() *SOA { | |||
| soa := new(SOA) | |||
| soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} | |||
| soa.Ns = "open.nlnetlabs.nl." | |||
| soa.Mbox = "miekg.atoom.net." | |||
| soa.Serial = 1293945905 | |||
| soa.Refresh = 14400 | |||
| soa.Retry = 3600 | |||
| soa.Expire = 604800 | |||
| soa.Minttl = 86400 | |||
| return soa | |||
| } | |||
| func TestGenerateEC(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| key := new(DNSKEY) | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = ECDSAP256SHA256 | |||
| privkey, _ := key.Generate(256) | |||
| t.Log(key.String()) | |||
| t.Log(key.PrivateKeyString(privkey)) | |||
| } | |||
| func TestGenerateDSA(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| key := new(DNSKEY) | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = DSA | |||
| privkey, _ := key.Generate(1024) | |||
| t.Log(key.String()) | |||
| t.Log(key.PrivateKeyString(privkey)) | |||
| } | |||
| func TestGenerateRSA(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| key := new(DNSKEY) | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| privkey, _ := key.Generate(1024) | |||
| t.Log(key.String()) | |||
| t.Log(key.PrivateKeyString(privkey)) | |||
| } | |||
| func TestSecure(t *testing.T) { | |||
| soa := getSoa() | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.TypeCovered = TypeSOA | |||
| sig.Algorithm = RSASHA256 | |||
| sig.Labels = 2 | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.OrigTtl = 14400 | |||
| sig.KeyTag = 12051 | |||
| sig.SignerName = "miek.nl." | |||
| sig.Signature = "oMCbslaAVIp/8kVtLSms3tDABpcPRUgHLrOR48OOplkYo+8TeEGWwkSwaz/MRo2fB4FxW0qj/hTlIjUGuACSd+b1wKdH5GvzRJc2pFmxtCbm55ygAh4EUL0F6U5cKtGJGSXxxg6UFCQ0doJCmiGFa78LolaUOXImJrk6AFrGa0M=" | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" | |||
| // It should validate. Period is checked separately, so this will keep on working | |||
| if sig.Verify(key, []RR{soa}) != nil { | |||
| t.Error("failure to validate") | |||
| } | |||
| } | |||
| func TestSignature(t *testing.T) { | |||
| sig := new(RRSIG) | |||
| sig.Hdr.Name = "miek.nl." | |||
| sig.Hdr.Class = ClassINET | |||
| sig.Hdr.Ttl = 3600 | |||
| sig.TypeCovered = TypeDNSKEY | |||
| sig.Algorithm = RSASHA1 | |||
| sig.Labels = 2 | |||
| sig.OrigTtl = 4000 | |||
| sig.Expiration = 1000 //Thu Jan 1 02:06:40 CET 1970 | |||
| sig.Inception = 800 //Thu Jan 1 01:13:20 CET 1970 | |||
| sig.KeyTag = 34641 | |||
| sig.SignerName = "miek.nl." | |||
| sig.Signature = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" | |||
| // Should not be valid | |||
| if sig.ValidityPeriod(time.Now()) { | |||
| t.Error("should not be valid") | |||
| } | |||
| sig.Inception = 315565800 //Tue Jan 1 10:10:00 CET 1980 | |||
| sig.Expiration = 4102477800 //Fri Jan 1 10:10:00 CET 2100 | |||
| if !sig.ValidityPeriod(time.Now()) { | |||
| t.Error("should be valid") | |||
| } | |||
| } | |||
| func TestSignVerify(t *testing.T) { | |||
| // The record we want to sign | |||
| soa := new(SOA) | |||
| soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} | |||
| soa.Ns = "open.nlnetlabs.nl." | |||
| soa.Mbox = "miekg.atoom.net." | |||
| soa.Serial = 1293945905 | |||
| soa.Refresh = 14400 | |||
| soa.Retry = 3600 | |||
| soa.Expire = 604800 | |||
| soa.Minttl = 86400 | |||
| soa1 := new(SOA) | |||
| soa1.Hdr = RR_Header{"*.miek.nl.", TypeSOA, ClassINET, 14400, 0} | |||
| soa1.Ns = "open.nlnetlabs.nl." | |||
| soa1.Mbox = "miekg.atoom.net." | |||
| soa1.Serial = 1293945905 | |||
| soa1.Refresh = 14400 | |||
| soa1.Retry = 3600 | |||
| soa1.Expire = 604800 | |||
| soa1.Minttl = 86400 | |||
| srv := new(SRV) | |||
| srv.Hdr = RR_Header{"srv.miek.nl.", TypeSRV, ClassINET, 14400, 0} | |||
| srv.Port = 1000 | |||
| srv.Weight = 800 | |||
| srv.Target = "web1.miek.nl." | |||
| hinfo := &HINFO{ | |||
| Hdr: RR_Header{ | |||
| Name: "miek.nl.", | |||
| Rrtype: TypeHINFO, | |||
| Class: ClassINET, | |||
| Ttl: 3789, | |||
| }, | |||
| Cpu: "X", | |||
| Os: "Y", | |||
| } | |||
| // With this key | |||
| key := new(DNSKEY) | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| privkey, _ := key.Generate(512) | |||
| // Fill in the values of the Sig, before signing | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.TypeCovered = soa.Hdr.Rrtype | |||
| sig.Labels = uint8(CountLabel(soa.Hdr.Name)) // works for all 3 | |||
| sig.OrigTtl = soa.Hdr.Ttl | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.KeyTag = key.KeyTag() // Get the keyfrom the Key | |||
| sig.SignerName = key.Hdr.Name | |||
| sig.Algorithm = RSASHA256 | |||
| for _, r := range []RR{soa, soa1, srv, hinfo} { | |||
| if err := sig.Sign(privkey.(*rsa.PrivateKey), []RR{r}); err != nil { | |||
| t.Error("failure to sign the record:", err) | |||
| continue | |||
| } | |||
| if err := sig.Verify(key, []RR{r}); err != nil { | |||
| t.Error("failure to validate") | |||
| continue | |||
| } | |||
| t.Logf("validated: %s", r.Header().Name) | |||
| } | |||
| } | |||
| func Test65534(t *testing.T) { | |||
| t6 := new(RFC3597) | |||
| t6.Hdr = RR_Header{"miek.nl.", 65534, ClassINET, 14400, 0} | |||
| t6.Rdata = "505D870001" | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| privkey, _ := key.Generate(1024) | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.TypeCovered = t6.Hdr.Rrtype | |||
| sig.Labels = uint8(CountLabel(t6.Hdr.Name)) | |||
| sig.OrigTtl = t6.Hdr.Ttl | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.KeyTag = key.KeyTag() | |||
| sig.SignerName = key.Hdr.Name | |||
| sig.Algorithm = RSASHA256 | |||
| if err := sig.Sign(privkey.(*rsa.PrivateKey), []RR{t6}); err != nil { | |||
| t.Error(err) | |||
| t.Error("failure to sign the TYPE65534 record") | |||
| } | |||
| if err := sig.Verify(key, []RR{t6}); err != nil { | |||
| t.Error(err) | |||
| t.Error("failure to validate") | |||
| } else { | |||
| t.Logf("validated: %s", t6.Header().Name) | |||
| } | |||
| } | |||
| func TestDnskey(t *testing.T) { | |||
| pubkey, err := ReadRR(strings.NewReader(` | |||
| miek.nl. IN DNSKEY 256 3 10 AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL ;{id = 5240 (zsk), size = 1024b} | |||
| `), "Kmiek.nl.+010+05240.key") | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| privStr := `Private-key-format: v1.3 | |||
| Algorithm: 10 (RSASHA512) | |||
| Modulus: m4wK7YV26AeROtdiCXmqLG9wPDVoMOW8vjr/EkpscEAdjXp81RvZvrlzCSjYmz9onFRgltmTl3AINnFh+t9tlW0M9C5zejxBoKFXELv8ljPYAdz2oe+pDWPhWsfvVFYg2VCjpViPM38EakyE5mhk4TDOnUd+w4TeU1hyhZTWyYs= | |||
| PublicExponent: AQAB | |||
| PrivateExponent: UfCoIQ/Z38l8vB6SSqOI/feGjHEl/fxIPX4euKf0D/32k30fHbSaNFrFOuIFmWMB3LimWVEs6u3dpbB9CQeCVg7hwU5puG7OtuiZJgDAhNeOnxvo5btp4XzPZrJSxR4WNQnwIiYWbl0aFlL1VGgHC/3By89ENZyWaZcMLW4KGWE= | |||
| Prime1: yxwC6ogAu8aVcDx2wg1V0b5M5P6jP8qkRFVMxWNTw60Vkn+ECvw6YAZZBHZPaMyRYZLzPgUlyYRd0cjupy4+fQ== | |||
| Prime2: xA1bF8M0RTIQ6+A11AoVG6GIR/aPGg5sogRkIZ7ID/sF6g9HMVU/CM2TqVEBJLRPp73cv6ZeC3bcqOCqZhz+pw== | |||
| Exponent1: xzkblyZ96bGYxTVZm2/vHMOXswod4KWIyMoOepK6B/ZPcZoIT6omLCgtypWtwHLfqyCz3MK51Nc0G2EGzg8rFQ== | |||
| Exponent2: Pu5+mCEb7T5F+kFNZhQadHUklt0JUHbi3hsEvVoHpEGSw3BGDQrtIflDde0/rbWHgDPM4WQY+hscd8UuTXrvLw== | |||
| Coefficient: UuRoNqe7YHnKmQzE6iDWKTMIWTuoqqrFAmXPmKQnC+Y+BQzOVEHUo9bXdDnoI9hzXP1gf8zENMYwYLeWpuYlFQ== | |||
| ` | |||
| privkey, err := pubkey.(*DNSKEY).ReadPrivateKey(strings.NewReader(privStr), | |||
| "Kmiek.nl.+010+05240.private") | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if pubkey.(*DNSKEY).PublicKey != "AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL" { | |||
| t.Error("pubkey is not what we've read") | |||
| } | |||
| if pubkey.(*DNSKEY).PrivateKeyString(privkey) != privStr { | |||
| t.Error("privkey is not what we've read") | |||
| t.Errorf("%v", pubkey.(*DNSKEY).PrivateKeyString(privkey)) | |||
| } | |||
| } | |||
| func TestTag(t *testing.T) { | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 3600 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" | |||
| tag := key.KeyTag() | |||
| if tag != 12051 { | |||
| t.Errorf("wrong key tag: %d for key %v", tag, key) | |||
| } | |||
| } | |||
| func TestKeyRSA(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 3600 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| priv, _ := key.Generate(2048) | |||
| soa := new(SOA) | |||
| soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} | |||
| soa.Ns = "open.nlnetlabs.nl." | |||
| soa.Mbox = "miekg.atoom.net." | |||
| soa.Serial = 1293945905 | |||
| soa.Refresh = 14400 | |||
| soa.Retry = 3600 | |||
| soa.Expire = 604800 | |||
| soa.Minttl = 86400 | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.TypeCovered = TypeSOA | |||
| sig.Algorithm = RSASHA256 | |||
| sig.Labels = 2 | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.OrigTtl = soa.Hdr.Ttl | |||
| sig.KeyTag = key.KeyTag() | |||
| sig.SignerName = key.Hdr.Name | |||
| if err := sig.Sign(priv.(*rsa.PrivateKey), []RR{soa}); err != nil { | |||
| t.Error("failed to sign") | |||
| return | |||
| } | |||
| if err := sig.Verify(key, []RR{soa}); err != nil { | |||
| t.Error("failed to verify") | |||
| } | |||
| } | |||
| func TestKeyToDS(t *testing.T) { | |||
| key := new(DNSKEY) | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 3600 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = RSASHA256 | |||
| key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" | |||
| ds := key.ToDS(SHA1) | |||
| if strings.ToUpper(ds.Digest) != "B5121BDB5B8D86D0CC5FFAFBAAABE26C3E20BAC1" { | |||
| t.Errorf("wrong DS digest for SHA1\n%v", ds) | |||
| } | |||
| } | |||
| func TestSignRSA(t *testing.T) { | |||
| pub := "miek.nl. IN DNSKEY 256 3 5 AwEAAb+8lGNCxJgLS8rYVer6EnHVuIkQDghdjdtewDzU3G5R7PbMbKVRvH2Ma7pQyYceoaqWZQirSj72euPWfPxQnMy9ucCylA+FuH9cSjIcPf4PqJfdupHk9X6EBYjxrCLY4p1/yBwgyBIRJtZtAqM3ceAH2WovEJD6rTtOuHo5AluJ" | |||
| priv := `Private-key-format: v1.3 | |||
| Algorithm: 5 (RSASHA1) | |||
| Modulus: v7yUY0LEmAtLythV6voScdW4iRAOCF2N217APNTcblHs9sxspVG8fYxrulDJhx6hqpZlCKtKPvZ649Z8/FCczL25wLKUD4W4f1xKMhw9/g+ol926keT1foQFiPGsItjinX/IHCDIEhEm1m0Cozdx4AfZai8QkPqtO064ejkCW4k= | |||
| PublicExponent: AQAB | |||
| PrivateExponent: YPwEmwjk5HuiROKU4xzHQ6l1hG8Iiha4cKRG3P5W2b66/EN/GUh07ZSf0UiYB67o257jUDVEgwCuPJz776zfApcCB4oGV+YDyEu7Hp/rL8KcSN0la0k2r9scKwxTp4BTJT23zyBFXsV/1wRDK1A5NxsHPDMYi2SoK63Enm/1ptk= | |||
| Prime1: /wjOG+fD0ybNoSRn7nQ79udGeR1b0YhUA5mNjDx/x2fxtIXzygYk0Rhx9QFfDy6LOBvz92gbNQlzCLz3DJt5hw== | |||
| Prime2: wHZsJ8OGhkp5p3mrJFZXMDc2mbYusDVTA+t+iRPdS797Tj0pjvU2HN4vTnTj8KBQp6hmnY7dLp9Y1qserySGbw== | |||
| Exponent1: N0A7FsSRIg+IAN8YPQqlawoTtG1t1OkJ+nWrurPootScApX6iMvn8fyvw3p2k51rv84efnzpWAYiC8SUaQDNxQ== | |||
| Exponent2: SvuYRaGyvo0zemE3oS+WRm2scxR8eiA8WJGeOc+obwOKCcBgeZblXzfdHGcEC1KaOcetOwNW/vwMA46lpLzJNw== | |||
| Coefficient: 8+7ZN/JgByqv0NfULiFKTjtyegUcijRuyij7yNxYbCBneDvZGxJwKNi4YYXWx743pcAj4Oi4Oh86gcmxLs+hGw== | |||
| Created: 20110302104537 | |||
| Publish: 20110302104537 | |||
| Activate: 20110302104537` | |||
| xk, _ := NewRR(pub) | |||
| k := xk.(*DNSKEY) | |||
| p, err := k.NewPrivateKey(priv) | |||
| if err != nil { | |||
| t.Error(err) | |||
| } | |||
| switch priv := p.(type) { | |||
| case *rsa.PrivateKey: | |||
| if 65537 != priv.PublicKey.E { | |||
| t.Error("exponenent should be 65537") | |||
| } | |||
| default: | |||
| t.Errorf("we should have read an RSA key: %v", priv) | |||
| } | |||
| if k.KeyTag() != 37350 { | |||
| t.Errorf("keytag should be 37350, got %d %v", k.KeyTag(), k) | |||
| } | |||
| soa := new(SOA) | |||
| soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0} | |||
| soa.Ns = "open.nlnetlabs.nl." | |||
| soa.Mbox = "miekg.atoom.net." | |||
| soa.Serial = 1293945905 | |||
| soa.Refresh = 14400 | |||
| soa.Retry = 3600 | |||
| soa.Expire = 604800 | |||
| soa.Minttl = 86400 | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.KeyTag = k.KeyTag() | |||
| sig.SignerName = k.Hdr.Name | |||
| sig.Algorithm = k.Algorithm | |||
| sig.Sign(p.(*rsa.PrivateKey), []RR{soa}) | |||
| if sig.Signature != "D5zsobpQcmMmYsUMLxCVEtgAdCvTu8V/IEeP4EyLBjqPJmjt96bwM9kqihsccofA5LIJ7DN91qkCORjWSTwNhzCv7bMyr2o5vBZElrlpnRzlvsFIoAZCD9xg6ZY7ZyzUJmU6IcTwG4v3xEYajcpbJJiyaw/RqR90MuRdKPiBzSo=" { | |||
| t.Errorf("signature is not correct: %v", sig) | |||
| } | |||
| } | |||
| func TestSignVerifyECDSA(t *testing.T) { | |||
| pub := `example.net. 3600 IN DNSKEY 257 3 14 ( | |||
| xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1 | |||
| w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8 | |||
| /uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )` | |||
| priv := `Private-key-format: v1.2 | |||
| Algorithm: 14 (ECDSAP384SHA384) | |||
| PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR` | |||
| eckey, err := NewRR(pub) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| privkey, err := eckey.(*DNSKEY).NewPrivateKey(priv) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| // TODO: Create separate test for this | |||
| ds := eckey.(*DNSKEY).ToDS(SHA384) | |||
| if ds.KeyTag != 10771 { | |||
| t.Fatal("wrong keytag on DS") | |||
| } | |||
| if ds.Digest != "72d7b62976ce06438e9c0bf319013cf801f09ecc84b8d7e9495f27e305c6a9b0563a9b5f4d288405c3008a946df983d6" { | |||
| t.Fatal("wrong DS Digest") | |||
| } | |||
| a, _ := NewRR("www.example.net. 3600 IN A 192.0.2.1") | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"example.net.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.Expiration, _ = StringToTime("20100909102025") | |||
| sig.Inception, _ = StringToTime("20100812102025") | |||
| sig.KeyTag = eckey.(*DNSKEY).KeyTag() | |||
| sig.SignerName = eckey.(*DNSKEY).Hdr.Name | |||
| sig.Algorithm = eckey.(*DNSKEY).Algorithm | |||
| if sig.Sign(privkey.(*ecdsa.PrivateKey), []RR{a}) != nil { | |||
| t.Fatal("failure to sign the record") | |||
| } | |||
| if err := sig.Verify(eckey.(*DNSKEY), []RR{a}); err != nil { | |||
| t.Fatalf("failure to validate:\n%s\n%s\n%s\n\n%s\n\n%v", | |||
| eckey.(*DNSKEY).String(), | |||
| a.String(), | |||
| sig.String(), | |||
| eckey.(*DNSKEY).PrivateKeyString(privkey), | |||
| err, | |||
| ) | |||
| } | |||
| } | |||
| func TestSignVerifyECDSA2(t *testing.T) { | |||
| srv1, err := NewRR("srv.miek.nl. IN SRV 1000 800 0 web1.miek.nl.") | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| srv := srv1.(*SRV) | |||
| // With this key | |||
| key := new(DNSKEY) | |||
| key.Hdr.Rrtype = TypeDNSKEY | |||
| key.Hdr.Name = "miek.nl." | |||
| key.Hdr.Class = ClassINET | |||
| key.Hdr.Ttl = 14400 | |||
| key.Flags = 256 | |||
| key.Protocol = 3 | |||
| key.Algorithm = ECDSAP256SHA256 | |||
| privkey, err := key.Generate(256) | |||
| if err != nil { | |||
| t.Fatal("failure to generate key") | |||
| } | |||
| // Fill in the values of the Sig, before signing | |||
| sig := new(RRSIG) | |||
| sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0} | |||
| sig.TypeCovered = srv.Hdr.Rrtype | |||
| sig.Labels = uint8(CountLabel(srv.Hdr.Name)) // works for all 3 | |||
| sig.OrigTtl = srv.Hdr.Ttl | |||
| sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05" | |||
| sig.Inception = 1293942305 // date -u '+%s' -d"2011-01-02 04:25:05" | |||
| sig.KeyTag = key.KeyTag() // Get the keyfrom the Key | |||
| sig.SignerName = key.Hdr.Name | |||
| sig.Algorithm = ECDSAP256SHA256 | |||
| if sig.Sign(privkey.(*ecdsa.PrivateKey), []RR{srv}) != nil { | |||
| t.Fatal("failure to sign the record") | |||
| } | |||
| err = sig.Verify(key, []RR{srv}) | |||
| if err != nil { | |||
| t.Logf("failure to validate:\n%s\n%s\n%s\n\n%s\n\n%v", | |||
| key.String(), | |||
| srv.String(), | |||
| sig.String(), | |||
| key.PrivateKeyString(privkey), | |||
| err, | |||
| ) | |||
| } | |||
| } | |||
| // Here the test vectors from the relevant RFCs are checked. | |||
| // rfc6605 6.1 | |||
| func TestRFC6605P256(t *testing.T) { | |||
| exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 13 ( | |||
| GojIhhXUN/u4v54ZQqGSnyhWJwaubCvTmeexv7bR6edb | |||
| krSqQpF64cYbcB7wNcP+e+MAnLr+Wi9xMWyQLc8NAA== )` | |||
| exPriv := `Private-key-format: v1.2 | |||
| Algorithm: 13 (ECDSAP256SHA256) | |||
| PrivateKey: GU6SnQ/Ou+xC5RumuIUIuJZteXT2z0O/ok1s38Et6mQ=` | |||
| rrDNSKEY, err := NewRR(exDNSKEY) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| exDS := `example.net. 3600 IN DS 55648 13 2 ( | |||
| b4c8c1fe2e7477127b27115656ad6256f424625bf5c1 | |||
| e2770ce6d6e37df61d17 )` | |||
| rrDS, err := NewRR(exDS) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA256) | |||
| if !reflect.DeepEqual(ourDS, rrDS.(*DS)) { | |||
| t.Errorf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS)) | |||
| } | |||
| exA := `www.example.net. 3600 IN A 192.0.2.1` | |||
| exRRSIG := `www.example.net. 3600 IN RRSIG A 13 3 3600 ( | |||
| 20100909100439 20100812100439 55648 example.net. | |||
| qx6wLYqmh+l9oCKTN6qIc+bw6ya+KJ8oMz0YP107epXA | |||
| yGmt+3SNruPFKG7tZoLBLlUzGGus7ZwmwWep666VCw== )` | |||
| rrA, err := NewRR(exA) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| rrRRSIG, err := NewRR(exRRSIG) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { | |||
| t.Errorf("failure to validate the spec RRSIG: %v", err) | |||
| } | |||
| ourRRSIG := &RRSIG{ | |||
| Hdr: RR_Header{ | |||
| Ttl: rrA.Header().Ttl, | |||
| }, | |||
| KeyTag: rrDNSKEY.(*DNSKEY).KeyTag(), | |||
| SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name, | |||
| Algorithm: rrDNSKEY.(*DNSKEY).Algorithm, | |||
| } | |||
| ourRRSIG.Expiration, _ = StringToTime("20100909100439") | |||
| ourRRSIG.Inception, _ = StringToTime("20100812100439") | |||
| err = ourRRSIG.Sign(priv.(*ecdsa.PrivateKey), []RR{rrA}) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { | |||
| t.Errorf("failure to validate our RRSIG: %v", err) | |||
| } | |||
| // Signatures are randomized | |||
| rrRRSIG.(*RRSIG).Signature = "" | |||
| ourRRSIG.Signature = "" | |||
| if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) { | |||
| t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG)) | |||
| } | |||
| } | |||
| // rfc6605 6.2 | |||
| func TestRFC6605P384(t *testing.T) { | |||
| exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 14 ( | |||
| xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1 | |||
| w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8 | |||
| /uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )` | |||
| exPriv := `Private-key-format: v1.2 | |||
| Algorithm: 14 (ECDSAP384SHA384) | |||
| PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR` | |||
| rrDNSKEY, err := NewRR(exDNSKEY) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| exDS := `example.net. 3600 IN DS 10771 14 4 ( | |||
| 72d7b62976ce06438e9c0bf319013cf801f09ecc84b8 | |||
| d7e9495f27e305c6a9b0563a9b5f4d288405c3008a94 | |||
| 6df983d6 )` | |||
| rrDS, err := NewRR(exDS) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA384) | |||
| if !reflect.DeepEqual(ourDS, rrDS.(*DS)) { | |||
| t.Fatalf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS)) | |||
| } | |||
| exA := `www.example.net. 3600 IN A 192.0.2.1` | |||
| exRRSIG := `www.example.net. 3600 IN RRSIG A 14 3 3600 ( | |||
| 20100909102025 20100812102025 10771 example.net. | |||
| /L5hDKIvGDyI1fcARX3z65qrmPsVz73QD1Mr5CEqOiLP | |||
| 95hxQouuroGCeZOvzFaxsT8Glr74hbavRKayJNuydCuz | |||
| WTSSPdz7wnqXL5bdcJzusdnI0RSMROxxwGipWcJm )` | |||
| rrA, err := NewRR(exA) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| rrRRSIG, err := NewRR(exRRSIG) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { | |||
| t.Errorf("failure to validate the spec RRSIG: %v", err) | |||
| } | |||
| ourRRSIG := &RRSIG{ | |||
| Hdr: RR_Header{ | |||
| Ttl: rrA.Header().Ttl, | |||
| }, | |||
| KeyTag: rrDNSKEY.(*DNSKEY).KeyTag(), | |||
| SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name, | |||
| Algorithm: rrDNSKEY.(*DNSKEY).Algorithm, | |||
| } | |||
| ourRRSIG.Expiration, _ = StringToTime("20100909102025") | |||
| ourRRSIG.Inception, _ = StringToTime("20100812102025") | |||
| err = ourRRSIG.Sign(priv.(*ecdsa.PrivateKey), []RR{rrA}) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil { | |||
| t.Errorf("failure to validate our RRSIG: %v", err) | |||
| } | |||
| // Signatures are randomized | |||
| rrRRSIG.(*RRSIG).Signature = "" | |||
| ourRRSIG.Signature = "" | |||
| if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) { | |||
| t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG)) | |||
| } | |||
| } | |||
| func TestInvalidRRSet(t *testing.T) { | |||
| goodRecords := make([]RR, 2) | |||
| goodRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| goodRecords[1] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"_o/"}} | |||
| // Generate key | |||
| keyname := "cloudflare.com." | |||
| key := &DNSKEY{ | |||
| Hdr: RR_Header{Name: keyname, Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 0}, | |||
| Algorithm: ECDSAP256SHA256, | |||
| Flags: ZONE, | |||
| Protocol: 3, | |||
| } | |||
| privatekey, err := key.Generate(256) | |||
| if err != nil { | |||
| t.Fatal(err.Error()) | |||
| } | |||
| // Need to fill in: Inception, Expiration, KeyTag, SignerName and Algorithm | |||
| curTime := time.Now() | |||
| signature := &RRSIG{ | |||
| Inception: uint32(curTime.Unix()), | |||
| Expiration: uint32(curTime.Add(time.Hour).Unix()), | |||
| KeyTag: key.KeyTag(), | |||
| SignerName: keyname, | |||
| Algorithm: ECDSAP256SHA256, | |||
| } | |||
| // Inconsistent name between records | |||
| badRecords := make([]RR, 2) | |||
| badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| badRecords[1] = &TXT{Hdr: RR_Header{Name: "nama.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"_o/"}} | |||
| if IsRRset(badRecords) { | |||
| t.Fatal("Record set with inconsistent names considered valid") | |||
| } | |||
| badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| badRecords[1] = &A{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeA, Class: ClassINET, Ttl: 0}} | |||
| if IsRRset(badRecords) { | |||
| t.Fatal("Record set with inconsistent record types considered valid") | |||
| } | |||
| badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| badRecords[1] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassCHAOS, Ttl: 0}, Txt: []string{"_o/"}} | |||
| if IsRRset(badRecords) { | |||
| t.Fatal("Record set with inconsistent record class considered valid") | |||
| } | |||
| // Sign the good record set and then make sure verification fails on the bad record set | |||
| if err := signature.Sign(privatekey.(crypto.Signer), goodRecords); err != nil { | |||
| t.Fatal("Signing good records failed") | |||
| } | |||
| if err := signature.Verify(key, badRecords); err != ErrRRset { | |||
| t.Fatal("Verification did not return ErrRRset with inconsistent records") | |||
| } | |||
| } | |||
| @ -0,0 +1,251 @@ | |||
| /* | |||
| Package dns implements a full featured interface to the Domain Name System. | |||
| Server- and client-side programming is supported. | |||
| The package allows complete control over what is send out to the DNS. The package | |||
| API follows the less-is-more principle, by presenting a small, clean interface. | |||
| The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, | |||
| TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. | |||
| Note that domain names MUST be fully qualified, before sending them, unqualified | |||
| names in a message will result in a packing failure. | |||
| Resource records are native types. They are not stored in wire format. | |||
| Basic usage pattern for creating a new resource record: | |||
| r := new(dns.MX) | |||
| r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, | |||
| Class: dns.ClassINET, Ttl: 3600} | |||
| r.Preference = 10 | |||
| r.Mx = "mx.miek.nl." | |||
| Or directly from a string: | |||
| mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") | |||
| Or when the default TTL (3600) and class (IN) suit you: | |||
| mx, err := dns.NewRR("miek.nl. MX 10 mx.miek.nl.") | |||
| Or even: | |||
| mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") | |||
| In the DNS messages are exchanged, these messages contain resource | |||
| records (sets). Use pattern for creating a message: | |||
| m := new(dns.Msg) | |||
| m.SetQuestion("miek.nl.", dns.TypeMX) | |||
| Or when not certain if the domain name is fully qualified: | |||
| m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) | |||
| The message m is now a message with the question section set to ask | |||
| the MX records for the miek.nl. zone. | |||
| The following is slightly more verbose, but more flexible: | |||
| m1 := new(dns.Msg) | |||
| m1.Id = dns.Id() | |||
| m1.RecursionDesired = true | |||
| m1.Question = make([]dns.Question, 1) | |||
| m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} | |||
| After creating a message it can be send. | |||
| Basic use pattern for synchronous querying the DNS at a | |||
| server configured on 127.0.0.1 and port 53: | |||
| c := new(dns.Client) | |||
| in, rtt, err := c.Exchange(m1, "127.0.0.1:53") | |||
| Suppressing multiple outstanding queries (with the same question, type and | |||
| class) is as easy as setting: | |||
| c.SingleInflight = true | |||
| If these "advanced" features are not needed, a simple UDP query can be send, | |||
| with: | |||
| in, err := dns.Exchange(m1, "127.0.0.1:53") | |||
| When this functions returns you will get dns message. A dns message consists | |||
| out of four sections. | |||
| The question section: in.Question, the answer section: in.Answer, | |||
| the authority section: in.Ns and the additional section: in.Extra. | |||
| Each of these sections (except the Question section) contain a []RR. Basic | |||
| use pattern for accessing the rdata of a TXT RR as the first RR in | |||
| the Answer section: | |||
| if t, ok := in.Answer[0].(*dns.TXT); ok { | |||
| // do something with t.Txt | |||
| } | |||
| Domain Name and TXT Character String Representations | |||
| Both domain names and TXT character strings are converted to presentation | |||
| form both when unpacked and when converted to strings. | |||
| For TXT character strings, tabs, carriage returns and line feeds will be | |||
| converted to \t, \r and \n respectively. Back slashes and quotations marks | |||
| will be escaped. Bytes below 32 and above 127 will be converted to \DDD | |||
| form. | |||
| For domain names, in addition to the above rules brackets, periods, | |||
| spaces, semicolons and the at symbol are escaped. | |||
| DNSSEC | |||
| DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It | |||
| 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. | |||
| m := new(dns.Msg) | |||
| m.SetEdns0(4096, true) | |||
| Signature generation, signature verification and key generation are all supported. | |||
| DYNAMIC UPDATES | |||
| Dynamic updates reuses the DNS message format, but renames three of | |||
| the sections. Question is Zone, Answer is Prerequisite, Authority is | |||
| Update, only the Additional is not renamed. See RFC 2136 for the gory details. | |||
| You can set a rather complex set of rules for the existence of absence of | |||
| certain resource records or names in a zone to specify if resource records | |||
| should be added or removed. The table from RFC 2136 supplemented with the Go | |||
| DNS function shows which functions exist to specify the prerequisites. | |||
| 3.2.4 - Table Of Metavalues Used In Prerequisite Section | |||
| CLASS TYPE RDATA Meaning Function | |||
| -------------------------------------------------------------- | |||
| ANY ANY empty Name is in use dns.NameUsed | |||
| ANY rrset empty RRset exists (value indep) dns.RRsetUsed | |||
| NONE ANY empty Name is not in use dns.NameNotUsed | |||
| NONE rrset empty RRset does not exist dns.RRsetNotUsed | |||
| zone rrset rr RRset exists (value dep) dns.Used | |||
| The prerequisite section can also be left empty. | |||
| If you have decided on the prerequisites you can tell what RRs should | |||
| be added or deleted. The next table shows the options you have and | |||
| what functions to call. | |||
| 3.4.2.6 - Table Of Metavalues Used In Update Section | |||
| CLASS TYPE RDATA Meaning Function | |||
| --------------------------------------------------------------- | |||
| ANY ANY empty Delete all RRsets from name dns.RemoveName | |||
| ANY rrset empty Delete an RRset dns.RemoveRRset | |||
| NONE rrset rr Delete an RR from RRset dns.Remove | |||
| zone rrset rr Add to an RRset dns.Insert | |||
| TRANSACTION SIGNATURE | |||
| An TSIG or transaction signature adds a HMAC TSIG record to each message sent. | |||
| The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512. | |||
| Basic use pattern when querying with a TSIG name "axfr." (note that these key names | |||
| must be fully qualified - as they are domain names) and the base64 secret | |||
| "so6ZGir4GPAqINNh9U5c3A==": | |||
| c := new(dns.Client) | |||
| c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} | |||
| m := new(dns.Msg) | |||
| m.SetQuestion("miek.nl.", dns.TypeMX) | |||
| m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) | |||
| ... | |||
| // When sending the TSIG RR is calculated and filled in before sending | |||
| When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with | |||
| TSIG, this is the basic use pattern. In this example we request an AXFR for | |||
| miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A==" | |||
| and using the server 176.58.119.54: | |||
| t := new(dns.Transfer) | |||
| m := new(dns.Msg) | |||
| t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} | |||
| m.SetAxfr("miek.nl.") | |||
| m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) | |||
| c, err := t.In(m, "176.58.119.54:53") | |||
| for r := range c { ... } | |||
| You can now read the records from the transfer as they come in. Each envelope is checked with TSIG. | |||
| If something is not correct an error is returned. | |||
| Basic use pattern validating and replying to a message that has TSIG set. | |||
| server := &dns.Server{Addr: ":53", Net: "udp"} | |||
| server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} | |||
| go server.ListenAndServe() | |||
| dns.HandleFunc(".", handleRequest) | |||
| func handleRequest(w dns.ResponseWriter, r *dns.Msg) { | |||
| m := new(dns.Msg) | |||
| m.SetReply(r) | |||
| if r.IsTsig() != nil { | |||
| if w.TsigStatus() == nil { | |||
| // *Msg r has an TSIG record and it was validated | |||
| m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) | |||
| } else { | |||
| // *Msg r has an TSIG records and it was not valided | |||
| } | |||
| } | |||
| w.WriteMsg(m) | |||
| } | |||
| PRIVATE RRS | |||
| RFC 6895 sets aside a range of type codes for private use. This range | |||
| is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these | |||
| can be used, before requesting an official type code from IANA. | |||
| see http://miek.nl/posts/2014/Sep/21/Private%20RRs%20and%20IDN%20in%20Go%20DNS/ for more | |||
| information. | |||
| EDNS0 | |||
| EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated | |||
| by RFC 6891. It defines an new RR type, the OPT RR, which is then completely | |||
| abused. | |||
| Basic use pattern for creating an (empty) OPT RR: | |||
| o := new(dns.OPT) | |||
| o.Hdr.Name = "." // MUST be the root zone, per definition. | |||
| o.Hdr.Rrtype = dns.TypeOPT | |||
| The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) | |||
| interfaces. Currently only a few have been standardized: EDNS0_NSID | |||
| (RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note | |||
| that these options may be combined in an OPT RR. | |||
| Basic use pattern for a server to check if (and which) options are set: | |||
| // o is a dns.OPT | |||
| for _, s := range o.Option { | |||
| switch e := s.(type) { | |||
| case *dns.EDNS0_NSID: | |||
| // do stuff with e.Nsid | |||
| case *dns.EDNS0_SUBNET: | |||
| // access e.Family, e.Address, etc. | |||
| } | |||
| } | |||
| SIG(0) | |||
| From RFC 2931: | |||
| SIG(0) provides protection for DNS transactions and requests .... | |||
| ... protection for glue records, DNS requests, protection for message headers | |||
| on requests and responses, and protection of the overall integrity of a response. | |||
| It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared | |||
| secret approach in TSIG. | |||
| Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and | |||
| RSASHA512. | |||
| Signing subsequent messages in multi-message sessions is not implemented. | |||
| */ | |||
| package dns | |||
| @ -0,0 +1,3 @@ | |||
| package dns | |||
| // Find better solution | |||
| @ -0,0 +1,505 @@ | |||
| package dns | |||
| import ( | |||
| "encoding/hex" | |||
| "errors" | |||
| "net" | |||
| "strconv" | |||
| ) | |||
| // EDNS0 Option codes. | |||
| const ( | |||
| EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 | |||
| EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt | |||
| EDNS0NSID = 0x3 // nsid (RFC5001) | |||
| EDNS0DAU = 0x5 // DNSSEC Algorithm Understood | |||
| EDNS0DHU = 0x6 // DS Hash Understood | |||
| EDNS0N3U = 0x7 // NSEC3 Hash Understood | |||
| EDNS0SUBNET = 0x8 // client-subnet (RFC6891) | |||
| EDNS0EXPIRE = 0x9 // EDNS0 expire | |||
| EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET | |||
| EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891) | |||
| EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891) | |||
| _DO = 1 << 15 // dnssec ok | |||
| ) | |||
| // OPT is the EDNS0 RR appended to messages to convey extra (meta) information. | |||
| // See RFC 6891. | |||
| type OPT struct { | |||
| Hdr RR_Header | |||
| Option []EDNS0 `dns:"opt"` | |||
| } | |||
| func (rr *OPT) String() string { | |||
| s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; " | |||
| if rr.Do() { | |||
| s += "flags: do; " | |||
| } else { | |||
| s += "flags: ; " | |||
| } | |||
| s += "udp: " + strconv.Itoa(int(rr.UDPSize())) | |||
| for _, o := range rr.Option { | |||
| switch o.(type) { | |||
| case *EDNS0_NSID: | |||
| s += "\n; NSID: " + o.String() | |||
| h, e := o.pack() | |||
| var r string | |||
| if e == nil { | |||
| for _, c := range h { | |||
| r += "(" + string(c) + ")" | |||
| } | |||
| s += " " + r | |||
| } | |||
| case *EDNS0_SUBNET: | |||
| s += "\n; SUBNET: " + o.String() | |||
| if o.(*EDNS0_SUBNET).DraftOption { | |||
| s += " (draft)" | |||
| } | |||
| case *EDNS0_UL: | |||
| s += "\n; UPDATE LEASE: " + o.String() | |||
| case *EDNS0_LLQ: | |||
| s += "\n; LONG LIVED QUERIES: " + o.String() | |||
| case *EDNS0_DAU: | |||
| s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String() | |||
| case *EDNS0_DHU: | |||
| s += "\n; DS HASH UNDERSTOOD: " + o.String() | |||
| case *EDNS0_N3U: | |||
| s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String() | |||
| case *EDNS0_LOCAL: | |||
| s += "\n; LOCAL OPT: " + o.String() | |||
| } | |||
| } | |||
| return s | |||
| } | |||
| func (rr *OPT) len() int { | |||
| l := rr.Hdr.len() | |||
| for i := 0; i < len(rr.Option); i++ { | |||
| l += 4 // Account for 2-byte option code and 2-byte option length. | |||
| lo, _ := rr.Option[i].pack() | |||
| l += len(lo) | |||
| } | |||
| return l | |||
| } | |||
| // return the old value -> delete SetVersion? | |||
| // Version returns the EDNS version used. Only zero is defined. | |||
| func (rr *OPT) Version() uint8 { | |||
| return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16) | |||
| } | |||
| // SetVersion sets the version of EDNS. This is usually zero. | |||
| func (rr *OPT) SetVersion(v uint8) { | |||
| rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16) | |||
| } | |||
| // 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) | |||
| } | |||
| // SetExtendedRcode sets the EDNS extended RCODE field. | |||
| func (rr *OPT) SetExtendedRcode(v uint8) { | |||
| rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24) | |||
| } | |||
| // UDPSize returns the UDP buffer size. | |||
| func (rr *OPT) UDPSize() uint16 { | |||
| return rr.Hdr.Class | |||
| } | |||
| // SetUDPSize sets the UDP buffer size. | |||
| func (rr *OPT) SetUDPSize(size uint16) { | |||
| rr.Hdr.Class = size | |||
| } | |||
| // Do returns the value of the DO (DNSSEC OK) bit. | |||
| func (rr *OPT) Do() bool { | |||
| return rr.Hdr.Ttl&_DO == _DO | |||
| } | |||
| // SetDo sets the DO (DNSSEC OK) bit. | |||
| func (rr *OPT) SetDo() { | |||
| rr.Hdr.Ttl |= _DO | |||
| } | |||
| // EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to | |||
| // it. | |||
| type EDNS0 interface { | |||
| // Option returns the option code for the option. | |||
| Option() uint16 | |||
| // pack returns the bytes of the option data. | |||
| pack() ([]byte, error) | |||
| // unpack sets the data as found in the buffer. Is also sets | |||
| // the length of the slice as the length of the option data. | |||
| unpack([]byte) error | |||
| // String returns the string representation of the option. | |||
| String() string | |||
| } | |||
| // The nsid EDNS0 option is used to retrieve a nameserver | |||
| // identifier. When sending a request Nsid must be set to the empty string | |||
| // The identifier is an opaque string encoded as hex. | |||
| // Basic use pattern for creating an nsid option: | |||
| // | |||
| // o := new(dns.OPT) | |||
| // o.Hdr.Name = "." | |||
| // o.Hdr.Rrtype = dns.TypeOPT | |||
| // e := new(dns.EDNS0_NSID) | |||
| // e.Code = dns.EDNS0NSID | |||
| // e.Nsid = "AA" | |||
| // o.Option = append(o.Option, e) | |||
| type EDNS0_NSID struct { | |||
| Code uint16 // Always EDNS0NSID | |||
| Nsid string // This string needs to be hex encoded | |||
| } | |||
| func (e *EDNS0_NSID) pack() ([]byte, error) { | |||
| h, err := hex.DecodeString(e.Nsid) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return h, nil | |||
| } | |||
| func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } | |||
| func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil } | |||
| func (e *EDNS0_NSID) String() string { return string(e.Nsid) } | |||
| // EDNS0_SUBNET is the subnet option that is used to give the remote nameserver | |||
| // an idea of where the client lives. It can then give back a different | |||
| // answer depending on the location or network topology. | |||
| // Basic use pattern for creating an subnet option: | |||
| // | |||
| // o := new(dns.OPT) | |||
| // o.Hdr.Name = "." | |||
| // o.Hdr.Rrtype = dns.TypeOPT | |||
| // e := new(dns.EDNS0_SUBNET) | |||
| // e.Code = dns.EDNS0SUBNET | |||
| // e.Family = 1 // 1 for IPv4 source address, 2 for IPv6 | |||
| // e.NetMask = 32 // 32 for IPV4, 128 for IPv6 | |||
| // e.SourceScope = 0 | |||
| // e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4 | |||
| // // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6 | |||
| // o.Option = append(o.Option, e) | |||
| // | |||
| // Note: the spec (draft-ietf-dnsop-edns-client-subnet-00) has some insane logic | |||
| // for which netmask applies to the address. This code will parse all the | |||
| // available bits when unpacking (up to optlen). When packing it will apply | |||
| // SourceNetmask. If you need more advanced logic, patches welcome and good luck. | |||
| type EDNS0_SUBNET struct { | |||
| Code uint16 // Always EDNS0SUBNET | |||
| Family uint16 // 1 for IP, 2 for IP6 | |||
| SourceNetmask uint8 | |||
| SourceScope uint8 | |||
| Address net.IP | |||
| DraftOption bool // Set to true if using the old (0x50fa) option code | |||
| } | |||
| func (e *EDNS0_SUBNET) Option() uint16 { | |||
| if e.DraftOption { | |||
| return EDNS0SUBNETDRAFT | |||
| } | |||
| return EDNS0SUBNET | |||
| } | |||
| func (e *EDNS0_SUBNET) pack() ([]byte, error) { | |||
| b := make([]byte, 4) | |||
| b[0], b[1] = packUint16(e.Family) | |||
| b[2] = e.SourceNetmask | |||
| b[3] = e.SourceScope | |||
| switch e.Family { | |||
| case 1: | |||
| if e.SourceNetmask > net.IPv4len*8 { | |||
| return nil, errors.New("dns: bad netmask") | |||
| } | |||
| if len(e.Address.To4()) != net.IPv4len { | |||
| return nil, errors.New("dns: bad address") | |||
| } | |||
| ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8)) | |||
| needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up | |||
| b = append(b, ip[:needLength]...) | |||
| case 2: | |||
| if e.SourceNetmask > net.IPv6len*8 { | |||
| return nil, errors.New("dns: bad netmask") | |||
| } | |||
| if len(e.Address) != net.IPv6len { | |||
| return nil, errors.New("dns: bad address") | |||
| } | |||
| ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8)) | |||
| needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up | |||
| b = append(b, ip[:needLength]...) | |||
| default: | |||
| return nil, errors.New("dns: bad address family") | |||
| } | |||
| return b, nil | |||
| } | |||
| func (e *EDNS0_SUBNET) unpack(b []byte) error { | |||
| if len(b) < 4 { | |||
| return ErrBuf | |||
| } | |||
| e.Family, _ = unpackUint16(b, 0) | |||
| e.SourceNetmask = b[2] | |||
| e.SourceScope = b[3] | |||
| switch e.Family { | |||
| case 1: | |||
| if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 { | |||
| return errors.New("dns: bad netmask") | |||
| } | |||
| addr := make([]byte, net.IPv4len) | |||
| for i := 0; i < net.IPv4len && 4+i < len(b); i++ { | |||
| addr[i] = b[4+i] | |||
| } | |||
| e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3]) | |||
| case 2: | |||
| if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 { | |||
| return errors.New("dns: bad netmask") | |||
| } | |||
| addr := make([]byte, net.IPv6len) | |||
| for i := 0; i < net.IPv6len && 4+i < len(b); i++ { | |||
| addr[i] = b[4+i] | |||
| } | |||
| e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4], | |||
| addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], | |||
| addr[11], addr[12], addr[13], addr[14], addr[15]} | |||
| default: | |||
| return errors.New("dns: bad address family") | |||
| } | |||
| return nil | |||
| } | |||
| func (e *EDNS0_SUBNET) String() (s string) { | |||
| if e.Address == nil { | |||
| s = "<nil>" | |||
| } else if e.Address.To4() != nil { | |||
| s = e.Address.String() | |||
| } else { | |||
| s = "[" + e.Address.String() + "]" | |||
| } | |||
| s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope)) | |||
| return | |||
| } | |||
| // The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set | |||
| // an expiration on an update RR. This is helpful for clients that cannot clean | |||
| // up after themselves. This is a draft RFC and more information can be found at | |||
| // http://files.dns-sd.org/draft-sekar-dns-ul.txt | |||
| // | |||
| // o := new(dns.OPT) | |||
| // o.Hdr.Name = "." | |||
| // o.Hdr.Rrtype = dns.TypeOPT | |||
| // e := new(dns.EDNS0_UL) | |||
| // e.Code = dns.EDNS0UL | |||
| // e.Lease = 120 // in seconds | |||
| // o.Option = append(o.Option, e) | |||
| type EDNS0_UL struct { | |||
| Code uint16 // Always EDNS0UL | |||
| Lease uint32 | |||
| } | |||
| func (e *EDNS0_UL) Option() uint16 { return EDNS0UL } | |||
| func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) } | |||
| // Copied: http://golang.org/src/pkg/net/dnsmsg.go | |||
| func (e *EDNS0_UL) pack() ([]byte, error) { | |||
| b := make([]byte, 4) | |||
| b[0] = byte(e.Lease >> 24) | |||
| b[1] = byte(e.Lease >> 16) | |||
| b[2] = byte(e.Lease >> 8) | |||
| b[3] = byte(e.Lease) | |||
| return b, nil | |||
| } | |||
| func (e *EDNS0_UL) unpack(b []byte) error { | |||
| if len(b) < 4 { | |||
| return ErrBuf | |||
| } | |||
| e.Lease = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) | |||
| return nil | |||
| } | |||
| // EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 | |||
| // Implemented for completeness, as the EDNS0 type code is assigned. | |||
| type EDNS0_LLQ struct { | |||
| Code uint16 // Always EDNS0LLQ | |||
| Version uint16 | |||
| Opcode uint16 | |||
| Error uint16 | |||
| Id uint64 | |||
| LeaseLife uint32 | |||
| } | |||
| func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ } | |||
| func (e *EDNS0_LLQ) pack() ([]byte, error) { | |||
| b := make([]byte, 18) | |||
| b[0], b[1] = packUint16(e.Version) | |||
| b[2], b[3] = packUint16(e.Opcode) | |||
| b[4], b[5] = packUint16(e.Error) | |||
| b[6] = byte(e.Id >> 56) | |||
| b[7] = byte(e.Id >> 48) | |||
| b[8] = byte(e.Id >> 40) | |||
| b[9] = byte(e.Id >> 32) | |||
| b[10] = byte(e.Id >> 24) | |||
| b[11] = byte(e.Id >> 16) | |||
| b[12] = byte(e.Id >> 8) | |||
| b[13] = byte(e.Id) | |||
| b[14] = byte(e.LeaseLife >> 24) | |||
| b[15] = byte(e.LeaseLife >> 16) | |||
| b[16] = byte(e.LeaseLife >> 8) | |||
| b[17] = byte(e.LeaseLife) | |||
| return b, nil | |||
| } | |||
| func (e *EDNS0_LLQ) unpack(b []byte) error { | |||
| if len(b) < 18 { | |||
| return ErrBuf | |||
| } | |||
| e.Version, _ = unpackUint16(b, 0) | |||
| e.Opcode, _ = unpackUint16(b, 2) | |||
| e.Error, _ = unpackUint16(b, 4) | |||
| e.Id = uint64(b[6])<<56 | uint64(b[6+1])<<48 | uint64(b[6+2])<<40 | | |||
| uint64(b[6+3])<<32 | uint64(b[6+4])<<24 | uint64(b[6+5])<<16 | uint64(b[6+6])<<8 | uint64(b[6+7]) | |||
| e.LeaseLife = uint32(b[14])<<24 | uint32(b[14+1])<<16 | uint32(b[14+2])<<8 | uint32(b[14+3]) | |||
| return nil | |||
| } | |||
| func (e *EDNS0_LLQ) String() string { | |||
| s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) + | |||
| " " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) + | |||
| " " + strconv.FormatUint(uint64(e.LeaseLife), 10) | |||
| return s | |||
| } | |||
| type EDNS0_DAU struct { | |||
| Code uint16 // Always EDNS0DAU | |||
| AlgCode []uint8 | |||
| } | |||
| func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU } | |||
| func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil } | |||
| func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil } | |||
| func (e *EDNS0_DAU) String() string { | |||
| s := "" | |||
| for i := 0; i < len(e.AlgCode); i++ { | |||
| if a, ok := AlgorithmToString[e.AlgCode[i]]; ok { | |||
| s += " " + a | |||
| } else { | |||
| s += " " + strconv.Itoa(int(e.AlgCode[i])) | |||
| } | |||
| } | |||
| return s | |||
| } | |||
| type EDNS0_DHU struct { | |||
| Code uint16 // Always EDNS0DHU | |||
| AlgCode []uint8 | |||
| } | |||
| func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU } | |||
| func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil } | |||
| func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil } | |||
| func (e *EDNS0_DHU) String() string { | |||
| s := "" | |||
| for i := 0; i < len(e.AlgCode); i++ { | |||
| if a, ok := HashToString[e.AlgCode[i]]; ok { | |||
| s += " " + a | |||
| } else { | |||
| s += " " + strconv.Itoa(int(e.AlgCode[i])) | |||
| } | |||
| } | |||
| return s | |||
| } | |||
| type EDNS0_N3U struct { | |||
| Code uint16 // Always EDNS0N3U | |||
| AlgCode []uint8 | |||
| } | |||
| func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U } | |||
| func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil } | |||
| func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil } | |||
| func (e *EDNS0_N3U) String() string { | |||
| // Re-use the hash map | |||
| s := "" | |||
| for i := 0; i < len(e.AlgCode); i++ { | |||
| if a, ok := HashToString[e.AlgCode[i]]; ok { | |||
| s += " " + a | |||
| } else { | |||
| s += " " + strconv.Itoa(int(e.AlgCode[i])) | |||
| } | |||
| } | |||
| return s | |||
| } | |||
| type EDNS0_EXPIRE struct { | |||
| Code uint16 // Always EDNS0EXPIRE | |||
| Expire uint32 | |||
| } | |||
| func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } | |||
| func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } | |||
| func (e *EDNS0_EXPIRE) pack() ([]byte, error) { | |||
| b := make([]byte, 4) | |||
| b[0] = byte(e.Expire >> 24) | |||
| b[1] = byte(e.Expire >> 16) | |||
| b[2] = byte(e.Expire >> 8) | |||
| b[3] = byte(e.Expire) | |||
| return b, nil | |||
| } | |||
| func (e *EDNS0_EXPIRE) unpack(b []byte) error { | |||
| if len(b) < 4 { | |||
| return ErrBuf | |||
| } | |||
| e.Expire = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) | |||
| return nil | |||
| } | |||
| // The EDNS0_LOCAL option is used for local/experimental purposes. The option | |||
| // code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] | |||
| // (RFC6891), although any unassigned code can actually be used. The content of | |||
| // the option is made available in Data, unaltered. | |||
| // Basic use pattern for creating a local option: | |||
| // | |||
| // o := new(dns.OPT) | |||
| // o.Hdr.Name = "." | |||
| // o.Hdr.Rrtype = dns.TypeOPT | |||
| // e := new(dns.EDNS0_LOCAL) | |||
| // e.Code = dns.EDNS0LOCALSTART | |||
| // e.Data = []byte{72, 82, 74} | |||
| // o.Option = append(o.Option, e) | |||
| type EDNS0_LOCAL struct { | |||
| Code uint16 | |||
| Data []byte | |||
| } | |||
| func (e *EDNS0_LOCAL) Option() uint16 { return e.Code } | |||
| func (e *EDNS0_LOCAL) String() string { | |||
| return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data) | |||
| } | |||
| func (e *EDNS0_LOCAL) pack() ([]byte, error) { | |||
| b := make([]byte, len(e.Data)) | |||
| copied := copy(b, e.Data) | |||
| if copied != len(e.Data) { | |||
| return nil, ErrBuf | |||
| } | |||
| return b, nil | |||
| } | |||
| func (e *EDNS0_LOCAL) unpack(b []byte) error { | |||
| e.Data = make([]byte, len(b)) | |||
| copied := copy(e.Data, b) | |||
| if copied != len(b) { | |||
| return ErrBuf | |||
| } | |||
| return nil | |||
| } | |||
| @ -0,0 +1,48 @@ | |||
| package dns | |||
| import "testing" | |||
| func TestOPTTtl(t *testing.T) { | |||
| e := &OPT{} | |||
| e.Hdr.Name = "." | |||
| e.Hdr.Rrtype = TypeOPT | |||
| if e.Do() { | |||
| t.Fail() | |||
| } | |||
| e.SetDo() | |||
| if !e.Do() { | |||
| t.Fail() | |||
| } | |||
| oldTtl := e.Hdr.Ttl | |||
| if e.Version() != 0 { | |||
| t.Fail() | |||
| } | |||
| 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() | |||
| } | |||
| e.SetExtendedRcode(42) | |||
| if e.ExtendedRcode() != 42 { | |||
| t.Fail() | |||
| } | |||
| e.SetExtendedRcode(0) | |||
| if e.Hdr.Ttl != oldTtl { | |||
| t.Fail() | |||
| } | |||
| } | |||
| @ -0,0 +1,146 @@ | |||
| package dns_test | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "log" | |||
| "net" | |||
| "github.com/miekg/dns" | |||
| ) | |||
| // Retrieve the MX records for miek.nl. | |||
| func ExampleMX() { | |||
| config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") | |||
| c := new(dns.Client) | |||
| m := new(dns.Msg) | |||
| m.SetQuestion("miek.nl.", dns.TypeMX) | |||
| m.RecursionDesired = true | |||
| r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) | |||
| if err != nil { | |||
| return | |||
| } | |||
| if r.Rcode != dns.RcodeSuccess { | |||
| return | |||
| } | |||
| for _, a := range r.Answer { | |||
| if mx, ok := a.(*dns.MX); ok { | |||
| fmt.Printf("%s\n", mx.String()) | |||
| } | |||
| } | |||
| } | |||
| // Retrieve the DNSKEY records of a zone and convert them | |||
| // to DS records for SHA1, SHA256 and SHA384. | |||
| func ExampleDS() { | |||
| config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") | |||
| c := new(dns.Client) | |||
| m := new(dns.Msg) | |||
| zone := "miek.nl" | |||
| m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY) | |||
| m.SetEdns0(4096, true) | |||
| r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) | |||
| if err != nil { | |||
| return | |||
| } | |||
| if r.Rcode != dns.RcodeSuccess { | |||
| return | |||
| } | |||
| for _, k := range r.Answer { | |||
| if key, ok := k.(*dns.DNSKEY); ok { | |||
| for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} { | |||
| fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| const TypeAPAIR = 0x0F99 | |||
| type APAIR struct { | |||
| addr [2]net.IP | |||
| } | |||
| func NewAPAIR() dns.PrivateRdata { return new(APAIR) } | |||
| func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() } | |||
| func (rd *APAIR) Parse(txt []string) error { | |||
| if len(txt) != 2 { | |||
| return errors.New("two addresses required for APAIR") | |||
| } | |||
| for i, s := range txt { | |||
| ip := net.ParseIP(s) | |||
| if ip == nil { | |||
| return errors.New("invalid IP in APAIR text representation") | |||
| } | |||
| rd.addr[i] = ip | |||
| } | |||
| return nil | |||
| } | |||
| func (rd *APAIR) Pack(buf []byte) (int, error) { | |||
| b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...) | |||
| n := copy(buf, b) | |||
| if n != len(b) { | |||
| return n, dns.ErrBuf | |||
| } | |||
| return n, nil | |||
| } | |||
| func (rd *APAIR) Unpack(buf []byte) (int, error) { | |||
| ln := net.IPv4len * 2 | |||
| if len(buf) != ln { | |||
| return 0, errors.New("invalid length of APAIR rdata") | |||
| } | |||
| cp := make([]byte, ln) | |||
| copy(cp, buf) // clone bytes to use them in IPs | |||
| rd.addr[0] = net.IP(cp[:3]) | |||
| rd.addr[1] = net.IP(cp[4:]) | |||
| return len(buf), nil | |||
| } | |||
| func (rd *APAIR) Copy(dest dns.PrivateRdata) error { | |||
| cp := make([]byte, rd.Len()) | |||
| _, err := rd.Pack(cp) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| d := dest.(*APAIR) | |||
| d.addr[0] = net.IP(cp[:3]) | |||
| d.addr[1] = net.IP(cp[4:]) | |||
| return nil | |||
| } | |||
| func (rd *APAIR) Len() int { | |||
| return net.IPv4len * 2 | |||
| } | |||
| func ExamplePrivateHandle() { | |||
| dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR) | |||
| defer dns.PrivateHandleRemove(TypeAPAIR) | |||
| rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)") | |||
| if err != nil { | |||
| log.Fatal("could not parse APAIR record: ", err) | |||
| } | |||
| fmt.Println(rr) | |||
| // Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 | |||
| m := new(dns.Msg) | |||
| m.Id = 12345 | |||
| m.SetQuestion("miek.nl.", TypeAPAIR) | |||
| m.Answer = append(m.Answer, rr) | |||
| fmt.Println(m) | |||
| // ;; opcode: QUERY, status: NOERROR, id: 12345 | |||
| // ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 | |||
| // | |||
| // ;; QUESTION SECTION: | |||
| // ;miek.nl. IN APAIR | |||
| // | |||
| // ;; ANSWER SECTION: | |||
| // miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 | |||
| } | |||
| @ -0,0 +1,96 @@ | |||
| package dns | |||
| import ( | |||
| "net" | |||
| "reflect" | |||
| "strconv" | |||
| ) | |||
| // NumField returns the number of rdata fields r has. | |||
| func NumField(r RR) int { | |||
| return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header | |||
| } | |||
| // Field returns the rdata field i as a string. Fields are indexed starting from 1. | |||
| // RR types that holds slice data, for instance the NSEC type bitmap will return a single | |||
| // string where the types are concatenated using a space. | |||
| // Accessing non existing fields will cause a panic. | |||
| func Field(r RR, i int) string { | |||
| if i == 0 { | |||
| return "" | |||
| } | |||
| d := reflect.ValueOf(r).Elem().Field(i) | |||
| switch k := d.Kind(); k { | |||
| case reflect.String: | |||
| return d.String() | |||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| return strconv.FormatInt(d.Int(), 10) | |||
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| return strconv.FormatUint(d.Uint(), 10) | |||
| case reflect.Slice: | |||
| switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { | |||
| case `dns:"a"`: | |||
| // TODO(miek): Hmm store this as 16 bytes | |||
| if d.Len() < net.IPv6len { | |||
| return net.IPv4(byte(d.Index(0).Uint()), | |||
| byte(d.Index(1).Uint()), | |||
| byte(d.Index(2).Uint()), | |||
| byte(d.Index(3).Uint())).String() | |||
| } | |||
| return net.IPv4(byte(d.Index(12).Uint()), | |||
| byte(d.Index(13).Uint()), | |||
| byte(d.Index(14).Uint()), | |||
| byte(d.Index(15).Uint())).String() | |||
| case `dns:"aaaa"`: | |||
| return net.IP{ | |||
| byte(d.Index(0).Uint()), | |||
| byte(d.Index(1).Uint()), | |||
| byte(d.Index(2).Uint()), | |||
| byte(d.Index(3).Uint()), | |||
| byte(d.Index(4).Uint()), | |||
| byte(d.Index(5).Uint()), | |||
| byte(d.Index(6).Uint()), | |||
| byte(d.Index(7).Uint()), | |||
| byte(d.Index(8).Uint()), | |||
| byte(d.Index(9).Uint()), | |||
| byte(d.Index(10).Uint()), | |||
| byte(d.Index(11).Uint()), | |||
| byte(d.Index(12).Uint()), | |||
| byte(d.Index(13).Uint()), | |||
| byte(d.Index(14).Uint()), | |||
| byte(d.Index(15).Uint()), | |||
| }.String() | |||
| case `dns:"nsec"`: | |||
| if d.Len() == 0 { | |||
| return "" | |||
| } | |||
| s := Type(d.Index(0).Uint()).String() | |||
| for i := 1; i < d.Len(); i++ { | |||
| s += " " + Type(d.Index(i).Uint()).String() | |||
| } | |||
| return s | |||
| case `dns:"wks"`: | |||
| if d.Len() == 0 { | |||
| return "" | |||
| } | |||
| s := strconv.Itoa(int(d.Index(0).Uint())) | |||
| for i := 0; i < d.Len(); i++ { | |||
| s += " " + strconv.Itoa(int(d.Index(i).Uint())) | |||
| } | |||
| return s | |||
| default: | |||
| // if it does not have a tag its a string slice | |||
| fallthrough | |||
| case `dns:"txt"`: | |||
| if d.Len() == 0 { | |||
| return "" | |||
| } | |||
| s := d.Index(0).String() | |||
| for i := 1; i < d.Len(); i++ { | |||
| s += " " + d.Index(i).String() | |||
| } | |||
| return s | |||
| } | |||
| } | |||
| return "" | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| package dns | |||
| import "testing" | |||
| func TestFuzzString(t *testing.T) { | |||
| testcases := []string{"", " MINFO ", " RP ", " NSEC 0 0", " \" NSEC 0 0\"", " \" MINFO \"", | |||
| ";a ", ";a����������", | |||
| " NSAP O ", " NSAP N ", | |||
| " TYPE4 TYPE6a789a3bc0045c8a5fb42c7d1bd998f5444 IN 9579b47d46817afbd17273e6", | |||
| " TYPE45 3 3 4147994 TYPE\\(\\)\\)\\(\\)\\(\\(\\)\\(\\)\\)\\)\\(\\)\\(\\)\\(\\(\\R 948\"\")\\(\\)\\)\\)\\(\\ ", | |||
| "$GENERATE 0-3 ${441189,5039418474430,o}", | |||
| "$INCLUDE 00 TYPE00000000000n ", | |||
| "$INCLUDE PE4 TYPE061463623/727071511 \\(\\)\\$GENERATE 6-462/0", | |||
| } | |||
| for i, tc := range testcases { | |||
| rr, err := NewRR(tc) | |||
| if err == nil { | |||
| // rr can be nil because we can (for instance) just parse a comment | |||
| if rr == nil { | |||
| continue | |||
| } | |||
| t.Fatalf("parsed mailformed RR %d: %s", i, rr.String()) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| package idn_test | |||
| import ( | |||
| "fmt" | |||
| "github.com/miekg/dns/idn" | |||
| ) | |||
| func ExampleToPunycode() { | |||
| name := "インターネット.テスト" | |||
| fmt.Printf("%s -> %s", name, idn.ToPunycode(name)) | |||
| // Output: インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah | |||
| } | |||
| func ExampleFromPunycode() { | |||
| name := "xn--mgbaja8a1hpac.xn--mgbachtv" | |||
| fmt.Printf("%s -> %s", name, idn.FromPunycode(name)) | |||
| // Output: xn--mgbaja8a1hpac.xn--mgbachtv -> الانترنت.اختبار | |||
| } | |||
| @ -0,0 +1,373 @@ | |||
| // Package idn implements encoding from and to punycode as speficied by RFC 3492. | |||
| package idn | |||
| import ( | |||
| "bytes" | |||
| "strings" | |||
| "unicode" | |||
| "unicode/utf8" | |||
| "github.com/miekg/dns" | |||
| ) | |||
| // Implementation idea from RFC itself and from from IDNA::Punycode created by | |||
| // Tatsuhiko Miyagawa <miyagawa@bulknews.net> and released under Perl Artistic | |||
| // License in 2002. | |||
| const ( | |||
| _MIN rune = 1 | |||
| _MAX rune = 26 | |||
| _SKEW rune = 38 | |||
| _BASE rune = 36 | |||
| _BIAS rune = 72 | |||
| _N rune = 128 | |||
| _DAMP rune = 700 | |||
| _DELIMITER = '-' | |||
| _PREFIX = "xn--" | |||
| ) | |||
| // ToPunycode converts unicode domain names to DNS-appropriate punycode names. | |||
| // This function will return an empty string result for domain names with | |||
| // invalid unicode strings. This function expects domain names in lowercase. | |||
| func ToPunycode(s string) string { | |||
| // Early check to see if encoding is needed. | |||
| // This will prevent making heap allocations when not needed. | |||
| if !needToPunycode(s) { | |||
| return s | |||
| } | |||
| tokens := dns.SplitDomainName(s) | |||
| switch { | |||
| case s == "": | |||
| return "" | |||
| case tokens == nil: // s == . | |||
| return "." | |||
| case s[len(s)-1] == '.': | |||
| tokens = append(tokens, "") | |||
| } | |||
| for i := range tokens { | |||
| t := encode([]byte(tokens[i])) | |||
| if t == nil { | |||
| return "" | |||
| } | |||
| tokens[i] = string(t) | |||
| } | |||
| return strings.Join(tokens, ".") | |||
| } | |||
| // FromPunycode returns unicode domain name from provided punycode string. | |||
| // This function expects punycode strings in lowercase. | |||
| func FromPunycode(s string) string { | |||
| // Early check to see if decoding is needed. | |||
| // This will prevent making heap allocations when not needed. | |||
| if !needFromPunycode(s) { | |||
| return s | |||
| } | |||
| tokens := dns.SplitDomainName(s) | |||
| switch { | |||
| case s == "": | |||
| return "" | |||
| case tokens == nil: // s == . | |||
| return "." | |||
| case s[len(s)-1] == '.': | |||
| tokens = append(tokens, "") | |||
| } | |||
| for i := range tokens { | |||
| tokens[i] = string(decode([]byte(tokens[i]))) | |||
| } | |||
| return strings.Join(tokens, ".") | |||
| } | |||
| // digitval converts single byte into meaningful value that's used to calculate decoded unicode character. | |||
| const errdigit = 0xffff | |||
| func digitval(code rune) rune { | |||
| switch { | |||
| case code >= 'A' && code <= 'Z': | |||
| return code - 'A' | |||
| case code >= 'a' && code <= 'z': | |||
| return code - 'a' | |||
| case code >= '0' && code <= '9': | |||
| return code - '0' + 26 | |||
| } | |||
| return errdigit | |||
| } | |||
| // lettercode finds BASE36 byte (a-z0-9) based on calculated number. | |||
| func lettercode(digit rune) rune { | |||
| switch { | |||
| case digit >= 0 && digit <= 25: | |||
| return digit + 'a' | |||
| case digit >= 26 && digit <= 36: | |||
| return digit - 26 + '0' | |||
| } | |||
| panic("dns: not reached") | |||
| } | |||
| // adapt calculates next bias to be used for next iteration delta. | |||
| func adapt(delta rune, numpoints int, firsttime bool) rune { | |||
| if firsttime { | |||
| delta /= _DAMP | |||
| } else { | |||
| delta /= 2 | |||
| } | |||
| var k rune | |||
| for delta = delta + delta/rune(numpoints); delta > (_BASE-_MIN)*_MAX/2; k += _BASE { | |||
| delta /= _BASE - _MIN | |||
| } | |||
| return k + ((_BASE-_MIN+1)*delta)/(delta+_SKEW) | |||
| } | |||
| // next finds minimal rune (one with lowest codepoint value) that should be equal or above boundary. | |||
| func next(b []rune, boundary rune) rune { | |||
| if len(b) == 0 { | |||
| panic("dns: invalid set of runes to determine next one") | |||
| } | |||
| m := b[0] | |||
| for _, x := range b[1:] { | |||
| if x >= boundary && (m < boundary || x < m) { | |||
| m = x | |||
| } | |||
| } | |||
| return m | |||
| } | |||
| // preprune converts unicode rune to lower case. At this time it's not | |||
| // supporting all things described in RFCs. | |||
| func preprune(r rune) rune { | |||
| if unicode.IsUpper(r) { | |||
| r = unicode.ToLower(r) | |||
| } | |||
| return r | |||
| } | |||
| // tfunc is a function that helps calculate each character weight. | |||
| func tfunc(k, bias rune) rune { | |||
| switch { | |||
| case k <= bias: | |||
| return _MIN | |||
| case k >= bias+_MAX: | |||
| return _MAX | |||
| } | |||
| return k - bias | |||
| } | |||
| // needToPunycode returns true for strings that require punycode encoding | |||
| // (contain unicode characters). | |||
| func needToPunycode(s string) bool { | |||
| // This function is very similar to bytes.Runes. We don't use bytes.Runes | |||
| // because it makes a heap allocation that's not needed here. | |||
| for i := 0; len(s) > 0; i++ { | |||
| r, l := utf8.DecodeRuneInString(s) | |||
| if r > 0x7f { | |||
| return true | |||
| } | |||
| s = s[l:] | |||
| } | |||
| return false | |||
| } | |||
| // needFromPunycode returns true for strings that require punycode decoding. | |||
| func needFromPunycode(s string) bool { | |||
| if s == "." { | |||
| return false | |||
| } | |||
| off := 0 | |||
| end := false | |||
| pl := len(_PREFIX) | |||
| sl := len(s) | |||
| // If s starts with _PREFIX. | |||
| if sl > pl && s[off:off+pl] == _PREFIX { | |||
| return true | |||
| } | |||
| for { | |||
| // Find the part after the next ".". | |||
| off, end = dns.NextLabel(s, off) | |||
| if end { | |||
| return false | |||
| } | |||
| // If this parts starts with _PREFIX. | |||
| if sl-off > pl && s[off:off+pl] == _PREFIX { | |||
| return true | |||
| } | |||
| } | |||
| } | |||
| // encode transforms Unicode input bytes (that represent DNS label) into | |||
| // punycode bytestream. This function would return nil if there's an invalid | |||
| // character in the label. | |||
| func encode(input []byte) []byte { | |||
| n, bias := _N, _BIAS | |||
| b := bytes.Runes(input) | |||
| for i := range b { | |||
| if !isValidRune(b[i]) { | |||
| return nil | |||
| } | |||
| b[i] = preprune(b[i]) | |||
| } | |||
| basic := make([]byte, 0, len(b)) | |||
| for _, ltr := range b { | |||
| if ltr <= 0x7f { | |||
| basic = append(basic, byte(ltr)) | |||
| } | |||
| } | |||
| basiclen := len(basic) | |||
| fulllen := len(b) | |||
| if basiclen == fulllen { | |||
| return basic | |||
| } | |||
| var out bytes.Buffer | |||
| out.WriteString(_PREFIX) | |||
| if basiclen > 0 { | |||
| out.Write(basic) | |||
| out.WriteByte(_DELIMITER) | |||
| } | |||
| var ( | |||
| ltr, nextltr rune | |||
| delta, q rune // delta calculation (see rfc) | |||
| t, k, cp rune // weight and codepoint calculation | |||
| ) | |||
| s := &bytes.Buffer{} | |||
| for h := basiclen; h < fulllen; n, delta = n+1, delta+1 { | |||
| nextltr = next(b, n) | |||
| s.Truncate(0) | |||
| s.WriteRune(nextltr) | |||
| delta, n = delta+(nextltr-n)*rune(h+1), nextltr | |||
| for _, ltr = range b { | |||
| if ltr < n { | |||
| delta++ | |||
| } | |||
| if ltr == n { | |||
| q = delta | |||
| for k = _BASE; ; k += _BASE { | |||
| t = tfunc(k, bias) | |||
| if q < t { | |||
| break | |||
| } | |||
| cp = t + ((q - t) % (_BASE - t)) | |||
| out.WriteRune(lettercode(cp)) | |||
| q = (q - t) / (_BASE - t) | |||
| } | |||
| out.WriteRune(lettercode(q)) | |||
| bias = adapt(delta, h+1, h == basiclen) | |||
| h, delta = h+1, 0 | |||
| } | |||
| } | |||
| } | |||
| return out.Bytes() | |||
| } | |||
| // decode transforms punycode input bytes (that represent DNS label) into Unicode bytestream. | |||
| func decode(b []byte) []byte { | |||
| src := b // b would move and we need to keep it | |||
| n, bias := _N, _BIAS | |||
| if !bytes.HasPrefix(b, []byte(_PREFIX)) { | |||
| return b | |||
| } | |||
| out := make([]rune, 0, len(b)) | |||
| b = b[len(_PREFIX):] | |||
| for pos := len(b) - 1; pos >= 0; pos-- { | |||
| // only last delimiter is our interest | |||
| if b[pos] == _DELIMITER { | |||
| out = append(out, bytes.Runes(b[:pos])...) | |||
| b = b[pos+1:] // trim source string | |||
| break | |||
| } | |||
| } | |||
| if len(b) == 0 { | |||
| return src | |||
| } | |||
| var ( | |||
| i, oldi, w rune | |||
| ch byte | |||
| t, digit rune | |||
| ln int | |||
| ) | |||
| for i = 0; len(b) > 0; i++ { | |||
| oldi, w = i, 1 | |||
| for k := _BASE; len(b) > 0; k += _BASE { | |||
| ch, b = b[0], b[1:] | |||
| digit = digitval(rune(ch)) | |||
| if digit == errdigit { | |||
| return src | |||
| } | |||
| i += digit * w | |||
| if i < 0 { | |||
| // safety check for rune overflow | |||
| return src | |||
| } | |||
| t = tfunc(k, bias) | |||
| if digit < t { | |||
| break | |||
| } | |||
| w *= _BASE - t | |||
| } | |||
| ln = len(out) + 1 | |||
| bias = adapt(i-oldi, ln, oldi == 0) | |||
| n += i / rune(ln) | |||
| i = i % rune(ln) | |||
| // insert | |||
| out = append(out, 0) | |||
| copy(out[i+1:], out[i:]) | |||
| out[i] = n | |||
| } | |||
| var ret bytes.Buffer | |||
| for _, r := range out { | |||
| ret.WriteRune(r) | |||
| } | |||
| return ret.Bytes() | |||
| } | |||
| // isValidRune checks if the character is valid. We will look for the | |||
| // character property in the code points list. For now we aren't checking special | |||
| // rules in case of contextual property | |||
| func isValidRune(r rune) bool { | |||
| return findProperty(r) == propertyPVALID | |||
| } | |||
| // findProperty will try to check the code point property of the given | |||
| // character. It will use a binary search algorithm as we have a slice of | |||
| // ordered ranges (average case performance O(log n)) | |||
| func findProperty(r rune) property { | |||
| imin, imax := 0, len(codePoints) | |||
| for imax >= imin { | |||
| imid := (imin + imax) / 2 | |||
| codePoint := codePoints[imid] | |||
| if (codePoint.start == r && codePoint.end == 0) || (codePoint.start <= r && codePoint.end >= r) { | |||
| return codePoint.state | |||
| } | |||
| if (codePoint.end > 0 && codePoint.end < r) || (codePoint.end == 0 && codePoint.start < r) { | |||
| imin = imid + 1 | |||
| } else { | |||
| imax = imid - 1 | |||
| } | |||
| } | |||
| return propertyUnknown | |||
| } | |||
| @ -0,0 +1,116 @@ | |||
| package idn | |||
| import ( | |||
| "strings" | |||
| "testing" | |||
| ) | |||
| var testcases = [][2]string{ | |||
| {"", ""}, | |||
| {"a", "a"}, | |||
| {"a-b", "a-b"}, | |||
| {"a-b-c", "a-b-c"}, | |||
| {"abc", "abc"}, | |||
| {"я", "xn--41a"}, | |||
| {"zя", "xn--z-0ub"}, | |||
| {"яZ", "xn--z-zub"}, | |||
| {"а-я", "xn----7sb8g"}, | |||
| {"إختبار", "xn--kgbechtv"}, | |||
| {"آزمایشی", "xn--hgbk6aj7f53bba"}, | |||
| {"测试", "xn--0zwm56d"}, | |||
| {"測試", "xn--g6w251d"}, | |||
| {"испытание", "xn--80akhbyknj4f"}, | |||
| {"परीक्षा", "xn--11b5bs3a9aj6g"}, | |||
| {"δοκιμή", "xn--jxalpdlp"}, | |||
| {"테스트", "xn--9t4b11yi5a"}, | |||
| {"טעסט", "xn--deba0ad"}, | |||
| {"テスト", "xn--zckzah"}, | |||
| {"பரிட்சை", "xn--hlcj6aya9esc7a"}, | |||
| {"mamão-com-açúcar", "xn--mamo-com-acar-yeb1e6q"}, | |||
| {"σ", "xn--4xa"}, | |||
| } | |||
| func TestEncodeDecodePunycode(t *testing.T) { | |||
| for _, tst := range testcases { | |||
| enc := encode([]byte(tst[0])) | |||
| if string(enc) != tst[1] { | |||
| t.Errorf("%s encodeded as %s but should be %s", tst[0], enc, tst[1]) | |||
| } | |||
| dec := decode([]byte(tst[1])) | |||
| if string(dec) != strings.ToLower(tst[0]) { | |||
| t.Errorf("%s decoded as %s but should be %s", tst[1], dec, strings.ToLower(tst[0])) | |||
| } | |||
| } | |||
| } | |||
| func TestToFromPunycode(t *testing.T) { | |||
| for _, tst := range testcases { | |||
| // assert unicode.com == punycode.com | |||
| full := ToPunycode(tst[0] + ".com") | |||
| if full != tst[1]+".com" { | |||
| t.Errorf("invalid result from string conversion to punycode, %s and should be %s.com", full, tst[1]) | |||
| } | |||
| // assert punycode.punycode == unicode.unicode | |||
| decoded := FromPunycode(tst[1] + "." + tst[1]) | |||
| if decoded != strings.ToLower(tst[0]+"."+tst[0]) { | |||
| t.Errorf("invalid result from string conversion to punycode, %s and should be %s.%s", decoded, tst[0], tst[0]) | |||
| } | |||
| } | |||
| } | |||
| func TestEncodeDecodeFinalPeriod(t *testing.T) { | |||
| for _, tst := range testcases { | |||
| // assert unicode.com. == punycode.com. | |||
| full := ToPunycode(tst[0] + ".") | |||
| if full != tst[1]+"." { | |||
| t.Errorf("invalid result from string conversion to punycode when period added at the end, %#v and should be %#v", full, tst[1]+".") | |||
| } | |||
| // assert punycode.com. == unicode.com. | |||
| decoded := FromPunycode(tst[1] + ".") | |||
| if decoded != strings.ToLower(tst[0]+".") { | |||
| t.Errorf("invalid result from string conversion to punycode when period added, %#v and should be %#v", decoded, tst[0]+".") | |||
| } | |||
| full = ToPunycode(tst[0]) | |||
| if full != tst[1] { | |||
| t.Errorf("invalid result from string conversion to punycode when no period added at the end, %#v and should be %#v", full, tst[1]+".") | |||
| } | |||
| // assert punycode.com. == unicode.com. | |||
| decoded = FromPunycode(tst[1]) | |||
| if decoded != strings.ToLower(tst[0]) { | |||
| t.Errorf("invalid result from string conversion to punycode when no period added, %#v and should be %#v", decoded, tst[0]+".") | |||
| } | |||
| } | |||
| } | |||
| var invalidACEs = []string{ | |||
| "xn--*", | |||
| "xn--", | |||
| "xn---", | |||
| "xn--a000000000", | |||
| } | |||
| func TestInvalidPunycode(t *testing.T) { | |||
| for _, d := range invalidACEs { | |||
| s := FromPunycode(d) | |||
| if s != d { | |||
| t.Errorf("Changed invalid name %s to %#v", d, s) | |||
| } | |||
| } | |||
| } | |||
| // You can verify the labels that are valid or not comparing to the Verisign | |||
| // website: http://mct.verisign-grs.com/ | |||
| var invalidUnicodes = []string{ | |||
| "Σ", | |||
| "ЯZ", | |||
| "Испытание", | |||
| } | |||
| func TestInvalidUnicodes(t *testing.T) { | |||
| for _, d := range invalidUnicodes { | |||
| s := ToPunycode(d) | |||
| if s != "" { | |||
| t.Errorf("Changed invalid name %s to %#v", d, s) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,162 @@ | |||
| package dns | |||
| // Holds a bunch of helper functions for dealing with labels. | |||
| // SplitDomainName splits a name string into it's labels. | |||
| // 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. | |||
| func SplitDomainName(s string) (labels []string) { | |||
| if len(s) == 0 { | |||
| return nil | |||
| } | |||
| fqdnEnd := 0 // offset of the final '.' or the length of the name | |||
| idx := Split(s) | |||
| begin := 0 | |||
| if s[len(s)-1] == '.' { | |||
| fqdnEnd = len(s) - 1 | |||
| } else { | |||
| fqdnEnd = len(s) | |||
| } | |||
| switch len(idx) { | |||
| case 0: | |||
| return nil | |||
| case 1: | |||
| // no-op | |||
| default: | |||
| end := 0 | |||
| for i := 1; i < len(idx); i++ { | |||
| end = idx[i] | |||
| labels = append(labels, s[begin:end-1]) | |||
| begin = end | |||
| } | |||
| } | |||
| labels = append(labels, s[begin:fqdnEnd]) | |||
| return labels | |||
| } | |||
| // CompareDomainName compares the names s1 and s2 and | |||
| // returns how many labels they have in common starting from the *right*. | |||
| // The comparison stops at the first inequality. The names are not downcased | |||
| // before the comparison. | |||
| // | |||
| // 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 | |||
| func CompareDomainName(s1, s2 string) (n int) { | |||
| s1 = Fqdn(s1) | |||
| s2 = Fqdn(s2) | |||
| l1 := Split(s1) | |||
| l2 := Split(s2) | |||
| // the first check: root label | |||
| if l1 == nil || l2 == nil { | |||
| return | |||
| } | |||
| j1 := len(l1) - 1 // end | |||
| i1 := len(l1) - 2 // start | |||
| j2 := len(l2) - 1 | |||
| i2 := len(l2) - 2 | |||
| // the second check can be done here: last/only label | |||
| // before we fall through into the for-loop below | |||
| if s1[l1[j1]:] == s2[l2[j2]:] { | |||
| n++ | |||
| } else { | |||
| return | |||
| } | |||
| for { | |||
| if i1 < 0 || i2 < 0 { | |||
| break | |||
| } | |||
| if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { | |||
| n++ | |||
| } else { | |||
| break | |||
| } | |||
| j1-- | |||
| i1-- | |||
| j2-- | |||
| i2-- | |||
| } | |||
| return | |||
| } | |||
| // CountLabel counts the the number of labels in the string s. | |||
| func CountLabel(s string) (labels int) { | |||
| if s == "." { | |||
| return | |||
| } | |||
| off := 0 | |||
| end := false | |||
| for { | |||
| off, end = NextLabel(s, off) | |||
| labels++ | |||
| if end { | |||
| return | |||
| } | |||
| } | |||
| } | |||
| // 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. | |||
| func Split(s string) []int { | |||
| if s == "." { | |||
| return nil | |||
| } | |||
| idx := make([]int, 1, 3) | |||
| off := 0 | |||
| end := false | |||
| for { | |||
| off, end = NextLabel(s, off) | |||
| if end { | |||
| return idx | |||
| } | |||
| idx = append(idx, off) | |||
| } | |||
| } | |||
| // NextLabel returns the index of the start of the next label in the | |||
| // string s starting at offset. | |||
| // The bool end is true when the end of the string has been reached. | |||
| // Also see PrevLabel. | |||
| func NextLabel(s string, offset int) (i int, end bool) { | |||
| quote := false | |||
| for i = offset; i < len(s)-1; i++ { | |||
| switch s[i] { | |||
| case '\\': | |||
| quote = !quote | |||
| default: | |||
| quote = false | |||
| case '.': | |||
| if quote { | |||
| quote = !quote | |||
| continue | |||
| } | |||
| return i + 1, false | |||
| } | |||
| } | |||
| return i + 1, true | |||
| } | |||
| // PrevLabel returns the index of the label when starting from the right and | |||
| // jumping n labels to the left. | |||
| // The bool start is true when the start of the string has been overshot. | |||
| // Also see NextLabel. | |||
| func PrevLabel(s string, n int) (i int, start bool) { | |||
| if n == 0 { | |||
| return len(s), false | |||
| } | |||
| lab := Split(s) | |||
| if lab == nil { | |||
| return 0, true | |||
| } | |||
| if n > len(lab) { | |||
| return 0, true | |||
| } | |||
| return lab[len(lab)-n], false | |||
| } | |||
| @ -0,0 +1,199 @@ | |||
| package dns | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func TestCompareDomainName(t *testing.T) { | |||
| s1 := "www.miek.nl." | |||
| s2 := "miek.nl." | |||
| s3 := "www.bla.nl." | |||
| s4 := "nl.www.bla." | |||
| s5 := "nl" | |||
| s6 := "miek.nl" | |||
| if CompareDomainName(s1, s2) != 2 { | |||
| t.Errorf("%s with %s should be %d", s1, s2, 2) | |||
| } | |||
| if CompareDomainName(s1, s3) != 1 { | |||
| t.Errorf("%s with %s should be %d", s1, s3, 1) | |||
| } | |||
| if CompareDomainName(s3, s4) != 0 { | |||
| t.Errorf("%s with %s should be %d", s3, s4, 0) | |||
| } | |||
| // Non qualified tests | |||
| if CompareDomainName(s1, s5) != 1 { | |||
| t.Errorf("%s with %s should be %d", s1, s5, 1) | |||
| } | |||
| if CompareDomainName(s1, s6) != 2 { | |||
| t.Errorf("%s with %s should be %d", s1, s5, 2) | |||
| } | |||
| if CompareDomainName(s1, ".") != 0 { | |||
| t.Errorf("%s with %s should be %d", s1, s5, 0) | |||
| } | |||
| if CompareDomainName(".", ".") != 0 { | |||
| t.Errorf("%s with %s should be %d", ".", ".", 0) | |||
| } | |||
| } | |||
| func TestSplit(t *testing.T) { | |||
| splitter := map[string]int{ | |||
| "www.miek.nl.": 3, | |||
| "www.miek.nl": 3, | |||
| "www..miek.nl": 4, | |||
| `www\.miek.nl.`: 2, | |||
| `www\\.miek.nl.`: 3, | |||
| ".": 0, | |||
| "nl.": 1, | |||
| "nl": 1, | |||
| "com.": 1, | |||
| ".com.": 2, | |||
| } | |||
| for s, i := range splitter { | |||
| if x := len(Split(s)); x != i { | |||
| t.Errorf("labels should be %d, got %d: %s %v", i, x, s, Split(s)) | |||
| } else { | |||
| t.Logf("%s %v", s, Split(s)) | |||
| } | |||
| } | |||
| } | |||
| 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}, | |||
| } | |||
| for s, i := range splitter { | |||
| x := Split(s) | |||
| switch len(i) { | |||
| case 1: | |||
| if x[0] != i[0] { | |||
| t.Errorf("labels should be %v, got %v: %s", i, x, s) | |||
| } | |||
| default: | |||
| if x[0] != i[0] || x[1] != i[1] || x[2] != i[2] { | |||
| t.Errorf("labels should be %v, got %v: %s", i, x, s) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func TestPrevLabel(t *testing.T) { | |||
| type prev struct { | |||
| string | |||
| int | |||
| } | |||
| prever := map[prev]int{ | |||
| prev{"www.miek.nl.", 0}: 12, | |||
| prev{"www.miek.nl.", 1}: 9, | |||
| prev{"www.miek.nl.", 2}: 4, | |||
| prev{"www.miek.nl", 0}: 11, | |||
| prev{"www.miek.nl", 1}: 9, | |||
| prev{"www.miek.nl", 2}: 4, | |||
| prev{"www.miek.nl.", 5}: 0, | |||
| prev{"www.miek.nl", 5}: 0, | |||
| prev{"www.miek.nl.", 3}: 0, | |||
| prev{"www.miek.nl", 3}: 0, | |||
| } | |||
| for s, i := range prever { | |||
| x, ok := PrevLabel(s.string, s.int) | |||
| if i != x { | |||
| t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string) | |||
| } | |||
| } | |||
| } | |||
| func TestCountLabel(t *testing.T) { | |||
| splitter := map[string]int{ | |||
| "www.miek.nl.": 3, | |||
| "www.miek.nl": 3, | |||
| "nl": 1, | |||
| ".": 0, | |||
| } | |||
| for s, i := range splitter { | |||
| x := CountLabel(s) | |||
| if x != i { | |||
| t.Errorf("CountLabel should have %d, got %d", i, x) | |||
| } | |||
| } | |||
| } | |||
| func TestSplitDomainName(t *testing.T) { | |||
| labels := map[string][]string{ | |||
| "miek.nl": []string{"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"}, | |||
| } | |||
| domainLoop: | |||
| for domain, splits := range labels { | |||
| parts := SplitDomainName(domain) | |||
| if len(parts) != len(splits) { | |||
| t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) | |||
| continue domainLoop | |||
| } | |||
| for i := range parts { | |||
| if parts[i] != splits[i] { | |||
| t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) | |||
| continue domainLoop | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func TestIsDomainName(t *testing.T) { | |||
| type ret struct { | |||
| ok bool | |||
| 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}, | |||
| } | |||
| for d, ok := range names { | |||
| l, k := IsDomainName(d) | |||
| if ok.ok != k || ok.lab != l { | |||
| t.Errorf(" got %v %d for %s ", k, l, d) | |||
| t.Errorf("have %v %d for %s ", ok.ok, ok.lab, d) | |||
| } | |||
| } | |||
| } | |||
| func BenchmarkSplitLabels(b *testing.B) { | |||
| for i := 0; i < b.N; i++ { | |||
| Split("www.example.com") | |||
| } | |||
| } | |||
| func BenchmarkLenLabels(b *testing.B) { | |||
| for i := 0; i < b.N; i++ { | |||
| CountLabel("www.example.com") | |||
| } | |||
| } | |||
| func BenchmarkCompareLabels(b *testing.B) { | |||
| for i := 0; i < b.N; i++ { | |||
| CompareDomainName("www.example.com", "aa.example.com") | |||
| } | |||
| } | |||
| func BenchmarkIsSubDomain(b *testing.B) { | |||
| for i := 0; i < b.N; i++ { | |||
| IsSubDomain("www.example.com", "aa.example.com") | |||
| IsSubDomain("example.com", "aa.example.com") | |||
| IsSubDomain("miek.nl", "aa.example.com") | |||
| } | |||
| } | |||
| @ -0,0 +1,112 @@ | |||
| package dns | |||
| import ( | |||
| "crypto/sha1" | |||
| "hash" | |||
| "io" | |||
| "strings" | |||
| ) | |||
| type saltWireFmt struct { | |||
| Salt string `dns:"size-hex"` | |||
| } | |||
| // HashName hashes a string (label) according to RFC 5155. It returns the hashed string in | |||
| // uppercase. | |||
| func HashName(label string, ha uint8, iter uint16, salt string) string { | |||
| saltwire := new(saltWireFmt) | |||
| saltwire.Salt = salt | |||
| wire := make([]byte, DefaultMsgSize) | |||
| n, err := PackStruct(saltwire, wire, 0) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| wire = wire[:n] | |||
| name := make([]byte, 255) | |||
| off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| name = name[:off] | |||
| var s hash.Hash | |||
| switch ha { | |||
| case SHA1: | |||
| s = sha1.New() | |||
| default: | |||
| return "" | |||
| } | |||
| // k = 0 | |||
| name = append(name, wire...) | |||
| io.WriteString(s, string(name)) | |||
| nsec3 := s.Sum(nil) | |||
| // k > 0 | |||
| for k := uint16(0); k < iter; k++ { | |||
| s.Reset() | |||
| nsec3 = append(nsec3, wire...) | |||
| io.WriteString(s, string(nsec3)) | |||
| nsec3 = s.Sum(nil) | |||
| } | |||
| return toBase32(nsec3) | |||
| } | |||
| // Denialer is an interface that should be implemented by types that are used to denial | |||
| // answers in DNSSEC. | |||
| type Denialer interface { | |||
| // Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3. | |||
| Cover(name string) bool | |||
| // Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3. | |||
| Match(name string) bool | |||
| } | |||
| // Cover implements the Denialer interface. | |||
| func (rr *NSEC) Cover(name string) bool { | |||
| return true | |||
| } | |||
| // Match implements the Denialer interface. | |||
| func (rr *NSEC) Match(name string) bool { | |||
| return true | |||
| } | |||
| // Cover implements the Denialer interface. | |||
| func (rr *NSEC3) Cover(name string) bool { | |||
| // FIXME(miek): check if the zones match | |||
| // FIXME(miek): check if we're not dealing with parent nsec3 | |||
| hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) | |||
| labels := Split(rr.Hdr.Name) | |||
| if len(labels) < 2 { | |||
| return false | |||
| } | |||
| hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot | |||
| if hash == rr.NextDomain { | |||
| return false // empty interval | |||
| } | |||
| if hash > rr.NextDomain { // last name, points to apex | |||
| // hname > hash | |||
| // hname > rr.NextDomain | |||
| // TODO(miek) | |||
| } | |||
| if hname <= hash { | |||
| return false | |||
| } | |||
| if hname >= rr.NextDomain { | |||
| return false | |||
| } | |||
| return true | |||
| } | |||
| // Match implements the Denialer interface. | |||
| func (rr *NSEC3) Match(name string) bool { | |||
| // FIXME(miek): Check if we are in the same zone | |||
| hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) | |||
| labels := Split(rr.Hdr.Name) | |||
| if len(labels) < 2 { | |||
| return false | |||
| } | |||
| hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the . | |||
| if hash == hname { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| @ -0,0 +1,29 @@ | |||
| package dns | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func TestPackNsec3(t *testing.T) { | |||
| nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD") | |||
| if nsec3 != "ROCCJAE8BJJU7HN6T7NG3TNM8ACRS87J" { | |||
| t.Error(nsec3) | |||
| } | |||
| nsec3 = HashName("a.b.c.example.org.", SHA1, 2, "DEAD") | |||
| if nsec3 != "6LQ07OAHBTOOEU2R9ANI2AT70K5O0RCG" { | |||
| t.Error(nsec3) | |||
| } | |||
| } | |||
| func TestNsec3(t *testing.T) { | |||
| // examples taken from .nl | |||
| nsec3, _ := NewRR("39p91242oslggest5e6a7cci4iaeqvnk.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6 NS DS RRSIG") | |||
| if !nsec3.(*NSEC3).Cover("snasajsksasasa.nl.") { // 39p94jrinub66hnpem8qdpstrec86pg3 | |||
| t.Error("39p94jrinub66hnpem8qdpstrec86pg3. should be covered by 39p91242oslggest5e6a7cci4iaeqvnk.nl. - 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6") | |||
| } | |||
| nsec3, _ = NewRR("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 SK4F38CQ0ATIEI8MH3RGD0P5I4II6QAN NS SOA TXT RRSIG DNSKEY NSEC3PARAM") | |||
| if !nsec3.(*NSEC3).Match("nl.") { // sk4e8fj94u78smusb40o1n0oltbblu2r.nl. | |||
| t.Error("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. should match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") | |||
| } | |||
| } | |||
| @ -0,0 +1,117 @@ | |||
| package dns | |||
| import ( | |||
| "fmt" | |||
| "strings" | |||
| ) | |||
| // PrivateRdata is an interface used for implementing "Private Use" RR types, see | |||
| // RFC 6895. This allows one to experiment with new RR types, without requesting an | |||
| // official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. | |||
| type PrivateRdata interface { | |||
| // String returns the text presentaton of the Rdata of the Private RR. | |||
| String() string | |||
| // Parse parses the Rdata of the private RR. | |||
| Parse([]string) error | |||
| // Pack is used when packing a private RR into a buffer. | |||
| Pack([]byte) (int, error) | |||
| // Unpack is used when unpacking a private RR from a buffer. | |||
| // TODO(miek): diff. signature than Pack, see edns0.go for instance. | |||
| Unpack([]byte) (int, error) | |||
| // Copy copies the Rdata. | |||
| Copy(PrivateRdata) error | |||
| // Len returns the length in octets of the Rdata. | |||
| Len() int | |||
| } | |||
| // PrivateRR represents an RR that uses a PrivateRdata user-defined type. | |||
| // It mocks normal RRs and implements dns.RR interface. | |||
| type PrivateRR struct { | |||
| Hdr RR_Header | |||
| Data PrivateRdata | |||
| } | |||
| func mkPrivateRR(rrtype uint16) *PrivateRR { | |||
| // Panics if RR is not an instance of PrivateRR. | |||
| rrfunc, ok := TypeToRR[rrtype] | |||
| if !ok { | |||
| panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) | |||
| } | |||
| anyrr := rrfunc() | |||
| switch rr := anyrr.(type) { | |||
| case *PrivateRR: | |||
| return rr | |||
| } | |||
| panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr)) | |||
| } | |||
| // Header return the RR header of r. | |||
| func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } | |||
| func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } | |||
| // Private len and copy parts to satisfy RR interface. | |||
| func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } | |||
| func (r *PrivateRR) copy() RR { | |||
| // make new RR like this: | |||
| rr := mkPrivateRR(r.Hdr.Rrtype) | |||
| newh := r.Hdr.copyHeader() | |||
| rr.Hdr = *newh | |||
| err := r.Data.Copy(rr.Data) | |||
| if err != nil { | |||
| panic("dns: got value that could not be used to copy Private rdata") | |||
| } | |||
| return rr | |||
| } | |||
| // PrivateHandle registers a private resource record type. It requires | |||
| // string and numeric representation of private RR type and generator function as argument. | |||
| func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { | |||
| rtypestr = strings.ToUpper(rtypestr) | |||
| TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } | |||
| TypeToString[rtype] = rtypestr | |||
| StringToType[rtypestr] = rtype | |||
| setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { | |||
| rr := mkPrivateRR(h.Rrtype) | |||
| rr.Hdr = h | |||
| var l lex | |||
| text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 | |||
| FETCH: | |||
| for { | |||
| // TODO(miek): we could also be returning _QUOTE, this might or might not | |||
| // be an issue (basically parsing TXT becomes hard) | |||
| switch l = <-c; l.value { | |||
| case zNewline, zEOF: | |||
| break FETCH | |||
| case zString: | |||
| text = append(text, l.token) | |||
| } | |||
| } | |||
| err := rr.Data.Parse(text) | |||
| if err != nil { | |||
| return nil, &ParseError{f, err.Error(), l}, "" | |||
| } | |||
| return rr, nil, "" | |||
| } | |||
| typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} | |||
| } | |||
| // PrivateHandleRemove removes defenitions required to support private RR type. | |||
| func PrivateHandleRemove(rtype uint16) { | |||
| rtypestr, ok := TypeToString[rtype] | |||
| if ok { | |||
| delete(TypeToRR, rtype) | |||
| delete(TypeToString, rtype) | |||
| delete(typeToparserFunc, rtype) | |||
| delete(StringToType, rtypestr) | |||
| } | |||
| return | |||
| } | |||
| @ -0,0 +1,170 @@ | |||
| package dns_test | |||
| import ( | |||
| "strings" | |||
| "testing" | |||
| "github.com/miekg/dns" | |||
| ) | |||
| const TypeISBN uint16 = 0x0F01 | |||
| // A crazy new RR type :) | |||
| type ISBN struct { | |||
| x string // rdata with 10 or 13 numbers, dashes or spaces allowed | |||
| } | |||
| func NewISBN() dns.PrivateRdata { return &ISBN{""} } | |||
| func (rd *ISBN) Len() int { return len([]byte(rd.x)) } | |||
| func (rd *ISBN) String() string { return rd.x } | |||
| func (rd *ISBN) Parse(txt []string) error { | |||
| rd.x = strings.TrimSpace(strings.Join(txt, " ")) | |||
| return nil | |||
| } | |||
| func (rd *ISBN) Pack(buf []byte) (int, error) { | |||
| b := []byte(rd.x) | |||
| n := copy(buf, b) | |||
| if n != len(b) { | |||
| return n, dns.ErrBuf | |||
| } | |||
| return n, nil | |||
| } | |||
| func (rd *ISBN) Unpack(buf []byte) (int, error) { | |||
| rd.x = string(buf) | |||
| return len(buf), nil | |||
| } | |||
| func (rd *ISBN) Copy(dest dns.PrivateRdata) error { | |||
| isbn, ok := dest.(*ISBN) | |||
| if !ok { | |||
| return dns.ErrRdata | |||
| } | |||
| isbn.x = rd.x | |||
| return nil | |||
| } | |||
| var testrecord = strings.Join([]string{"example.org.", "3600", "IN", "ISBN", "12-3 456789-0-123"}, "\t") | |||
| func TestPrivateText(t *testing.T) { | |||
| dns.PrivateHandle("ISBN", TypeISBN, NewISBN) | |||
| defer dns.PrivateHandleRemove(TypeISBN) | |||
| rr, err := dns.NewRR(testrecord) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| if rr.String() != testrecord { | |||
| t.Errorf("record string representation did not match original %#v != %#v", rr.String(), testrecord) | |||
| } else { | |||
| t.Log(rr.String()) | |||
| } | |||
| } | |||
| func TestPrivateByteSlice(t *testing.T) { | |||
| dns.PrivateHandle("ISBN", TypeISBN, NewISBN) | |||
| defer dns.PrivateHandleRemove(TypeISBN) | |||
| rr, err := dns.NewRR(testrecord) | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| buf := make([]byte, 100) | |||
| off, err := dns.PackRR(rr, buf, 0, nil, false) | |||
| if err != nil { | |||
| t.Errorf("got error packing ISBN: %v", err) | |||
| } | |||
| custrr := rr.(*dns.PrivateRR) | |||
| if ln := custrr.Data.Len() + len(custrr.Header().Name) + 11; ln != off { | |||
| t.Errorf("offset is not matching to length of Private RR: %d!=%d", off, ln) | |||
| } | |||
| rr1, off1, err := dns.UnpackRR(buf[:off], 0) | |||
| if err != nil { | |||
| t.Errorf("got error unpacking ISBN: %v", err) | |||
| } | |||
| if off1 != off { | |||
| t.Errorf("offset after unpacking differs: %d != %d", off1, off) | |||
| } | |||
| if rr1.String() != testrecord { | |||
| t.Errorf("record string representation did not match original %#v != %#v", rr1.String(), testrecord) | |||
| } else { | |||
| t.Log(rr1.String()) | |||
| } | |||
| } | |||
| const TypeVERSION uint16 = 0x0F02 | |||
| type VERSION struct { | |||
| x string | |||
| } | |||
| func NewVersion() dns.PrivateRdata { return &VERSION{""} } | |||
| func (rd *VERSION) String() string { return rd.x } | |||
| func (rd *VERSION) Parse(txt []string) error { | |||
| rd.x = strings.TrimSpace(strings.Join(txt, " ")) | |||
| return nil | |||
| } | |||
| func (rd *VERSION) Pack(buf []byte) (int, error) { | |||
| b := []byte(rd.x) | |||
| n := copy(buf, b) | |||
| if n != len(b) { | |||
| return n, dns.ErrBuf | |||
| } | |||
| return n, nil | |||
| } | |||
| func (rd *VERSION) Unpack(buf []byte) (int, error) { | |||
| rd.x = string(buf) | |||
| return len(buf), nil | |||
| } | |||
| func (rd *VERSION) Copy(dest dns.PrivateRdata) error { | |||
| isbn, ok := dest.(*VERSION) | |||
| if !ok { | |||
| return dns.ErrRdata | |||
| } | |||
| isbn.x = rd.x | |||
| return nil | |||
| } | |||
| func (rd *VERSION) Len() int { | |||
| return len([]byte(rd.x)) | |||
| } | |||
| var smallzone = `$ORIGIN example.org. | |||
| @ SOA sns.dns.icann.org. noc.dns.icann.org. ( | |||
| 2014091518 7200 3600 1209600 3600 | |||
| ) | |||
| A 1.2.3.4 | |||
| ok ISBN 1231-92110-12 | |||
| go VERSION ( | |||
| 1.3.1 ; comment | |||
| ) | |||
| www ISBN 1231-92110-16 | |||
| * CNAME @ | |||
| ` | |||
| func TestPrivateZoneParser(t *testing.T) { | |||
| dns.PrivateHandle("ISBN", TypeISBN, NewISBN) | |||
| dns.PrivateHandle("VERSION", TypeVERSION, NewVersion) | |||
| defer dns.PrivateHandleRemove(TypeISBN) | |||
| defer dns.PrivateHandleRemove(TypeVERSION) | |||
| r := strings.NewReader(smallzone) | |||
| for x := range dns.ParseZone(r, ".", "") { | |||
| if err := x.Error; err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| t.Log(x.RR) | |||
| } | |||
| } | |||
| @ -0,0 +1,95 @@ | |||
| package dns | |||
| // These raw* functions do not use reflection, they directly set the values | |||
| // in the buffer. There are faster than their reflection counterparts. | |||
| // RawSetId sets the message id in buf. | |||
| func rawSetId(msg []byte, i uint16) bool { | |||
| if len(msg) < 2 { | |||
| return false | |||
| } | |||
| msg[0], msg[1] = packUint16(i) | |||
| return true | |||
| } | |||
| // rawSetQuestionLen sets the length of the question section. | |||
| func rawSetQuestionLen(msg []byte, i uint16) bool { | |||
| if len(msg) < 6 { | |||
| return false | |||
| } | |||
| msg[4], msg[5] = packUint16(i) | |||
| return true | |||
| } | |||
| // rawSetAnswerLen sets the length of the answer section. | |||
| func rawSetAnswerLen(msg []byte, i uint16) bool { | |||
| if len(msg) < 8 { | |||
| return false | |||
| } | |||
| msg[6], msg[7] = packUint16(i) | |||
| return true | |||
| } | |||
| // rawSetsNsLen sets the length of the authority section. | |||
| func rawSetNsLen(msg []byte, i uint16) bool { | |||
| if len(msg) < 10 { | |||
| return false | |||
| } | |||
| msg[8], msg[9] = packUint16(i) | |||
| return true | |||
| } | |||
| // rawSetExtraLen sets the length of the additional section. | |||
| func rawSetExtraLen(msg []byte, i uint16) bool { | |||
| if len(msg) < 12 { | |||
| return false | |||
| } | |||
| msg[10], msg[11] = packUint16(i) | |||
| return true | |||
| } | |||
| // rawSetRdlength sets the rdlength in the header of | |||
| // the RR. The offset 'off' must be positioned at the | |||
| // start of the header of the RR, 'end' must be the | |||
| // end of the RR. | |||
| func rawSetRdlength(msg []byte, off, end int) bool { | |||
| l := len(msg) | |||
| Loop: | |||
| for { | |||
| if off+1 > l { | |||
| return false | |||
| } | |||
| c := int(msg[off]) | |||
| off++ | |||
| switch c & 0xC0 { | |||
| case 0x00: | |||
| if c == 0x00 { | |||
| // End of the domainname | |||
| break Loop | |||
| } | |||
| if off+c > l { | |||
| return false | |||
| } | |||
| off += c | |||
| case 0xC0: | |||
| // pointer, next byte included, ends domainname | |||
| off++ | |||
| break Loop | |||
| } | |||
| } | |||
| // The domainname has been seen, we at the start of the fixed part in the header. | |||
| // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. | |||
| off += 2 + 2 + 4 | |||
| if off+2 > l { | |||
| return false | |||
| } | |||
| //off+1 is the end of the header, 'end' is the end of the rr | |||
| //so 'end' - 'off+2' is the length of the rdata | |||
| rdatalen := end - (off + 2) | |||
| if rdatalen > 0xFFFF { | |||
| return false | |||
| } | |||
| msg[off], msg[off+1] = packUint16(uint16(rdatalen)) | |||
| return true | |||
| } | |||
| @ -0,0 +1,19 @@ | |||
| package dns | |||
| import "testing" | |||
| const LinodeAddr = "176.58.119.54:53" | |||
| func TestClientRemote(t *testing.T) { | |||
| m := new(Msg) | |||
| m.SetQuestion("go.dns.miek.nl.", TypeTXT) | |||
| c := new(Client) | |||
| r, _, err := c.Exchange(m, LinodeAddr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| if r != nil && r.Rcode != RcodeSuccess { | |||
| t.Errorf("failed to get an valid answer\n%v", r) | |||
| } | |||
| } | |||
| @ -0,0 +1,84 @@ | |||
| package dns | |||
| // Dedup removes identical RRs from rrs. It preserves the original ordering. | |||
| // The lowest TTL of any duplicates is used in the remaining one. Dedup modifies | |||
| // rrs. | |||
| // m is used to store the RRs temporay. If it is nil a new map will be allocated. | |||
| func Dedup(rrs []RR, m map[string]RR) []RR { | |||
| if m == nil { | |||
| m = make(map[string]RR) | |||
| } | |||
| // Save the keys, so we don't have to call normalizedString twice. | |||
| keys := make([]*string, 0, len(rrs)) | |||
| for _, r := range rrs { | |||
| key := normalizedString(r) | |||
| keys = append(keys, &key) | |||
| if _, ok := m[key]; ok { | |||
| // Shortest TTL wins. | |||
| if m[key].Header().Ttl > r.Header().Ttl { | |||
| m[key].Header().Ttl = r.Header().Ttl | |||
| } | |||
| continue | |||
| } | |||
| m[key] = r | |||
| } | |||
| // If the length of the result map equals the amount of RRs we got, | |||
| // it means they were all different. We can then just return the original rrset. | |||
| if len(m) == len(rrs) { | |||
| return rrs | |||
| } | |||
| j := 0 | |||
| for i, r := range rrs { | |||
| // If keys[i] lives in the map, we should copy and remove it. | |||
| if _, ok := m[*keys[i]]; ok { | |||
| delete(m, *keys[i]) | |||
| rrs[j] = r | |||
| j++ | |||
| } | |||
| if len(m) == 0 { | |||
| break | |||
| } | |||
| } | |||
| return rrs[:j] | |||
| } | |||
| // normalizedString returns a normalized string from r. The TTL | |||
| // is removed and the domain name is lowercased. We go from this: | |||
| // DomainName<TAB>TTL<TAB>CLASS<TAB>TYPE<TAB>RDATA to: | |||
| // lowercasename<TAB>CLASS<TAB>TYPE... | |||
| func normalizedString(r RR) string { | |||
| // A string Go DNS makes has: domainname<TAB>TTL<TAB>... | |||
| b := []byte(r.String()) | |||
| // find the first non-escaped tab, then another, so we capture where the TTL lives. | |||
| esc := false | |||
| ttlStart, ttlEnd := 0, 0 | |||
| for i := 0; i < len(b) && ttlEnd == 0; i++ { | |||
| switch { | |||
| case b[i] == '\\': | |||
| esc = !esc | |||
| case b[i] == '\t' && !esc: | |||
| if ttlStart == 0 { | |||
| ttlStart = i | |||
| continue | |||
| } | |||
| if ttlEnd == 0 { | |||
| ttlEnd = i | |||
| } | |||
| case b[i] >= 'A' && b[i] <= 'Z' && !esc: | |||
| b[i] += 32 | |||
| default: | |||
| esc = false | |||
| } | |||
| } | |||
| // remove TTL. | |||
| copy(b[ttlStart:], b[ttlEnd:]) | |||
| cut := ttlEnd - ttlStart | |||
| return string(b[:len(b)-cut]) | |||
| } | |||
| @ -0,0 +1,85 @@ | |||
| package dns | |||
| import "testing" | |||
| func TestDedup(t *testing.T) { | |||
| // make it []string | |||
| testcases := map[[3]RR][]string{ | |||
| [...]RR{ | |||
| 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"}, | |||
| [...]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"}, | |||
| [...]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\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\tIN\tA\t127.0.0.1", | |||
| "miek.de.\t3600\tIN\tA\t127.0.0.1", | |||
| }, | |||
| [...]RR{ | |||
| 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.nl.\t200\tIN\tA\t127.0.0.1", | |||
| }, | |||
| } | |||
| for rr, expected := range testcases { | |||
| out := Dedup([]RR{rr[0], rr[1], rr[2]}, nil) | |||
| for i, o := range out { | |||
| if o.String() != expected[i] { | |||
| t.Fatalf("expected %v, got %v", expected[i], o.String()) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func BenchmarkDedup(b *testing.B) { | |||
| rrs := []RR{ | |||
| newRR(nil, "miEk.nl. 2000 IN A 127.0.0.1"), | |||
| newRR(nil, "mieK.Nl. 1000 IN A 127.0.0.1"), | |||
| newRR(nil, "Miek.nL. 500 IN A 127.0.0.1"), | |||
| } | |||
| m := make(map[string]RR) | |||
| for i := 0; i < b.N; i++ { | |||
| Dedup(rrs,m ) | |||
| } | |||
| } | |||
| func TestNormalizedString(t *testing.T) { | |||
| tests := map[RR]string{ | |||
| newRR(t, "mIEk.Nl. 3600 IN A 127.0.0.1"): "miek.nl.\tIN\tA\t127.0.0.1", | |||
| newRR(t, "m\\ iek.nL. 3600 IN A 127.0.0.1"): "m\\ iek.nl.\tIN\tA\t127.0.0.1", | |||
| newRR(t, "m\\\tIeK.nl. 3600 in A 127.0.0.1"): "m\\tiek.nl.\tIN\tA\t127.0.0.1", | |||
| } | |||
| for tc, expected := range tests { | |||
| n := normalizedString(tc) | |||
| if n != expected { | |||
| t.Logf("expected %s, got %s", expected, n) | |||
| t.Fail() | |||
| } | |||
| } | |||
| } | |||
| func newRR(t *testing.T, s string) RR { | |||
| r, e := NewRR(s) | |||
| if e != nil { | |||
| t.Logf("newRR: %s", e) | |||
| } | |||
| return r | |||
| } | |||
| @ -0,0 +1,43 @@ | |||
| package dns | |||
| // Implement a simple scanner, return a byte stream from an io reader. | |||
| import ( | |||
| "bufio" | |||
| "io" | |||
| "text/scanner" | |||
| ) | |||
| type scan struct { | |||
| src *bufio.Reader | |||
| position scanner.Position | |||
| eof bool // Have we just seen a eof | |||
| } | |||
| func scanInit(r io.Reader) *scan { | |||
| s := new(scan) | |||
| s.src = bufio.NewReader(r) | |||
| s.position.Line = 1 | |||
| return s | |||
| } | |||
| // tokenText returns the next byte from the input | |||
| func (s *scan) tokenText() (byte, error) { | |||
| c, err := s.src.ReadByte() | |||
| if err != nil { | |||
| return c, err | |||
| } | |||
| // delay the newline handling until the next token is delivered, | |||
| // fixes off-by-one errors when reporting a parse error. | |||
| if s.eof == true { | |||
| s.position.Line++ | |||
| s.position.Column = 0 | |||
| s.eof = false | |||
| } | |||
| if c == '\n' { | |||
| s.eof = true | |||
| return c, nil | |||
| } | |||
| s.position.Column++ | |||
| return c, nil | |||
| } | |||
| @ -0,0 +1,731 @@ | |||
| // DNS server implementation. | |||
| package dns | |||
| import ( | |||
| "bytes" | |||
| "crypto/tls" | |||
| "io" | |||
| "net" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| // Maximum number of TCP queries before we close the socket. | |||
| const maxTCPQueries = 128 | |||
| // Handler is implemented by any value that implements ServeDNS. | |||
| type Handler interface { | |||
| ServeDNS(w ResponseWriter, r *Msg) | |||
| } | |||
| // A ResponseWriter interface is used by an DNS handler to | |||
| // construct an DNS response. | |||
| type ResponseWriter interface { | |||
| // LocalAddr returns the net.Addr of the server | |||
| LocalAddr() net.Addr | |||
| // RemoteAddr returns the net.Addr of the client that sent the current request. | |||
| RemoteAddr() net.Addr | |||
| // WriteMsg writes a reply back to the client. | |||
| WriteMsg(*Msg) error | |||
| // Write writes a raw buffer back to the client. | |||
| Write([]byte) (int, error) | |||
| // Close closes the connection. | |||
| Close() error | |||
| // TsigStatus returns the status of the Tsig. | |||
| TsigStatus() error | |||
| // TsigTimersOnly sets the tsig timers only boolean. | |||
| TsigTimersOnly(bool) | |||
| // Hijack lets the caller take over the connection. | |||
| // After a call to Hijack(), the DNS package will not do anything with the connection. | |||
| Hijack() | |||
| } | |||
| type response struct { | |||
| hijacked bool // connection has been hijacked by handler | |||
| tsigStatus error | |||
| tsigTimersOnly bool | |||
| tsigRequestMAC string | |||
| tsigSecret map[string]string // the tsig secrets | |||
| udp *net.UDPConn // i/o connection if UDP was used | |||
| tcp net.Conn // i/o connection if TCP was used | |||
| udpSession *SessionUDP // oob data to get egress interface right | |||
| remoteAddr net.Addr // address of the client | |||
| writer Writer // writer to output the raw DNS bits | |||
| } | |||
| // ServeMux is an DNS request multiplexer. It matches the | |||
| // zone name of each incoming request against a list of | |||
| // registered patterns add calls the handler for the pattern | |||
| // that most closely matches the zone name. ServeMux is DNSSEC aware, meaning | |||
| // that queries for the DS record are redirected to the parent zone (if that | |||
| // is also registered), otherwise the child gets the query. | |||
| // ServeMux is also safe for concurrent access from multiple goroutines. | |||
| type ServeMux struct { | |||
| z map[string]Handler | |||
| m *sync.RWMutex | |||
| } | |||
| // NewServeMux allocates and returns a new ServeMux. | |||
| func NewServeMux() *ServeMux { return &ServeMux{z: make(map[string]Handler), m: new(sync.RWMutex)} } | |||
| // DefaultServeMux is the default ServeMux used by Serve. | |||
| var DefaultServeMux = NewServeMux() | |||
| // The HandlerFunc type is an adapter to allow the use of | |||
| // ordinary functions as DNS handlers. If f is a function | |||
| // with the appropriate signature, HandlerFunc(f) is a | |||
| // Handler object that calls f. | |||
| type HandlerFunc func(ResponseWriter, *Msg) | |||
| // ServeDNS calls f(w, r). | |||
| func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) { | |||
| f(w, r) | |||
| } | |||
| // HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets. | |||
| func HandleFailed(w ResponseWriter, r *Msg) { | |||
| m := new(Msg) | |||
| m.SetRcode(r, RcodeServerFailure) | |||
| // does not matter if this write fails | |||
| w.WriteMsg(m) | |||
| } | |||
| func failedHandler() Handler { return HandlerFunc(HandleFailed) } | |||
| // ListenAndServe Starts a server on address and network specified Invoke handler | |||
| // for incoming queries. | |||
| func ListenAndServe(addr string, network string, handler Handler) error { | |||
| server := &Server{Addr: addr, Net: network, Handler: handler} | |||
| return server.ListenAndServe() | |||
| } | |||
| // ListenAndServeTLS acts like http.ListenAndServeTLS, more information in | |||
| // http://golang.org/pkg/net/http/#ListenAndServeTLS | |||
| func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { | |||
| cert, err := tls.LoadX509KeyPair(certFile, keyFile) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| config := tls.Config{ | |||
| Certificates: []tls.Certificate{cert}, | |||
| } | |||
| server := &Server{ | |||
| Addr: addr, | |||
| Net: "tcp-tls", | |||
| TLSConfig: &config, | |||
| Handler: handler, | |||
| } | |||
| return server.ListenAndServe() | |||
| } | |||
| // ActivateAndServe activates a server with a listener from systemd, | |||
| // l and p should not both be non-nil. | |||
| // If both l and p are not nil only p will be used. | |||
| // Invoke handler for incoming queries. | |||
| func ActivateAndServe(l net.Listener, p net.PacketConn, handler Handler) error { | |||
| server := &Server{Listener: l, PacketConn: p, Handler: handler} | |||
| return server.ActivateAndServe() | |||
| } | |||
| func (mux *ServeMux) match(q string, t uint16) Handler { | |||
| mux.m.RLock() | |||
| defer mux.m.RUnlock() | |||
| var handler Handler | |||
| b := make([]byte, len(q)) // worst case, one label of length q | |||
| off := 0 | |||
| end := false | |||
| for { | |||
| l := len(q[off:]) | |||
| for i := 0; i < l; i++ { | |||
| b[i] = q[off+i] | |||
| if b[i] >= 'A' && b[i] <= 'Z' { | |||
| b[i] |= ('a' - 'A') | |||
| } | |||
| } | |||
| if h, ok := mux.z[string(b[:l])]; ok { // 'causes garbage, might want to change the map key | |||
| if t != TypeDS { | |||
| return h | |||
| } | |||
| // Continue for DS to see if we have a parent too, if so delegeate to the parent | |||
| handler = h | |||
| } | |||
| off, end = NextLabel(q, off) | |||
| if end { | |||
| break | |||
| } | |||
| } | |||
| // Wildcard match, if we have found nothing try the root zone as a last resort. | |||
| if h, ok := mux.z["."]; ok { | |||
| return h | |||
| } | |||
| return handler | |||
| } | |||
| // Handle adds a handler to the ServeMux for pattern. | |||
| func (mux *ServeMux) Handle(pattern string, handler Handler) { | |||
| if pattern == "" { | |||
| panic("dns: invalid pattern " + pattern) | |||
| } | |||
| mux.m.Lock() | |||
| mux.z[Fqdn(pattern)] = handler | |||
| mux.m.Unlock() | |||
| } | |||
| // HandleFunc adds a handler function to the ServeMux for pattern. | |||
| func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { | |||
| mux.Handle(pattern, HandlerFunc(handler)) | |||
| } | |||
| // HandleRemove deregistrars the handler specific for pattern from the ServeMux. | |||
| func (mux *ServeMux) HandleRemove(pattern string) { | |||
| if pattern == "" { | |||
| panic("dns: invalid pattern " + pattern) | |||
| } | |||
| mux.m.Lock() | |||
| delete(mux.z, Fqdn(pattern)) | |||
| mux.m.Unlock() | |||
| } | |||
| // ServeDNS dispatches the request to the handler whose | |||
| // pattern most closely matches the request message. If DefaultServeMux | |||
| // is used the correct thing for DS queries is done: a possible parent | |||
| // is sought. | |||
| // If no handler is found a standard SERVFAIL message is returned | |||
| // If the request message does not have exactly one question in the | |||
| // question section a SERVFAIL is returned, unlesss Unsafe is true. | |||
| func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) { | |||
| var h Handler | |||
| if len(request.Question) < 1 { // allow more than one question | |||
| h = failedHandler() | |||
| } else { | |||
| if h = mux.match(request.Question[0].Name, request.Question[0].Qtype); h == nil { | |||
| h = failedHandler() | |||
| } | |||
| } | |||
| h.ServeDNS(w, request) | |||
| } | |||
| // Handle registers the handler with the given pattern | |||
| // in the DefaultServeMux. The documentation for | |||
| // ServeMux explains how patterns are matched. | |||
| func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } | |||
| // HandleRemove deregisters the handle with the given pattern | |||
| // in the DefaultServeMux. | |||
| func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) } | |||
| // HandleFunc registers the handler function with the given pattern | |||
| // in the DefaultServeMux. | |||
| func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { | |||
| DefaultServeMux.HandleFunc(pattern, handler) | |||
| } | |||
| // Writer writes raw DNS messages; each call to Write should send an entire message. | |||
| type Writer interface { | |||
| io.Writer | |||
| } | |||
| // Reader reads raw DNS messages; each call to ReadTCP or ReadUDP should return an entire message. | |||
| type Reader interface { | |||
| // ReadTCP reads a raw message from a TCP connection. Implementations may alter | |||
| // connection properties, for example the read-deadline. | |||
| ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) | |||
| // ReadUDP reads a raw message from a UDP connection. Implementations may alter | |||
| // connection properties, for example the read-deadline. | |||
| ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) | |||
| } | |||
| // defaultReader is an adapter for the Server struct that implements the Reader interface | |||
| // using the readTCP and readUDP func of the embedded Server. | |||
| type defaultReader struct { | |||
| *Server | |||
| } | |||
| func (dr *defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { | |||
| return dr.readTCP(conn, timeout) | |||
| } | |||
| func (dr *defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { | |||
| return dr.readUDP(conn, timeout) | |||
| } | |||
| // DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader. | |||
| // Implementations should never return a nil Reader. | |||
| type DecorateReader func(Reader) Reader | |||
| // DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer. | |||
| // Implementations should never return a nil Writer. | |||
| type DecorateWriter func(Writer) Writer | |||
| // A Server defines parameters for running an DNS server. | |||
| type Server struct { | |||
| // Address to listen on, ":dns" if empty. | |||
| Addr string | |||
| // if "tcp" or "tcp-tls" (DNS over TLS) it will invoke a TCP listener, otherwise an UDP one | |||
| Net string | |||
| // TCP Listener to use, this is to aid in systemd's socket activation. | |||
| Listener net.Listener | |||
| // TLS connection configuration | |||
| TLSConfig *tls.Config | |||
| // UDP "Listener" to use, this is to aid in systemd's socket activation. | |||
| PacketConn net.PacketConn | |||
| // Handler to invoke, dns.DefaultServeMux if nil. | |||
| Handler Handler | |||
| // Default buffer size to use to read incoming UDP messages. If not set | |||
| // it defaults to MinMsgSize (512 B). | |||
| UDPSize int | |||
| // The net.Conn.SetReadTimeout value for new connections, defaults to 2 * time.Second. | |||
| ReadTimeout time.Duration | |||
| // The net.Conn.SetWriteTimeout value for new connections, defaults to 2 * time.Second. | |||
| WriteTimeout time.Duration | |||
| // TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966). | |||
| IdleTimeout func() time.Duration | |||
| // Secret(s) for Tsig map[<zonename>]<base64 secret>. | |||
| TsigSecret map[string]string | |||
| // Unsafe instructs the server to disregard any sanity checks and directly hand the message to | |||
| // the handler. It will specfically not check if the query has the QR bit not set. | |||
| Unsafe bool | |||
| // If NotifyStartedFunc is set it is called once the server has started listening. | |||
| NotifyStartedFunc func() | |||
| // DecorateReader is optional, allows customization of the process that reads raw DNS messages. | |||
| DecorateReader DecorateReader | |||
| // DecorateWriter is optional, allows customization of the process that writes raw DNS messages. | |||
| DecorateWriter DecorateWriter | |||
| // Graceful shutdown handling | |||
| inFlight sync.WaitGroup | |||
| lock sync.RWMutex | |||
| started bool | |||
| } | |||
| // ListenAndServe starts a nameserver on the configured address in *Server. | |||
| func (srv *Server) ListenAndServe() error { | |||
| srv.lock.Lock() | |||
| defer srv.lock.Unlock() | |||
| if srv.started { | |||
| return &Error{err: "server already started"} | |||
| } | |||
| addr := srv.Addr | |||
| if addr == "" { | |||
| addr = ":domain" | |||
| } | |||
| if srv.UDPSize == 0 { | |||
| srv.UDPSize = MinMsgSize | |||
| } | |||
| switch srv.Net { | |||
| case "tcp", "tcp4", "tcp6": | |||
| a, e := net.ResolveTCPAddr(srv.Net, addr) | |||
| if e != nil { | |||
| return e | |||
| } | |||
| l, e := net.ListenTCP(srv.Net, a) | |||
| if e != nil { | |||
| return e | |||
| } | |||
| srv.Listener = l | |||
| srv.started = true | |||
| srv.lock.Unlock() | |||
| e = srv.serveTCP(l) | |||
| srv.lock.Lock() // to satisfy the defer at the top | |||
| return e | |||
| case "tcp-tls", "tcp4-tls", "tcp6-tls": | |||
| network := "tcp" | |||
| if srv.Net == "tcp4-tls" { | |||
| network = "tcp4" | |||
| } else if srv.Net == "tcp6" { | |||
| network = "tcp6" | |||
| } | |||
| l, e := tls.Listen(network, addr, srv.TLSConfig) | |||
| if e != nil { | |||
| return e | |||
| } | |||
| srv.Listener = l | |||
| srv.started = true | |||
| srv.lock.Unlock() | |||
| e = srv.serveTCP(l) | |||
| srv.lock.Lock() // to satisfy the defer at the top | |||
| return e | |||
| case "udp", "udp4", "udp6": | |||
| a, e := net.ResolveUDPAddr(srv.Net, addr) | |||
| if e != nil { | |||
| return e | |||
| } | |||
| l, e := net.ListenUDP(srv.Net, a) | |||
| if e != nil { | |||
| return e | |||
| } | |||
| if e := setUDPSocketOptions(l); e != nil { | |||
| return e | |||
| } | |||
| srv.PacketConn = l | |||
| srv.started = true | |||
| srv.lock.Unlock() | |||
| e = srv.serveUDP(l) | |||
| srv.lock.Lock() // to satisfy the defer at the top | |||
| return e | |||
| } | |||
| return &Error{err: "bad network"} | |||
| } | |||
| // ActivateAndServe starts a nameserver with the PacketConn or Listener | |||
| // configured in *Server. Its main use is to start a server from systemd. | |||
| func (srv *Server) ActivateAndServe() error { | |||
| srv.lock.Lock() | |||
| defer srv.lock.Unlock() | |||
| if srv.started { | |||
| return &Error{err: "server already started"} | |||
| } | |||
| pConn := srv.PacketConn | |||
| l := srv.Listener | |||
| if pConn != nil { | |||
| if srv.UDPSize == 0 { | |||
| srv.UDPSize = MinMsgSize | |||
| } | |||
| if t, ok := pConn.(*net.UDPConn); ok { | |||
| if e := setUDPSocketOptions(t); e != nil { | |||
| return e | |||
| } | |||
| srv.started = true | |||
| srv.lock.Unlock() | |||
| e := srv.serveUDP(t) | |||
| srv.lock.Lock() // to satisfy the defer at the top | |||
| return e | |||
| } | |||
| } | |||
| if l != nil { | |||
| srv.started = true | |||
| srv.lock.Unlock() | |||
| e := srv.serveTCP(l) | |||
| srv.lock.Lock() // to satisfy the defer at the top | |||
| return e | |||
| } | |||
| return &Error{err: "bad listeners"} | |||
| } | |||
| // Shutdown gracefully shuts down a server. After a call to Shutdown, ListenAndServe and | |||
| // ActivateAndServe will return. All in progress queries are completed before the server | |||
| // is taken down. If the Shutdown is taking longer than the reading timeout an error | |||
| // is returned. | |||
| func (srv *Server) Shutdown() error { | |||
| srv.lock.Lock() | |||
| if !srv.started { | |||
| srv.lock.Unlock() | |||
| return &Error{err: "server not started"} | |||
| } | |||
| srv.started = false | |||
| srv.lock.Unlock() | |||
| if srv.PacketConn != nil { | |||
| srv.PacketConn.Close() | |||
| } | |||
| if srv.Listener != nil { | |||
| srv.Listener.Close() | |||
| } | |||
| fin := make(chan bool) | |||
| go func() { | |||
| srv.inFlight.Wait() | |||
| fin <- true | |||
| }() | |||
| select { | |||
| case <-time.After(srv.getReadTimeout()): | |||
| return &Error{err: "server shutdown is pending"} | |||
| case <-fin: | |||
| return nil | |||
| } | |||
| } | |||
| // getReadTimeout is a helper func to use system timeout if server did not intend to change it. | |||
| func (srv *Server) getReadTimeout() time.Duration { | |||
| rtimeout := dnsTimeout | |||
| if srv.ReadTimeout != 0 { | |||
| rtimeout = srv.ReadTimeout | |||
| } | |||
| return rtimeout | |||
| } | |||
| // serveTCP starts a TCP listener for the server. | |||
| // Each request is handled in a separate goroutine. | |||
| func (srv *Server) serveTCP(l net.Listener) error { | |||
| defer l.Close() | |||
| if srv.NotifyStartedFunc != nil { | |||
| srv.NotifyStartedFunc() | |||
| } | |||
| reader := Reader(&defaultReader{srv}) | |||
| if srv.DecorateReader != nil { | |||
| reader = srv.DecorateReader(reader) | |||
| } | |||
| handler := srv.Handler | |||
| if handler == nil { | |||
| handler = DefaultServeMux | |||
| } | |||
| rtimeout := srv.getReadTimeout() | |||
| // deadline is not used here | |||
| for { | |||
| rw, e := l.Accept() | |||
| if e != nil { | |||
| if neterr, ok := e.(net.Error); ok && neterr.Temporary() { | |||
| continue | |||
| } | |||
| return e | |||
| } | |||
| m, e := reader.ReadTCP(rw, rtimeout) | |||
| srv.lock.RLock() | |||
| if !srv.started { | |||
| srv.lock.RUnlock() | |||
| return nil | |||
| } | |||
| srv.lock.RUnlock() | |||
| if e != nil { | |||
| continue | |||
| } | |||
| srv.inFlight.Add(1) | |||
| go srv.serve(rw.RemoteAddr(), handler, m, nil, nil, rw) | |||
| } | |||
| } | |||
| // serveUDP starts a UDP listener for the server. | |||
| // Each request is handled in a separate goroutine. | |||
| func (srv *Server) serveUDP(l *net.UDPConn) error { | |||
| defer l.Close() | |||
| if srv.NotifyStartedFunc != nil { | |||
| srv.NotifyStartedFunc() | |||
| } | |||
| reader := Reader(&defaultReader{srv}) | |||
| if srv.DecorateReader != nil { | |||
| reader = srv.DecorateReader(reader) | |||
| } | |||
| handler := srv.Handler | |||
| if handler == nil { | |||
| handler = DefaultServeMux | |||
| } | |||
| rtimeout := srv.getReadTimeout() | |||
| // deadline is not used here | |||
| for { | |||
| m, s, e := reader.ReadUDP(l, rtimeout) | |||
| srv.lock.RLock() | |||
| if !srv.started { | |||
| srv.lock.RUnlock() | |||
| return nil | |||
| } | |||
| srv.lock.RUnlock() | |||
| if e != nil { | |||
| continue | |||
| } | |||
| srv.inFlight.Add(1) | |||
| go srv.serve(s.RemoteAddr(), handler, m, l, s, nil) | |||
| } | |||
| } | |||
| // Serve a new connection. | |||
| func (srv *Server) serve(a net.Addr, h Handler, m []byte, u *net.UDPConn, s *SessionUDP, t net.Conn) { | |||
| defer srv.inFlight.Done() | |||
| w := &response{tsigSecret: srv.TsigSecret, udp: u, tcp: t, remoteAddr: a, udpSession: s} | |||
| if srv.DecorateWriter != nil { | |||
| w.writer = srv.DecorateWriter(w) | |||
| } else { | |||
| w.writer = w | |||
| } | |||
| q := 0 // counter for the amount of TCP queries we get | |||
| reader := Reader(&defaultReader{srv}) | |||
| if srv.DecorateReader != nil { | |||
| reader = srv.DecorateReader(reader) | |||
| } | |||
| Redo: | |||
| req := new(Msg) | |||
| err := req.Unpack(m) | |||
| if err != nil { // Send a FormatError back | |||
| x := new(Msg) | |||
| x.SetRcodeFormatError(req) | |||
| w.WriteMsg(x) | |||
| goto Exit | |||
| } | |||
| if !srv.Unsafe && req.Response { | |||
| goto Exit | |||
| } | |||
| w.tsigStatus = nil | |||
| if w.tsigSecret != nil { | |||
| if t := req.IsTsig(); t != nil { | |||
| secret := t.Hdr.Name | |||
| if _, ok := w.tsigSecret[secret]; !ok { | |||
| w.tsigStatus = ErrKeyAlg | |||
| } | |||
| w.tsigStatus = TsigVerify(m, w.tsigSecret[secret], "", false) | |||
| w.tsigTimersOnly = false | |||
| w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC | |||
| } | |||
| } | |||
| h.ServeDNS(w, req) // Writes back to the client | |||
| Exit: | |||
| if w.tcp == nil { | |||
| return | |||
| } | |||
| // TODO(miek): make this number configurable? | |||
| if q > maxTCPQueries { // close socket after this many queries | |||
| w.Close() | |||
| return | |||
| } | |||
| if w.hijacked { | |||
| return // client calls Close() | |||
| } | |||
| if u != nil { // UDP, "close" and return | |||
| w.Close() | |||
| return | |||
| } | |||
| idleTimeout := tcpIdleTimeout | |||
| if srv.IdleTimeout != nil { | |||
| idleTimeout = srv.IdleTimeout() | |||
| } | |||
| m, e := reader.ReadTCP(w.tcp, idleTimeout) | |||
| if e == nil { | |||
| q++ | |||
| goto Redo | |||
| } | |||
| w.Close() | |||
| return | |||
| } | |||
| func (srv *Server) readTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { | |||
| conn.SetReadDeadline(time.Now().Add(timeout)) | |||
| l := make([]byte, 2) | |||
| n, err := conn.Read(l) | |||
| if err != nil || n != 2 { | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return nil, ErrShortRead | |||
| } | |||
| length, _ := unpackUint16(l, 0) | |||
| if length == 0 { | |||
| return nil, ErrShortRead | |||
| } | |||
| m := make([]byte, int(length)) | |||
| n, err = conn.Read(m[:int(length)]) | |||
| if err != nil || n == 0 { | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return nil, ErrShortRead | |||
| } | |||
| i := n | |||
| for i < int(length) { | |||
| j, err := conn.Read(m[i:int(length)]) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| i += j | |||
| } | |||
| n = i | |||
| m = m[:n] | |||
| return m, nil | |||
| } | |||
| func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { | |||
| conn.SetReadDeadline(time.Now().Add(timeout)) | |||
| m := make([]byte, srv.UDPSize) | |||
| n, s, e := ReadFromSessionUDP(conn, m) | |||
| if e != nil || n == 0 { | |||
| if e != nil { | |||
| return nil, nil, e | |||
| } | |||
| return nil, nil, ErrShortRead | |||
| } | |||
| m = m[:n] | |||
| return m, s, nil | |||
| } | |||
| // WriteMsg implements the ResponseWriter.WriteMsg method. | |||
| func (w *response) WriteMsg(m *Msg) (err error) { | |||
| var data []byte | |||
| if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check) | |||
| if t := m.IsTsig(); t != nil { | |||
| data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| _, err = w.writer.Write(data) | |||
| return err | |||
| } | |||
| } | |||
| data, err = m.Pack() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| _, err = w.writer.Write(data) | |||
| return err | |||
| } | |||
| // Write implements the ResponseWriter.Write method. | |||
| func (w *response) Write(m []byte) (int, error) { | |||
| switch { | |||
| case w.udp != nil: | |||
| n, err := WriteToSessionUDP(w.udp, m, w.udpSession) | |||
| return n, err | |||
| case w.tcp != nil: | |||
| lm := len(m) | |||
| if lm < 2 { | |||
| return 0, io.ErrShortBuffer | |||
| } | |||
| if lm > MaxMsgSize { | |||
| return 0, &Error{err: "message too large"} | |||
| } | |||
| l := make([]byte, 2, 2+lm) | |||
| l[0], l[1] = packUint16(uint16(lm)) | |||
| m = append(l, m...) | |||
| n, err := io.Copy(w.tcp, bytes.NewReader(m)) | |||
| return int(n), err | |||
| } | |||
| panic("not reached") | |||
| } | |||
| // LocalAddr implements the ResponseWriter.LocalAddr method. | |||
| func (w *response) LocalAddr() net.Addr { | |||
| if w.tcp != nil { | |||
| return w.tcp.LocalAddr() | |||
| } | |||
| return w.udp.LocalAddr() | |||
| } | |||
| // RemoteAddr implements the ResponseWriter.RemoteAddr method. | |||
| func (w *response) RemoteAddr() net.Addr { return w.remoteAddr } | |||
| // TsigStatus implements the ResponseWriter.TsigStatus method. | |||
| func (w *response) TsigStatus() error { return w.tsigStatus } | |||
| // TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method. | |||
| func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b } | |||
| // Hijack implements the ResponseWriter.Hijack method. | |||
| func (w *response) Hijack() { w.hijacked = true } | |||
| // Close implements the ResponseWriter.Close method | |||
| func (w *response) Close() error { | |||
| // Can't close the udp conn, as that is actually the listener. | |||
| if w.tcp != nil { | |||
| e := w.tcp.Close() | |||
| w.tcp = nil | |||
| return e | |||
| } | |||
| return nil | |||
| } | |||
| @ -0,0 +1,679 @@ | |||
| package dns | |||
| import ( | |||
| "crypto/tls" | |||
| "fmt" | |||
| "io" | |||
| "net" | |||
| "runtime" | |||
| "sync" | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func HelloServer(w ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Extra = make([]RR, 1) | |||
| m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| w.WriteMsg(m) | |||
| } | |||
| func HelloServerBadId(w ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Id++ | |||
| m.Extra = make([]RR, 1) | |||
| m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| w.WriteMsg(m) | |||
| } | |||
| func AnotherHelloServer(w ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Extra = make([]RR, 1) | |||
| m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello example"}} | |||
| w.WriteMsg(m) | |||
| } | |||
| func RunLocalUDPServer(laddr string) (*Server, string, error) { | |||
| server, l, _, err := RunLocalUDPServerWithFinChan(laddr) | |||
| return server, l, err | |||
| } | |||
| func RunLocalUDPServerWithFinChan(laddr string) (*Server, string, chan struct{}, error) { | |||
| pc, err := net.ListenPacket("udp", laddr) | |||
| if err != nil { | |||
| return nil, "", nil, err | |||
| } | |||
| server := &Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour} | |||
| waitLock := sync.Mutex{} | |||
| waitLock.Lock() | |||
| server.NotifyStartedFunc = waitLock.Unlock | |||
| fin := make(chan struct{}, 0) | |||
| go func() { | |||
| server.ActivateAndServe() | |||
| close(fin) | |||
| pc.Close() | |||
| }() | |||
| waitLock.Lock() | |||
| return server, pc.LocalAddr().String(), fin, nil | |||
| } | |||
| func RunLocalUDPServerUnsafe(laddr string) (*Server, string, error) { | |||
| pc, err := net.ListenPacket("udp", laddr) | |||
| if err != nil { | |||
| return nil, "", err | |||
| } | |||
| server := &Server{PacketConn: pc, Unsafe: true, | |||
| ReadTimeout: time.Hour, WriteTimeout: time.Hour} | |||
| waitLock := sync.Mutex{} | |||
| waitLock.Lock() | |||
| server.NotifyStartedFunc = waitLock.Unlock | |||
| go func() { | |||
| server.ActivateAndServe() | |||
| pc.Close() | |||
| }() | |||
| waitLock.Lock() | |||
| return server, pc.LocalAddr().String(), nil | |||
| } | |||
| func RunLocalTCPServer(laddr string) (*Server, string, error) { | |||
| l, err := net.Listen("tcp", laddr) | |||
| if err != nil { | |||
| return nil, "", err | |||
| } | |||
| server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour} | |||
| waitLock := sync.Mutex{} | |||
| waitLock.Lock() | |||
| server.NotifyStartedFunc = waitLock.Unlock | |||
| go func() { | |||
| server.ActivateAndServe() | |||
| l.Close() | |||
| }() | |||
| waitLock.Lock() | |||
| return server, l.Addr().String(), nil | |||
| } | |||
| func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, error) { | |||
| l, err := tls.Listen("tcp", laddr, config) | |||
| if err != nil { | |||
| return nil, "", err | |||
| } | |||
| server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour} | |||
| waitLock := sync.Mutex{} | |||
| waitLock.Lock() | |||
| server.NotifyStartedFunc = waitLock.Unlock | |||
| go func() { | |||
| server.ActivateAndServe() | |||
| l.Close() | |||
| }() | |||
| waitLock.Lock() | |||
| return server, l.Addr().String(), nil | |||
| } | |||
| func TestServing(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| HandleFunc("example.com.", AnotherHelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| defer HandleRemove("example.com.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeTXT) | |||
| r, _, err := c.Exchange(m, addrstr) | |||
| if err != nil || len(r.Extra) == 0 { | |||
| t.Fatal("failed to exchange miek.nl", err) | |||
| } | |||
| txt := r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello world" { | |||
| t.Error("unexpected result for miek.nl", txt, "!= Hello world") | |||
| } | |||
| m.SetQuestion("example.com.", TypeTXT) | |||
| r, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Fatal("failed to exchange example.com", err) | |||
| } | |||
| txt = r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello example" { | |||
| t.Error("unexpected result for example.com", txt, "!= Hello example") | |||
| } | |||
| // Test Mixes cased as noticed by Ask. | |||
| m.SetQuestion("eXaMplE.cOm.", TypeTXT) | |||
| r, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Error("failed to exchange eXaMplE.cOm", err) | |||
| } | |||
| txt = r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello example" { | |||
| t.Error("unexpected result for example.com", txt, "!= Hello example") | |||
| } | |||
| } | |||
| func TestServingTLS(t *testing.T) { | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| HandleFunc("example.com.", AnotherHelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| defer HandleRemove("example.com.") | |||
| cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock) | |||
| if err != nil { | |||
| t.Fatalf("unable to build certificate: %v", err) | |||
| } | |||
| config := tls.Config{ | |||
| Certificates: []tls.Certificate{cert}, | |||
| } | |||
| s, addrstr, err := RunLocalTLSServer("127.0.0.1:0", &config) | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| c := new(Client) | |||
| c.Net = "tcp-tls" | |||
| c.TLSConfig = &tls.Config{ | |||
| InsecureSkipVerify: true, | |||
| } | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeTXT) | |||
| r, _, err := c.Exchange(m, addrstr) | |||
| if err != nil || len(r.Extra) == 0 { | |||
| t.Fatal("failed to exchange miek.nl", err) | |||
| } | |||
| txt := r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello world" { | |||
| t.Error("unexpected result for miek.nl", txt, "!= Hello world") | |||
| } | |||
| m.SetQuestion("example.com.", TypeTXT) | |||
| r, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Fatal("failed to exchange example.com", err) | |||
| } | |||
| txt = r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello example" { | |||
| t.Error("unexpected result for example.com", txt, "!= Hello example") | |||
| } | |||
| // Test Mixes cased as noticed by Ask. | |||
| m.SetQuestion("eXaMplE.cOm.", TypeTXT) | |||
| r, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Error("failed to exchange eXaMplE.cOm", err) | |||
| } | |||
| txt = r.Extra[0].(*TXT).Txt[0] | |||
| if txt != "Hello example" { | |||
| t.Error("unexpected result for example.com", txt, "!= Hello example") | |||
| } | |||
| } | |||
| func BenchmarkServe(b *testing.B) { | |||
| b.StopTimer() | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| a := runtime.GOMAXPROCS(4) | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| b.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl", TypeSOA) | |||
| b.StartTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| c.Exchange(m, addrstr) | |||
| } | |||
| runtime.GOMAXPROCS(a) | |||
| } | |||
| func benchmarkServe6(b *testing.B) { | |||
| b.StopTimer() | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| defer HandleRemove("miek.nl.") | |||
| a := runtime.GOMAXPROCS(4) | |||
| s, addrstr, err := RunLocalUDPServer("[::1]:0") | |||
| if err != nil { | |||
| b.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl", TypeSOA) | |||
| b.StartTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| c.Exchange(m, addrstr) | |||
| } | |||
| runtime.GOMAXPROCS(a) | |||
| } | |||
| func HelloServerCompress(w ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Extra = make([]RR, 1) | |||
| m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} | |||
| m.Compress = true | |||
| w.WriteMsg(m) | |||
| } | |||
| func BenchmarkServeCompress(b *testing.B) { | |||
| b.StopTimer() | |||
| HandleFunc("miek.nl.", HelloServerCompress) | |||
| defer HandleRemove("miek.nl.") | |||
| a := runtime.GOMAXPROCS(4) | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| b.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl", TypeSOA) | |||
| b.StartTimer() | |||
| for i := 0; i < b.N; i++ { | |||
| c.Exchange(m, addrstr) | |||
| } | |||
| runtime.GOMAXPROCS(a) | |||
| } | |||
| func TestDotAsCatchAllWildcard(t *testing.T) { | |||
| mux := NewServeMux() | |||
| mux.Handle(".", HandlerFunc(HelloServer)) | |||
| mux.Handle("example.com.", HandlerFunc(AnotherHelloServer)) | |||
| handler := mux.match("www.miek.nl.", TypeTXT) | |||
| if handler == nil { | |||
| t.Error("wildcard match failed") | |||
| } | |||
| handler = mux.match("www.example.com.", TypeTXT) | |||
| if handler == nil { | |||
| t.Error("example.com match failed") | |||
| } | |||
| handler = mux.match("a.www.example.com.", TypeTXT) | |||
| if handler == nil { | |||
| t.Error("a.www.example.com match failed") | |||
| } | |||
| handler = mux.match("boe.", TypeTXT) | |||
| if handler == nil { | |||
| t.Error("boe. match failed") | |||
| } | |||
| } | |||
| func TestCaseFolding(t *testing.T) { | |||
| mux := NewServeMux() | |||
| mux.Handle("_udp.example.com.", HandlerFunc(HelloServer)) | |||
| handler := mux.match("_dns._udp.example.com.", TypeSRV) | |||
| if handler == nil { | |||
| t.Error("case sensitive characters folded") | |||
| } | |||
| handler = mux.match("_DNS._UDP.EXAMPLE.COM.", TypeSRV) | |||
| if handler == nil { | |||
| t.Error("case insensitive characters not folded") | |||
| } | |||
| } | |||
| func TestRootServer(t *testing.T) { | |||
| mux := NewServeMux() | |||
| mux.Handle(".", HandlerFunc(HelloServer)) | |||
| handler := mux.match(".", TypeNS) | |||
| if handler == nil { | |||
| t.Error("root match failed") | |||
| } | |||
| } | |||
| type maxRec struct { | |||
| max int | |||
| sync.RWMutex | |||
| } | |||
| var M = new(maxRec) | |||
| func HelloServerLargeResponse(resp ResponseWriter, req *Msg) { | |||
| m := new(Msg) | |||
| m.SetReply(req) | |||
| m.Authoritative = true | |||
| m1 := 0 | |||
| M.RLock() | |||
| m1 = M.max | |||
| M.RUnlock() | |||
| for i := 0; i < m1; i++ { | |||
| aRec := &A{ | |||
| Hdr: RR_Header{ | |||
| Name: req.Question[0].Name, | |||
| Rrtype: TypeA, | |||
| Class: ClassINET, | |||
| Ttl: 0, | |||
| }, | |||
| A: net.ParseIP(fmt.Sprintf("127.0.0.%d", i+1)).To4(), | |||
| } | |||
| m.Answer = append(m.Answer, aRec) | |||
| } | |||
| resp.WriteMsg(m) | |||
| } | |||
| func TestServingLargeResponses(t *testing.T) { | |||
| HandleFunc("example.", HelloServerLargeResponse) | |||
| defer HandleRemove("example.") | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| // Create request | |||
| m := new(Msg) | |||
| m.SetQuestion("web.service.example.", TypeANY) | |||
| c := new(Client) | |||
| c.Net = "udp" | |||
| M.Lock() | |||
| M.max = 2 | |||
| M.Unlock() | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| // This must fail | |||
| M.Lock() | |||
| M.max = 20 | |||
| M.Unlock() | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err == nil { | |||
| t.Error("failed to fail exchange, this should generate packet error") | |||
| } | |||
| // But this must work again | |||
| c.UDPSize = 7000 | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Errorf("failed to exchange: %v", err) | |||
| } | |||
| } | |||
| func TestServingResponse(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeTXT) | |||
| m.Response = false | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Fatal("failed to exchange", err) | |||
| } | |||
| m.Response = true | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err == nil { | |||
| t.Fatal("exchanged response message") | |||
| } | |||
| s.Shutdown() | |||
| s, addrstr, err = RunLocalUDPServerUnsafe("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| defer s.Shutdown() | |||
| m.Response = true | |||
| _, _, err = c.Exchange(m, addrstr) | |||
| if err != nil { | |||
| t.Fatal("could exchanged response message in Unsafe mode") | |||
| } | |||
| } | |||
| func TestShutdownTCP(t *testing.T) { | |||
| s, _, err := RunLocalTCPServer("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| err = s.Shutdown() | |||
| if err != nil { | |||
| t.Errorf("could not shutdown test TCP server, %v", err) | |||
| } | |||
| } | |||
| func TestShutdownTLS(t *testing.T) { | |||
| cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock) | |||
| if err != nil { | |||
| t.Fatalf("unable to build certificate: %v", err) | |||
| } | |||
| config := tls.Config{ | |||
| Certificates: []tls.Certificate{cert}, | |||
| } | |||
| s, _, err := RunLocalTLSServer("127.0.0.1:0", &config) | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| err = s.Shutdown() | |||
| if err != nil { | |||
| t.Errorf("could not shutdown test TLS server, %v", err) | |||
| } | |||
| } | |||
| type trigger struct { | |||
| done bool | |||
| sync.RWMutex | |||
| } | |||
| func (t *trigger) Set() { | |||
| t.Lock() | |||
| defer t.Unlock() | |||
| t.done = true | |||
| } | |||
| func (t *trigger) Get() bool { | |||
| t.RLock() | |||
| defer t.RUnlock() | |||
| return t.done | |||
| } | |||
| func TestHandlerCloseTCP(t *testing.T) { | |||
| ln, err := net.Listen("tcp", "127.0.0.1:0") | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| addr := ln.Addr().String() | |||
| server := &Server{Addr: addr, Net: "tcp", Listener: ln} | |||
| hname := "testhandlerclosetcp." | |||
| triggered := &trigger{} | |||
| HandleFunc(hname, func(w ResponseWriter, r *Msg) { | |||
| triggered.Set() | |||
| w.Close() | |||
| }) | |||
| defer HandleRemove(hname) | |||
| go func() { | |||
| defer server.Shutdown() | |||
| c := &Client{Net: "tcp"} | |||
| m := new(Msg).SetQuestion(hname, 1) | |||
| tries := 0 | |||
| exchange: | |||
| _, _, err := c.Exchange(m, addr) | |||
| if err != nil && err != io.EOF { | |||
| t.Logf("exchange failed: %s\n", err) | |||
| if tries == 3 { | |||
| return | |||
| } | |||
| time.Sleep(time.Second / 10) | |||
| tries += 1 | |||
| goto exchange | |||
| } | |||
| }() | |||
| server.ActivateAndServe() | |||
| if !triggered.Get() { | |||
| t.Fatalf("handler never called") | |||
| } | |||
| } | |||
| func TestShutdownUDP(t *testing.T) { | |||
| s, _, fin, err := RunLocalUDPServerWithFinChan("127.0.0.1:0") | |||
| if err != nil { | |||
| t.Fatalf("unable to run test server: %v", err) | |||
| } | |||
| err = s.Shutdown() | |||
| if err != nil { | |||
| t.Errorf("could not shutdown test UDP server, %v", err) | |||
| } | |||
| select { | |||
| case <-fin: | |||
| case <-time.After(2 * time.Second): | |||
| t.Error("Could not shutdown test UDP server. Gave up waiting") | |||
| } | |||
| } | |||
| type ExampleFrameLengthWriter struct { | |||
| Writer | |||
| } | |||
| func (e *ExampleFrameLengthWriter) Write(m []byte) (int, error) { | |||
| fmt.Println("writing raw DNS message of length", len(m)) | |||
| return e.Writer.Write(m) | |||
| } | |||
| func ExampleDecorateWriter() { | |||
| // instrument raw DNS message writing | |||
| wf := DecorateWriter(func(w Writer) Writer { | |||
| return &ExampleFrameLengthWriter{w} | |||
| }) | |||
| // simple UDP server | |||
| pc, err := net.ListenPacket("udp", "127.0.0.1:0") | |||
| if err != nil { | |||
| fmt.Println(err.Error()) | |||
| return | |||
| } | |||
| server := &Server{ | |||
| PacketConn: pc, | |||
| DecorateWriter: wf, | |||
| ReadTimeout: time.Hour, WriteTimeout: time.Hour, | |||
| } | |||
| waitLock := sync.Mutex{} | |||
| waitLock.Lock() | |||
| server.NotifyStartedFunc = waitLock.Unlock | |||
| defer server.Shutdown() | |||
| go func() { | |||
| server.ActivateAndServe() | |||
| pc.Close() | |||
| }() | |||
| waitLock.Lock() | |||
| HandleFunc("miek.nl.", HelloServer) | |||
| c := new(Client) | |||
| m := new(Msg) | |||
| m.SetQuestion("miek.nl.", TypeTXT) | |||
| _, _, err = c.Exchange(m, pc.LocalAddr().String()) | |||
| if err != nil { | |||
| fmt.Println("failed to exchange", err.Error()) | |||
| return | |||
| } | |||
| // Output: writing raw DNS message of length 56 | |||
| } | |||
| var ( | |||
| // CertPEMBlock is a X509 data used to test TLS servers (used with tls.X509KeyPair) | |||
| CertPEMBlock = []byte(`-----BEGIN CERTIFICATE----- | |||
| MIIDAzCCAeugAwIBAgIRAJFYMkcn+b8dpU15wjf++GgwDQYJKoZIhvcNAQELBQAw | |||
| EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjAxMDgxMjAzNTNaFw0xNzAxMDcxMjAz | |||
| NTNaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw | |||
| ggEKAoIBAQDXjqO6skvP03k58CNjQggd9G/mt+Wa+xRU+WXiKCCHttawM8x+slq5 | |||
| yfsHCwxlwsGn79HmJqecNqgHb2GWBXAvVVokFDTcC1hUP4+gp2gu9Ny27UHTjlLm | |||
| O0l/xZ5MN8tfKyYlFw18tXu3fkaPyHj8v/D1RDkuo4ARdFvGSe8TqisbhLk2+9ow | |||
| xfIGbEM9Fdiw8qByC2+d+FfvzIKz3GfQVwn0VoRom8L6NBIANq1IGrB5JefZB6nv | |||
| DnfuxkBmY7F1513HKuEJ8KsLWWZWV9OPU4j4I4Rt+WJNlKjbD2srHxyrS2RDsr91 | |||
| 8nCkNoWVNO3sZq0XkWKecdc921vL4ginAgMBAAGjVDBSMA4GA1UdDwEB/wQEAwIC | |||
| pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQT | |||
| MBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAGcU3iyLBIVZj | |||
| aDzSvEDHUd1bnLBl1C58Xu/CyKlPqVU7mLfK0JcgEaYQTSX6fCJVNLbbCrcGLsPJ | |||
| fbjlBbyeLjTV413fxPVuona62pBFjqdtbli2Qe8FRH2KBdm41JUJGdo+SdsFu7nc | |||
| BFOcubdw6LLIXvsTvwndKcHWx1rMX709QU1Vn1GAIsbJV/DWI231Jyyb+lxAUx/C | |||
| 8vce5uVxiKcGS+g6OjsN3D3TtiEQGSXLh013W6Wsih8td8yMCMZ3w8LQ38br1GUe | |||
| ahLIgUJ9l6HDguM17R7kGqxNvbElsMUHfTtXXP7UDQUiYXDakg8xDP6n9DCDhJ8Y | |||
| bSt7OLB7NQ== | |||
| -----END CERTIFICATE-----`) | |||
| // KeyPEMBlock is a X509 data used to test TLS servers (used with tls.X509KeyPair) | |||
| KeyPEMBlock = []byte(`-----BEGIN RSA PRIVATE KEY----- | |||
| MIIEpQIBAAKCAQEA146jurJLz9N5OfAjY0IIHfRv5rflmvsUVPll4iggh7bWsDPM | |||
| frJaucn7BwsMZcLBp+/R5iannDaoB29hlgVwL1VaJBQ03AtYVD+PoKdoLvTctu1B | |||
| 045S5jtJf8WeTDfLXysmJRcNfLV7t35Gj8h4/L/w9UQ5LqOAEXRbxknvE6orG4S5 | |||
| NvvaMMXyBmxDPRXYsPKgcgtvnfhX78yCs9xn0FcJ9FaEaJvC+jQSADatSBqweSXn | |||
| 2Qep7w537sZAZmOxdeddxyrhCfCrC1lmVlfTj1OI+COEbfliTZSo2w9rKx8cq0tk | |||
| Q7K/dfJwpDaFlTTt7GatF5FinnHXPdtby+IIpwIDAQABAoIBAAJK4RDmPooqTJrC | |||
| JA41MJLo+5uvjwCT9QZmVKAQHzByUFw1YNJkITTiognUI0CdzqNzmH7jIFs39ZeG | |||
| proKusO2G6xQjrNcZ4cV2fgyb5g4QHStl0qhs94A+WojduiGm2IaumAgm6Mc5wDv | |||
| ld6HmknN3Mku/ZCyanVFEIjOVn2WB7ZQLTBs6ZYaebTJG2Xv6p9t2YJW7pPQ9Xce | |||
| s9ohAWohyM4X/OvfnfnLtQp2YLw/BxwehBsCR5SXM3ibTKpFNtxJC8hIfTuWtxZu | |||
| 2ywrmXShYBRB1WgtZt5k04bY/HFncvvcHK3YfI1+w4URKtwdaQgPUQRbVwDwuyBn | |||
| flfkCJECgYEA/eWt01iEyE/lXkGn6V9lCocUU7lCU6yk5UT8VXVUc5If4KZKPfCk | |||
| p4zJDOqwn2eM673aWz/mG9mtvAvmnugaGjcaVCyXOp/D/GDmKSoYcvW5B/yjfkLy | |||
| dK6Yaa5LDRVYlYgyzcdCT5/9Qc626NzFwKCZNI4ncIU8g7ViATRxWJ8CgYEA2Ver | |||
| vZ0M606sfgC0H3NtwNBxmuJ+lIF5LNp/wDi07lDfxRR1rnZMX5dnxjcpDr/zvm8J | |||
| WtJJX3xMgqjtHuWKL3yKKony9J5ZPjichSbSbhrzfovgYIRZLxLLDy4MP9L3+CX/ | |||
| yBXnqMWuSnFX+M5fVGxdDWiYF3V+wmeOv9JvavkCgYEAiXAPDFzaY+R78O3xiu7M | |||
| r0o3wqqCMPE/wav6O/hrYrQy9VSO08C0IM6g9pEEUwWmzuXSkZqhYWoQFb8Lc/GI | |||
| T7CMXAxXQLDDUpbRgG79FR3Wr3AewHZU8LyiXHKwxcBMV4WGmsXGK3wbh8fyU1NO | |||
| 6NsGk+BvkQVOoK1LBAPzZ1kCgYEAsBSmD8U33T9s4dxiEYTrqyV0lH3g/SFz8ZHH | |||
| pAyNEPI2iC1ONhyjPWKlcWHpAokiyOqeUpVBWnmSZtzC1qAydsxYB6ShT+sl9BHb | |||
| RMix/QAauzBJhQhUVJ3OIys0Q1UBDmqCsjCE8SfOT4NKOUnA093C+YT+iyrmmktZ | |||
| zDCJkckCgYEAndqM5KXGk5xYo+MAA1paZcbTUXwaWwjLU+XSRSSoyBEi5xMtfvUb | |||
| 7+a1OMhLwWbuz+pl64wFKrbSUyimMOYQpjVE/1vk/kb99pxbgol27hdKyTH1d+ov | |||
| kFsxKCqxAnBVGEWAvVZAiiTOxleQFjz5RnL0BQp9Lg2cQe+dvuUmIAA= | |||
| -----END RSA PRIVATE KEY-----`) | |||
| ) | |||
| @ -0,0 +1,216 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "crypto/dsa" | |||
| "crypto/ecdsa" | |||
| "crypto/rsa" | |||
| "math/big" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // Sign signs a dns.Msg. It fills the signature with the appropriate data. | |||
| // The SIG record should have the SignerName, KeyTag, Algorithm, Inception | |||
| // and Expiration set. | |||
| func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { | |||
| if k == nil { | |||
| return nil, ErrPrivKey | |||
| } | |||
| if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { | |||
| return nil, ErrKey | |||
| } | |||
| rr.Header().Rrtype = TypeSIG | |||
| rr.Header().Class = ClassANY | |||
| rr.Header().Ttl = 0 | |||
| rr.Header().Name = "." | |||
| rr.OrigTtl = 0 | |||
| rr.TypeCovered = 0 | |||
| rr.Labels = 0 | |||
| buf := make([]byte, m.Len()+rr.len()) | |||
| mbuf, err := m.PackBuffer(buf) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if &buf[0] != &mbuf[0] { | |||
| return nil, ErrBuf | |||
| } | |||
| off, err := PackRR(rr, buf, len(mbuf), nil, false) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| buf = buf[:off:cap(buf)] | |||
| hash, ok := AlgorithmToHash[rr.Algorithm] | |||
| if !ok { | |||
| return nil, ErrAlg | |||
| } | |||
| hasher := hash.New() | |||
| // Write SIG rdata | |||
| hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) | |||
| // Write message | |||
| hasher.Write(buf[:len(mbuf)]) | |||
| signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| rr.Signature = toBase64(signature) | |||
| sig := string(signature) | |||
| buf = append(buf, sig...) | |||
| if len(buf) > int(^uint16(0)) { | |||
| return nil, ErrBuf | |||
| } | |||
| // Adjust sig data length | |||
| rdoff := len(mbuf) + 1 + 2 + 2 + 4 | |||
| rdlen, _ := unpackUint16(buf, rdoff) | |||
| rdlen += uint16(len(sig)) | |||
| buf[rdoff], buf[rdoff+1] = packUint16(rdlen) | |||
| // Adjust additional count | |||
| adc, _ := unpackUint16(buf, 10) | |||
| adc++ | |||
| buf[10], buf[11] = packUint16(adc) | |||
| return buf, nil | |||
| } | |||
| // Verify validates the message buf using the key k. | |||
| // It's assumed that buf is a valid message from which rr was unpacked. | |||
| func (rr *SIG) Verify(k *KEY, buf []byte) error { | |||
| if k == nil { | |||
| return ErrKey | |||
| } | |||
| if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { | |||
| return ErrKey | |||
| } | |||
| var hash crypto.Hash | |||
| switch rr.Algorithm { | |||
| case DSA, RSASHA1: | |||
| hash = crypto.SHA1 | |||
| case RSASHA256, ECDSAP256SHA256: | |||
| hash = crypto.SHA256 | |||
| case ECDSAP384SHA384: | |||
| hash = crypto.SHA384 | |||
| case RSASHA512: | |||
| hash = crypto.SHA512 | |||
| default: | |||
| return ErrAlg | |||
| } | |||
| hasher := hash.New() | |||
| buflen := len(buf) | |||
| qdc, _ := unpackUint16(buf, 4) | |||
| anc, _ := unpackUint16(buf, 6) | |||
| auc, _ := unpackUint16(buf, 8) | |||
| adc, offset := unpackUint16(buf, 10) | |||
| var err error | |||
| for i := uint16(0); i < qdc && offset < buflen; i++ { | |||
| _, offset, err = UnpackDomainName(buf, offset) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Skip past Type and Class | |||
| offset += 2 + 2 | |||
| } | |||
| for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { | |||
| _, offset, err = UnpackDomainName(buf, offset) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Skip past Type, Class and TTL | |||
| offset += 2 + 2 + 4 | |||
| if offset+1 >= buflen { | |||
| continue | |||
| } | |||
| var rdlen uint16 | |||
| rdlen, offset = unpackUint16(buf, offset) | |||
| offset += int(rdlen) | |||
| } | |||
| if offset >= buflen { | |||
| return &Error{err: "overflowing unpacking signed message"} | |||
| } | |||
| // offset should be just prior to SIG | |||
| bodyend := offset | |||
| // owner name SHOULD be root | |||
| _, offset, err = UnpackDomainName(buf, offset) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Skip Type, Class, TTL, RDLen | |||
| offset += 2 + 2 + 4 + 2 | |||
| sigstart := offset | |||
| // Skip Type Covered, Algorithm, Labels, Original TTL | |||
| offset += 2 + 1 + 1 + 4 | |||
| if offset+4+4 >= buflen { | |||
| return &Error{err: "overflow unpacking signed message"} | |||
| } | |||
| expire := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) | |||
| offset += 4 | |||
| incept := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) | |||
| offset += 4 | |||
| now := uint32(time.Now().Unix()) | |||
| if now < incept || now > expire { | |||
| return ErrTime | |||
| } | |||
| // Skip key tag | |||
| offset += 2 | |||
| var signername string | |||
| signername, offset, err = UnpackDomainName(buf, offset) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // If key has come from the DNS name compression might | |||
| // have mangled the case of the name | |||
| if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { | |||
| return &Error{err: "signer name doesn't match key name"} | |||
| } | |||
| sigend := offset | |||
| hasher.Write(buf[sigstart:sigend]) | |||
| hasher.Write(buf[:10]) | |||
| hasher.Write([]byte{ | |||
| byte((adc - 1) << 8), | |||
| byte(adc - 1), | |||
| }) | |||
| hasher.Write(buf[12:bodyend]) | |||
| hashed := hasher.Sum(nil) | |||
| sig := buf[sigend:] | |||
| switch k.Algorithm { | |||
| case DSA: | |||
| pk := k.publicKeyDSA() | |||
| sig = sig[1:] | |||
| r := big.NewInt(0) | |||
| r.SetBytes(sig[:len(sig)/2]) | |||
| s := big.NewInt(0) | |||
| s.SetBytes(sig[len(sig)/2:]) | |||
| if pk != nil { | |||
| if dsa.Verify(pk, hashed, r, s) { | |||
| return nil | |||
| } | |||
| return ErrSig | |||
| } | |||
| case RSASHA1, RSASHA256, RSASHA512: | |||
| pk := k.publicKeyRSA() | |||
| if pk != nil { | |||
| return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) | |||
| } | |||
| case ECDSAP256SHA256, ECDSAP384SHA384: | |||
| pk := k.publicKeyECDSA() | |||
| r := big.NewInt(0) | |||
| r.SetBytes(sig[:len(sig)/2]) | |||
| s := big.NewInt(0) | |||
| s.SetBytes(sig[len(sig)/2:]) | |||
| if pk != nil { | |||
| if ecdsa.Verify(pk, hashed, r, s) { | |||
| return nil | |||
| } | |||
| return ErrSig | |||
| } | |||
| } | |||
| return ErrKeyAlg | |||
| } | |||
| @ -0,0 +1,89 @@ | |||
| package dns | |||
| import ( | |||
| "crypto" | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func TestSIG0(t *testing.T) { | |||
| if testing.Short() { | |||
| t.Skip("skipping test in short mode.") | |||
| } | |||
| m := new(Msg) | |||
| m.SetQuestion("example.org.", TypeSOA) | |||
| for _, alg := range []uint8{ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} { | |||
| algstr := AlgorithmToString[alg] | |||
| keyrr := new(KEY) | |||
| keyrr.Hdr.Name = algstr + "." | |||
| keyrr.Hdr.Rrtype = TypeKEY | |||
| keyrr.Hdr.Class = ClassINET | |||
| keyrr.Algorithm = alg | |||
| keysize := 1024 | |||
| switch alg { | |||
| case ECDSAP256SHA256: | |||
| keysize = 256 | |||
| case ECDSAP384SHA384: | |||
| keysize = 384 | |||
| } | |||
| pk, err := keyrr.Generate(keysize) | |||
| if err != nil { | |||
| t.Errorf("failed to generate key for “%s”: %v", algstr, err) | |||
| continue | |||
| } | |||
| now := uint32(time.Now().Unix()) | |||
| sigrr := new(SIG) | |||
| sigrr.Hdr.Name = "." | |||
| sigrr.Hdr.Rrtype = TypeSIG | |||
| sigrr.Hdr.Class = ClassANY | |||
| sigrr.Algorithm = alg | |||
| sigrr.Expiration = now + 300 | |||
| sigrr.Inception = now - 300 | |||
| sigrr.KeyTag = keyrr.KeyTag() | |||
| sigrr.SignerName = keyrr.Hdr.Name | |||
| mb, err := sigrr.Sign(pk.(crypto.Signer), m) | |||
| if err != nil { | |||
| t.Errorf("failed to sign message using “%s”: %v", algstr, err) | |||
| continue | |||
| } | |||
| m := new(Msg) | |||
| if err := m.Unpack(mb); err != nil { | |||
| t.Errorf("failed to unpack message signed using “%s”: %v", algstr, err) | |||
| continue | |||
| } | |||
| if len(m.Extra) != 1 { | |||
| t.Errorf("missing SIG for message signed using “%s”", algstr) | |||
| continue | |||
| } | |||
| var sigrrwire *SIG | |||
| switch rr := m.Extra[0].(type) { | |||
| case *SIG: | |||
| sigrrwire = rr | |||
| default: | |||
| t.Errorf("expected SIG RR, instead: %v", rr) | |||
| continue | |||
| } | |||
| for _, rr := range []*SIG{sigrr, sigrrwire} { | |||
| id := "sigrr" | |||
| if rr == sigrrwire { | |||
| id = "sigrrwire" | |||
| } | |||
| if err := rr.Verify(keyrr, mb); err != nil { | |||
| t.Errorf("failed to verify “%s” signed SIG(%s): %v", algstr, id, err) | |||
| continue | |||
| } | |||
| } | |||
| mb[13]++ | |||
| if err := sigrr.Verify(keyrr, mb); err == nil { | |||
| t.Errorf("verify succeeded on an altered message using “%s”", algstr) | |||
| continue | |||
| } | |||
| sigrr.Expiration = 2 | |||
| sigrr.Inception = 1 | |||
| mb, _ = sigrr.Sign(pk.(crypto.Signer), m) | |||
| if err := sigrr.Verify(keyrr, mb); err == nil { | |||
| t.Errorf("verify succeeded on an expired message using “%s”", algstr) | |||
| continue | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,57 @@ | |||
| // Copyright 2013 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. | |||
| // Adapted for dns package usage by Miek Gieben. | |||
| package dns | |||
| import "sync" | |||
| import "time" | |||
| // call is an in-flight or completed singleflight.Do call | |||
| type call struct { | |||
| wg sync.WaitGroup | |||
| val *Msg | |||
| rtt time.Duration | |||
| err error | |||
| dups int | |||
| } | |||
| // singleflight represents a class of work and forms a namespace in | |||
| // which units of work can be executed with duplicate suppression. | |||
| type singleflight struct { | |||
| sync.Mutex // protects m | |||
| m map[string]*call // lazily initialized | |||
| } | |||
| // Do executes and returns the results of the given function, making | |||
| // sure that only one execution is in-flight for a given key at a | |||
| // time. If a duplicate comes in, the duplicate caller waits for the | |||
| // original to complete and receives the same results. | |||
| // The return value shared indicates whether v was given to multiple callers. | |||
| func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { | |||
| g.Lock() | |||
| if g.m == nil { | |||
| g.m = make(map[string]*call) | |||
| } | |||
| if c, ok := g.m[key]; ok { | |||
| c.dups++ | |||
| g.Unlock() | |||
| c.wg.Wait() | |||
| return c.val, c.rtt, c.err, true | |||
| } | |||
| c := new(call) | |||
| c.wg.Add(1) | |||
| g.m[key] = c | |||
| g.Unlock() | |||
| c.val, c.rtt, c.err = fn() | |||
| c.wg.Done() | |||
| g.Lock() | |||
| delete(g.m, key) | |||
| g.Unlock() | |||
| return c.val, c.rtt, c.err, c.dups > 0 | |||
| } | |||
| @ -0,0 +1,86 @@ | |||
| package dns | |||
| import ( | |||
| "crypto/sha256" | |||
| "crypto/sha512" | |||
| "crypto/x509" | |||
| "encoding/hex" | |||
| "errors" | |||
| "io" | |||
| "net" | |||
| "strconv" | |||
| ) | |||
| // CertificateToDANE converts a certificate to a hex string as used in the TLSA record. | |||
| func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { | |||
| switch matchingType { | |||
| case 0: | |||
| switch selector { | |||
| case 0: | |||
| return hex.EncodeToString(cert.Raw), nil | |||
| case 1: | |||
| return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil | |||
| } | |||
| case 1: | |||
| h := sha256.New() | |||
| switch selector { | |||
| case 0: | |||
| io.WriteString(h, string(cert.Raw)) | |||
| return hex.EncodeToString(h.Sum(nil)), nil | |||
| case 1: | |||
| io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) | |||
| return hex.EncodeToString(h.Sum(nil)), nil | |||
| } | |||
| case 2: | |||
| h := sha512.New() | |||
| switch selector { | |||
| case 0: | |||
| io.WriteString(h, string(cert.Raw)) | |||
| return hex.EncodeToString(h.Sum(nil)), nil | |||
| case 1: | |||
| io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) | |||
| return hex.EncodeToString(h.Sum(nil)), nil | |||
| } | |||
| } | |||
| return "", errors.New("dns: bad TLSA MatchingType or TLSA Selector") | |||
| } | |||
| // Sign creates a TLSA record from an SSL certificate. | |||
| func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { | |||
| r.Hdr.Rrtype = TypeTLSA | |||
| r.Usage = uint8(usage) | |||
| r.Selector = uint8(selector) | |||
| r.MatchingType = uint8(matchingType) | |||
| r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // Verify verifies a TLSA record against an SSL certificate. If it is OK | |||
| // a nil error is returned. | |||
| func (r *TLSA) Verify(cert *x509.Certificate) error { | |||
| c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) | |||
| if err != nil { | |||
| return err // Not also ErrSig? | |||
| } | |||
| if r.Certificate == c { | |||
| return nil | |||
| } | |||
| return ErrSig // ErrSig, really? | |||
| } | |||
| // TLSAName returns the ownername of a TLSA resource record as per the | |||
| // rules specified in RFC 6698, Section 3. | |||
| func TLSAName(name, service, network string) (string, error) { | |||
| if !IsFqdn(name) { | |||
| return "", ErrFqdn | |||
| } | |||
| p, e := net.LookupPort(network, service) | |||
| if e != nil { | |||
| return "", e | |||
| } | |||
| return "_" + strconv.Itoa(p) + "_" + network + "." + name, nil | |||
| } | |||
| @ -0,0 +1,320 @@ | |||
| package dns | |||
| import ( | |||
| "crypto/hmac" | |||
| "crypto/md5" | |||
| "crypto/sha1" | |||
| "crypto/sha256" | |||
| "crypto/sha512" | |||
| "encoding/hex" | |||
| "hash" | |||
| "io" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // HMAC hashing codes. These are transmitted as domain names. | |||
| const ( | |||
| HmacMD5 = "hmac-md5.sig-alg.reg.int." | |||
| HmacSHA1 = "hmac-sha1." | |||
| HmacSHA256 = "hmac-sha256." | |||
| HmacSHA512 = "hmac-sha512." | |||
| ) | |||
| // TSIG is the RR the holds the transaction signature of a message. | |||
| // See RFC 2845 and RFC 4635. | |||
| type TSIG struct { | |||
| Hdr RR_Header | |||
| Algorithm string `dns:"domain-name"` | |||
| TimeSigned uint64 `dns:"uint48"` | |||
| Fudge uint16 | |||
| MACSize uint16 | |||
| MAC string `dns:"size-hex"` | |||
| OrigId uint16 | |||
| Error uint16 | |||
| OtherLen uint16 | |||
| OtherData string `dns:"size-hex"` | |||
| } | |||
| // TSIG has no official presentation format, but this will suffice. | |||
| func (rr *TSIG) String() string { | |||
| s := "\n;; TSIG PSEUDOSECTION:\n" | |||
| s += rr.Hdr.String() + | |||
| " " + rr.Algorithm + | |||
| " " + tsigTimeToString(rr.TimeSigned) + | |||
| " " + strconv.Itoa(int(rr.Fudge)) + | |||
| " " + strconv.Itoa(int(rr.MACSize)) + | |||
| " " + strings.ToUpper(rr.MAC) + | |||
| " " + strconv.Itoa(int(rr.OrigId)) + | |||
| " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR | |||
| " " + strconv.Itoa(int(rr.OtherLen)) + | |||
| " " + rr.OtherData | |||
| return s | |||
| } | |||
| // The following values must be put in wireformat, so that the MAC can be calculated. | |||
| // RFC 2845, section 3.4.2. TSIG Variables. | |||
| type tsigWireFmt struct { | |||
| // From RR_Header | |||
| Name string `dns:"domain-name"` | |||
| Class uint16 | |||
| Ttl uint32 | |||
| // Rdata of the TSIG | |||
| Algorithm string `dns:"domain-name"` | |||
| TimeSigned uint64 `dns:"uint48"` | |||
| Fudge uint16 | |||
| // MACSize, MAC and OrigId excluded | |||
| Error uint16 | |||
| OtherLen uint16 | |||
| OtherData string `dns:"size-hex"` | |||
| } | |||
| // If we have the MAC use this type to convert it to wiredata. | |||
| // Section 3.4.3. Request MAC | |||
| type macWireFmt struct { | |||
| MACSize uint16 | |||
| MAC string `dns:"size-hex"` | |||
| } | |||
| // 3.3. Time values used in TSIG calculations | |||
| type timerWireFmt struct { | |||
| TimeSigned uint64 `dns:"uint48"` | |||
| Fudge uint16 | |||
| } | |||
| // TsigGenerate fills out the TSIG record attached to the message. | |||
| // The message should contain | |||
| // a "stub" TSIG RR with the algorithm, key name (owner name of the RR), | |||
| // time fudge (defaults to 300 seconds) and the current time | |||
| // The TSIG MAC is saved in that Tsig RR. | |||
| // When TsigGenerate is called for the first time requestMAC is set to the empty string and | |||
| // timersOnly is false. | |||
| // If something goes wrong an error is returned, otherwise it is nil. | |||
| func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { | |||
| if m.IsTsig() == nil { | |||
| panic("dns: TSIG not last RR in additional") | |||
| } | |||
| // If we barf here, the caller is to blame | |||
| rawsecret, err := fromBase64([]byte(secret)) | |||
| if err != nil { | |||
| return nil, "", err | |||
| } | |||
| rr := m.Extra[len(m.Extra)-1].(*TSIG) | |||
| m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg | |||
| mbuf, err := m.Pack() | |||
| if err != nil { | |||
| return nil, "", err | |||
| } | |||
| buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) | |||
| t := new(TSIG) | |||
| var h hash.Hash | |||
| switch rr.Algorithm { | |||
| case HmacMD5: | |||
| h = hmac.New(md5.New, []byte(rawsecret)) | |||
| case HmacSHA1: | |||
| h = hmac.New(sha1.New, []byte(rawsecret)) | |||
| case HmacSHA256: | |||
| h = hmac.New(sha256.New, []byte(rawsecret)) | |||
| case HmacSHA512: | |||
| h = hmac.New(sha512.New, []byte(rawsecret)) | |||
| default: | |||
| return nil, "", ErrKeyAlg | |||
| } | |||
| io.WriteString(h, string(buf)) | |||
| t.MAC = hex.EncodeToString(h.Sum(nil)) | |||
| t.MACSize = uint16(len(t.MAC) / 2) // Size is half! | |||
| t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} | |||
| t.Fudge = rr.Fudge | |||
| t.TimeSigned = rr.TimeSigned | |||
| t.Algorithm = rr.Algorithm | |||
| t.OrigId = m.Id | |||
| tbuf := make([]byte, t.len()) | |||
| if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { | |||
| tbuf = tbuf[:off] // reset to actual size used | |||
| } else { | |||
| return nil, "", err | |||
| } | |||
| mbuf = append(mbuf, tbuf...) | |||
| rawSetExtraLen(mbuf, uint16(len(m.Extra)+1)) | |||
| return mbuf, t.MAC, nil | |||
| } | |||
| // TsigVerify verifies the TSIG on a message. | |||
| // If the signature does not validate err contains the | |||
| // error, otherwise it is nil. | |||
| func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { | |||
| rawsecret, err := fromBase64([]byte(secret)) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Strip the TSIG from the incoming msg | |||
| stripped, tsig, err := stripTsig(msg) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| msgMAC, err := hex.DecodeString(tsig.MAC) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) | |||
| // Fudge factor works both ways. A message can arrive before it was signed because | |||
| // of clock skew. | |||
| now := uint64(time.Now().Unix()) | |||
| ti := now - tsig.TimeSigned | |||
| if now < tsig.TimeSigned { | |||
| ti = tsig.TimeSigned - now | |||
| } | |||
| if uint64(tsig.Fudge) < ti { | |||
| return ErrTime | |||
| } | |||
| var h hash.Hash | |||
| switch tsig.Algorithm { | |||
| case HmacMD5: | |||
| h = hmac.New(md5.New, rawsecret) | |||
| case HmacSHA1: | |||
| h = hmac.New(sha1.New, rawsecret) | |||
| case HmacSHA256: | |||
| h = hmac.New(sha256.New, rawsecret) | |||
| case HmacSHA512: | |||
| h = hmac.New(sha512.New, rawsecret) | |||
| default: | |||
| return ErrKeyAlg | |||
| } | |||
| h.Write(buf) | |||
| if !hmac.Equal(h.Sum(nil), msgMAC) { | |||
| return ErrSig | |||
| } | |||
| return nil | |||
| } | |||
| // Create a wiredata buffer for the MAC calculation. | |||
| func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { | |||
| var buf []byte | |||
| if rr.TimeSigned == 0 { | |||
| rr.TimeSigned = uint64(time.Now().Unix()) | |||
| } | |||
| if rr.Fudge == 0 { | |||
| rr.Fudge = 300 // Standard (RFC) default. | |||
| } | |||
| if requestMAC != "" { | |||
| m := new(macWireFmt) | |||
| m.MACSize = uint16(len(requestMAC) / 2) | |||
| m.MAC = requestMAC | |||
| buf = make([]byte, len(requestMAC)) // long enough | |||
| n, _ := PackStruct(m, buf, 0) | |||
| buf = buf[:n] | |||
| } | |||
| tsigvar := make([]byte, DefaultMsgSize) | |||
| if timersOnly { | |||
| tsig := new(timerWireFmt) | |||
| tsig.TimeSigned = rr.TimeSigned | |||
| tsig.Fudge = rr.Fudge | |||
| n, _ := PackStruct(tsig, tsigvar, 0) | |||
| tsigvar = tsigvar[:n] | |||
| } else { | |||
| tsig := new(tsigWireFmt) | |||
| tsig.Name = strings.ToLower(rr.Hdr.Name) | |||
| tsig.Class = ClassANY | |||
| tsig.Ttl = rr.Hdr.Ttl | |||
| tsig.Algorithm = strings.ToLower(rr.Algorithm) | |||
| tsig.TimeSigned = rr.TimeSigned | |||
| tsig.Fudge = rr.Fudge | |||
| tsig.Error = rr.Error | |||
| tsig.OtherLen = rr.OtherLen | |||
| tsig.OtherData = rr.OtherData | |||
| n, _ := PackStruct(tsig, tsigvar, 0) | |||
| tsigvar = tsigvar[:n] | |||
| } | |||
| if requestMAC != "" { | |||
| x := append(buf, msgbuf...) | |||
| buf = append(x, tsigvar...) | |||
| } else { | |||
| buf = append(msgbuf, tsigvar...) | |||
| } | |||
| return buf | |||
| } | |||
| // Strip the TSIG from the raw message. | |||
| func stripTsig(msg []byte) ([]byte, *TSIG, error) { | |||
| // Copied from msg.go's Unpack() | |||
| // Header. | |||
| var dh Header | |||
| var err error | |||
| dns := new(Msg) | |||
| rr := new(TSIG) | |||
| off := 0 | |||
| tsigoff := 0 | |||
| if off, err = UnpackStruct(&dh, msg, off); err != nil { | |||
| return nil, nil, err | |||
| } | |||
| if dh.Arcount == 0 { | |||
| return nil, nil, ErrNoSig | |||
| } | |||
| // Rcode, see msg.go Unpack() | |||
| if int(dh.Bits&0xF) == RcodeNotAuth { | |||
| return nil, nil, ErrAuth | |||
| } | |||
| // Arrays. | |||
| dns.Question = make([]Question, dh.Qdcount) | |||
| dns.Answer = make([]RR, dh.Ancount) | |||
| dns.Ns = make([]RR, dh.Nscount) | |||
| dns.Extra = make([]RR, dh.Arcount) | |||
| for i := 0; i < len(dns.Question); i++ { | |||
| off, err = UnpackStruct(&dns.Question[i], msg, off) | |||
| if err != nil { | |||
| return nil, nil, err | |||
| } | |||
| } | |||
| for i := 0; i < len(dns.Answer); i++ { | |||
| dns.Answer[i], off, err = UnpackRR(msg, off) | |||
| if err != nil { | |||
| return nil, nil, err | |||
| } | |||
| } | |||
| for i := 0; i < len(dns.Ns); i++ { | |||
| dns.Ns[i], off, err = UnpackRR(msg, off) | |||
| if err != nil { | |||
| return nil, nil, err | |||
| } | |||
| } | |||
| for i := 0; i < len(dns.Extra); i++ { | |||
| tsigoff = off | |||
| dns.Extra[i], off, err = UnpackRR(msg, off) | |||
| if err != nil { | |||
| return nil, nil, err | |||
| } | |||
| if dns.Extra[i].Header().Rrtype == TypeTSIG { | |||
| rr = dns.Extra[i].(*TSIG) | |||
| // Adjust Arcount. | |||
| arcount, _ := unpackUint16(msg, 10) | |||
| msg[10], msg[11] = packUint16(arcount - 1) | |||
| break | |||
| } | |||
| } | |||
| if rr == nil { | |||
| return nil, nil, ErrNoSig | |||
| } | |||
| return msg[:tsigoff], rr, nil | |||
| } | |||
| // Translate the TSIG time signed into a date. There is no | |||
| // need for RFC1982 calculations as this date is 48 bits. | |||
| func tsigTimeToString(t uint64) string { | |||
| ti := time.Unix(int64(t), 0).UTC() | |||
| return ti.Format("20060102150405") | |||
| } | |||
| @ -0,0 +1,266 @@ | |||
| //+build ignore | |||
| // types_generate.go is meant to run with go generate. It will use | |||
| // go/{importer,types} to track down all the RR struct types. Then for each type | |||
| // it will generate conversion tables (TypeToRR and TypeToString) and banal | |||
| // methods (len, Header, copy) based on the struct tags. The generated source is | |||
| // written to ztypes.go, and is meant to be checked into git. | |||
| package main | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "go/format" | |||
| "go/importer" | |||
| "go/types" | |||
| "log" | |||
| "os" | |||
| "strings" | |||
| "text/template" | |||
| ) | |||
| var skipLen = map[string]struct{}{ | |||
| "NSEC": struct{}{}, | |||
| "NSEC3": struct{}{}, | |||
| "OPT": struct{}{}, | |||
| "WKS": struct{}{}, | |||
| "IPSECKEY": struct{}{}, | |||
| } | |||
| var packageHdr = ` | |||
| // *** DO NOT MODIFY *** | |||
| // AUTOGENERATED BY go generate | |||
| package dns | |||
| import ( | |||
| "encoding/base64" | |||
| "net" | |||
| ) | |||
| ` | |||
| var TypeToRR = template.Must(template.New("TypeToRR").Parse(` | |||
| // TypeToRR is a map of constructors for each RR type. | |||
| var TypeToRR = map[uint16]func() RR{ | |||
| {{range .}}{{if ne . "RFC3597"}} Type{{.}}: func() RR { return new({{.}}) }, | |||
| {{end}}{{end}} } | |||
| `)) | |||
| var typeToString = template.Must(template.New("typeToString").Parse(` | |||
| // TypeToString is a map of strings for each RR type. | |||
| var TypeToString = map[uint16]string{ | |||
| {{range .}}{{if ne . "NSAPPTR"}} Type{{.}}: "{{.}}", | |||
| {{end}}{{end}} TypeNSAPPTR: "NSAP-PTR", | |||
| } | |||
| `)) | |||
| var headerFunc = template.Must(template.New("headerFunc").Parse(` | |||
| // Header() functions | |||
| {{range .}} func (rr *{{.}}) Header() *RR_Header { return &rr.Hdr } | |||
| {{end}} | |||
| `)) | |||
| // getTypeStruct will take a type and the package scope, and return the | |||
| // (innermost) struct if the type is considered a RR type (currently defined as | |||
| // those structs beginning with a RR_Header, could be redefined as implementing | |||
| // the RR interface). The bool return value indicates if embedded structs were | |||
| // resolved. | |||
| func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { | |||
| st, ok := t.Underlying().(*types.Struct) | |||
| if !ok { | |||
| return nil, false | |||
| } | |||
| if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { | |||
| return st, false | |||
| } | |||
| if st.Field(0).Anonymous() { | |||
| st, _ := getTypeStruct(st.Field(0).Type(), scope) | |||
| return st, true | |||
| } | |||
| return nil, false | |||
| } | |||
| func main() { | |||
| // Import and type-check the package | |||
| pkg, err := importer.Default().Import("github.com/miekg/dns") | |||
| fatalIfErr(err) | |||
| scope := pkg.Scope() | |||
| // Collect constants like TypeX | |||
| var numberedTypes []string | |||
| for _, name := range scope.Names() { | |||
| o := scope.Lookup(name) | |||
| if o == nil || !o.Exported() { | |||
| continue | |||
| } | |||
| b, ok := o.Type().(*types.Basic) | |||
| if !ok || b.Kind() != types.Uint16 { | |||
| continue | |||
| } | |||
| if !strings.HasPrefix(o.Name(), "Type") { | |||
| continue | |||
| } | |||
| name := strings.TrimPrefix(o.Name(), "Type") | |||
| if name == "PrivateRR" { | |||
| continue | |||
| } | |||
| numberedTypes = append(numberedTypes, name) | |||
| } | |||
| // Collect actual types (*X) | |||
| var namedTypes []string | |||
| for _, name := range scope.Names() { | |||
| o := scope.Lookup(name) | |||
| if o == nil || !o.Exported() { | |||
| continue | |||
| } | |||
| if st, _ := getTypeStruct(o.Type(), scope); st == nil { | |||
| continue | |||
| } | |||
| if name == "PrivateRR" { | |||
| continue | |||
| } | |||
| // Check if corresponding TypeX exists | |||
| if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { | |||
| log.Fatalf("Constant Type%s does not exist.", o.Name()) | |||
| } | |||
| namedTypes = append(namedTypes, o.Name()) | |||
| } | |||
| b := &bytes.Buffer{} | |||
| b.WriteString(packageHdr) | |||
| // Generate TypeToRR | |||
| fatalIfErr(TypeToRR.Execute(b, namedTypes)) | |||
| // Generate typeToString | |||
| fatalIfErr(typeToString.Execute(b, numberedTypes)) | |||
| // Generate headerFunc | |||
| fatalIfErr(headerFunc.Execute(b, namedTypes)) | |||
| // Generate len() | |||
| fmt.Fprint(b, "// len() functions\n") | |||
| for _, name := range namedTypes { | |||
| if _, ok := skipLen[name]; ok { | |||
| continue | |||
| } | |||
| o := scope.Lookup(name) | |||
| st, isEmbedded := getTypeStruct(o.Type(), scope) | |||
| if isEmbedded { | |||
| continue | |||
| } | |||
| fmt.Fprintf(b, "func (rr *%s) len() int {\n", name) | |||
| fmt.Fprintf(b, "l := rr.Hdr.len()\n") | |||
| for i := 1; i < st.NumFields(); i++ { | |||
| o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) } | |||
| if _, ok := st.Field(i).Type().(*types.Slice); ok { | |||
| switch st.Tag(i) { | |||
| case `dns:"-"`: | |||
| // ignored | |||
| case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`: | |||
| o("for _, x := range rr.%s { l += len(x) + 1 }\n") | |||
| default: | |||
| log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) | |||
| } | |||
| continue | |||
| } | |||
| switch st.Tag(i) { | |||
| case `dns:"-"`: | |||
| // ignored | |||
| case `dns:"cdomain-name"`, `dns:"domain-name"`: | |||
| o("l += len(rr.%s) + 1\n") | |||
| case `dns:"octet"`: | |||
| o("l += len(rr.%s)\n") | |||
| case `dns:"base64"`: | |||
| o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n") | |||
| case `dns:"size-hex"`, `dns:"hex"`: | |||
| o("l += len(rr.%s)/2 + 1\n") | |||
| case `dns:"a"`: | |||
| o("l += net.IPv4len // %s\n") | |||
| case `dns:"aaaa"`: | |||
| o("l += net.IPv6len // %s\n") | |||
| case `dns:"txt"`: | |||
| o("for _, t := range rr.%s { l += len(t) + 1 }\n") | |||
| case `dns:"uint48"`: | |||
| o("l += 6 // %s\n") | |||
| case "": | |||
| switch st.Field(i).Type().(*types.Basic).Kind() { | |||
| case types.Uint8: | |||
| o("l += 1 // %s\n") | |||
| case types.Uint16: | |||
| o("l += 2 // %s\n") | |||
| case types.Uint32: | |||
| o("l += 4 // %s\n") | |||
| case types.Uint64: | |||
| o("l += 8 // %s\n") | |||
| case types.String: | |||
| o("l += len(rr.%s) + 1\n") | |||
| default: | |||
| log.Fatalln(name, st.Field(i).Name()) | |||
| } | |||
| default: | |||
| log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) | |||
| } | |||
| } | |||
| fmt.Fprintf(b, "return l }\n") | |||
| } | |||
| // Generate copy() | |||
| fmt.Fprint(b, "// copy() functions\n") | |||
| for _, name := range namedTypes { | |||
| o := scope.Lookup(name) | |||
| st, isEmbedded := getTypeStruct(o.Type(), scope) | |||
| if isEmbedded { | |||
| continue | |||
| } | |||
| fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) | |||
| fields := []string{"*rr.Hdr.copyHeader()"} | |||
| for i := 1; i < st.NumFields(); i++ { | |||
| f := st.Field(i).Name() | |||
| 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.") | |||
| fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", | |||
| f, t, f, f, f) | |||
| fields = append(fields, f) | |||
| continue | |||
| } | |||
| if st.Field(i).Type().String() == "net.IP" { | |||
| fields = append(fields, "copyIP(rr."+f+")") | |||
| continue | |||
| } | |||
| fields = append(fields, "rr."+f) | |||
| } | |||
| fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ",")) | |||
| fmt.Fprintf(b, "}\n") | |||
| } | |||
| // gofmt | |||
| res, err := format.Source(b.Bytes()) | |||
| if err != nil { | |||
| b.WriteTo(os.Stderr) | |||
| log.Fatal(err) | |||
| } | |||
| // write result | |||
| f, err := os.Create("ztypes.go") | |||
| fatalIfErr(err) | |||
| defer f.Close() | |||
| f.Write(res) | |||
| } | |||
| func fatalIfErr(err error) { | |||
| if err != nil { | |||
| log.Fatal(err) | |||
| } | |||
| } | |||
| @ -0,0 +1,42 @@ | |||
| package dns | |||
| import ( | |||
| "testing" | |||
| ) | |||
| func TestCmToM(t *testing.T) { | |||
| s := cmToM(0, 0) | |||
| if s != "0.00" { | |||
| t.Error("0, 0") | |||
| } | |||
| s = cmToM(1, 0) | |||
| if s != "0.01" { | |||
| t.Error("1, 0") | |||
| } | |||
| s = cmToM(3, 1) | |||
| if s != "0.30" { | |||
| t.Error("3, 1") | |||
| } | |||
| s = cmToM(4, 2) | |||
| if s != "4" { | |||
| t.Error("4, 2") | |||
| } | |||
| s = cmToM(5, 3) | |||
| if s != "50" { | |||
| t.Error("5, 3") | |||
| } | |||
| s = cmToM(7, 5) | |||
| if s != "7000" { | |||
| t.Error("7, 5") | |||
| } | |||
| s = cmToM(9, 9) | |||
| if s != "90000000" { | |||
| t.Error("9, 9") | |||
| } | |||
| } | |||
| @ -0,0 +1,58 @@ | |||
| // +build !windows | |||
| package dns | |||
| import ( | |||
| "net" | |||
| "syscall" | |||
| ) | |||
| // 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 } | |||
| // setUDPSocketOptions sets the UDP socket options. | |||
| // This function is implemented on a per platform basis. See udp_*.go for more details | |||
| func setUDPSocketOptions(conn *net.UDPConn) error { | |||
| sa, err := getUDPSocketName(conn) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch sa.(type) { | |||
| case *syscall.SockaddrInet6: | |||
| v6only, err := getUDPSocketOptions6Only(conn) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| setUDPSocketOptions6(conn) | |||
| if !v6only { | |||
| setUDPSocketOptions4(conn) | |||
| } | |||
| case *syscall.SockaddrInet4: | |||
| setUDPSocketOptions4(conn) | |||
| } | |||
| return nil | |||
| } | |||
| // 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 | |||
| } | |||
| @ -0,0 +1,73 @@ | |||
| // +build linux | |||
| package dns | |||
| // See: | |||
| // * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and | |||
| // * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ | |||
| // | |||
| // Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing | |||
| // interface, this might not always be the correct one. This code will make sure the egress | |||
| // packet's interface matched the ingress' one. | |||
| import ( | |||
| "net" | |||
| "syscall" | |||
| ) | |||
| // setUDPSocketOptions4 prepares the v4 socket for sessions. | |||
| func setUDPSocketOptions4(conn *net.UDPConn) error { | |||
| file, err := conn.File() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { | |||
| return err | |||
| } | |||
| // Calling File() above results in the connection becoming blocking, we must fix that. | |||
| // See https://github.com/miekg/dns/issues/279 | |||
| err = syscall.SetNonblock(int(file.Fd()), true) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // setUDPSocketOptions6 prepares the v6 socket for sessions. | |||
| func setUDPSocketOptions6(conn *net.UDPConn) error { | |||
| file, err := conn.File() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { | |||
| return err | |||
| } | |||
| err = syscall.SetNonblock(int(file.Fd()), true) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined | |||
| // (dualstack). | |||
| func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { | |||
| file, err := conn.File() | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections | |||
| v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| return v6only == 1, nil | |||
| } | |||
| func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { | |||
| file, err := conn.File() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return syscall.Getsockname(int(file.Fd())) | |||
| } | |||
| @ -0,0 +1,17 @@ | |||
| // +build !linux | |||
| package dns | |||
| import ( | |||
| "net" | |||
| "syscall" | |||
| ) | |||
| // These do nothing. See udp_linux.go for an example of how to implement this. | |||
| // We tried to adhire to some kind of naming scheme. | |||
| func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } | |||
| func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } | |||
| func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } | |||
| func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil } | |||
| @ -0,0 +1,34 @@ | |||
| // +build windows | |||
| package dns | |||
| import "net" | |||
| type SessionUDP struct { | |||
| raddr *net.UDPAddr | |||
| } | |||
| // 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) { | |||
| n, raddr, err := conn.ReadFrom(b) | |||
| if err != nil { | |||
| return n, nil, err | |||
| } | |||
| session := &SessionUDP{raddr.(*net.UDPAddr)} | |||
| return n, session, 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.WriteTo(b, session.raddr) | |||
| return n, err | |||
| } | |||
| func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } | |||
| // setUDPSocketOptions sets the UDP socket options. | |||
| // This function is implemented on a per platform basis. See udp_*.go for more details | |||
| func setUDPSocketOptions(conn *net.UDPConn) error { | |||
| return nil | |||
| } | |||
| @ -0,0 +1,94 @@ | |||
| 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}} | |||
| } | |||
| } | |||
| // 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}} | |||
| } | |||
| } | |||
| // Used sets the RRs in the prereq section to | |||
| // "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. | |||
| 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 | |||
| } | |||
| } | |||
| // 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 | |||
| } | |||
| } | |||
| // 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 | |||
| } | |||
| } | |||
| // Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. | |||
| 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 | |||
| } | |||
| } | |||
| // 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}} | |||
| } | |||
| } | |||
| // 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}} | |||
| } | |||
| } | |||
| // Remove creates a dynamic update packet deletes RR from the 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 | |||
| } | |||
| } | |||
| @ -0,0 +1,85 @@ | |||
| package dns | |||
| import ( | |||
| "bytes" | |||
| "testing" | |||
| ) | |||
| func TestDynamicUpdateParsing(t *testing.T) { | |||
| prefix := "example.com. IN " | |||
| for _, typ := range TypeToString { | |||
| if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" || | |||
| typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" || | |||
| typ == "Reserved" || typ == "None" || typ == "NXT" || typ == "MAILB" || typ == "MAILA" { | |||
| continue | |||
| } | |||
| r, err := NewRR(prefix + typ) | |||
| if err != nil { | |||
| t.Errorf("failure to parse: %s %s: %v", prefix, typ, err) | |||
| } else { | |||
| t.Logf("parsed: %s", r.String()) | |||
| } | |||
| } | |||
| } | |||
| func TestDynamicUpdateUnpack(t *testing.T) { | |||
| // From https://github.com/miekg/dns/issues/150#issuecomment-62296803 | |||
| // It should be an update message for the zone "example.", | |||
| // deleting the A RRset "example." and then adding an A record at "example.". | |||
| // class ANY, TYPE A | |||
| buf := []byte{171, 68, 40, 0, 0, 1, 0, 0, 0, 2, 0, 0, 7, 101, 120, 97, 109, 112, 108, 101, 0, 0, 6, 0, 1, 192, 12, 0, 1, 0, 255, 0, 0, 0, 0, 0, 0, 192, 12, 0, 1, 0, 1, 0, 0, 0, 0, 0, 4, 127, 0, 0, 1} | |||
| msg := new(Msg) | |||
| err := msg.Unpack(buf) | |||
| if err != nil { | |||
| t.Errorf("failed to unpack: %v\n%s", err, msg.String()) | |||
| } | |||
| } | |||
| func TestDynamicUpdateZeroRdataUnpack(t *testing.T) { | |||
| m := new(Msg) | |||
| rr := &RR_Header{Name: ".", Rrtype: 0, Class: 1, Ttl: ^uint32(0), Rdlength: 0} | |||
| m.Answer = []RR{rr, rr, rr, rr, rr} | |||
| m.Ns = m.Answer | |||
| for n, s := range TypeToString { | |||
| rr.Rrtype = n | |||
| bytes, err := m.Pack() | |||
| if err != nil { | |||
| t.Errorf("failed to pack %s: %v", s, err) | |||
| continue | |||
| } | |||
| if err := new(Msg).Unpack(bytes); err != nil { | |||
| t.Errorf("failed to unpack %s: %v", s, err) | |||
| } | |||
| } | |||
| } | |||
| func TestRemoveRRset(t *testing.T) { | |||
| // Should add a zero data RR in Class ANY with a TTL of 0 | |||
| // for each set mentioned in the RRs provided to it. | |||
| rr, err := NewRR(". 100 IN A 127.0.0.1") | |||
| if err != nil { | |||
| t.Fatalf("error constructing RR: %v", err) | |||
| } | |||
| m := new(Msg) | |||
| m.Ns = []RR{&RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY, Ttl: 0, Rdlength: 0}} | |||
| expectstr := m.String() | |||
| expect, err := m.Pack() | |||
| if err != nil { | |||
| t.Fatalf("error packing expected msg: %v", err) | |||
| } | |||
| m.Ns = nil | |||
| m.RemoveRRset([]RR{rr}) | |||
| actual, err := m.Pack() | |||
| if err != nil { | |||
| t.Fatalf("error packing actual msg: %v", err) | |||
| } | |||
| if !bytes.Equal(actual, expect) { | |||
| tmp := new(Msg) | |||
| if err := tmp.Unpack(actual); err != nil { | |||
| t.Fatalf("error unpacking actual msg: %v", err) | |||
| } | |||
| t.Errorf("expected msg:\n%s", expectstr) | |||
| t.Errorf("actual msg:\n%v", tmp) | |||
| } | |||
| } | |||
| @ -0,0 +1,244 @@ | |||
| package dns | |||
| import ( | |||
| "time" | |||
| ) | |||
| // Envelope is used when doing a zone transfer with a remote server. | |||
| type Envelope struct { | |||
| RR []RR // The set of RRs in the answer section of the xfr reply message. | |||
| Error error // If something went wrong, this contains the error. | |||
| } | |||
| // A Transfer defines parameters that are used during a zone transfer. | |||
| type Transfer struct { | |||
| *Conn | |||
| 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 | |||
| TsigSecret map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified | |||
| tsigTimersOnly bool | |||
| } | |||
| // Think we need to away to stop the transfer | |||
| // In performs an incoming transfer with the server in a. | |||
| // If you would like to set the source IP, or some other attribute | |||
| // of a Dialer for a Transfer, you can do so by specifying the attributes | |||
| // in the Transfer.Conn: | |||
| // | |||
| // d := net.Dialer{LocalAddr: transfer_source} | |||
| // con, err := d.Dial("tcp", master) | |||
| // dnscon := &dns.Conn{Conn:con} | |||
| // transfer = &dns.Transfer{Conn: dnscon} | |||
| // channel, err := transfer.In(message, master) | |||
| // | |||
| func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { | |||
| timeout := dnsTimeout | |||
| if t.DialTimeout != 0 { | |||
| timeout = t.DialTimeout | |||
| } | |||
| if t.Conn == nil { | |||
| t.Conn, err = DialTimeout("tcp", a, timeout) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| if err := t.WriteMsg(q); err != nil { | |||
| return nil, err | |||
| } | |||
| env = make(chan *Envelope) | |||
| go func() { | |||
| if q.Question[0].Qtype == TypeAXFR { | |||
| go t.inAxfr(q.Id, env) | |||
| return | |||
| } | |||
| if q.Question[0].Qtype == TypeIXFR { | |||
| go t.inIxfr(q.Id, env) | |||
| return | |||
| } | |||
| }() | |||
| return env, nil | |||
| } | |||
| func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { | |||
| first := true | |||
| defer t.Close() | |||
| defer close(c) | |||
| timeout := dnsTimeout | |||
| if t.ReadTimeout != 0 { | |||
| timeout = t.ReadTimeout | |||
| } | |||
| for { | |||
| t.Conn.SetReadDeadline(time.Now().Add(timeout)) | |||
| in, err := t.ReadMsg() | |||
| if err != nil { | |||
| c <- &Envelope{nil, err} | |||
| return | |||
| } | |||
| if id != in.Id { | |||
| c <- &Envelope{in.Answer, ErrId} | |||
| return | |||
| } | |||
| if first { | |||
| if !isSOAFirst(in) { | |||
| c <- &Envelope{in.Answer, ErrSoa} | |||
| return | |||
| } | |||
| first = !first | |||
| // only one answer that is SOA, receive more | |||
| if len(in.Answer) == 1 { | |||
| t.tsigTimersOnly = true | |||
| c <- &Envelope{in.Answer, nil} | |||
| continue | |||
| } | |||
| } | |||
| if !first { | |||
| t.tsigTimersOnly = true // Subsequent envelopes use this. | |||
| if isSOALast(in) { | |||
| c <- &Envelope{in.Answer, nil} | |||
| return | |||
| } | |||
| c <- &Envelope{in.Answer, nil} | |||
| } | |||
| } | |||
| } | |||
| func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { | |||
| serial := uint32(0) // The first serial seen is the current server serial | |||
| first := true | |||
| defer t.Close() | |||
| defer close(c) | |||
| timeout := dnsTimeout | |||
| if t.ReadTimeout != 0 { | |||
| timeout = t.ReadTimeout | |||
| } | |||
| for { | |||
| t.SetReadDeadline(time.Now().Add(timeout)) | |||
| in, err := t.ReadMsg() | |||
| if err != nil { | |||
| c <- &Envelope{nil, err} | |||
| return | |||
| } | |||
| if id != in.Id { | |||
| c <- &Envelope{in.Answer, ErrId} | |||
| return | |||
| } | |||
| if first { | |||
| // A single SOA RR signals "no changes" | |||
| if len(in.Answer) == 1 && isSOAFirst(in) { | |||
| c <- &Envelope{in.Answer, nil} | |||
| return | |||
| } | |||
| // Check if the returned answer is ok | |||
| if !isSOAFirst(in) { | |||
| c <- &Envelope{in.Answer, ErrSoa} | |||
| return | |||
| } | |||
| // This serial is important | |||
| serial = in.Answer[0].(*SOA).Serial | |||
| first = !first | |||
| } | |||
| // Now we need to check each message for SOA records, to see what we need to do | |||
| if !first { | |||
| t.tsigTimersOnly = true | |||
| // If the last record in the IXFR contains the servers' SOA, we should quit | |||
| if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { | |||
| if v.Serial == serial { | |||
| c <- &Envelope{in.Answer, nil} | |||
| return | |||
| } | |||
| } | |||
| c <- &Envelope{in.Answer, nil} | |||
| } | |||
| } | |||
| } | |||
| // Out performs an outgoing transfer with the client connecting in w. | |||
| // Basic use pattern: | |||
| // | |||
| // 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}} | |||
| // close(ch) | |||
| // w.Hijack() | |||
| // // w.Close() // Client closes connection | |||
| // | |||
| // The server is responsible for sending the correct sequence of RRs through the | |||
| // channel ch. | |||
| func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { | |||
| for x := range ch { | |||
| r := new(Msg) | |||
| // Compress? | |||
| r.SetReply(q) | |||
| r.Authoritative = true | |||
| // assume it fits TODO(miek): fix | |||
| r.Answer = append(r.Answer, x.RR...) | |||
| if err := w.WriteMsg(r); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| w.TsigTimersOnly(true) | |||
| return nil | |||
| } | |||
| // ReadMsg reads a message from the transfer connection t. | |||
| func (t *Transfer) ReadMsg() (*Msg, error) { | |||
| m := new(Msg) | |||
| p := make([]byte, MaxMsgSize) | |||
| n, err := t.Read(p) | |||
| if err != nil && n == 0 { | |||
| return nil, err | |||
| } | |||
| p = p[:n] | |||
| if err := m.Unpack(p); err != nil { | |||
| return nil, err | |||
| } | |||
| if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { | |||
| if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { | |||
| return m, ErrSecret | |||
| } | |||
| // Need to work on the original message p, as that was used to calculate the tsig. | |||
| err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) | |||
| t.tsigRequestMAC = ts.MAC | |||
| } | |||
| return m, err | |||
| } | |||
| // WriteMsg writes a message through the transfer connection t. | |||
| func (t *Transfer) WriteMsg(m *Msg) (err error) { | |||
| var out []byte | |||
| if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { | |||
| if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { | |||
| return ErrSecret | |||
| } | |||
| out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) | |||
| } else { | |||
| out, err = m.Pack() | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if _, err = t.Write(out); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func isSOAFirst(in *Msg) bool { | |||
| if len(in.Answer) > 0 { | |||
| return in.Answer[0].Header().Rrtype == TypeSOA | |||
| } | |||
| return false | |||
| } | |||
| func isSOALast(in *Msg) bool { | |||
| if len(in.Answer) > 0 { | |||
| return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA | |||
| } | |||
| return false | |||
| } | |||
| @ -0,0 +1,161 @@ | |||
| // +build net | |||
| package dns | |||
| import ( | |||
| "net" | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func getIP(s string) string { | |||
| a, err := net.LookupAddr(s) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| return a[0] | |||
| } | |||
| // flaky, need to setup local server and test from | |||
| // that. | |||
| func TestAXFR_Miek(t *testing.T) { | |||
| // This test runs against a server maintained by Miek | |||
| if testing.Short() { | |||
| return | |||
| } | |||
| m := new(Msg) | |||
| m.SetAxfr("miek.nl.") | |||
| server := getIP("linode.atoom.net") | |||
| tr := new(Transfer) | |||
| if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { | |||
| t.Fatal("failed to setup axfr: ", err) | |||
| } else { | |||
| for ex := range a { | |||
| if ex.Error != nil { | |||
| t.Errorf("error %v", ex.Error) | |||
| break | |||
| } | |||
| for _, rr := range ex.RR { | |||
| t.Log(rr.String()) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // fails. | |||
| func TestAXFR_NLNL_MultipleEnvelopes(t *testing.T) { | |||
| // This test runs against a server maintained by NLnet Labs | |||
| if testing.Short() { | |||
| return | |||
| } | |||
| m := new(Msg) | |||
| m.SetAxfr("nlnetlabs.nl.") | |||
| server := getIP("open.nlnetlabs.nl.") | |||
| tr := new(Transfer) | |||
| if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { | |||
| t.Fatalf("failed to setup axfr %v for server: %v", err, server) | |||
| } else { | |||
| for ex := range a { | |||
| if ex.Error != nil { | |||
| t.Errorf("error %v", ex.Error) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func TestAXFR_Miek_Tsig(t *testing.T) { | |||
| // This test runs against a server maintained by Miek | |||
| if testing.Short() { | |||
| return | |||
| } | |||
| m := new(Msg) | |||
| m.SetAxfr("example.nl.") | |||
| m.SetTsig("axfr.", HmacMD5, 300, time.Now().Unix()) | |||
| tr := new(Transfer) | |||
| tr.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} | |||
| if a, err := tr.In(m, "176.58.119.54:53"); err != nil { | |||
| t.Fatal("failed to setup axfr: ", err) | |||
| } else { | |||
| for ex := range a { | |||
| if ex.Error != nil { | |||
| t.Errorf("error %v", ex.Error) | |||
| break | |||
| } | |||
| for _, rr := range ex.RR { | |||
| t.Log(rr.String()) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func TestAXFR_SIDN_NSD3_NONE(t *testing.T) { testAXFRSIDN(t, "nsd", "") } | |||
| func TestAXFR_SIDN_NSD3_MD5(t *testing.T) { testAXFRSIDN(t, "nsd", HmacMD5) } | |||
| func TestAXFR_SIDN_NSD3_SHA1(t *testing.T) { testAXFRSIDN(t, "nsd", HmacSHA1) } | |||
| func TestAXFR_SIDN_NSD3_SHA256(t *testing.T) { testAXFRSIDN(t, "nsd", HmacSHA256) } | |||
| func TestAXFR_SIDN_NSD4_NONE(t *testing.T) { testAXFRSIDN(t, "nsd4", "") } | |||
| func TestAXFR_SIDN_NSD4_MD5(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacMD5) } | |||
| func TestAXFR_SIDN_NSD4_SHA1(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacSHA1) } | |||
| func TestAXFR_SIDN_NSD4_SHA256(t *testing.T) { testAXFRSIDN(t, "nsd4", HmacSHA256) } | |||
| func TestAXFR_SIDN_BIND9_NONE(t *testing.T) { testAXFRSIDN(t, "bind9", "") } | |||
| func TestAXFR_SIDN_BIND9_MD5(t *testing.T) { testAXFRSIDN(t, "bind9", HmacMD5) } | |||
| func TestAXFR_SIDN_BIND9_SHA1(t *testing.T) { testAXFRSIDN(t, "bind9", HmacSHA1) } | |||
| func TestAXFR_SIDN_BIND9_SHA256(t *testing.T) { testAXFRSIDN(t, "bind9", HmacSHA256) } | |||
| func TestAXFR_SIDN_KNOT_NONE(t *testing.T) { testAXFRSIDN(t, "knot", "") } | |||
| func TestAXFR_SIDN_KNOT_MD5(t *testing.T) { testAXFRSIDN(t, "knot", HmacMD5) } | |||
| func TestAXFR_SIDN_KNOT_SHA1(t *testing.T) { testAXFRSIDN(t, "knot", HmacSHA1) } | |||
| func TestAXFR_SIDN_KNOT_SHA256(t *testing.T) { testAXFRSIDN(t, "knot", HmacSHA256) } | |||
| func TestAXFR_SIDN_POWERDNS_NONE(t *testing.T) { testAXFRSIDN(t, "powerdns", "") } | |||
| func TestAXFR_SIDN_POWERDNS_MD5(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacMD5) } | |||
| func TestAXFR_SIDN_POWERDNS_SHA1(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacSHA1) } | |||
| func TestAXFR_SIDN_POWERDNS_SHA256(t *testing.T) { testAXFRSIDN(t, "powerdns", HmacSHA256) } | |||
| func TestAXFR_SIDN_YADIFA_NONE(t *testing.T) { testAXFRSIDN(t, "yadifa", "") } | |||
| func TestAXFR_SIDN_YADIFA_MD5(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacMD5) } | |||
| func TestAXFR_SIDN_YADIFA_SHA1(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacSHA1) } | |||
| func TestAXFR_SIDN_YADIFA_SHA256(t *testing.T) { testAXFRSIDN(t, "yadifa", HmacSHA256) } | |||
| func testAXFRSIDN(t *testing.T, host, alg string) { | |||
| // This tests run against a server maintained by SIDN labs, see: | |||
| // https://workbench.sidnlabs.nl/ | |||
| if testing.Short() { | |||
| return | |||
| } | |||
| x := new(Transfer) | |||
| x.TsigSecret = map[string]string{ | |||
| "wb_md5.": "Wu/utSasZUkoeCNku152Zw==", | |||
| "wb_sha1_longkey.": "uhMpEhPq/RAD9Bt4mqhfmi+7ZdKmjLQb/lcrqYPXR4s/nnbsqw==", | |||
| "wb_sha256.": "npfrIJjt/MJOjGJoBNZtsjftKMhkSpIYMv2RzRZt1f8=", | |||
| } | |||
| keyname := map[string]string{ | |||
| HmacMD5: "wb_md5.", | |||
| HmacSHA1: "wb_sha1_longkey.", | |||
| HmacSHA256: "wb_sha256.", | |||
| }[alg] | |||
| m := new(Msg) | |||
| m.SetAxfr("types.wb.sidnlabs.nl.") | |||
| if keyname != "" { | |||
| m.SetTsig(keyname, alg, 300, time.Now().Unix()) | |||
| } | |||
| c, err := x.In(m, host+".sidnlabs.nl:53") | |||
| if err != nil { | |||
| t.Fatal(err) | |||
| } | |||
| for e := range c { | |||
| if e.Error != nil { | |||
| t.Fatal(e.Error) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,158 @@ | |||
| package dns | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| // Parse the $GENERATE statement as used in BIND9 zones. | |||
| // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. | |||
| // We are called after '$GENERATE '. After which we expect: | |||
| // * the range (12-24/2) | |||
| // * lhs (ownername) | |||
| // * [[ttl][class]] | |||
| // * type | |||
| // * rhs (rdata) | |||
| // But we are lazy here, only the range is parsed *all* occurences | |||
| // of $ after that are interpreted. | |||
| // Any error are returned as a string value, the empty string signals | |||
| // "no error". | |||
| func generate(l lex, c chan lex, t chan *Token, o string) string { | |||
| step := 1 | |||
| if i := strings.IndexAny(l.token, "/"); i != -1 { | |||
| if i+1 == len(l.token) { | |||
| return "bad step in $GENERATE range" | |||
| } | |||
| if s, e := strconv.Atoi(l.token[i+1:]); e == nil { | |||
| if s < 0 { | |||
| return "bad step in $GENERATE range" | |||
| } | |||
| step = s | |||
| } else { | |||
| return "bad step in $GENERATE range" | |||
| } | |||
| l.token = l.token[:i] | |||
| } | |||
| sx := strings.SplitN(l.token, "-", 2) | |||
| if len(sx) != 2 { | |||
| return "bad start-stop in $GENERATE range" | |||
| } | |||
| start, err := strconv.Atoi(sx[0]) | |||
| if err != nil { | |||
| return "bad start in $GENERATE range" | |||
| } | |||
| end, err := strconv.Atoi(sx[1]) | |||
| if err != nil { | |||
| return "bad stop in $GENERATE range" | |||
| } | |||
| if end < 0 || start < 0 || end < start { | |||
| return "bad range in $GENERATE range" | |||
| } | |||
| <-c // _BLANK | |||
| // Create a complete new string, which we then parse again. | |||
| s := "" | |||
| BuildRR: | |||
| l = <-c | |||
| if l.value != zNewline && l.value != zEOF { | |||
| s += l.token | |||
| goto BuildRR | |||
| } | |||
| for i := start; i <= end; i += step { | |||
| var ( | |||
| escape bool | |||
| dom bytes.Buffer | |||
| mod string | |||
| err string | |||
| offset int | |||
| ) | |||
| for j := 0; j < len(s); j++ { // No 'range' because we need to jump around | |||
| switch s[j] { | |||
| case '\\': | |||
| if escape { | |||
| dom.WriteByte('\\') | |||
| escape = false | |||
| continue | |||
| } | |||
| escape = true | |||
| case '$': | |||
| mod = "%d" | |||
| offset = 0 | |||
| if escape { | |||
| dom.WriteByte('$') | |||
| escape = false | |||
| continue | |||
| } | |||
| escape = false | |||
| if j+1 >= len(s) { // End of the string | |||
| dom.WriteString(fmt.Sprintf(mod, i+offset)) | |||
| continue | |||
| } else { | |||
| if s[j+1] == '$' { | |||
| dom.WriteByte('$') | |||
| j++ | |||
| continue | |||
| } | |||
| } | |||
| // Search for { and } | |||
| if s[j+1] == '{' { // Modifier block | |||
| sep := strings.Index(s[j+2:], "}") | |||
| if sep == -1 { | |||
| return "bad modifier in $GENERATE" | |||
| } | |||
| mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) | |||
| if err != "" { | |||
| return err | |||
| } | |||
| j += 2 + sep // Jump to it | |||
| } | |||
| dom.WriteString(fmt.Sprintf(mod, i+offset)) | |||
| default: | |||
| if escape { // Pretty useless here | |||
| escape = false | |||
| continue | |||
| } | |||
| dom.WriteByte(s[j]) | |||
| } | |||
| } | |||
| // Re-parse the RR and send it on the current channel t | |||
| rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String()) | |||
| if e != nil { | |||
| return e.(*ParseError).err | |||
| } | |||
| t <- &Token{RR: rx} | |||
| // Its more efficient to first built the rrlist and then parse it in | |||
| // one go! But is this a problem? | |||
| } | |||
| return "" | |||
| } | |||
| // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. | |||
| func modToPrintf(s string) (string, int, string) { | |||
| xs := strings.SplitN(s, ",", 3) | |||
| if len(xs) != 3 { | |||
| return "", 0, "bad modifier in $GENERATE" | |||
| } | |||
| // xs[0] is offset, xs[1] is width, xs[2] is base | |||
| if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { | |||
| return "", 0, "bad base in $GENERATE" | |||
| } | |||
| offset, err := strconv.Atoi(xs[0]) | |||
| if err != nil || offset > 255 { | |||
| return "", 0, "bad offset in $GENERATE" | |||
| } | |||
| width, err := strconv.Atoi(xs[1]) | |||
| if err != nil || width > 255 { | |||
| return "", offset, "bad width in $GENERATE" | |||
| } | |||
| switch { | |||
| case width < 0: | |||
| return "", offset, "bad width in $GENERATE" | |||
| case width == 0: | |||
| return "%" + xs[1] + xs[2], offset, "" | |||
| } | |||
| return "%0" + xs[1] + xs[2], offset, "" | |||
| } | |||
| @ -0,0 +1,974 @@ | |||
| package dns | |||
| import ( | |||
| "io" | |||
| "log" | |||
| "os" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| type debugging bool | |||
| const debug debugging = false | |||
| func (d debugging) Printf(format string, args ...interface{}) { | |||
| if d { | |||
| log.Printf(format, args...) | |||
| } | |||
| } | |||
| const maxTok = 2048 // Largest token we can return. | |||
| const maxUint16 = 1<<16 - 1 | |||
| // Tokinize a RFC 1035 zone file. The tokenizer will normalize it: | |||
| // * Add ownernames if they are left blank; | |||
| // * Suppress sequences of spaces; | |||
| // * Make each RR fit on one line (_NEWLINE is send as last) | |||
| // * Handle comments: ; | |||
| // * Handle braces - anywhere. | |||
| const ( | |||
| // Zonefile | |||
| zEOF = iota | |||
| zString | |||
| zBlank | |||
| zQuote | |||
| zNewline | |||
| zRrtpe | |||
| zOwner | |||
| zClass | |||
| zDirOrigin // $ORIGIN | |||
| zDirTtl // $TTL | |||
| zDirInclude // $INCLUDE | |||
| zDirGenerate // $GENERATE | |||
| // Privatekey file | |||
| zValue | |||
| zKey | |||
| zExpectOwnerDir // Ownername | |||
| zExpectOwnerBl // Whitespace after the ownername | |||
| zExpectAny // Expect rrtype, ttl or class | |||
| zExpectAnyNoClass // Expect rrtype or ttl | |||
| zExpectAnyNoClassBl // The whitespace after _EXPECT_ANY_NOCLASS | |||
| zExpectAnyNoTtl // Expect rrtype or class | |||
| zExpectAnyNoTtlBl // Whitespace after _EXPECT_ANY_NOTTL | |||
| zExpectRrtype // Expect rrtype | |||
| zExpectRrtypeBl // Whitespace BEFORE rrtype | |||
| zExpectRdata // The first element of the rdata | |||
| zExpectDirTtlBl // Space after directive $TTL | |||
| zExpectDirTtl // Directive $TTL | |||
| zExpectDirOriginBl // Space after directive $ORIGIN | |||
| zExpectDirOrigin // Directive $ORIGIN | |||
| zExpectDirIncludeBl // Space after directive $INCLUDE | |||
| zExpectDirInclude // Directive $INCLUDE | |||
| zExpectDirGenerate // Directive $GENERATE | |||
| zExpectDirGenerateBl // Space after directive $GENERATE | |||
| ) | |||
| // ParseError is a parsing error. It contains the parse error and the location in the io.Reader | |||
| // where the error occurred. | |||
| type ParseError struct { | |||
| file string | |||
| err string | |||
| lex lex | |||
| } | |||
| func (e *ParseError) Error() (s string) { | |||
| if e.file != "" { | |||
| s = e.file + ": " | |||
| } | |||
| s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " + | |||
| strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column) | |||
| return | |||
| } | |||
| type lex struct { | |||
| token string // text of the token | |||
| tokenUpper string // uppercase text of the token | |||
| length int // length of the token | |||
| err bool // when true, token text has lexer error | |||
| value uint8 // value: zString, _BLANK, etc. | |||
| line int // line in the file | |||
| column int // column in the file | |||
| torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar | |||
| comment string // any comment text seen | |||
| } | |||
| // Token holds the token that are returned when a zone file is parsed. | |||
| type Token struct { | |||
| // The scanned resource record when error is not nil. | |||
| RR | |||
| // When an error occurred, this has the error specifics. | |||
| Error *ParseError | |||
| // A potential comment positioned after the RR and on the same line. | |||
| Comment string | |||
| } | |||
| // NewRR reads the RR contained in the string s. Only the first RR is | |||
| // returned. If s contains no RR, return nil with no error. The class | |||
| // defaults to IN and TTL defaults to 3600. The full zone file syntax | |||
| // like $TTL, $ORIGIN, etc. is supported. All fields of the returned | |||
| // RR are set, except RR.Header().Rdlength which is set to 0. | |||
| func NewRR(s string) (RR, error) { | |||
| if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline | |||
| return ReadRR(strings.NewReader(s+"\n"), "") | |||
| } | |||
| return ReadRR(strings.NewReader(s), "") | |||
| } | |||
| // ReadRR reads the RR contained in q. | |||
| // See NewRR for more documentation. | |||
| func ReadRR(q io.Reader, filename string) (RR, error) { | |||
| r := <-parseZoneHelper(q, ".", filename, 1) | |||
| if r == nil { | |||
| return nil, nil | |||
| } | |||
| if r.Error != nil { | |||
| return nil, r.Error | |||
| } | |||
| return r.RR, nil | |||
| } | |||
| // ParseZone reads a RFC 1035 style zonefile from r. It returns *Tokens on the | |||
| // returned channel, which consist out the parsed RR, a potential comment or an error. | |||
| // If there is an error the RR is nil. The string file is only used | |||
| // in error reporting. The string origin is used as the initial origin, as | |||
| // if the file would start with: $ORIGIN origin . | |||
| // The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are supported. | |||
| // The channel t is closed by ParseZone when the end of r is reached. | |||
| // | |||
| // Basic usage pattern when reading from a string (z) containing the | |||
| // zone data: | |||
| // | |||
| // for x := range dns.ParseZone(strings.NewReader(z), "", "") { | |||
| // if x.Error != nil { | |||
| // // log.Println(x.Error) | |||
| // } else { | |||
| // // Do something with x.RR | |||
| // } | |||
| // } | |||
| // | |||
| // Comments specified after an RR (and on the same line!) are returned too: | |||
| // | |||
| // foo. IN A 10.0.0.1 ; this is a comment | |||
| // | |||
| // The text "; this is comment" is returned in Token.Comment. Comments inside the | |||
| // RR are discarded. Comments on a line by themselves are discarded too. | |||
| func ParseZone(r io.Reader, origin, file string) chan *Token { | |||
| return parseZoneHelper(r, origin, file, 10000) | |||
| } | |||
| func parseZoneHelper(r io.Reader, origin, file string, chansize int) chan *Token { | |||
| t := make(chan *Token, chansize) | |||
| go parseZone(r, origin, file, t, 0) | |||
| return t | |||
| } | |||
| func parseZone(r io.Reader, origin, f string, t chan *Token, include int) { | |||
| defer func() { | |||
| if include == 0 { | |||
| close(t) | |||
| } | |||
| }() | |||
| s := scanInit(r) | |||
| c := make(chan lex) | |||
| // Start the lexer | |||
| go zlexer(s, c) | |||
| // 6 possible beginnings of a line, _ is a space | |||
| // 0. zRRTYPE -> all omitted until the rrtype | |||
| // 1. zOwner _ zRrtype -> class/ttl omitted | |||
| // 2. zOwner _ zString _ zRrtype -> class omitted | |||
| // 3. zOwner _ zString _ zClass _ zRrtype -> ttl/class | |||
| // 4. zOwner _ zClass _ zRrtype -> ttl omitted | |||
| // 5. zOwner _ zClass _ zString _ zRrtype -> class/ttl (reversed) | |||
| // After detecting these, we know the zRrtype so we can jump to functions | |||
| // handling the rdata for each of these types. | |||
| if origin == "" { | |||
| origin = "." | |||
| } | |||
| origin = Fqdn(origin) | |||
| if _, ok := IsDomainName(origin); !ok { | |||
| t <- &Token{Error: &ParseError{f, "bad initial origin name", lex{}}} | |||
| return | |||
| } | |||
| st := zExpectOwnerDir // initial state | |||
| var h RR_Header | |||
| var defttl uint32 = defaultTtl | |||
| var prevName string | |||
| for l := range c { | |||
| // Lexer spotted an error already | |||
| if l.err == true { | |||
| t <- &Token{Error: &ParseError{f, l.token, l}} | |||
| return | |||
| } | |||
| switch st { | |||
| case zExpectOwnerDir: | |||
| // We can also expect a directive, like $TTL or $ORIGIN | |||
| h.Ttl = defttl | |||
| h.Class = ClassINET | |||
| switch l.value { | |||
| case zNewline: | |||
| st = zExpectOwnerDir | |||
| case zOwner: | |||
| h.Name = l.token | |||
| if l.token[0] == '@' { | |||
| h.Name = origin | |||
| prevName = h.Name | |||
| st = zExpectOwnerBl | |||
| break | |||
| } | |||
| if h.Name[l.length-1] != '.' { | |||
| h.Name = appendOrigin(h.Name, origin) | |||
| } | |||
| _, ok := IsDomainName(l.token) | |||
| if !ok { | |||
| t <- &Token{Error: &ParseError{f, "bad owner name", l}} | |||
| return | |||
| } | |||
| prevName = h.Name | |||
| st = zExpectOwnerBl | |||
| case zDirTtl: | |||
| st = zExpectDirTtlBl | |||
| case zDirOrigin: | |||
| st = zExpectDirOriginBl | |||
| case zDirInclude: | |||
| st = zExpectDirIncludeBl | |||
| case zDirGenerate: | |||
| st = zExpectDirGenerateBl | |||
| case zRrtpe: | |||
| h.Name = prevName | |||
| h.Rrtype = l.torc | |||
| st = zExpectRdata | |||
| case zClass: | |||
| h.Name = prevName | |||
| h.Class = l.torc | |||
| st = zExpectAnyNoClassBl | |||
| case zBlank: | |||
| // Discard, can happen when there is nothing on the | |||
| // line except the RR type | |||
| case zString: | |||
| ttl, ok := stringToTtl(l.token) | |||
| if !ok { | |||
| t <- &Token{Error: &ParseError{f, "not a TTL", l}} | |||
| return | |||
| } | |||
| h.Ttl = ttl | |||
| // Don't about the defttl, we should take the $TTL value | |||
| // defttl = ttl | |||
| st = zExpectAnyNoTtlBl | |||
| default: | |||
| t <- &Token{Error: &ParseError{f, "syntax error at beginning", l}} | |||
| return | |||
| } | |||
| case zExpectDirIncludeBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank after $INCLUDE-directive", l}} | |||
| return | |||
| } | |||
| st = zExpectDirInclude | |||
| case zExpectDirInclude: | |||
| if l.value != zString { | |||
| t <- &Token{Error: &ParseError{f, "expecting $INCLUDE value, not this...", l}} | |||
| return | |||
| } | |||
| neworigin := origin // There may be optionally a new origin set after the filename, if not use current one | |||
| l := <-c | |||
| switch l.value { | |||
| case zBlank: | |||
| l := <-c | |||
| if l.value == zString { | |||
| if _, ok := IsDomainName(l.token); !ok || l.length == 0 || l.err { | |||
| t <- &Token{Error: &ParseError{f, "bad origin name", l}} | |||
| return | |||
| } | |||
| // a new origin is specified. | |||
| if l.token[l.length-1] != '.' { | |||
| if origin != "." { // Prevent .. endings | |||
| neworigin = l.token + "." + origin | |||
| } else { | |||
| neworigin = l.token + origin | |||
| } | |||
| } else { | |||
| neworigin = l.token | |||
| } | |||
| } | |||
| case zNewline, zEOF: | |||
| // Ok | |||
| default: | |||
| t <- &Token{Error: &ParseError{f, "garbage after $INCLUDE", l}} | |||
| return | |||
| } | |||
| // Start with the new file | |||
| r1, e1 := os.Open(l.token) | |||
| if e1 != nil { | |||
| t <- &Token{Error: &ParseError{f, "failed to open `" + l.token + "'", l}} | |||
| return | |||
| } | |||
| if include+1 > 7 { | |||
| t <- &Token{Error: &ParseError{f, "too deeply nested $INCLUDE", l}} | |||
| return | |||
| } | |||
| parseZone(r1, l.token, neworigin, t, include+1) | |||
| st = zExpectOwnerDir | |||
| case zExpectDirTtlBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank after $TTL-directive", l}} | |||
| return | |||
| } | |||
| st = zExpectDirTtl | |||
| case zExpectDirTtl: | |||
| if l.value != zString { | |||
| t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} | |||
| return | |||
| } | |||
| if e, _ := slurpRemainder(c, f); e != nil { | |||
| t <- &Token{Error: e} | |||
| return | |||
| } | |||
| ttl, ok := stringToTtl(l.token) | |||
| if !ok { | |||
| t <- &Token{Error: &ParseError{f, "expecting $TTL value, not this...", l}} | |||
| return | |||
| } | |||
| defttl = ttl | |||
| st = zExpectOwnerDir | |||
| case zExpectDirOriginBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank after $ORIGIN-directive", l}} | |||
| return | |||
| } | |||
| st = zExpectDirOrigin | |||
| case zExpectDirOrigin: | |||
| if l.value != zString { | |||
| t <- &Token{Error: &ParseError{f, "expecting $ORIGIN value, not this...", l}} | |||
| return | |||
| } | |||
| if e, _ := slurpRemainder(c, f); e != nil { | |||
| t <- &Token{Error: e} | |||
| } | |||
| if _, ok := IsDomainName(l.token); !ok { | |||
| t <- &Token{Error: &ParseError{f, "bad origin name", l}} | |||
| return | |||
| } | |||
| if l.token[l.length-1] != '.' { | |||
| if origin != "." { // Prevent .. endings | |||
| origin = l.token + "." + origin | |||
| } else { | |||
| origin = l.token + origin | |||
| } | |||
| } else { | |||
| origin = l.token | |||
| } | |||
| st = zExpectOwnerDir | |||
| case zExpectDirGenerateBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank after $GENERATE-directive", l}} | |||
| return | |||
| } | |||
| st = zExpectDirGenerate | |||
| case zExpectDirGenerate: | |||
| if l.value != zString { | |||
| t <- &Token{Error: &ParseError{f, "expecting $GENERATE value, not this...", l}} | |||
| return | |||
| } | |||
| if e := generate(l, c, t, origin); e != "" { | |||
| t <- &Token{Error: &ParseError{f, e, l}} | |||
| return | |||
| } | |||
| st = zExpectOwnerDir | |||
| case zExpectOwnerBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank after owner", l}} | |||
| return | |||
| } | |||
| st = zExpectAny | |||
| case zExpectAny: | |||
| switch l.value { | |||
| case zRrtpe: | |||
| h.Rrtype = l.torc | |||
| st = zExpectRdata | |||
| case zClass: | |||
| h.Class = l.torc | |||
| st = zExpectAnyNoClassBl | |||
| case zString: | |||
| ttl, ok := stringToTtl(l.token) | |||
| if !ok { | |||
| t <- &Token{Error: &ParseError{f, "not a TTL", l}} | |||
| return | |||
| } | |||
| h.Ttl = ttl | |||
| // defttl = ttl // don't set the defttl here | |||
| st = zExpectAnyNoTtlBl | |||
| default: | |||
| t <- &Token{Error: &ParseError{f, "expecting RR type, TTL or class, not this...", l}} | |||
| return | |||
| } | |||
| case zExpectAnyNoClassBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank before class", l}} | |||
| return | |||
| } | |||
| st = zExpectAnyNoClass | |||
| case zExpectAnyNoTtlBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank before TTL", l}} | |||
| return | |||
| } | |||
| st = zExpectAnyNoTtl | |||
| case zExpectAnyNoTtl: | |||
| switch l.value { | |||
| case zClass: | |||
| h.Class = l.torc | |||
| st = zExpectRrtypeBl | |||
| case zRrtpe: | |||
| h.Rrtype = l.torc | |||
| st = zExpectRdata | |||
| default: | |||
| t <- &Token{Error: &ParseError{f, "expecting RR type or class, not this...", l}} | |||
| return | |||
| } | |||
| case zExpectAnyNoClass: | |||
| switch l.value { | |||
| case zString: | |||
| ttl, ok := stringToTtl(l.token) | |||
| if !ok { | |||
| t <- &Token{Error: &ParseError{f, "not a TTL", l}} | |||
| return | |||
| } | |||
| h.Ttl = ttl | |||
| // defttl = ttl // don't set the def ttl anymore | |||
| st = zExpectRrtypeBl | |||
| case zRrtpe: | |||
| h.Rrtype = l.torc | |||
| st = zExpectRdata | |||
| default: | |||
| t <- &Token{Error: &ParseError{f, "expecting RR type or TTL, not this...", l}} | |||
| return | |||
| } | |||
| case zExpectRrtypeBl: | |||
| if l.value != zBlank { | |||
| t <- &Token{Error: &ParseError{f, "no blank before RR type", l}} | |||
| return | |||
| } | |||
| st = zExpectRrtype | |||
| case zExpectRrtype: | |||
| if l.value != zRrtpe { | |||
| t <- &Token{Error: &ParseError{f, "unknown RR type", l}} | |||
| return | |||
| } | |||
| h.Rrtype = l.torc | |||
| st = zExpectRdata | |||
| case zExpectRdata: | |||
| r, e, c1 := setRR(h, c, origin, f) | |||
| if e != nil { | |||
| // If e.lex is nil than we have encounter a unknown RR type | |||
| // in that case we substitute our current lex token | |||
| if e.lex.token == "" && e.lex.value == 0 { | |||
| e.lex = l // Uh, dirty | |||
| } | |||
| t <- &Token{Error: e} | |||
| return | |||
| } | |||
| t <- &Token{RR: r, Comment: c1} | |||
| st = zExpectOwnerDir | |||
| } | |||
| } | |||
| // If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this | |||
| // is not an error, because an empty zone file is still a zone file. | |||
| } | |||
| // zlexer scans the sourcefile and returns tokens on the channel c. | |||
| func zlexer(s *scan, c chan lex) { | |||
| var l lex | |||
| str := make([]byte, maxTok) // Should be enough for any token | |||
| stri := 0 // Offset in str (0 means empty) | |||
| com := make([]byte, maxTok) // Hold comment text | |||
| comi := 0 | |||
| quote := false | |||
| escape := false | |||
| space := false | |||
| commt := false | |||
| rrtype := false | |||
| owner := true | |||
| brace := 0 | |||
| x, err := s.tokenText() | |||
| defer close(c) | |||
| for err == nil { | |||
| l.column = s.position.Column | |||
| l.line = s.position.Line | |||
| if stri >= maxTok { | |||
| l.token = "token length insufficient for parsing" | |||
| l.err = true | |||
| debug.Printf("[%+v]", l.token) | |||
| c <- l | |||
| return | |||
| } | |||
| if comi >= maxTok { | |||
| l.token = "comment length insufficient for parsing" | |||
| l.err = true | |||
| debug.Printf("[%+v]", l.token) | |||
| c <- l | |||
| return | |||
| } | |||
| switch x { | |||
| case ' ', '\t': | |||
| if escape { | |||
| escape = false | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| if quote { | |||
| // Inside quotes this is legal | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| if commt { | |||
| com[comi] = x | |||
| comi++ | |||
| break | |||
| } | |||
| if stri == 0 { | |||
| // Space directly in the beginning, handled in the grammar | |||
| } else if owner { | |||
| // If we have a string and its the first, make it an owner | |||
| l.value = zOwner | |||
| l.token = string(str[:stri]) | |||
| l.tokenUpper = strings.ToUpper(l.token) | |||
| l.length = stri | |||
| // escape $... start with a \ not a $, so this will work | |||
| switch l.tokenUpper { | |||
| case "$TTL": | |||
| l.value = zDirTtl | |||
| case "$ORIGIN": | |||
| l.value = zDirOrigin | |||
| case "$INCLUDE": | |||
| l.value = zDirInclude | |||
| case "$GENERATE": | |||
| l.value = zDirGenerate | |||
| } | |||
| debug.Printf("[7 %+v]", l.token) | |||
| c <- l | |||
| } else { | |||
| l.value = zString | |||
| l.token = string(str[:stri]) | |||
| l.tokenUpper = strings.ToUpper(l.token) | |||
| l.length = stri | |||
| if !rrtype { | |||
| if t, ok := StringToType[l.tokenUpper]; ok { | |||
| l.value = zRrtpe | |||
| l.torc = t | |||
| rrtype = true | |||
| } else { | |||
| if strings.HasPrefix(l.tokenUpper, "TYPE") { | |||
| t, ok := typeToInt(l.token) | |||
| if !ok { | |||
| l.token = "unknown RR type" | |||
| l.err = true | |||
| c <- l | |||
| return | |||
| } | |||
| l.value = zRrtpe | |||
| l.torc = t | |||
| } | |||
| } | |||
| if t, ok := StringToClass[l.tokenUpper]; ok { | |||
| l.value = zClass | |||
| l.torc = t | |||
| } else { | |||
| if strings.HasPrefix(l.tokenUpper, "CLASS") { | |||
| t, ok := classToInt(l.token) | |||
| if !ok { | |||
| l.token = "unknown class" | |||
| l.err = true | |||
| c <- l | |||
| return | |||
| } | |||
| l.value = zClass | |||
| l.torc = t | |||
| } | |||
| } | |||
| } | |||
| debug.Printf("[6 %+v]", l.token) | |||
| c <- l | |||
| } | |||
| stri = 0 | |||
| // I reverse space stuff here | |||
| if !space && !commt { | |||
| l.value = zBlank | |||
| l.token = " " | |||
| l.length = 1 | |||
| debug.Printf("[5 %+v]", l.token) | |||
| c <- l | |||
| } | |||
| owner = false | |||
| space = true | |||
| case ';': | |||
| if escape { | |||
| escape = false | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| if quote { | |||
| // Inside quotes this is legal | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| if stri > 0 { | |||
| l.value = zString | |||
| l.token = string(str[:stri]) | |||
| l.length = stri | |||
| debug.Printf("[4 %+v]", l.token) | |||
| c <- l | |||
| stri = 0 | |||
| } | |||
| commt = true | |||
| com[comi] = ';' | |||
| comi++ | |||
| case '\r': | |||
| escape = false | |||
| if quote { | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| // discard if outside of quotes | |||
| case '\n': | |||
| escape = false | |||
| // Escaped newline | |||
| if quote { | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| // inside quotes this is legal | |||
| if commt { | |||
| // Reset a comment | |||
| commt = false | |||
| rrtype = false | |||
| stri = 0 | |||
| // If not in a brace this ends the comment AND the RR | |||
| if brace == 0 { | |||
| owner = true | |||
| owner = true | |||
| l.value = zNewline | |||
| l.token = "\n" | |||
| l.length = 1 | |||
| l.comment = string(com[:comi]) | |||
| debug.Printf("[3 %+v %+v]", l.token, l.comment) | |||
| c <- l | |||
| l.comment = "" | |||
| comi = 0 | |||
| break | |||
| } | |||
| com[comi] = ' ' // convert newline to space | |||
| comi++ | |||
| break | |||
| } | |||
| if brace == 0 { | |||
| // If there is previous text, we should output it here | |||
| if stri != 0 { | |||
| l.value = zString | |||
| l.token = string(str[:stri]) | |||
| l.tokenUpper = strings.ToUpper(l.token) | |||
| l.length = stri | |||
| if !rrtype { | |||
| if t, ok := StringToType[l.tokenUpper]; ok { | |||
| l.value = zRrtpe | |||
| l.torc = t | |||
| rrtype = true | |||
| } | |||
| } | |||
| debug.Printf("[2 %+v]", l.token) | |||
| c <- l | |||
| } | |||
| l.value = zNewline | |||
| l.token = "\n" | |||
| l.length = 1 | |||
| debug.Printf("[1 %+v]", l.token) | |||
| c <- l | |||
| stri = 0 | |||
| commt = false | |||
| rrtype = false | |||
| owner = true | |||
| comi = 0 | |||
| } | |||
| case '\\': | |||
| // comments do not get escaped chars, everything is copied | |||
| if commt { | |||
| com[comi] = x | |||
| comi++ | |||
| break | |||
| } | |||
| // something already escaped must be in string | |||
| if escape { | |||
| str[stri] = x | |||
| stri++ | |||
| escape = false | |||
| break | |||
| } | |||
| // something escaped outside of string gets added to string | |||
| str[stri] = x | |||
| stri++ | |||
| escape = true | |||
| case '"': | |||
| if commt { | |||
| com[comi] = x | |||
| comi++ | |||
| break | |||
| } | |||
| if escape { | |||
| str[stri] = x | |||
| stri++ | |||
| escape = false | |||
| break | |||
| } | |||
| space = false | |||
| // send previous gathered text and the quote | |||
| if stri != 0 { | |||
| l.value = zString | |||
| l.token = string(str[:stri]) | |||
| l.length = stri | |||
| debug.Printf("[%+v]", l.token) | |||
| c <- l | |||
| stri = 0 | |||
| } | |||
| // send quote itself as separate token | |||
| l.value = zQuote | |||
| l.token = "\"" | |||
| l.length = 1 | |||
| c <- l | |||
| quote = !quote | |||
| case '(', ')': | |||
| if commt { | |||
| com[comi] = x | |||
| comi++ | |||
| break | |||
| } | |||
| if escape { | |||
| str[stri] = x | |||
| stri++ | |||
| escape = false | |||
| break | |||
| } | |||
| if quote { | |||
| str[stri] = x | |||
| stri++ | |||
| break | |||
| } | |||
| switch x { | |||
| case ')': | |||
| brace-- | |||
| if brace < 0 { | |||
| l.token = "extra closing brace" | |||
| l.err = true | |||
| debug.Printf("[%+v]", l.token) | |||
| c <- l | |||
| return | |||
| } | |||
| case '(': | |||
| brace++ | |||
| } | |||
| default: | |||
| escape = false | |||
| if commt { | |||
| com[comi] = x | |||
| comi++ | |||
| break | |||
| } | |||
| str[stri] = x | |||
| stri++ | |||
| space = false | |||
| } | |||
| x, err = s.tokenText() | |||
| } | |||
| if stri > 0 { | |||
| // Send remainder | |||
| l.token = string(str[:stri]) | |||
| l.length = stri | |||
| l.value = zString | |||
| debug.Printf("[%+v]", l.token) | |||
| c <- l | |||
| } | |||
| } | |||
| // Extract the class number from CLASSxx | |||
| func classToInt(token string) (uint16, bool) { | |||
| offset := 5 | |||
| if len(token) < offset+1 { | |||
| return 0, false | |||
| } | |||
| class, ok := strconv.Atoi(token[offset:]) | |||
| if ok != nil || class > maxUint16 { | |||
| return 0, false | |||
| } | |||
| return uint16(class), true | |||
| } | |||
| // Extract the rr number from TYPExxx | |||
| func typeToInt(token string) (uint16, bool) { | |||
| offset := 4 | |||
| if len(token) < offset+1 { | |||
| return 0, false | |||
| } | |||
| typ, ok := strconv.Atoi(token[offset:]) | |||
| if ok != nil || typ > maxUint16 { | |||
| return 0, false | |||
| } | |||
| return uint16(typ), true | |||
| } | |||
| // Parse things like 2w, 2m, etc, Return the time in seconds. | |||
| func stringToTtl(token string) (uint32, bool) { | |||
| s := uint32(0) | |||
| i := uint32(0) | |||
| for _, c := range token { | |||
| switch c { | |||
| case 's', 'S': | |||
| s += i | |||
| i = 0 | |||
| case 'm', 'M': | |||
| s += i * 60 | |||
| i = 0 | |||
| case 'h', 'H': | |||
| s += i * 60 * 60 | |||
| i = 0 | |||
| case 'd', 'D': | |||
| s += i * 60 * 60 * 24 | |||
| i = 0 | |||
| case 'w', 'W': | |||
| s += i * 60 * 60 * 24 * 7 | |||
| i = 0 | |||
| case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | |||
| i *= 10 | |||
| i += uint32(c) - '0' | |||
| default: | |||
| return 0, false | |||
| } | |||
| } | |||
| return s + i, true | |||
| } | |||
| // Parse LOC records' <digits>[.<digits>][mM] into a | |||
| // mantissa exponent format. Token should contain the entire | |||
| // string (i.e. no spaces allowed) | |||
| func stringToCm(token string) (e, m uint8, ok bool) { | |||
| if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' { | |||
| token = token[0 : len(token)-1] | |||
| } | |||
| s := strings.SplitN(token, ".", 2) | |||
| var meters, cmeters, val int | |||
| var err error | |||
| switch len(s) { | |||
| case 2: | |||
| if cmeters, err = strconv.Atoi(s[1]); err != nil { | |||
| return | |||
| } | |||
| fallthrough | |||
| case 1: | |||
| if meters, err = strconv.Atoi(s[0]); err != nil { | |||
| return | |||
| } | |||
| case 0: | |||
| // huh? | |||
| return 0, 0, false | |||
| } | |||
| ok = true | |||
| if meters > 0 { | |||
| e = 2 | |||
| val = meters | |||
| } else { | |||
| e = 0 | |||
| val = cmeters | |||
| } | |||
| for val > 10 { | |||
| e++ | |||
| val /= 10 | |||
| } | |||
| if e > 9 { | |||
| ok = false | |||
| } | |||
| m = uint8(val) | |||
| return | |||
| } | |||
| func appendOrigin(name, origin string) string { | |||
| if origin == "." { | |||
| return name + origin | |||
| } | |||
| return name + "." + origin | |||
| } | |||
| // LOC record helper function | |||
| func locCheckNorth(token string, latitude uint32) (uint32, bool) { | |||
| switch token { | |||
| case "n", "N": | |||
| return LOC_EQUATOR + latitude, true | |||
| case "s", "S": | |||
| return LOC_EQUATOR - latitude, true | |||
| } | |||
| return latitude, false | |||
| } | |||
| // LOC record helper function | |||
| func locCheckEast(token string, longitude uint32) (uint32, bool) { | |||
| switch token { | |||
| case "e", "E": | |||
| return LOC_EQUATOR + longitude, true | |||
| case "w", "W": | |||
| return LOC_EQUATOR - longitude, true | |||
| } | |||
| return longitude, false | |||
| } | |||
| // "Eat" the rest of the "line". Return potential comments | |||
| func slurpRemainder(c chan lex, f string) (*ParseError, string) { | |||
| l := <-c | |||
| com := "" | |||
| switch l.value { | |||
| case zBlank: | |||
| l = <-c | |||
| com = l.comment | |||
| if l.value != zNewline && l.value != zEOF { | |||
| return &ParseError{f, "garbage after rdata", l}, "" | |||
| } | |||
| case zNewline: | |||
| com = l.comment | |||
| case zEOF: | |||
| default: | |||
| return &ParseError{f, "garbage after rdata", l}, "" | |||
| } | |||
| return nil, com | |||
| } | |||
| // Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64" | |||
| // Used for NID and L64 record. | |||
| func stringToNodeID(l lex) (uint64, *ParseError) { | |||
| if len(l.token) < 19 { | |||
| return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} | |||
| } | |||
| // There must be three colons at fixes postitions, if not its a parse error | |||
| if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' { | |||
| return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} | |||
| } | |||
| s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19] | |||
| u, e := strconv.ParseUint(s, 16, 64) | |||
| if e != nil { | |||
| return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} | |||
| } | |||
| return u, nil | |||
| } | |||
| @ -0,0 +1,842 @@ | |||
| // *** DO NOT MODIFY *** | |||
| // AUTOGENERATED BY go generate | |||
| package dns | |||
| import ( | |||
| "encoding/base64" | |||
| "net" | |||
| ) | |||
| // TypeToRR is a map of constructors for each RR type. | |||
| var TypeToRR = map[uint16]func() RR{ | |||
| TypeA: func() RR { return new(A) }, | |||
| TypeAAAA: func() RR { return new(AAAA) }, | |||
| TypeAFSDB: func() RR { return new(AFSDB) }, | |||
| TypeANY: func() RR { return new(ANY) }, | |||
| TypeCAA: func() RR { return new(CAA) }, | |||
| TypeCDNSKEY: func() RR { return new(CDNSKEY) }, | |||
| TypeCDS: func() RR { return new(CDS) }, | |||
| TypeCERT: func() RR { return new(CERT) }, | |||
| TypeCNAME: func() RR { return new(CNAME) }, | |||
| TypeDHCID: func() RR { return new(DHCID) }, | |||
| TypeDLV: func() RR { return new(DLV) }, | |||
| TypeDNAME: func() RR { return new(DNAME) }, | |||
| TypeDNSKEY: func() RR { return new(DNSKEY) }, | |||
| TypeDS: func() RR { return new(DS) }, | |||
| TypeEID: func() RR { return new(EID) }, | |||
| TypeEUI48: func() RR { return new(EUI48) }, | |||
| TypeEUI64: func() RR { return new(EUI64) }, | |||
| TypeGID: func() RR { return new(GID) }, | |||
| TypeGPOS: func() RR { return new(GPOS) }, | |||
| TypeHINFO: func() RR { return new(HINFO) }, | |||
| TypeHIP: func() RR { return new(HIP) }, | |||
| TypeIPSECKEY: func() RR { return new(IPSECKEY) }, | |||
| TypeKEY: func() RR { return new(KEY) }, | |||
| TypeKX: func() RR { return new(KX) }, | |||
| TypeL32: func() RR { return new(L32) }, | |||
| TypeL64: func() RR { return new(L64) }, | |||
| TypeLOC: func() RR { return new(LOC) }, | |||
| TypeLP: func() RR { return new(LP) }, | |||
| TypeMB: func() RR { return new(MB) }, | |||
| TypeMD: func() RR { return new(MD) }, | |||
| TypeMF: func() RR { return new(MF) }, | |||
| TypeMG: func() RR { return new(MG) }, | |||
| TypeMINFO: func() RR { return new(MINFO) }, | |||
| TypeMR: func() RR { return new(MR) }, | |||
| TypeMX: func() RR { return new(MX) }, | |||
| TypeNAPTR: func() RR { return new(NAPTR) }, | |||
| TypeNID: func() RR { return new(NID) }, | |||
| TypeNIMLOC: func() RR { return new(NIMLOC) }, | |||
| TypeNINFO: func() RR { return new(NINFO) }, | |||
| TypeNS: func() RR { return new(NS) }, | |||
| TypeNSAPPTR: func() RR { return new(NSAPPTR) }, | |||
| TypeNSEC: func() RR { return new(NSEC) }, | |||
| TypeNSEC3: func() RR { return new(NSEC3) }, | |||
| TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) }, | |||
| TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) }, | |||
| TypeOPT: func() RR { return new(OPT) }, | |||
| TypePTR: func() RR { return new(PTR) }, | |||
| TypePX: func() RR { return new(PX) }, | |||
| TypeRKEY: func() RR { return new(RKEY) }, | |||
| TypeRP: func() RR { return new(RP) }, | |||
| TypeRRSIG: func() RR { return new(RRSIG) }, | |||
| TypeRT: func() RR { return new(RT) }, | |||
| TypeSIG: func() RR { return new(SIG) }, | |||
| TypeSOA: func() RR { return new(SOA) }, | |||
| TypeSPF: func() RR { return new(SPF) }, | |||
| TypeSRV: func() RR { return new(SRV) }, | |||
| TypeSSHFP: func() RR { return new(SSHFP) }, | |||
| TypeTA: func() RR { return new(TA) }, | |||
| TypeTALINK: func() RR { return new(TALINK) }, | |||
| TypeTKEY: func() RR { return new(TKEY) }, | |||
| TypeTLSA: func() RR { return new(TLSA) }, | |||
| TypeTSIG: func() RR { return new(TSIG) }, | |||
| TypeTXT: func() RR { return new(TXT) }, | |||
| TypeUID: func() RR { return new(UID) }, | |||
| TypeUINFO: func() RR { return new(UINFO) }, | |||
| TypeURI: func() RR { return new(URI) }, | |||
| TypeWKS: func() RR { return new(WKS) }, | |||
| TypeX25: func() RR { return new(X25) }, | |||
| } | |||
| // TypeToString is a map of strings for each RR type. | |||
| var TypeToString = map[uint16]string{ | |||
| TypeA: "A", | |||
| TypeAAAA: "AAAA", | |||
| TypeAFSDB: "AFSDB", | |||
| TypeANY: "ANY", | |||
| TypeATMA: "ATMA", | |||
| TypeAXFR: "AXFR", | |||
| TypeCAA: "CAA", | |||
| TypeCDNSKEY: "CDNSKEY", | |||
| TypeCDS: "CDS", | |||
| TypeCERT: "CERT", | |||
| TypeCNAME: "CNAME", | |||
| TypeDHCID: "DHCID", | |||
| TypeDLV: "DLV", | |||
| TypeDNAME: "DNAME", | |||
| TypeDNSKEY: "DNSKEY", | |||
| TypeDS: "DS", | |||
| TypeEID: "EID", | |||
| TypeEUI48: "EUI48", | |||
| TypeEUI64: "EUI64", | |||
| TypeGID: "GID", | |||
| TypeGPOS: "GPOS", | |||
| TypeHINFO: "HINFO", | |||
| TypeHIP: "HIP", | |||
| TypeIPSECKEY: "IPSECKEY", | |||
| TypeISDN: "ISDN", | |||
| TypeIXFR: "IXFR", | |||
| TypeKEY: "KEY", | |||
| TypeKX: "KX", | |||
| TypeL32: "L32", | |||
| TypeL64: "L64", | |||
| TypeLOC: "LOC", | |||
| TypeLP: "LP", | |||
| TypeMAILA: "MAILA", | |||
| TypeMAILB: "MAILB", | |||
| TypeMB: "MB", | |||
| TypeMD: "MD", | |||
| TypeMF: "MF", | |||
| TypeMG: "MG", | |||
| TypeMINFO: "MINFO", | |||
| TypeMR: "MR", | |||
| TypeMX: "MX", | |||
| TypeNAPTR: "NAPTR", | |||
| TypeNID: "NID", | |||
| TypeNIMLOC: "NIMLOC", | |||
| TypeNINFO: "NINFO", | |||
| TypeNS: "NS", | |||
| TypeNSEC: "NSEC", | |||
| TypeNSEC3: "NSEC3", | |||
| TypeNSEC3PARAM: "NSEC3PARAM", | |||
| TypeNULL: "NULL", | |||
| TypeNXT: "NXT", | |||
| TypeNone: "None", | |||
| TypeOPENPGPKEY: "OPENPGPKEY", | |||
| TypeOPT: "OPT", | |||
| TypePTR: "PTR", | |||
| TypePX: "PX", | |||
| TypeRKEY: "RKEY", | |||
| TypeRP: "RP", | |||
| TypeRRSIG: "RRSIG", | |||
| TypeRT: "RT", | |||
| TypeReserved: "Reserved", | |||
| TypeSIG: "SIG", | |||
| TypeSOA: "SOA", | |||
| TypeSPF: "SPF", | |||
| TypeSRV: "SRV", | |||
| TypeSSHFP: "SSHFP", | |||
| TypeTA: "TA", | |||
| TypeTALINK: "TALINK", | |||
| TypeTKEY: "TKEY", | |||
| TypeTLSA: "TLSA", | |||
| TypeTSIG: "TSIG", | |||
| TypeTXT: "TXT", | |||
| TypeUID: "UID", | |||
| TypeUINFO: "UINFO", | |||
| TypeUNSPEC: "UNSPEC", | |||
| TypeURI: "URI", | |||
| TypeWKS: "WKS", | |||
| TypeX25: "X25", | |||
| TypeNSAPPTR: "NSAP-PTR", | |||
| } | |||
| // Header() functions | |||
| func (rr *A) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *ANY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *CAA) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *CDS) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *CERT) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *CNAME) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *DHCID) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *DLV) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *DNAME) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *DNSKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *DS) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *EID) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *EUI48) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *EUI64) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *GID) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *GPOS) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *HINFO) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *HIP) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *IPSECKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *KEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *KX) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *L32) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *L64) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *LOC) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *LP) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MB) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MD) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MF) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MG) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MINFO) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MR) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *MX) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NAPTR) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NID) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NIMLOC) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NINFO) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NS) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NSEC) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *OPT) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *PTR) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *PX) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *RKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *RP) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *RRSIG) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *RT) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *SIG) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *SOA) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *SPF) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *SRV) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TA) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TALINK) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TKEY) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TLSA) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TSIG) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *TXT) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *UID) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *UINFO) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *URI) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *WKS) Header() *RR_Header { return &rr.Hdr } | |||
| func (rr *X25) Header() *RR_Header { return &rr.Hdr } | |||
| // len() functions | |||
| func (rr *A) len() int { | |||
| l := rr.Hdr.len() | |||
| l += net.IPv4len // A | |||
| return l | |||
| } | |||
| func (rr *AAAA) len() int { | |||
| l := rr.Hdr.len() | |||
| l += net.IPv6len // AAAA | |||
| return l | |||
| } | |||
| func (rr *AFSDB) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Subtype | |||
| l += len(rr.Hostname) + 1 | |||
| return l | |||
| } | |||
| func (rr *ANY) len() int { | |||
| l := rr.Hdr.len() | |||
| return l | |||
| } | |||
| func (rr *CAA) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // Flag | |||
| l += len(rr.Tag) + 1 | |||
| l += len(rr.Value) | |||
| return l | |||
| } | |||
| func (rr *CERT) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Type | |||
| l += 2 // KeyTag | |||
| l += 1 // Algorithm | |||
| l += base64.StdEncoding.DecodedLen(len(rr.Certificate)) | |||
| return l | |||
| } | |||
| func (rr *CNAME) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Target) + 1 | |||
| return l | |||
| } | |||
| func (rr *DHCID) len() int { | |||
| l := rr.Hdr.len() | |||
| l += base64.StdEncoding.DecodedLen(len(rr.Digest)) | |||
| return l | |||
| } | |||
| func (rr *DNAME) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Target) + 1 | |||
| return l | |||
| } | |||
| func (rr *DNSKEY) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Flags | |||
| l += 1 // Protocol | |||
| l += 1 // Algorithm | |||
| l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) | |||
| return l | |||
| } | |||
| func (rr *DS) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // KeyTag | |||
| l += 1 // Algorithm | |||
| l += 1 // DigestType | |||
| l += len(rr.Digest)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *EID) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Endpoint)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *EUI48) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 6 // Address | |||
| return l | |||
| } | |||
| func (rr *EUI64) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 8 // Address | |||
| return l | |||
| } | |||
| func (rr *GID) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 4 // Gid | |||
| return l | |||
| } | |||
| func (rr *GPOS) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Longitude) + 1 | |||
| l += len(rr.Latitude) + 1 | |||
| l += len(rr.Altitude) + 1 | |||
| return l | |||
| } | |||
| func (rr *HINFO) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Cpu) + 1 | |||
| l += len(rr.Os) + 1 | |||
| return l | |||
| } | |||
| func (rr *HIP) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // HitLength | |||
| l += 1 // PublicKeyAlgorithm | |||
| l += 2 // PublicKeyLength | |||
| l += len(rr.Hit)/2 + 1 | |||
| l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) | |||
| for _, x := range rr.RendezvousServers { | |||
| l += len(x) + 1 | |||
| } | |||
| return l | |||
| } | |||
| func (rr *KX) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += len(rr.Exchanger) + 1 | |||
| return l | |||
| } | |||
| func (rr *L32) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += net.IPv4len // Locator32 | |||
| return l | |||
| } | |||
| func (rr *L64) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += 8 // Locator64 | |||
| return l | |||
| } | |||
| func (rr *LOC) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // Version | |||
| l += 1 // Size | |||
| l += 1 // HorizPre | |||
| l += 1 // VertPre | |||
| l += 4 // Latitude | |||
| l += 4 // Longitude | |||
| l += 4 // Altitude | |||
| return l | |||
| } | |||
| func (rr *LP) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += len(rr.Fqdn) + 1 | |||
| return l | |||
| } | |||
| func (rr *MB) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Mb) + 1 | |||
| return l | |||
| } | |||
| func (rr *MD) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Md) + 1 | |||
| return l | |||
| } | |||
| func (rr *MF) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Mf) + 1 | |||
| return l | |||
| } | |||
| func (rr *MG) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Mg) + 1 | |||
| return l | |||
| } | |||
| func (rr *MINFO) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Rmail) + 1 | |||
| l += len(rr.Email) + 1 | |||
| return l | |||
| } | |||
| func (rr *MR) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Mr) + 1 | |||
| return l | |||
| } | |||
| func (rr *MX) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += len(rr.Mx) + 1 | |||
| return l | |||
| } | |||
| func (rr *NAPTR) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Order | |||
| l += 2 // Preference | |||
| l += len(rr.Flags) + 1 | |||
| l += len(rr.Service) + 1 | |||
| l += len(rr.Regexp) + 1 | |||
| l += len(rr.Replacement) + 1 | |||
| return l | |||
| } | |||
| func (rr *NID) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += 8 // NodeID | |||
| return l | |||
| } | |||
| func (rr *NIMLOC) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Locator)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *NINFO) len() int { | |||
| l := rr.Hdr.len() | |||
| for _, x := range rr.ZSData { | |||
| l += len(x) + 1 | |||
| } | |||
| return l | |||
| } | |||
| func (rr *NS) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Ns) + 1 | |||
| return l | |||
| } | |||
| func (rr *NSAPPTR) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Ptr) + 1 | |||
| return l | |||
| } | |||
| func (rr *NSEC3PARAM) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // Hash | |||
| l += 1 // Flags | |||
| l += 2 // Iterations | |||
| l += 1 // SaltLength | |||
| l += len(rr.Salt)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *OPENPGPKEY) len() int { | |||
| l := rr.Hdr.len() | |||
| l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) | |||
| return l | |||
| } | |||
| func (rr *PTR) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Ptr) + 1 | |||
| return l | |||
| } | |||
| func (rr *PX) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += len(rr.Map822) + 1 | |||
| l += len(rr.Mapx400) + 1 | |||
| return l | |||
| } | |||
| func (rr *RFC3597) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Rdata)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *RKEY) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Flags | |||
| l += 1 // Protocol | |||
| l += 1 // Algorithm | |||
| l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) | |||
| return l | |||
| } | |||
| func (rr *RP) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Mbox) + 1 | |||
| l += len(rr.Txt) + 1 | |||
| return l | |||
| } | |||
| func (rr *RRSIG) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // TypeCovered | |||
| l += 1 // Algorithm | |||
| l += 1 // Labels | |||
| l += 4 // OrigTtl | |||
| l += 4 // Expiration | |||
| l += 4 // Inception | |||
| l += 2 // KeyTag | |||
| l += len(rr.SignerName) + 1 | |||
| l += base64.StdEncoding.DecodedLen(len(rr.Signature)) | |||
| return l | |||
| } | |||
| func (rr *RT) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Preference | |||
| l += len(rr.Host) + 1 | |||
| return l | |||
| } | |||
| func (rr *SOA) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Ns) + 1 | |||
| l += len(rr.Mbox) + 1 | |||
| l += 4 // Serial | |||
| l += 4 // Refresh | |||
| l += 4 // Retry | |||
| l += 4 // Expire | |||
| l += 4 // Minttl | |||
| return l | |||
| } | |||
| func (rr *SPF) len() int { | |||
| l := rr.Hdr.len() | |||
| for _, x := range rr.Txt { | |||
| l += len(x) + 1 | |||
| } | |||
| return l | |||
| } | |||
| func (rr *SRV) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Priority | |||
| l += 2 // Weight | |||
| l += 2 // Port | |||
| l += len(rr.Target) + 1 | |||
| return l | |||
| } | |||
| func (rr *SSHFP) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // Algorithm | |||
| l += 1 // Type | |||
| l += len(rr.FingerPrint)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *TA) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // KeyTag | |||
| l += 1 // Algorithm | |||
| l += 1 // DigestType | |||
| l += len(rr.Digest)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *TALINK) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.PreviousName) + 1 | |||
| l += len(rr.NextName) + 1 | |||
| return l | |||
| } | |||
| func (rr *TKEY) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Algorithm) + 1 | |||
| l += 4 // Inception | |||
| l += 4 // Expiration | |||
| l += 2 // Mode | |||
| l += 2 // Error | |||
| l += 2 // KeySize | |||
| l += len(rr.Key) + 1 | |||
| l += 2 // OtherLen | |||
| l += len(rr.OtherData) + 1 | |||
| return l | |||
| } | |||
| func (rr *TLSA) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 1 // Usage | |||
| l += 1 // Selector | |||
| l += 1 // MatchingType | |||
| l += len(rr.Certificate)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *TSIG) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Algorithm) + 1 | |||
| l += 6 // TimeSigned | |||
| l += 2 // Fudge | |||
| l += 2 // MACSize | |||
| l += len(rr.MAC)/2 + 1 | |||
| l += 2 // OrigId | |||
| l += 2 // Error | |||
| l += 2 // OtherLen | |||
| l += len(rr.OtherData)/2 + 1 | |||
| return l | |||
| } | |||
| func (rr *TXT) len() int { | |||
| l := rr.Hdr.len() | |||
| for _, x := range rr.Txt { | |||
| l += len(x) + 1 | |||
| } | |||
| return l | |||
| } | |||
| func (rr *UID) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 4 // Uid | |||
| return l | |||
| } | |||
| func (rr *UINFO) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.Uinfo) + 1 | |||
| return l | |||
| } | |||
| func (rr *URI) len() int { | |||
| l := rr.Hdr.len() | |||
| l += 2 // Priority | |||
| l += 2 // Weight | |||
| l += len(rr.Target) | |||
| return l | |||
| } | |||
| func (rr *X25) len() int { | |||
| l := rr.Hdr.len() | |||
| l += len(rr.PSDNAddress) + 1 | |||
| return l | |||
| } | |||
| // copy() functions | |||
| func (rr *A) copy() RR { | |||
| return &A{*rr.Hdr.copyHeader(), copyIP(rr.A)} | |||
| } | |||
| func (rr *AAAA) copy() RR { | |||
| return &AAAA{*rr.Hdr.copyHeader(), copyIP(rr.AAAA)} | |||
| } | |||
| func (rr *AFSDB) copy() RR { | |||
| return &AFSDB{*rr.Hdr.copyHeader(), rr.Subtype, rr.Hostname} | |||
| } | |||
| func (rr *ANY) copy() RR { | |||
| return &ANY{*rr.Hdr.copyHeader()} | |||
| } | |||
| func (rr *CAA) copy() RR { | |||
| return &CAA{*rr.Hdr.copyHeader(), rr.Flag, rr.Tag, rr.Value} | |||
| } | |||
| func (rr *CERT) copy() RR { | |||
| return &CERT{*rr.Hdr.copyHeader(), rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate} | |||
| } | |||
| func (rr *CNAME) copy() RR { | |||
| return &CNAME{*rr.Hdr.copyHeader(), rr.Target} | |||
| } | |||
| func (rr *DHCID) copy() RR { | |||
| return &DHCID{*rr.Hdr.copyHeader(), rr.Digest} | |||
| } | |||
| func (rr *DNAME) copy() RR { | |||
| return &DNAME{*rr.Hdr.copyHeader(), rr.Target} | |||
| } | |||
| func (rr *DNSKEY) copy() RR { | |||
| return &DNSKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} | |||
| } | |||
| func (rr *DS) copy() RR { | |||
| return &DS{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} | |||
| } | |||
| func (rr *EID) copy() RR { | |||
| return &EID{*rr.Hdr.copyHeader(), rr.Endpoint} | |||
| } | |||
| func (rr *EUI48) copy() RR { | |||
| return &EUI48{*rr.Hdr.copyHeader(), rr.Address} | |||
| } | |||
| func (rr *EUI64) copy() RR { | |||
| return &EUI64{*rr.Hdr.copyHeader(), rr.Address} | |||
| } | |||
| func (rr *GID) copy() RR { | |||
| return &GID{*rr.Hdr.copyHeader(), rr.Gid} | |||
| } | |||
| func (rr *GPOS) copy() RR { | |||
| return &GPOS{*rr.Hdr.copyHeader(), rr.Longitude, rr.Latitude, rr.Altitude} | |||
| } | |||
| func (rr *HINFO) copy() RR { | |||
| return &HINFO{*rr.Hdr.copyHeader(), rr.Cpu, rr.Os} | |||
| } | |||
| func (rr *HIP) copy() RR { | |||
| RendezvousServers := make([]string, len(rr.RendezvousServers)) | |||
| copy(RendezvousServers, rr.RendezvousServers) | |||
| return &HIP{*rr.Hdr.copyHeader(), rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers} | |||
| } | |||
| func (rr *IPSECKEY) copy() RR { | |||
| return &IPSECKEY{*rr.Hdr.copyHeader(), rr.Precedence, rr.GatewayType, rr.Algorithm, copyIP(rr.GatewayA), copyIP(rr.GatewayAAAA), rr.GatewayName, rr.PublicKey} | |||
| } | |||
| func (rr *KX) copy() RR { | |||
| return &KX{*rr.Hdr.copyHeader(), rr.Preference, rr.Exchanger} | |||
| } | |||
| func (rr *L32) copy() RR { | |||
| return &L32{*rr.Hdr.copyHeader(), rr.Preference, copyIP(rr.Locator32)} | |||
| } | |||
| func (rr *L64) copy() RR { | |||
| return &L64{*rr.Hdr.copyHeader(), rr.Preference, rr.Locator64} | |||
| } | |||
| func (rr *LOC) copy() RR { | |||
| return &LOC{*rr.Hdr.copyHeader(), rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude} | |||
| } | |||
| func (rr *LP) copy() RR { | |||
| return &LP{*rr.Hdr.copyHeader(), rr.Preference, rr.Fqdn} | |||
| } | |||
| func (rr *MB) copy() RR { | |||
| return &MB{*rr.Hdr.copyHeader(), rr.Mb} | |||
| } | |||
| func (rr *MD) copy() RR { | |||
| return &MD{*rr.Hdr.copyHeader(), rr.Md} | |||
| } | |||
| func (rr *MF) copy() RR { | |||
| return &MF{*rr.Hdr.copyHeader(), rr.Mf} | |||
| } | |||
| func (rr *MG) copy() RR { | |||
| return &MG{*rr.Hdr.copyHeader(), rr.Mg} | |||
| } | |||
| func (rr *MINFO) copy() RR { | |||
| return &MINFO{*rr.Hdr.copyHeader(), rr.Rmail, rr.Email} | |||
| } | |||
| func (rr *MR) copy() RR { | |||
| return &MR{*rr.Hdr.copyHeader(), rr.Mr} | |||
| } | |||
| func (rr *MX) copy() RR { | |||
| return &MX{*rr.Hdr.copyHeader(), rr.Preference, rr.Mx} | |||
| } | |||
| func (rr *NAPTR) copy() RR { | |||
| return &NAPTR{*rr.Hdr.copyHeader(), rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement} | |||
| } | |||
| func (rr *NID) copy() RR { | |||
| return &NID{*rr.Hdr.copyHeader(), rr.Preference, rr.NodeID} | |||
| } | |||
| func (rr *NIMLOC) copy() RR { | |||
| return &NIMLOC{*rr.Hdr.copyHeader(), rr.Locator} | |||
| } | |||
| func (rr *NINFO) copy() RR { | |||
| ZSData := make([]string, len(rr.ZSData)) | |||
| copy(ZSData, rr.ZSData) | |||
| return &NINFO{*rr.Hdr.copyHeader(), ZSData} | |||
| } | |||
| func (rr *NS) copy() RR { | |||
| return &NS{*rr.Hdr.copyHeader(), rr.Ns} | |||
| } | |||
| func (rr *NSAPPTR) copy() RR { | |||
| return &NSAPPTR{*rr.Hdr.copyHeader(), rr.Ptr} | |||
| } | |||
| func (rr *NSEC) copy() RR { | |||
| TypeBitMap := make([]uint16, len(rr.TypeBitMap)) | |||
| copy(TypeBitMap, rr.TypeBitMap) | |||
| return &NSEC{*rr.Hdr.copyHeader(), rr.NextDomain, TypeBitMap} | |||
| } | |||
| func (rr *NSEC3) copy() RR { | |||
| TypeBitMap := make([]uint16, len(rr.TypeBitMap)) | |||
| copy(TypeBitMap, rr.TypeBitMap) | |||
| return &NSEC3{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, TypeBitMap} | |||
| } | |||
| func (rr *NSEC3PARAM) copy() RR { | |||
| return &NSEC3PARAM{*rr.Hdr.copyHeader(), rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt} | |||
| } | |||
| func (rr *OPENPGPKEY) copy() RR { | |||
| return &OPENPGPKEY{*rr.Hdr.copyHeader(), rr.PublicKey} | |||
| } | |||
| func (rr *OPT) copy() RR { | |||
| Option := make([]EDNS0, len(rr.Option)) | |||
| copy(Option, rr.Option) | |||
| return &OPT{*rr.Hdr.copyHeader(), Option} | |||
| } | |||
| func (rr *PTR) copy() RR { | |||
| return &PTR{*rr.Hdr.copyHeader(), rr.Ptr} | |||
| } | |||
| func (rr *PX) copy() RR { | |||
| return &PX{*rr.Hdr.copyHeader(), rr.Preference, rr.Map822, rr.Mapx400} | |||
| } | |||
| func (rr *RFC3597) copy() RR { | |||
| return &RFC3597{*rr.Hdr.copyHeader(), rr.Rdata} | |||
| } | |||
| func (rr *RKEY) copy() RR { | |||
| return &RKEY{*rr.Hdr.copyHeader(), rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} | |||
| } | |||
| func (rr *RP) copy() RR { | |||
| return &RP{*rr.Hdr.copyHeader(), rr.Mbox, rr.Txt} | |||
| } | |||
| func (rr *RRSIG) copy() RR { | |||
| return &RRSIG{*rr.Hdr.copyHeader(), rr.TypeCovered, rr.Algorithm, rr.Labels, rr.OrigTtl, rr.Expiration, rr.Inception, rr.KeyTag, rr.SignerName, rr.Signature} | |||
| } | |||
| func (rr *RT) copy() RR { | |||
| return &RT{*rr.Hdr.copyHeader(), rr.Preference, rr.Host} | |||
| } | |||
| func (rr *SOA) copy() RR { | |||
| return &SOA{*rr.Hdr.copyHeader(), rr.Ns, rr.Mbox, rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl} | |||
| } | |||
| func (rr *SPF) copy() RR { | |||
| Txt := make([]string, len(rr.Txt)) | |||
| copy(Txt, rr.Txt) | |||
| return &SPF{*rr.Hdr.copyHeader(), Txt} | |||
| } | |||
| func (rr *SRV) copy() RR { | |||
| return &SRV{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Port, rr.Target} | |||
| } | |||
| func (rr *SSHFP) copy() RR { | |||
| return &SSHFP{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Type, rr.FingerPrint} | |||
| } | |||
| func (rr *TA) copy() RR { | |||
| return &TA{*rr.Hdr.copyHeader(), rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} | |||
| } | |||
| func (rr *TALINK) copy() RR { | |||
| return &TALINK{*rr.Hdr.copyHeader(), rr.PreviousName, rr.NextName} | |||
| } | |||
| func (rr *TKEY) copy() RR { | |||
| return &TKEY{*rr.Hdr.copyHeader(), rr.Algorithm, rr.Inception, rr.Expiration, rr.Mode, rr.Error, rr.KeySize, rr.Key, rr.OtherLen, rr.OtherData} | |||
| } | |||
| func (rr *TLSA) copy() RR { | |||
| return &TLSA{*rr.Hdr.copyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} | |||
| } | |||
| func (rr *TSIG) copy() RR { | |||
| return &TSIG{*rr.Hdr.copyHeader(), rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData} | |||
| } | |||
| func (rr *TXT) copy() RR { | |||
| Txt := make([]string, len(rr.Txt)) | |||
| copy(Txt, rr.Txt) | |||
| return &TXT{*rr.Hdr.copyHeader(), Txt} | |||
| } | |||
| func (rr *UID) copy() RR { | |||
| return &UID{*rr.Hdr.copyHeader(), rr.Uid} | |||
| } | |||
| func (rr *UINFO) copy() RR { | |||
| return &UINFO{*rr.Hdr.copyHeader(), rr.Uinfo} | |||
| } | |||
| func (rr *URI) copy() RR { | |||
| return &URI{*rr.Hdr.copyHeader(), rr.Priority, rr.Weight, rr.Target} | |||
| } | |||
| func (rr *WKS) copy() RR { | |||
| BitMap := make([]uint16, len(rr.BitMap)) | |||
| copy(BitMap, rr.BitMap) | |||
| return &WKS{*rr.Hdr.copyHeader(), copyIP(rr.Address), rr.Protocol, BitMap} | |||
| } | |||
| func (rr *X25) copy() RR { | |||
| return &X25{*rr.Hdr.copyHeader(), rr.PSDNAddress} | |||
| } | |||