git-subtree-dir: vendor/github.com/miekg/dns
git-subtree-split: 5c01f20c3a
master
| @ -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 through 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, although 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.": {0, 4, 9}, | |||||
| "www.miek.nl": {0, 4, 9}, | |||||
| "nl": {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": {"miek", "nl"}, | |||||
| ".": nil, | |||||
| "www.miek.nl.": {"www", "miek", "nl"}, | |||||
| "www.miek.nl": {"www", "miek", "nl"}, | |||||
| "www..miek.nl": {"www", "", "miek", "nl"}, | |||||
| `www\.miek.nl`: {`www\.miek`, "nl"}, | |||||
| `www\\.miek.nl`: {`www\\`, "miek", "nl"}, | |||||
| } | |||||
| domainLoop: | |||||
| for domain, splits := range labels { | |||||
| 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{ | |||||
| "..": {false, 1}, | |||||
| "@.": {true, 1}, | |||||
| "www.example.com": {true, 3}, | |||||
| "www.e%ample.com": {true, 3}, | |||||
| "www.example.com.": {true, 3}, | |||||
| "mi\\k.nl.": {true, 2}, | |||||
| "mi\\k.nl": {true, 2}, | |||||
| } | |||||
| for d, ok := range names { | |||||
| l, k := IsDomainName(d) | |||||
| 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"), | |||||
| }: {"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"), | |||||
| }: {"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"), | |||||
| }: {"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"), | |||||
| }: {"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"), | |||||
| }: {"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 specifically 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": {}, | |||||
| "NSEC3": {}, | |||||
| "OPT": {}, | |||||
| "WKS": {}, | |||||
| "IPSECKEY": {}, | |||||
| } | |||||
| 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* occurrences | |||||
| // 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} | |||||
| } | |||||