├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTORS ├── COPYRIGHT ├── LICENSE ├── README.md ├── client.go ├── client_test.go ├── clientconfig.go ├── clientconfig_test.go ├── defaults.go ├── dns.go ├── dns_test.go ├── dnssec.go ├── dnssec_keygen.go ├── dnssec_keyscan.go ├── dnssec_privkey.go ├── dnssec_test.go ├── doc.go ├── dyn_test.go ├── edns.go ├── edns_test.go ├── example_test.go ├── format.go ├── fuzz_test.go ├── idn ├── code_points.go ├── example_test.go ├── punycode.go └── punycode_test.go ├── labels.go ├── labels_test.go ├── msg.go ├── nsecx.go ├── nsecx_test.go ├── parse_test.go ├── privaterr.go ├── privaterr_test.go ├── rawmsg.go ├── scanner.go ├── server.go ├── server_test.go ├── sig0.go ├── sig0_test.go ├── singleinflight.go ├── tlsa.go ├── tsig.go ├── types.go ├── types_test.go ├── udp.go ├── udp_linux.go ├── udp_other.go ├── udp_windows.go ├── update.go ├── update_test.go ├── xfr.go ├── xfr_test.go ├── zgenerate.go ├── zscan.go └── zscan_rr.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | tags 3 | test.out 4 | a.out 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3 4 | - 1.4 5 | script: 6 | - go test -short -bench=. 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Miek Gieben 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alex A. Skinner 2 | Andrew Tunnell-Jones 3 | Ask Bjørn Hansen 4 | Dave Cheney 5 | Dusty Wilson 6 | Marek Majkowski 7 | Peter van Dijk 8 | Omri Bahumi 9 | Alex Sergeyev 10 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. All rights reserved. Use of this source code 2 | is governed by a BSD-style license that can be found in the LICENSE file. 3 | Extensions of the original work are copyright (c) 2011 Miek Gieben 4 | 5 | Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is 6 | governed by a BSD-style license that can be found in the LICENSE file. 7 | 8 | Copyright 2014 CloudFlare. All rights reserved. Use of this source code is 9 | governed by a BSD-style license that can be found in the LICENSE file. 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Extensions of the original work are copyright (c) 2011 Miek Gieben 2 | 3 | As this is fork of the official Go code the same license applies: 4 | 5 | Copyright (c) 2009 The Go Authors. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following disclaimer 15 | in the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Google Inc. nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) 2 | 3 | # Alternative (more granular) approach to a DNS library 4 | 5 | > Less is more. 6 | 7 | Complete and usable DNS library. All widely used Resource Records are 8 | supported, including the DNSSEC types. It follows a lean and mean philosophy. 9 | If there is stuff you should know as a DNS programmer there isn't a convenience 10 | function for it. Server side and client side programming is supported, i.e. you 11 | can build servers and resolvers with it. 12 | 13 | If you like this, you may also be interested in: 14 | 15 | * https://github.com/miekg/unbound -- Go wrapper for the Unbound resolver. 16 | 17 | # Goals 18 | 19 | * KISS; 20 | * Fast; 21 | * Small API, if its easy to code in Go, don't make a function for it. 22 | 23 | # Users 24 | 25 | A not-so-up-to-date-list-that-may-be-actually-current: 26 | 27 | * https://cloudflare.com 28 | * https://github.com/abh/geodns 29 | * http://www.statdns.com/ 30 | * http://www.dnsinspect.com/ 31 | * https://github.com/chuangbo/jianbing-dictionary-dns 32 | * http://www.dns-lg.com/ 33 | * https://github.com/fcambus/rrda 34 | * https://github.com/kenshinx/godns 35 | * https://github.com/skynetservices/skydns 36 | * https://github.com/DevelopersPL/godnsagent 37 | * https://github.com/duedil-ltd/discodns 38 | * https://github.com/StalkR/dns-reverse-proxy 39 | * https://github.com/tianon/rawdns 40 | * https://mesosphere.github.io/mesos-dns/ 41 | * https://pulse.turbobytes.com/ 42 | * https://play.google.com/store/apps/details?id=com.turbobytes.dig 43 | 44 | Send pull request if you want to be listed here. 45 | 46 | # Features 47 | 48 | * UDP/TCP queries, IPv4 and IPv6; 49 | * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported; 50 | * Fast: 51 | * Reply speed around ~ 80K qps (faster hardware results in more qps); 52 | * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds; 53 | * Server side programming (mimicking the net/http package); 54 | * Client side programming; 55 | * DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA; 56 | * EDNS0, NSID; 57 | * AXFR/IXFR; 58 | * TSIG, SIG(0); 59 | * DNS name compression; 60 | * Depends only on the standard library. 61 | 62 | Have fun! 63 | 64 | Miek Gieben - 2010-2012 - 65 | 66 | # Building 67 | 68 | Building is done with the `go` tool. If you have setup your GOPATH 69 | correctly, the following should work: 70 | 71 | go get github.com/miekg/dns 72 | go build github.com/miekg/dns 73 | 74 | ## Examples 75 | 76 | A short "how to use the API" is at the beginning of doc.go (this also will show 77 | when you call `godoc github.com/miekg/dns`). 78 | 79 | Example programs can be found in the `github.com/miekg/exdns` repository. 80 | 81 | ## Supported RFCs 82 | 83 | *all of them* 84 | 85 | * 103{4,5} - DNS standard 86 | * 1348 - NSAP record (removed the record) 87 | * 1982 - Serial Arithmetic 88 | * 1876 - LOC record 89 | * 1995 - IXFR 90 | * 1996 - DNS notify 91 | * 2136 - DNS Update (dynamic updates) 92 | * 2181 - RRset definition - there is no RRset type though, just []RR 93 | * 2537 - RSAMD5 DNS keys 94 | * 2065 - DNSSEC (updated in later RFCs) 95 | * 2671 - EDNS record 96 | * 2782 - SRV record 97 | * 2845 - TSIG record 98 | * 2915 - NAPTR record 99 | * 2929 - DNS IANA Considerations 100 | * 3110 - RSASHA1 DNS keys 101 | * 3225 - DO bit (DNSSEC OK) 102 | * 340{1,2,3} - NAPTR record 103 | * 3445 - Limiting the scope of (DNS)KEY 104 | * 3597 - Unknown RRs 105 | * 4025 - IPSECKEY 106 | * 403{3,4,5} - DNSSEC + validation functions 107 | * 4255 - SSHFP record 108 | * 4343 - Case insensitivity 109 | * 4408 - SPF record 110 | * 4509 - SHA256 Hash in DS 111 | * 4592 - Wildcards in the DNS 112 | * 4635 - HMAC SHA TSIG 113 | * 4701 - DHCID 114 | * 4892 - id.server 115 | * 5001 - NSID 116 | * 5155 - NSEC3 record 117 | * 5205 - HIP record 118 | * 5702 - SHA2 in the DNS 119 | * 5936 - AXFR 120 | * 5966 - TCP implementation recommendations 121 | * 6605 - ECDSA 122 | * 6725 - IANA Registry Update 123 | * 6742 - ILNP DNS 124 | * 6844 - CAA record 125 | * 6891 - EDNS0 update 126 | * 6895 - DNS IANA considerations 127 | * 6975 - Algorithm Understanding in DNSSEC 128 | * 7043 - EUI48/EUI64 records 129 | * 7314 - DNS (EDNS) EXPIRE Option 130 | * 7553 - URI record 131 | * xxxx - EDNS0 DNS Update Lease (draft) 132 | 133 | ## Loosely based upon 134 | 135 | * `ldns` 136 | * `NSD` 137 | * `Net::DNS` 138 | * `GRONG` 139 | 140 | ## TODO 141 | 142 | * privatekey.Precompute() when signing? 143 | * Last remaining RRs: APL, ATMA, A6, NSAP and NXT. 144 | * Missing in parsing: ISDN, UNSPEC, NSAP and ATMA. 145 | * NSEC(3) cover/match/closest enclose. 146 | * Replies with TC bit are not parsed to the end. 147 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // A client implementation. 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "net" 9 | "time" 10 | ) 11 | 12 | const dnsTimeout time.Duration = 2 * time.Second 13 | const tcpIdleTimeout time.Duration = 8 * time.Second 14 | 15 | // A Conn represents a connection to a DNS server. 16 | type Conn struct { 17 | net.Conn // a net.Conn holding the connection 18 | UDPSize uint16 // minimum receive buffer for UDP messages 19 | TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified 20 | rtt time.Duration 21 | t time.Time 22 | tsigRequestMAC string 23 | } 24 | 25 | // A Client defines parameters for a DNS client. 26 | type Client struct { 27 | Net string // if "tcp" a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) 28 | UDPSize uint16 // minimum receive buffer for UDP messages 29 | DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds 30 | ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds 31 | WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds 32 | TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified 33 | SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass 34 | group singleflight 35 | } 36 | 37 | // Exchange performs a synchronous UDP query. It sends the message m to the address 38 | // contained in a and waits for an reply. Exchange does not retry a failed query, nor 39 | // will it fall back to TCP in case of truncation. 40 | // If you need to send a DNS message on an already existing connection, you can use the 41 | // following: 42 | // 43 | // co := &dns.Conn{Conn: c} // c is your net.Conn 44 | // co.WriteMsg(m) 45 | // in, err := co.ReadMsg() 46 | // co.Close() 47 | // 48 | func Exchange(m *Msg, a string) (r *Msg, err error) { 49 | var co *Conn 50 | co, err = DialTimeout("udp", a, dnsTimeout) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | defer co.Close() 56 | co.SetReadDeadline(time.Now().Add(dnsTimeout)) 57 | co.SetWriteDeadline(time.Now().Add(dnsTimeout)) 58 | 59 | opt := m.IsEdns0() 60 | // If EDNS0 is used use that for size. 61 | if opt != nil && opt.UDPSize() >= MinMsgSize { 62 | co.UDPSize = opt.UDPSize() 63 | } 64 | 65 | if err = co.WriteMsg(m); err != nil { 66 | return nil, err 67 | } 68 | r, err = co.ReadMsg() 69 | if err == nil && r.Id != m.Id { 70 | err = ErrId 71 | } 72 | return r, err 73 | } 74 | 75 | // ExchangeConn performs a synchronous query. It sends the message m via the connection 76 | // c and waits for a reply. The connection c is not closed by ExchangeConn. 77 | // This function is going away, but can easily be mimicked: 78 | // 79 | // co := &dns.Conn{Conn: c} // c is your net.Conn 80 | // co.WriteMsg(m) 81 | // in, _ := co.ReadMsg() 82 | // co.Close() 83 | // 84 | func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { 85 | println("dns: this function is deprecated") 86 | co := new(Conn) 87 | co.Conn = c 88 | if err = co.WriteMsg(m); err != nil { 89 | return nil, err 90 | } 91 | r, err = co.ReadMsg() 92 | if err == nil && r.Id != m.Id { 93 | err = ErrId 94 | } 95 | return r, err 96 | } 97 | 98 | // Exchange performs an synchronous query. It sends the message m to the address 99 | // contained in a and waits for an reply. Basic use pattern with a *dns.Client: 100 | // 101 | // c := new(dns.Client) 102 | // in, rtt, err := c.Exchange(message, "127.0.0.1:53") 103 | // 104 | // Exchange does not retry a failed query, nor will it fall back to TCP in 105 | // case of truncation. 106 | func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { 107 | if !c.SingleInflight { 108 | return c.exchange(m, a) 109 | } 110 | // This adds a bunch of garbage, TODO(miek). 111 | t := "nop" 112 | if t1, ok := TypeToString[m.Question[0].Qtype]; ok { 113 | t = t1 114 | } 115 | cl := "nop" 116 | if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { 117 | cl = cl1 118 | } 119 | r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { 120 | return c.exchange(m, a) 121 | }) 122 | if err != nil { 123 | return r, rtt, err 124 | } 125 | if shared { 126 | return r.Copy(), rtt, nil 127 | } 128 | return r, rtt, nil 129 | } 130 | 131 | func (c *Client) dialTimeout() time.Duration { 132 | if c.DialTimeout != 0 { 133 | return c.DialTimeout 134 | } 135 | return dnsTimeout 136 | } 137 | 138 | func (c *Client) readTimeout() time.Duration { 139 | if c.ReadTimeout != 0 { 140 | return c.ReadTimeout 141 | } 142 | return dnsTimeout 143 | } 144 | 145 | func (c *Client) writeTimeout() time.Duration { 146 | if c.WriteTimeout != 0 { 147 | return c.WriteTimeout 148 | } 149 | return dnsTimeout 150 | } 151 | 152 | func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { 153 | var co *Conn 154 | if c.Net == "" { 155 | co, err = DialTimeout("udp", a, c.dialTimeout()) 156 | } else { 157 | co, err = DialTimeout(c.Net, a, c.dialTimeout()) 158 | } 159 | if err != nil { 160 | return nil, 0, err 161 | } 162 | defer co.Close() 163 | 164 | opt := m.IsEdns0() 165 | // If EDNS0 is used use that for size. 166 | if opt != nil && opt.UDPSize() >= MinMsgSize { 167 | co.UDPSize = opt.UDPSize() 168 | } 169 | // Otherwise use the client's configured UDP size. 170 | if opt == nil && c.UDPSize >= MinMsgSize { 171 | co.UDPSize = c.UDPSize 172 | } 173 | 174 | co.SetReadDeadline(time.Now().Add(c.readTimeout())) 175 | co.SetWriteDeadline(time.Now().Add(c.writeTimeout())) 176 | 177 | co.TsigSecret = c.TsigSecret 178 | if err = co.WriteMsg(m); err != nil { 179 | return nil, 0, err 180 | } 181 | r, err = co.ReadMsg() 182 | if err == nil && r.Id != m.Id { 183 | err = ErrId 184 | } 185 | return r, co.rtt, err 186 | } 187 | 188 | // ReadMsg reads a message from the connection co. 189 | // If the received message contains a TSIG record the transaction 190 | // signature is verified. 191 | func (co *Conn) ReadMsg() (*Msg, error) { 192 | p, err := co.ReadMsgHeader(nil) 193 | if err != nil { 194 | return nil, err 195 | } 196 | 197 | m := new(Msg) 198 | if err := m.Unpack(p); err != nil { 199 | return nil, err 200 | } 201 | if t := m.IsTsig(); t != nil { 202 | if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { 203 | return m, ErrSecret 204 | } 205 | // Need to work on the original message p, as that was used to calculate the tsig. 206 | err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) 207 | } 208 | return m, err 209 | } 210 | 211 | // ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil). 212 | // Returns message as a byte slice to be parsed with Msg.Unpack later on. 213 | // Note that error handling on the message body is not possible as only the header is parsed. 214 | func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { 215 | var ( 216 | p []byte 217 | n int 218 | err error 219 | ) 220 | 221 | if t, ok := co.Conn.(*net.TCPConn); ok { 222 | // First two bytes specify the length of the entire message. 223 | l, err := tcpMsgLen(t) 224 | if err != nil { 225 | return nil, err 226 | } 227 | p = make([]byte, l) 228 | n, err = tcpRead(t, p) 229 | } else { 230 | if co.UDPSize > MinMsgSize { 231 | p = make([]byte, co.UDPSize) 232 | } else { 233 | p = make([]byte, MinMsgSize) 234 | } 235 | n, err = co.Read(p) 236 | } 237 | 238 | if err != nil { 239 | return nil, err 240 | } else if n < headerSize { 241 | return nil, ErrShortRead 242 | } 243 | 244 | p = p[:n] 245 | if hdr != nil { 246 | if _, err = UnpackStruct(hdr, p, 0); err != nil { 247 | return nil, err 248 | } 249 | } 250 | return p, err 251 | } 252 | 253 | // tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length. 254 | func tcpMsgLen(t *net.TCPConn) (int, error) { 255 | p := []byte{0, 0} 256 | n, err := t.Read(p) 257 | if err != nil { 258 | return 0, err 259 | } 260 | if n != 2 { 261 | return 0, ErrShortRead 262 | } 263 | l, _ := unpackUint16(p, 0) 264 | if l == 0 { 265 | return 0, ErrShortRead 266 | } 267 | return int(l), nil 268 | } 269 | 270 | // tcpRead calls TCPConn.Read enough times to fill allocated buffer. 271 | func tcpRead(t *net.TCPConn, p []byte) (int, error) { 272 | n, err := t.Read(p) 273 | if err != nil { 274 | return n, err 275 | } 276 | for n < len(p) { 277 | j, err := t.Read(p[n:]) 278 | if err != nil { 279 | return n, err 280 | } 281 | n += j 282 | } 283 | return n, err 284 | } 285 | 286 | // Read implements the net.Conn read method. 287 | func (co *Conn) Read(p []byte) (n int, err error) { 288 | if co.Conn == nil { 289 | return 0, ErrConnEmpty 290 | } 291 | if len(p) < 2 { 292 | return 0, io.ErrShortBuffer 293 | } 294 | if t, ok := co.Conn.(*net.TCPConn); ok { 295 | l, err := tcpMsgLen(t) 296 | if err != nil { 297 | return 0, err 298 | } 299 | if l > len(p) { 300 | return int(l), io.ErrShortBuffer 301 | } 302 | return tcpRead(t, p[:l]) 303 | } 304 | // UDP connection 305 | n, err = co.Conn.Read(p) 306 | if err != nil { 307 | return n, err 308 | } 309 | 310 | co.rtt = time.Since(co.t) 311 | return n, err 312 | } 313 | 314 | // WriteMsg sends a message throught the connection co. 315 | // If the message m contains a TSIG record the transaction 316 | // signature is calculated. 317 | func (co *Conn) WriteMsg(m *Msg) (err error) { 318 | var out []byte 319 | if t := m.IsTsig(); t != nil { 320 | mac := "" 321 | if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { 322 | return ErrSecret 323 | } 324 | out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) 325 | // Set for the next read, allthough only used in zone transfers 326 | co.tsigRequestMAC = mac 327 | } else { 328 | out, err = m.Pack() 329 | } 330 | if err != nil { 331 | return err 332 | } 333 | co.t = time.Now() 334 | if _, err = co.Write(out); err != nil { 335 | return err 336 | } 337 | return nil 338 | } 339 | 340 | // Write implements the net.Conn Write method. 341 | func (co *Conn) Write(p []byte) (n int, err error) { 342 | if t, ok := co.Conn.(*net.TCPConn); ok { 343 | lp := len(p) 344 | if lp < 2 { 345 | return 0, io.ErrShortBuffer 346 | } 347 | if lp > MaxMsgSize { 348 | return 0, &Error{err: "message too large"} 349 | } 350 | l := make([]byte, 2, lp+2) 351 | l[0], l[1] = packUint16(uint16(lp)) 352 | p = append(l, p...) 353 | n, err := io.Copy(t, bytes.NewReader(p)) 354 | return int(n), err 355 | } 356 | n, err = co.Conn.(*net.UDPConn).Write(p) 357 | return n, err 358 | } 359 | 360 | // Dial connects to the address on the named network. 361 | func Dial(network, address string) (conn *Conn, err error) { 362 | conn = new(Conn) 363 | conn.Conn, err = net.Dial(network, address) 364 | if err != nil { 365 | return nil, err 366 | } 367 | return conn, nil 368 | } 369 | 370 | // DialTimeout acts like Dial but takes a timeout. 371 | func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { 372 | conn = new(Conn) 373 | conn.Conn, err = net.DialTimeout(network, address, timeout) 374 | if err != nil { 375 | return nil, err 376 | } 377 | return conn, nil 378 | } 379 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestClientSync(t *testing.T) { 10 | HandleFunc("miek.nl.", HelloServer) 11 | defer HandleRemove("miek.nl.") 12 | 13 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 14 | if err != nil { 15 | t.Fatalf("Unable to run test server: %v", err) 16 | } 17 | defer s.Shutdown() 18 | 19 | m := new(Msg) 20 | m.SetQuestion("miek.nl.", TypeSOA) 21 | 22 | c := new(Client) 23 | r, _, err := c.Exchange(m, addrstr) 24 | if err != nil { 25 | t.Errorf("failed to exchange: %v", err) 26 | } 27 | if r != nil && r.Rcode != RcodeSuccess { 28 | t.Errorf("failed to get an valid answer\n%v", r) 29 | } 30 | // And now with plain Exchange(). 31 | r, err = Exchange(m, addrstr) 32 | if err != nil { 33 | t.Errorf("failed to exchange: %v", err) 34 | } 35 | if r == nil || r.Rcode != RcodeSuccess { 36 | t.Errorf("failed to get an valid answer\n%v", r) 37 | } 38 | } 39 | 40 | func TestClientSyncBadId(t *testing.T) { 41 | HandleFunc("miek.nl.", HelloServerBadId) 42 | defer HandleRemove("miek.nl.") 43 | 44 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 45 | if err != nil { 46 | t.Fatalf("Unable to run test server: %v", err) 47 | } 48 | defer s.Shutdown() 49 | 50 | m := new(Msg) 51 | m.SetQuestion("miek.nl.", TypeSOA) 52 | 53 | c := new(Client) 54 | if _, _, err := c.Exchange(m, addrstr); err != ErrId { 55 | t.Errorf("did not find a bad Id") 56 | } 57 | // And now with plain Exchange(). 58 | if _, err := Exchange(m, addrstr); err != ErrId { 59 | t.Errorf("did not find a bad Id") 60 | } 61 | } 62 | 63 | func TestClientEDNS0(t *testing.T) { 64 | HandleFunc("miek.nl.", HelloServer) 65 | defer HandleRemove("miek.nl.") 66 | 67 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 68 | if err != nil { 69 | t.Fatalf("Unable to run test server: %v", err) 70 | } 71 | defer s.Shutdown() 72 | 73 | m := new(Msg) 74 | m.SetQuestion("miek.nl.", TypeDNSKEY) 75 | 76 | m.SetEdns0(2048, true) 77 | 78 | c := new(Client) 79 | r, _, err := c.Exchange(m, addrstr) 80 | if err != nil { 81 | t.Errorf("failed to exchange: %v", err) 82 | } 83 | 84 | if r != nil && r.Rcode != RcodeSuccess { 85 | t.Errorf("failed to get an valid answer\n%v", r) 86 | } 87 | } 88 | 89 | // Validates the transmission and parsing of local EDNS0 options. 90 | func TestClientEDNS0Local(t *testing.T) { 91 | 92 | optStr1 := "1979:0x0707" 93 | optStr2 := strconv.Itoa(EDNS0LOCALSTART) + ":0x0601" 94 | 95 | handler := func(w ResponseWriter, req *Msg) { 96 | m := new(Msg) 97 | m.SetReply(req) 98 | 99 | m.Extra = make([]RR, 1, 2) 100 | m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello local edns"}} 101 | 102 | // If the local options are what we expect, then reflect them back. 103 | ec1 := req.Extra[0].(*OPT).Option[0].(*EDNS0_LOCAL).String() 104 | ec2 := req.Extra[0].(*OPT).Option[1].(*EDNS0_LOCAL).String() 105 | if ec1 == optStr1 && ec2 == optStr2 { 106 | m.Extra = append(m.Extra, req.Extra[0]) 107 | } 108 | 109 | w.WriteMsg(m) 110 | } 111 | 112 | HandleFunc("miek.nl.", handler) 113 | defer HandleRemove("miek.nl.") 114 | 115 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 116 | if err != nil { 117 | t.Fatalf("Unable to run test server: %s", err) 118 | } 119 | defer s.Shutdown() 120 | 121 | m := new(Msg) 122 | m.SetQuestion("miek.nl.", TypeTXT) 123 | 124 | // Add two local edns options to the query. 125 | ec1 := &EDNS0_LOCAL{Code: 1979, Data: []byte{7, 7}} 126 | ec2 := &EDNS0_LOCAL{Code: EDNS0LOCALSTART, Data: []byte{6, 1}} 127 | o := &OPT{Hdr: RR_Header{Name: ".", Rrtype: TypeOPT}, Option: []EDNS0{ec1, ec2}} 128 | m.Extra = append(m.Extra, o) 129 | 130 | c := new(Client) 131 | r, _, e := c.Exchange(m, addrstr) 132 | if e != nil { 133 | t.Logf("failed to exchange: %s", e.Error()) 134 | t.Fail() 135 | } 136 | 137 | if r != nil && r.Rcode != RcodeSuccess { 138 | t.Log("failed to get a valid answer") 139 | t.Fail() 140 | t.Logf("%v\n", r) 141 | } 142 | 143 | txt := r.Extra[0].(*TXT).Txt[0] 144 | if txt != "Hello local edns" { 145 | t.Log("Unexpected result for miek.nl", txt, "!= Hello local edns") 146 | t.Fail() 147 | } 148 | 149 | // Validate the local options in the reply. 150 | got := r.Extra[1].(*OPT).Option[0].(*EDNS0_LOCAL).String() 151 | if got != optStr1 { 152 | t.Log("failed to get local edns0 answer; got %s, expected %s", got, optStr1) 153 | t.Fail() 154 | t.Logf("%v\n", r) 155 | } 156 | 157 | got = r.Extra[1].(*OPT).Option[1].(*EDNS0_LOCAL).String() 158 | if got != optStr2 { 159 | t.Log("failed to get local edns0 answer; got %s, expected %s", got, optStr2) 160 | t.Fail() 161 | t.Logf("%v\n", r) 162 | } 163 | } 164 | 165 | func TestSingleSingleInflight(t *testing.T) { 166 | HandleFunc("miek.nl.", HelloServer) 167 | defer HandleRemove("miek.nl.") 168 | 169 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 170 | if err != nil { 171 | t.Fatalf("Unable to run test server: %v", err) 172 | } 173 | defer s.Shutdown() 174 | 175 | m := new(Msg) 176 | m.SetQuestion("miek.nl.", TypeDNSKEY) 177 | 178 | c := new(Client) 179 | c.SingleInflight = true 180 | nr := 10 181 | ch := make(chan time.Duration) 182 | for i := 0; i < nr; i++ { 183 | go func() { 184 | _, rtt, _ := c.Exchange(m, addrstr) 185 | ch <- rtt 186 | }() 187 | } 188 | i := 0 189 | var first time.Duration 190 | // With inflight *all* rtt are identical, and by doing actual lookups 191 | // the changes that this is a coincidence is small. 192 | Loop: 193 | for { 194 | select { 195 | case rtt := <-ch: 196 | if i == 0 { 197 | first = rtt 198 | } else { 199 | if first != rtt { 200 | t.Errorf("all rtts should be equal. got %d want %d", rtt, first) 201 | } 202 | } 203 | i++ 204 | if i == 10 { 205 | break Loop 206 | } 207 | } 208 | } 209 | } 210 | 211 | // ExampleUpdateLeaseTSIG shows how to update a lease signed with TSIG. 212 | func ExampleUpdateLeaseTSIG(t *testing.T) { 213 | m := new(Msg) 214 | m.SetUpdate("t.local.ip6.io.") 215 | rr, _ := NewRR("t.local.ip6.io. 30 A 127.0.0.1") 216 | rrs := make([]RR, 1) 217 | rrs[0] = rr 218 | m.Insert(rrs) 219 | 220 | leaseRr := new(OPT) 221 | leaseRr.Hdr.Name = "." 222 | leaseRr.Hdr.Rrtype = TypeOPT 223 | e := new(EDNS0_UL) 224 | e.Code = EDNS0UL 225 | e.Lease = 120 226 | leaseRr.Option = append(leaseRr.Option, e) 227 | m.Extra = append(m.Extra, leaseRr) 228 | 229 | c := new(Client) 230 | m.SetTsig("polvi.", HmacMD5, 300, time.Now().Unix()) 231 | c.TsigSecret = map[string]string{"polvi.": "pRZgBrBvI4NAHZYhxmhs/Q=="} 232 | 233 | _, _, err := c.Exchange(m, "127.0.0.1:53") 234 | if err != nil { 235 | t.Error(err) 236 | } 237 | } 238 | 239 | func TestClientConn(t *testing.T) { 240 | HandleFunc("miek.nl.", HelloServer) 241 | defer HandleRemove("miek.nl.") 242 | 243 | // This uses TCP just to make it slightly different than TestClientSync 244 | s, addrstr, err := RunLocalTCPServer("127.0.0.1:0") 245 | if err != nil { 246 | t.Fatalf("Unable to run test server: %v", err) 247 | } 248 | defer s.Shutdown() 249 | 250 | m := new(Msg) 251 | m.SetQuestion("miek.nl.", TypeSOA) 252 | 253 | cn, err := Dial("tcp", addrstr) 254 | if err != nil { 255 | t.Errorf("failed to dial %s: %v", addrstr, err) 256 | } 257 | 258 | err = cn.WriteMsg(m) 259 | if err != nil { 260 | t.Errorf("failed to exchange: %v", err) 261 | } 262 | r, err := cn.ReadMsg() 263 | if r == nil || r.Rcode != RcodeSuccess { 264 | t.Errorf("failed to get an valid answer\n%v", r) 265 | } 266 | 267 | err = cn.WriteMsg(m) 268 | if err != nil { 269 | t.Errorf("failed to exchange: %v", err) 270 | } 271 | h := new(Header) 272 | buf, err := cn.ReadMsgHeader(h) 273 | if buf == nil { 274 | t.Errorf("failed to get an valid answer\n%v", r) 275 | } 276 | if int(h.Bits&0xF) != RcodeSuccess { 277 | t.Errorf("failed to get an valid answer in ReadMsgHeader\n%v", r) 278 | } 279 | if h.Ancount != 0 || h.Qdcount != 1 || h.Nscount != 0 || h.Arcount != 1 { 280 | t.Errorf("expected to have question and additional in response; got something else: %+v", h) 281 | } 282 | if err = r.Unpack(buf); err != nil { 283 | t.Errorf("unable to unpack message fully: %v", err) 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /clientconfig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // ClientConfig wraps the contents of the /etc/resolv.conf file. 11 | type ClientConfig struct { 12 | Servers []string // servers to use 13 | Search []string // suffixes to append to local name 14 | Port string // what port to use 15 | Ndots int // number of dots in name to trigger absolute lookup 16 | Timeout int // seconds before giving up on packet 17 | Attempts int // lost packets before giving up on server, not used in the package dns 18 | } 19 | 20 | // ClientConfigFromFile parses a resolv.conf(5) like file and returns 21 | // a *ClientConfig. 22 | func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { 23 | file, err := os.Open(resolvconf) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer file.Close() 28 | c := new(ClientConfig) 29 | scanner := bufio.NewScanner(file) 30 | c.Servers = make([]string, 0) 31 | c.Search = make([]string, 0) 32 | c.Port = "53" 33 | c.Ndots = 1 34 | c.Timeout = 5 35 | c.Attempts = 2 36 | 37 | for scanner.Scan() { 38 | if err := scanner.Err(); err != nil { 39 | return nil, err 40 | } 41 | line := scanner.Text() 42 | f := strings.Fields(line) 43 | if len(f) < 1 { 44 | continue 45 | } 46 | switch f[0] { 47 | case "nameserver": // add one name server 48 | if len(f) > 1 { 49 | // One more check: make sure server name is 50 | // just an IP address. Otherwise we need DNS 51 | // to look it up. 52 | name := f[1] 53 | c.Servers = append(c.Servers, name) 54 | } 55 | 56 | case "domain": // set search path to just this domain 57 | if len(f) > 1 { 58 | c.Search = make([]string, 1) 59 | c.Search[0] = f[1] 60 | } else { 61 | c.Search = make([]string, 0) 62 | } 63 | 64 | case "search": // set search path to given servers 65 | c.Search = make([]string, len(f)-1) 66 | for i := 0; i < len(c.Search); i++ { 67 | c.Search[i] = f[i+1] 68 | } 69 | 70 | case "options": // magic options 71 | for i := 1; i < len(f); i++ { 72 | s := f[i] 73 | switch { 74 | case len(s) >= 6 && s[:6] == "ndots:": 75 | n, _ := strconv.Atoi(s[6:]) 76 | if n < 1 { 77 | n = 1 78 | } 79 | c.Ndots = n 80 | case len(s) >= 8 && s[:8] == "timeout:": 81 | n, _ := strconv.Atoi(s[8:]) 82 | if n < 1 { 83 | n = 1 84 | } 85 | c.Timeout = n 86 | case len(s) >= 8 && s[:9] == "attempts:": 87 | n, _ := strconv.Atoi(s[9:]) 88 | if n < 1 { 89 | n = 1 90 | } 91 | c.Attempts = n 92 | case s == "rotate": 93 | /* not imp */ 94 | } 95 | } 96 | } 97 | } 98 | return c, nil 99 | } 100 | -------------------------------------------------------------------------------- /clientconfig_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | const normal string = ` 11 | # Comment 12 | domain somedomain.com 13 | nameserver 10.28.10.2 14 | nameserver 11.28.10.1 15 | ` 16 | 17 | const missingNewline string = ` 18 | domain somedomain.com 19 | nameserver 10.28.10.2 20 | nameserver 11.28.10.1` // <- NOTE: NO newline. 21 | 22 | func testConfig(t *testing.T, data string) { 23 | tempDir, err := ioutil.TempDir("", "") 24 | if err != nil { 25 | t.Fatalf("TempDir: %v", err) 26 | } 27 | defer os.RemoveAll(tempDir) 28 | 29 | path := filepath.Join(tempDir, "resolv.conf") 30 | if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil { 31 | t.Fatalf("WriteFile: %v", err) 32 | } 33 | cc, err := ClientConfigFromFile(path) 34 | if err != nil { 35 | t.Errorf("error parsing resolv.conf: %v", err) 36 | } 37 | if l := len(cc.Servers); l != 2 { 38 | t.Errorf("incorrect number of nameservers detected: %d", l) 39 | } 40 | if l := len(cc.Search); l != 1 { 41 | t.Errorf("domain directive not parsed correctly: %v", cc.Search) 42 | } else { 43 | if cc.Search[0] != "somedomain.com" { 44 | t.Errorf("domain is unexpected: %v", cc.Search[0]) 45 | } 46 | } 47 | } 48 | 49 | func TestNameserver(t *testing.T) { 50 | testConfig(t, normal) 51 | } 52 | 53 | func TestMissingFinalNewLine(t *testing.T) { 54 | testConfig(t, missingNewline) 55 | } 56 | -------------------------------------------------------------------------------- /defaults.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | const hexDigit = "0123456789abcdef" 10 | 11 | // Everything is assumed in ClassINET. 12 | 13 | // SetReply creates a reply message from a request message. 14 | func (dns *Msg) SetReply(request *Msg) *Msg { 15 | dns.Id = request.Id 16 | dns.RecursionDesired = request.RecursionDesired // Copy rd bit 17 | dns.Response = true 18 | dns.Opcode = OpcodeQuery 19 | dns.Rcode = RcodeSuccess 20 | if len(request.Question) > 0 { 21 | dns.Question = make([]Question, 1) 22 | dns.Question[0] = request.Question[0] 23 | } 24 | return dns 25 | } 26 | 27 | // SetQuestion creates a question message, it sets the Question 28 | // section, generates an Id and sets the RecursionDesired (RD) 29 | // bit to true. 30 | func (dns *Msg) SetQuestion(z string, t uint16) *Msg { 31 | dns.Id = Id() 32 | dns.RecursionDesired = true 33 | dns.Question = make([]Question, 1) 34 | dns.Question[0] = Question{z, t, ClassINET} 35 | return dns 36 | } 37 | 38 | // SetNotify creates a notify message, it sets the Question 39 | // section, generates an Id and sets the Authoritative (AA) 40 | // bit to true. 41 | func (dns *Msg) SetNotify(z string) *Msg { 42 | dns.Opcode = OpcodeNotify 43 | dns.Authoritative = true 44 | dns.Id = Id() 45 | dns.Question = make([]Question, 1) 46 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 47 | return dns 48 | } 49 | 50 | // SetRcode creates an error message suitable for the request. 51 | func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { 52 | dns.SetReply(request) 53 | dns.Rcode = rcode 54 | return dns 55 | } 56 | 57 | // SetRcodeFormatError creates a message with FormError set. 58 | func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { 59 | dns.Rcode = RcodeFormatError 60 | dns.Opcode = OpcodeQuery 61 | dns.Response = true 62 | dns.Authoritative = false 63 | dns.Id = request.Id 64 | return dns 65 | } 66 | 67 | // SetUpdate makes the message a dynamic update message. It 68 | // sets the ZONE section to: z, TypeSOA, ClassINET. 69 | func (dns *Msg) SetUpdate(z string) *Msg { 70 | dns.Id = Id() 71 | dns.Response = false 72 | dns.Opcode = OpcodeUpdate 73 | dns.Compress = false // BIND9 cannot handle compression 74 | dns.Question = make([]Question, 1) 75 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 76 | return dns 77 | } 78 | 79 | // SetIxfr creates message for requesting an IXFR. 80 | func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { 81 | dns.Id = Id() 82 | dns.Question = make([]Question, 1) 83 | dns.Ns = make([]RR, 1) 84 | s := new(SOA) 85 | s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} 86 | s.Serial = serial 87 | s.Ns = ns 88 | s.Mbox = mbox 89 | dns.Question[0] = Question{z, TypeIXFR, ClassINET} 90 | dns.Ns[0] = s 91 | return dns 92 | } 93 | 94 | // SetAxfr creates message for requesting an AXFR. 95 | func (dns *Msg) SetAxfr(z string) *Msg { 96 | dns.Id = Id() 97 | dns.Question = make([]Question, 1) 98 | dns.Question[0] = Question{z, TypeAXFR, ClassINET} 99 | return dns 100 | } 101 | 102 | // SetTsig appends a TSIG RR to the message. 103 | // This is only a skeleton TSIG RR that is added as the last RR in the 104 | // additional section. The Tsig is calculated when the message is being send. 105 | func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { 106 | t := new(TSIG) 107 | t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} 108 | t.Algorithm = algo 109 | t.Fudge = 300 110 | t.TimeSigned = uint64(timesigned) 111 | t.OrigId = dns.Id 112 | dns.Extra = append(dns.Extra, t) 113 | return dns 114 | } 115 | 116 | // SetEdns0 appends a EDNS0 OPT RR to the message. 117 | // TSIG should always the last RR in a message. 118 | func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { 119 | e := new(OPT) 120 | e.Hdr.Name = "." 121 | e.Hdr.Rrtype = TypeOPT 122 | e.SetUDPSize(udpsize) 123 | if do { 124 | e.SetDo() 125 | } 126 | dns.Extra = append(dns.Extra, e) 127 | return dns 128 | } 129 | 130 | // IsTsig checks if the message has a TSIG record as the last record 131 | // in the additional section. It returns the TSIG record found or nil. 132 | func (dns *Msg) IsTsig() *TSIG { 133 | if len(dns.Extra) > 0 { 134 | if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { 135 | return dns.Extra[len(dns.Extra)-1].(*TSIG) 136 | } 137 | } 138 | return nil 139 | } 140 | 141 | // IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 142 | // record in the additional section will do. It returns the OPT record 143 | // found or nil. 144 | func (dns *Msg) IsEdns0() *OPT { 145 | for _, r := range dns.Extra { 146 | if r.Header().Rrtype == TypeOPT { 147 | return r.(*OPT) 148 | } 149 | } 150 | return nil 151 | } 152 | 153 | // IsDomainName checks if s is a valid domainname, it returns 154 | // the number of labels and true, when a domain name is valid. 155 | // Note that non fully qualified domain name is considered valid, in this case the 156 | // last label is counted in the number of labels. 157 | // When false is returned the number of labels is not defined. 158 | func IsDomainName(s string) (labels int, ok bool) { 159 | _, labels, err := packDomainName(s, nil, 0, nil, false) 160 | return labels, err == nil 161 | } 162 | 163 | // IsSubDomain checks if child is indeed a child of the parent. Both child and 164 | // parent are *not* downcased before doing the comparison. 165 | func IsSubDomain(parent, child string) bool { 166 | // Entire child is contained in parent 167 | return CompareDomainName(parent, child) == CountLabel(parent) 168 | } 169 | 170 | // IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. 171 | // The checking is performed on the binary payload. 172 | func IsMsg(buf []byte) error { 173 | // Header 174 | if len(buf) < 12 { 175 | return errors.New("dns: bad message header") 176 | } 177 | // Header: Opcode 178 | // TODO(miek): more checks here, e.g. check all header bits. 179 | return nil 180 | } 181 | 182 | // IsFqdn checks if a domain name is fully qualified. 183 | func IsFqdn(s string) bool { 184 | l := len(s) 185 | if l == 0 { 186 | return false 187 | } 188 | return s[l-1] == '.' 189 | } 190 | 191 | // IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. 192 | // This means the RRs need to have the same type, name, and class. Returns true 193 | // if the RR set is valid, otherwise false. 194 | func IsRRset(rrset []RR) bool { 195 | if len(rrset) == 0 { 196 | return false 197 | } 198 | if len(rrset) == 1 { 199 | return true 200 | } 201 | rrHeader := rrset[0].Header() 202 | rrType := rrHeader.Rrtype 203 | rrClass := rrHeader.Class 204 | rrName := rrHeader.Name 205 | 206 | for _, rr := range rrset[1:] { 207 | curRRHeader := rr.Header() 208 | if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { 209 | // Mismatch between the records, so this is not a valid rrset for 210 | //signing/verifying 211 | return false 212 | } 213 | } 214 | 215 | return true 216 | } 217 | 218 | // Fqdn return the fully qualified domain name from s. 219 | // If s is already fully qualified, it behaves as the identity function. 220 | func Fqdn(s string) string { 221 | if IsFqdn(s) { 222 | return s 223 | } 224 | return s + "." 225 | } 226 | 227 | // Copied from the official Go code. 228 | 229 | // ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP 230 | // address suitable for reverse DNS (PTR) record lookups or an error if it fails 231 | // to parse the IP address. 232 | func ReverseAddr(addr string) (arpa string, err error) { 233 | ip := net.ParseIP(addr) 234 | if ip == nil { 235 | return "", &Error{err: "unrecognized address: " + addr} 236 | } 237 | if ip.To4() != nil { 238 | return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + 239 | strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil 240 | } 241 | // Must be IPv6 242 | buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) 243 | // Add it, in reverse, to the buffer 244 | for i := len(ip) - 1; i >= 0; i-- { 245 | v := ip[i] 246 | buf = append(buf, hexDigit[v&0xF]) 247 | buf = append(buf, '.') 248 | buf = append(buf, hexDigit[v>>4]) 249 | buf = append(buf, '.') 250 | } 251 | // Append "ip6.arpa." and return (buf already has the final .) 252 | buf = append(buf, "ip6.arpa."...) 253 | return string(buf), nil 254 | } 255 | 256 | // String returns the string representation for the type t. 257 | func (t Type) String() string { 258 | if t1, ok := TypeToString[uint16(t)]; ok { 259 | return t1 260 | } 261 | return "TYPE" + strconv.Itoa(int(t)) 262 | } 263 | 264 | // String returns the string representation for the class c. 265 | func (c Class) String() string { 266 | if c1, ok := ClassToString[uint16(c)]; ok { 267 | return c1 268 | } 269 | return "CLASS" + strconv.Itoa(int(c)) 270 | } 271 | 272 | // String returns the string representation for the name n. 273 | func (n Name) String() string { 274 | return sprintName(string(n)) 275 | } 276 | -------------------------------------------------------------------------------- /dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "strconv" 4 | 5 | const ( 6 | year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. 7 | // DefaultMsgSize is the standard default for messages larger than 512 bytes. 8 | DefaultMsgSize = 4096 9 | // MinMsgSize is the minimal size of a DNS packet. 10 | MinMsgSize = 512 11 | // MaxMsgSize is the largest possible DNS packet. 12 | MaxMsgSize = 65535 13 | defaultTtl = 3600 // Default internal TTL. 14 | ) 15 | 16 | // Error represents a DNS error 17 | type Error struct{ err string } 18 | 19 | func (e *Error) Error() string { 20 | if e == nil { 21 | return "dns: " 22 | } 23 | return "dns: " + e.err 24 | } 25 | 26 | // An RR represents a resource record. 27 | type RR interface { 28 | // Header returns the header of an resource record. The header contains 29 | // everything up to the rdata. 30 | Header() *RR_Header 31 | // String returns the text representation of the resource record. 32 | String() string 33 | // copy returns a copy of the RR 34 | copy() RR 35 | // len returns the length (in octets) of the uncompressed RR in wire format. 36 | len() int 37 | } 38 | 39 | // DNS resource records. 40 | // There are many types of RRs, 41 | // but they all share the same header. 42 | type RR_Header struct { 43 | Name string `dns:"cdomain-name"` 44 | Rrtype uint16 45 | Class uint16 46 | Ttl uint32 47 | Rdlength uint16 // length of data after header 48 | } 49 | 50 | // Header returns itself. This is here to make RR_Header implement the RR interface. 51 | func (h *RR_Header) Header() *RR_Header { return h } 52 | 53 | // Just to imlement the RR interface. 54 | func (h *RR_Header) copy() RR { return nil } 55 | 56 | func (h *RR_Header) copyHeader() *RR_Header { 57 | r := new(RR_Header) 58 | r.Name = h.Name 59 | r.Rrtype = h.Rrtype 60 | r.Class = h.Class 61 | r.Ttl = h.Ttl 62 | r.Rdlength = h.Rdlength 63 | return r 64 | } 65 | 66 | func (h *RR_Header) String() string { 67 | var s string 68 | 69 | if h.Rrtype == TypeOPT { 70 | s = ";" 71 | // and maybe other things 72 | } 73 | 74 | s += sprintName(h.Name) + "\t" 75 | s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" 76 | s += Class(h.Class).String() + "\t" 77 | s += Type(h.Rrtype).String() + "\t" 78 | return s 79 | } 80 | 81 | func (h *RR_Header) len() int { 82 | l := len(h.Name) + 1 83 | l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) 84 | return l 85 | } 86 | 87 | // ToRFC3597 converts a known RR to the unknown RR representation 88 | // from RFC 3597. 89 | func (rr *RFC3597) ToRFC3597(r RR) error { 90 | buf := make([]byte, r.len()*2) 91 | off, err := PackStruct(r, buf, 0) 92 | if err != nil { 93 | return err 94 | } 95 | buf = buf[:off] 96 | rawSetRdlength(buf, 0, off) 97 | _, err = UnpackStruct(rr, buf, 0) 98 | if err != nil { 99 | return err 100 | } 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /dnssec.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/dsa" 7 | "crypto/ecdsa" 8 | "crypto/elliptic" 9 | "crypto/md5" 10 | "crypto/rsa" 11 | "crypto/sha1" 12 | "crypto/sha256" 13 | "crypto/sha512" 14 | "encoding/hex" 15 | "hash" 16 | "io" 17 | "math/big" 18 | "sort" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | // DNSSEC encryption algorithm codes. 24 | const ( 25 | _ uint8 = iota 26 | RSAMD5 27 | DH 28 | DSA 29 | _ // Skip 4, RFC 6725, section 2.1 30 | RSASHA1 31 | DSANSEC3SHA1 32 | RSASHA1NSEC3SHA1 33 | RSASHA256 34 | _ // Skip 9, RFC 6725, section 2.1 35 | RSASHA512 36 | _ // Skip 11, RFC 6725, section 2.1 37 | ECCGOST 38 | ECDSAP256SHA256 39 | ECDSAP384SHA384 40 | INDIRECT uint8 = 252 41 | PRIVATEDNS uint8 = 253 // Private (experimental keys) 42 | PRIVATEOID uint8 = 254 43 | ) 44 | 45 | // DNSSEC hashing algorithm codes. 46 | const ( 47 | _ uint8 = iota 48 | SHA1 // RFC 4034 49 | SHA256 // RFC 4509 50 | GOST94 // RFC 5933 51 | SHA384 // Experimental 52 | SHA512 // Experimental 53 | ) 54 | 55 | // DNSKEY flag values. 56 | const ( 57 | SEP = 1 58 | REVOKE = 1 << 7 59 | ZONE = 1 << 8 60 | ) 61 | 62 | // The RRSIG needs to be converted to wireformat with some of 63 | // the rdata (the signature) missing. Use this struct to easy 64 | // the conversion (and re-use the pack/unpack functions). 65 | type rrsigWireFmt struct { 66 | TypeCovered uint16 67 | Algorithm uint8 68 | Labels uint8 69 | OrigTtl uint32 70 | Expiration uint32 71 | Inception uint32 72 | KeyTag uint16 73 | SignerName string `dns:"domain-name"` 74 | /* No Signature */ 75 | } 76 | 77 | // Used for converting DNSKEY's rdata to wirefmt. 78 | type dnskeyWireFmt struct { 79 | Flags uint16 80 | Protocol uint8 81 | Algorithm uint8 82 | PublicKey string `dns:"base64"` 83 | /* Nothing is left out */ 84 | } 85 | 86 | func divRoundUp(a, b int) int { 87 | return (a + b - 1) / b 88 | } 89 | 90 | // KeyTag calculates the keytag (or key-id) of the DNSKEY. 91 | func (k *DNSKEY) KeyTag() uint16 { 92 | if k == nil { 93 | return 0 94 | } 95 | var keytag int 96 | switch k.Algorithm { 97 | case RSAMD5: 98 | // Look at the bottom two bytes of the modules, which the last 99 | // item in the pubkey. We could do this faster by looking directly 100 | // at the base64 values. But I'm lazy. 101 | modulus, _ := fromBase64([]byte(k.PublicKey)) 102 | if len(modulus) > 1 { 103 | x, _ := unpackUint16(modulus, len(modulus)-2) 104 | keytag = int(x) 105 | } 106 | default: 107 | keywire := new(dnskeyWireFmt) 108 | keywire.Flags = k.Flags 109 | keywire.Protocol = k.Protocol 110 | keywire.Algorithm = k.Algorithm 111 | keywire.PublicKey = k.PublicKey 112 | wire := make([]byte, DefaultMsgSize) 113 | n, err := PackStruct(keywire, wire, 0) 114 | if err != nil { 115 | return 0 116 | } 117 | wire = wire[:n] 118 | for i, v := range wire { 119 | if i&1 != 0 { 120 | keytag += int(v) // must be larger than uint32 121 | } else { 122 | keytag += int(v) << 8 123 | } 124 | } 125 | keytag += (keytag >> 16) & 0xFFFF 126 | keytag &= 0xFFFF 127 | } 128 | return uint16(keytag) 129 | } 130 | 131 | // ToDS converts a DNSKEY record to a DS record. 132 | func (k *DNSKEY) ToDS(h uint8) *DS { 133 | if k == nil { 134 | return nil 135 | } 136 | ds := new(DS) 137 | ds.Hdr.Name = k.Hdr.Name 138 | ds.Hdr.Class = k.Hdr.Class 139 | ds.Hdr.Rrtype = TypeDS 140 | ds.Hdr.Ttl = k.Hdr.Ttl 141 | ds.Algorithm = k.Algorithm 142 | ds.DigestType = h 143 | ds.KeyTag = k.KeyTag() 144 | 145 | keywire := new(dnskeyWireFmt) 146 | keywire.Flags = k.Flags 147 | keywire.Protocol = k.Protocol 148 | keywire.Algorithm = k.Algorithm 149 | keywire.PublicKey = k.PublicKey 150 | wire := make([]byte, DefaultMsgSize) 151 | n, err := PackStruct(keywire, wire, 0) 152 | if err != nil { 153 | return nil 154 | } 155 | wire = wire[:n] 156 | 157 | owner := make([]byte, 255) 158 | off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false) 159 | if err1 != nil { 160 | return nil 161 | } 162 | owner = owner[:off] 163 | // RFC4034: 164 | // digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); 165 | // "|" denotes concatenation 166 | // DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. 167 | 168 | // digest buffer 169 | digest := append(owner, wire...) // another copy 170 | 171 | switch h { 172 | case SHA1: 173 | s := sha1.New() 174 | io.WriteString(s, string(digest)) 175 | ds.Digest = hex.EncodeToString(s.Sum(nil)) 176 | case SHA256: 177 | s := sha256.New() 178 | io.WriteString(s, string(digest)) 179 | ds.Digest = hex.EncodeToString(s.Sum(nil)) 180 | case SHA384: 181 | s := sha512.New384() 182 | io.WriteString(s, string(digest)) 183 | ds.Digest = hex.EncodeToString(s.Sum(nil)) 184 | case GOST94: 185 | /* I have no clue */ 186 | default: 187 | return nil 188 | } 189 | return ds 190 | } 191 | 192 | // ToCDNSKEY converts a DNSKEY record to a CDNSKEY record. 193 | func (k *DNSKEY) ToCDNSKEY() *CDNSKEY { 194 | c := &CDNSKEY{DNSKEY: *k} 195 | c.Hdr = *k.Hdr.copyHeader() 196 | c.Hdr.Rrtype = TypeCDNSKEY 197 | return c 198 | } 199 | 200 | // ToCDS converts a DS record to a CDS record. 201 | func (d *DS) ToCDS() *CDS { 202 | c := &CDS{DS: *d} 203 | c.Hdr = *d.Hdr.copyHeader() 204 | c.Hdr.Rrtype = TypeCDS 205 | return c 206 | } 207 | 208 | // Sign signs an RRSet. The signature needs to be filled in with 209 | // the values: Inception, Expiration, KeyTag, SignerName and Algorithm. 210 | // The rest is copied from the RRset. Sign returns true when the signing went OK, 211 | // otherwise false. 212 | // There is no check if RRSet is a proper (RFC 2181) RRSet. 213 | // If OrigTTL is non zero, it is used as-is, otherwise the TTL of the RRset 214 | // is used as the OrigTTL. 215 | func (rr *RRSIG) Sign(k PrivateKey, rrset []RR) error { 216 | if k == nil { 217 | return ErrPrivKey 218 | } 219 | // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set 220 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 221 | return ErrKey 222 | } 223 | 224 | rr.Hdr.Rrtype = TypeRRSIG 225 | rr.Hdr.Name = rrset[0].Header().Name 226 | rr.Hdr.Class = rrset[0].Header().Class 227 | if rr.OrigTtl == 0 { // If set don't override 228 | rr.OrigTtl = rrset[0].Header().Ttl 229 | } 230 | rr.TypeCovered = rrset[0].Header().Rrtype 231 | rr.Labels = uint8(CountLabel(rrset[0].Header().Name)) 232 | 233 | if strings.HasPrefix(rrset[0].Header().Name, "*") { 234 | rr.Labels-- // wildcard, remove from label count 235 | } 236 | 237 | sigwire := new(rrsigWireFmt) 238 | sigwire.TypeCovered = rr.TypeCovered 239 | sigwire.Algorithm = rr.Algorithm 240 | sigwire.Labels = rr.Labels 241 | sigwire.OrigTtl = rr.OrigTtl 242 | sigwire.Expiration = rr.Expiration 243 | sigwire.Inception = rr.Inception 244 | sigwire.KeyTag = rr.KeyTag 245 | // For signing, lowercase this name 246 | sigwire.SignerName = strings.ToLower(rr.SignerName) 247 | 248 | // Create the desired binary blob 249 | signdata := make([]byte, DefaultMsgSize) 250 | n, err := PackStruct(sigwire, signdata, 0) 251 | if err != nil { 252 | return err 253 | } 254 | signdata = signdata[:n] 255 | wire, err := rawSignatureData(rrset, rr) 256 | if err != nil { 257 | return err 258 | } 259 | signdata = append(signdata, wire...) 260 | 261 | var h hash.Hash 262 | switch rr.Algorithm { 263 | case DSA, DSANSEC3SHA1: 264 | // TODO: this seems bugged, will panic 265 | case RSASHA1, RSASHA1NSEC3SHA1: 266 | h = sha1.New() 267 | case RSASHA256, ECDSAP256SHA256: 268 | h = sha256.New() 269 | case ECDSAP384SHA384: 270 | h = sha512.New384() 271 | case RSASHA512: 272 | h = sha512.New() 273 | case RSAMD5: 274 | fallthrough // Deprecated in RFC 6725 275 | default: 276 | return ErrAlg 277 | } 278 | 279 | _, err = h.Write(signdata) 280 | if err != nil { 281 | return err 282 | } 283 | sighash := h.Sum(nil) 284 | 285 | signature, err := k.Sign(sighash, rr.Algorithm) 286 | if err != nil { 287 | return err 288 | } 289 | rr.Signature = toBase64(signature) 290 | 291 | return nil 292 | } 293 | 294 | // Verify validates an RRSet with the signature and key. This is only the 295 | // cryptographic test, the signature validity period must be checked separately. 296 | // This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. 297 | func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { 298 | // First the easy checks 299 | if !IsRRset(rrset) { 300 | return ErrRRset 301 | } 302 | if rr.KeyTag != k.KeyTag() { 303 | return ErrKey 304 | } 305 | if rr.Hdr.Class != k.Hdr.Class { 306 | return ErrKey 307 | } 308 | if rr.Algorithm != k.Algorithm { 309 | return ErrKey 310 | } 311 | if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) { 312 | return ErrKey 313 | } 314 | if k.Protocol != 3 { 315 | return ErrKey 316 | } 317 | 318 | // IsRRset checked that we have at least one RR and that the RRs in 319 | // the set have consistent type, class, and name. Also check that type and 320 | // class matches the RRSIG record. 321 | if rrset[0].Header().Class != rr.Hdr.Class { 322 | return ErrRRset 323 | } 324 | if rrset[0].Header().Rrtype != rr.TypeCovered { 325 | return ErrRRset 326 | } 327 | 328 | // RFC 4035 5.3.2. Reconstructing the Signed Data 329 | // Copy the sig, except the rrsig data 330 | sigwire := new(rrsigWireFmt) 331 | sigwire.TypeCovered = rr.TypeCovered 332 | sigwire.Algorithm = rr.Algorithm 333 | sigwire.Labels = rr.Labels 334 | sigwire.OrigTtl = rr.OrigTtl 335 | sigwire.Expiration = rr.Expiration 336 | sigwire.Inception = rr.Inception 337 | sigwire.KeyTag = rr.KeyTag 338 | sigwire.SignerName = strings.ToLower(rr.SignerName) 339 | // Create the desired binary blob 340 | signeddata := make([]byte, DefaultMsgSize) 341 | n, err := PackStruct(sigwire, signeddata, 0) 342 | if err != nil { 343 | return err 344 | } 345 | signeddata = signeddata[:n] 346 | wire, err := rawSignatureData(rrset, rr) 347 | if err != nil { 348 | return err 349 | } 350 | signeddata = append(signeddata, wire...) 351 | 352 | sigbuf := rr.sigBuf() // Get the binary signature data 353 | if rr.Algorithm == PRIVATEDNS { // PRIVATEOID 354 | // TODO(mg) 355 | // remove the domain name and assume its our 356 | } 357 | 358 | switch rr.Algorithm { 359 | case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5: 360 | // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? 361 | pubkey := k.publicKeyRSA() // Get the key 362 | if pubkey == nil { 363 | return ErrKey 364 | } 365 | // Setup the hash as defined for this alg. 366 | var h hash.Hash 367 | var ch crypto.Hash 368 | switch rr.Algorithm { 369 | case RSAMD5: 370 | h = md5.New() 371 | ch = crypto.MD5 372 | case RSASHA1, RSASHA1NSEC3SHA1: 373 | h = sha1.New() 374 | ch = crypto.SHA1 375 | case RSASHA256: 376 | h = sha256.New() 377 | ch = crypto.SHA256 378 | case RSASHA512: 379 | h = sha512.New() 380 | ch = crypto.SHA512 381 | } 382 | io.WriteString(h, string(signeddata)) 383 | sighash := h.Sum(nil) 384 | return rsa.VerifyPKCS1v15(pubkey, ch, sighash, sigbuf) 385 | case ECDSAP256SHA256, ECDSAP384SHA384: 386 | pubkey := k.publicKeyECDSA() 387 | if pubkey == nil { 388 | return ErrKey 389 | } 390 | var h hash.Hash 391 | switch rr.Algorithm { 392 | case ECDSAP256SHA256: 393 | h = sha256.New() 394 | case ECDSAP384SHA384: 395 | h = sha512.New384() 396 | } 397 | io.WriteString(h, string(signeddata)) 398 | sighash := h.Sum(nil) 399 | // Split sigbuf into the r and s coordinates 400 | r := big.NewInt(0) 401 | r.SetBytes(sigbuf[:len(sigbuf)/2]) 402 | s := big.NewInt(0) 403 | s.SetBytes(sigbuf[len(sigbuf)/2:]) 404 | if ecdsa.Verify(pubkey, sighash, r, s) { 405 | return nil 406 | } 407 | return ErrSig 408 | } 409 | // Unknown alg 410 | return ErrAlg 411 | } 412 | 413 | // ValidityPeriod uses RFC1982 serial arithmetic to calculate 414 | // if a signature period is valid. If t is the zero time, the 415 | // current time is taken other t is. Returns true if the signature 416 | // is valid at the given time, otherwise returns false. 417 | func (rr *RRSIG) ValidityPeriod(t time.Time) bool { 418 | var utc int64 419 | if t.IsZero() { 420 | utc = time.Now().UTC().Unix() 421 | } else { 422 | utc = t.UTC().Unix() 423 | } 424 | modi := (int64(rr.Inception) - utc) / year68 425 | mode := (int64(rr.Expiration) - utc) / year68 426 | ti := int64(rr.Inception) + (modi * year68) 427 | te := int64(rr.Expiration) + (mode * year68) 428 | return ti <= utc && utc <= te 429 | } 430 | 431 | // Return the signatures base64 encodedig sigdata as a byte slice. 432 | func (rr *RRSIG) sigBuf() []byte { 433 | sigbuf, err := fromBase64([]byte(rr.Signature)) 434 | if err != nil { 435 | return nil 436 | } 437 | return sigbuf 438 | } 439 | 440 | // publicKeyRSA returns the RSA public key from a DNSKEY record. 441 | func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey { 442 | keybuf, err := fromBase64([]byte(k.PublicKey)) 443 | if err != nil { 444 | return nil 445 | } 446 | 447 | // RFC 2537/3110, section 2. RSA Public KEY Resource Records 448 | // Length is in the 0th byte, unless its zero, then it 449 | // it in bytes 1 and 2 and its a 16 bit number 450 | explen := uint16(keybuf[0]) 451 | keyoff := 1 452 | if explen == 0 { 453 | explen = uint16(keybuf[1])<<8 | uint16(keybuf[2]) 454 | keyoff = 3 455 | } 456 | pubkey := new(rsa.PublicKey) 457 | 458 | pubkey.N = big.NewInt(0) 459 | shift := uint64((explen - 1) * 8) 460 | expo := uint64(0) 461 | for i := int(explen - 1); i > 0; i-- { 462 | expo += uint64(keybuf[keyoff+i]) << shift 463 | shift -= 8 464 | } 465 | // Remainder 466 | expo += uint64(keybuf[keyoff]) 467 | if expo > 2<<31 { 468 | // Larger expo than supported. 469 | // println("dns: F5 primes (or larger) are not supported") 470 | return nil 471 | } 472 | pubkey.E = int(expo) 473 | 474 | pubkey.N.SetBytes(keybuf[keyoff+int(explen):]) 475 | return pubkey 476 | } 477 | 478 | // publicKeyECDSA returns the Curve public key from the DNSKEY record. 479 | func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey { 480 | keybuf, err := fromBase64([]byte(k.PublicKey)) 481 | if err != nil { 482 | return nil 483 | } 484 | pubkey := new(ecdsa.PublicKey) 485 | switch k.Algorithm { 486 | case ECDSAP256SHA256: 487 | pubkey.Curve = elliptic.P256() 488 | if len(keybuf) != 64 { 489 | // wrongly encoded key 490 | return nil 491 | } 492 | case ECDSAP384SHA384: 493 | pubkey.Curve = elliptic.P384() 494 | if len(keybuf) != 96 { 495 | // Wrongly encoded key 496 | return nil 497 | } 498 | } 499 | pubkey.X = big.NewInt(0) 500 | pubkey.X.SetBytes(keybuf[:len(keybuf)/2]) 501 | pubkey.Y = big.NewInt(0) 502 | pubkey.Y.SetBytes(keybuf[len(keybuf)/2:]) 503 | return pubkey 504 | } 505 | 506 | func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey { 507 | keybuf, err := fromBase64([]byte(k.PublicKey)) 508 | if err != nil { 509 | return nil 510 | } 511 | if len(keybuf) < 22 { 512 | return nil 513 | } 514 | t, keybuf := int(keybuf[0]), keybuf[1:] 515 | size := 64 + t*8 516 | q, keybuf := keybuf[:20], keybuf[20:] 517 | if len(keybuf) != 3*size { 518 | return nil 519 | } 520 | p, keybuf := keybuf[:size], keybuf[size:] 521 | g, y := keybuf[:size], keybuf[size:] 522 | pubkey := new(dsa.PublicKey) 523 | pubkey.Parameters.Q = big.NewInt(0).SetBytes(q) 524 | pubkey.Parameters.P = big.NewInt(0).SetBytes(p) 525 | pubkey.Parameters.G = big.NewInt(0).SetBytes(g) 526 | pubkey.Y = big.NewInt(0).SetBytes(y) 527 | return pubkey 528 | } 529 | 530 | type wireSlice [][]byte 531 | 532 | func (p wireSlice) Len() int { return len(p) } 533 | func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 534 | func (p wireSlice) Less(i, j int) bool { 535 | _, ioff, _ := UnpackDomainName(p[i], 0) 536 | _, joff, _ := UnpackDomainName(p[j], 0) 537 | return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0 538 | } 539 | 540 | // Return the raw signature data. 541 | func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) { 542 | wires := make(wireSlice, len(rrset)) 543 | for i, r := range rrset { 544 | r1 := r.copy() 545 | r1.Header().Ttl = s.OrigTtl 546 | labels := SplitDomainName(r1.Header().Name) 547 | // 6.2. Canonical RR Form. (4) - wildcards 548 | if len(labels) > int(s.Labels) { 549 | // Wildcard 550 | r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." 551 | } 552 | // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase 553 | r1.Header().Name = strings.ToLower(r1.Header().Name) 554 | // 6.2. Canonical RR Form. (3) - domain rdata to lowercase. 555 | // NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, 556 | // HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, 557 | // SRV, DNAME, A6 558 | switch x := r1.(type) { 559 | case *NS: 560 | x.Ns = strings.ToLower(x.Ns) 561 | case *CNAME: 562 | x.Target = strings.ToLower(x.Target) 563 | case *SOA: 564 | x.Ns = strings.ToLower(x.Ns) 565 | x.Mbox = strings.ToLower(x.Mbox) 566 | case *MB: 567 | x.Mb = strings.ToLower(x.Mb) 568 | case *MG: 569 | x.Mg = strings.ToLower(x.Mg) 570 | case *MR: 571 | x.Mr = strings.ToLower(x.Mr) 572 | case *PTR: 573 | x.Ptr = strings.ToLower(x.Ptr) 574 | case *MINFO: 575 | x.Rmail = strings.ToLower(x.Rmail) 576 | x.Email = strings.ToLower(x.Email) 577 | case *MX: 578 | x.Mx = strings.ToLower(x.Mx) 579 | case *NAPTR: 580 | x.Replacement = strings.ToLower(x.Replacement) 581 | case *KX: 582 | x.Exchanger = strings.ToLower(x.Exchanger) 583 | case *SRV: 584 | x.Target = strings.ToLower(x.Target) 585 | case *DNAME: 586 | x.Target = strings.ToLower(x.Target) 587 | } 588 | // 6.2. Canonical RR Form. (5) - origTTL 589 | wire := make([]byte, r1.len()+1) // +1 to be safe(r) 590 | off, err1 := PackRR(r1, wire, 0, nil, false) 591 | if err1 != nil { 592 | return nil, err1 593 | } 594 | wire = wire[:off] 595 | wires[i] = wire 596 | } 597 | sort.Sort(wires) 598 | for i, wire := range wires { 599 | if i > 0 && bytes.Equal(wire, wires[i-1]) { 600 | continue 601 | } 602 | buf = append(buf, wire...) 603 | } 604 | return buf, nil 605 | } 606 | 607 | // Map for algorithm names. 608 | var AlgorithmToString = map[uint8]string{ 609 | RSAMD5: "RSAMD5", 610 | DH: "DH", 611 | DSA: "DSA", 612 | RSASHA1: "RSASHA1", 613 | DSANSEC3SHA1: "DSA-NSEC3-SHA1", 614 | RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1", 615 | RSASHA256: "RSASHA256", 616 | RSASHA512: "RSASHA512", 617 | ECCGOST: "ECC-GOST", 618 | ECDSAP256SHA256: "ECDSAP256SHA256", 619 | ECDSAP384SHA384: "ECDSAP384SHA384", 620 | INDIRECT: "INDIRECT", 621 | PRIVATEDNS: "PRIVATEDNS", 622 | PRIVATEOID: "PRIVATEOID", 623 | } 624 | 625 | // Map of algorithm strings. 626 | var StringToAlgorithm = reverseInt8(AlgorithmToString) 627 | 628 | // Map for hash names. 629 | var HashToString = map[uint8]string{ 630 | SHA1: "SHA1", 631 | SHA256: "SHA256", 632 | GOST94: "GOST94", 633 | SHA384: "SHA384", 634 | SHA512: "SHA512", 635 | } 636 | 637 | // Map of hash strings. 638 | var StringToHash = reverseInt8(HashToString) 639 | -------------------------------------------------------------------------------- /dnssec_keygen.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/dsa" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "math/big" 10 | ) 11 | 12 | // Generate generates a DNSKEY of the given bit size. 13 | // The public part is put inside the DNSKEY record. 14 | // The Algorithm in the key must be set as this will define 15 | // what kind of DNSKEY will be generated. 16 | // The ECDSA algorithms imply a fixed keysize, in that case 17 | // bits should be set to the size of the algorithm. 18 | func (k *DNSKEY) Generate(bits int) (PrivateKey, error) { 19 | switch k.Algorithm { 20 | case DSA, DSANSEC3SHA1: 21 | if bits != 1024 { 22 | return nil, ErrKeySize 23 | } 24 | case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: 25 | if bits < 512 || bits > 4096 { 26 | return nil, ErrKeySize 27 | } 28 | case RSASHA512: 29 | if bits < 1024 || bits > 4096 { 30 | return nil, ErrKeySize 31 | } 32 | case ECDSAP256SHA256: 33 | if bits != 256 { 34 | return nil, ErrKeySize 35 | } 36 | case ECDSAP384SHA384: 37 | if bits != 384 { 38 | return nil, ErrKeySize 39 | } 40 | } 41 | 42 | switch k.Algorithm { 43 | case DSA, DSANSEC3SHA1: 44 | params := new(dsa.Parameters) 45 | if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { 46 | return nil, err 47 | } 48 | priv := new(dsa.PrivateKey) 49 | priv.PublicKey.Parameters = *params 50 | err := dsa.GenerateKey(priv, rand.Reader) 51 | if err != nil { 52 | return nil, err 53 | } 54 | k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) 55 | return (*DSAPrivateKey)(priv), nil 56 | case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: 57 | priv, err := rsa.GenerateKey(rand.Reader, bits) 58 | if err != nil { 59 | return nil, err 60 | } 61 | k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) 62 | return (*RSAPrivateKey)(priv), nil 63 | case ECDSAP256SHA256, ECDSAP384SHA384: 64 | var c elliptic.Curve 65 | switch k.Algorithm { 66 | case ECDSAP256SHA256: 67 | c = elliptic.P256() 68 | case ECDSAP384SHA384: 69 | c = elliptic.P384() 70 | } 71 | priv, err := ecdsa.GenerateKey(c, rand.Reader) 72 | if err != nil { 73 | return nil, err 74 | } 75 | k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) 76 | return (*ECDSAPrivateKey)(priv), nil 77 | default: 78 | return nil, ErrAlg 79 | } 80 | } 81 | 82 | // Set the public key (the value E and N) 83 | func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { 84 | if _E == 0 || _N == nil { 85 | return false 86 | } 87 | buf := exponentToBuf(_E) 88 | buf = append(buf, _N.Bytes()...) 89 | k.PublicKey = toBase64(buf) 90 | return true 91 | } 92 | 93 | // Set the public key for Elliptic Curves 94 | func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { 95 | if _X == nil || _Y == nil { 96 | return false 97 | } 98 | var intlen int 99 | switch k.Algorithm { 100 | case ECDSAP256SHA256: 101 | intlen = 32 102 | case ECDSAP384SHA384: 103 | intlen = 48 104 | } 105 | k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) 106 | return true 107 | } 108 | 109 | // Set the public key for DSA 110 | func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { 111 | if _Q == nil || _P == nil || _G == nil || _Y == nil { 112 | return false 113 | } 114 | buf := dsaToBuf(_Q, _P, _G, _Y) 115 | k.PublicKey = toBase64(buf) 116 | return true 117 | } 118 | 119 | // Set the public key (the values E and N) for RSA 120 | // RFC 3110: Section 2. RSA Public KEY Resource Records 121 | func exponentToBuf(_E int) []byte { 122 | var buf []byte 123 | i := big.NewInt(int64(_E)) 124 | if len(i.Bytes()) < 256 { 125 | buf = make([]byte, 1) 126 | buf[0] = uint8(len(i.Bytes())) 127 | } else { 128 | buf = make([]byte, 3) 129 | buf[0] = 0 130 | buf[1] = uint8(len(i.Bytes()) >> 8) 131 | buf[2] = uint8(len(i.Bytes())) 132 | } 133 | buf = append(buf, i.Bytes()...) 134 | return buf 135 | } 136 | 137 | // Set the public key for X and Y for Curve. The two 138 | // values are just concatenated. 139 | func curveToBuf(_X, _Y *big.Int, intlen int) []byte { 140 | buf := intToBytes(_X, intlen) 141 | buf = append(buf, intToBytes(_Y, intlen)...) 142 | return buf 143 | } 144 | 145 | // Set the public key for X and Y for Curve. The two 146 | // values are just concatenated. 147 | func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { 148 | t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) 149 | buf := []byte{byte(t)} 150 | buf = append(buf, intToBytes(_Q, 20)...) 151 | buf = append(buf, intToBytes(_P, 64+t*8)...) 152 | buf = append(buf, intToBytes(_G, 64+t*8)...) 153 | buf = append(buf, intToBytes(_Y, 64+t*8)...) 154 | return buf 155 | } 156 | -------------------------------------------------------------------------------- /dnssec_keyscan.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/dsa" 5 | "crypto/ecdsa" 6 | "crypto/rsa" 7 | "io" 8 | "math/big" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // NewPrivateKey returns a PrivateKey by parsing the string s. 14 | // s should be in the same form of the BIND private key files. 15 | func (k *DNSKEY) NewPrivateKey(s string) (PrivateKey, error) { 16 | if s[len(s)-1] != '\n' { // We need a closing newline 17 | return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") 18 | } 19 | return k.ReadPrivateKey(strings.NewReader(s), "") 20 | } 21 | 22 | // ReadPrivateKey reads a private key from the io.Reader q. The string file is 23 | // only used in error reporting. 24 | // The public key must be known, because some cryptographic algorithms embed 25 | // the public inside the privatekey. 26 | func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (PrivateKey, error) { 27 | m, e := parseKey(q, file) 28 | if m == nil { 29 | return nil, e 30 | } 31 | if _, ok := m["private-key-format"]; !ok { 32 | return nil, ErrPrivKey 33 | } 34 | if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { 35 | return nil, ErrPrivKey 36 | } 37 | // TODO(mg): check if the pubkey matches the private key 38 | algo, err := strconv.Atoi(strings.SplitN(m["algorithm"], " ", 2)[0]) 39 | if err != nil { 40 | return nil, ErrPrivKey 41 | } 42 | switch uint8(algo) { 43 | case DSA: 44 | priv, e := readPrivateKeyDSA(m) 45 | if e != nil { 46 | return nil, e 47 | } 48 | pub := k.publicKeyDSA() 49 | if pub == nil { 50 | return nil, ErrKey 51 | } 52 | priv.PublicKey = *pub 53 | return (*DSAPrivateKey)(priv), e 54 | case RSAMD5: 55 | fallthrough 56 | case RSASHA1: 57 | fallthrough 58 | case RSASHA1NSEC3SHA1: 59 | fallthrough 60 | case RSASHA256: 61 | fallthrough 62 | case RSASHA512: 63 | priv, e := readPrivateKeyRSA(m) 64 | if e != nil { 65 | return nil, e 66 | } 67 | pub := k.publicKeyRSA() 68 | if pub == nil { 69 | return nil, ErrKey 70 | } 71 | priv.PublicKey = *pub 72 | return (*RSAPrivateKey)(priv), e 73 | case ECCGOST: 74 | return nil, ErrPrivKey 75 | case ECDSAP256SHA256: 76 | fallthrough 77 | case ECDSAP384SHA384: 78 | priv, e := readPrivateKeyECDSA(m) 79 | if e != nil { 80 | return nil, e 81 | } 82 | pub := k.publicKeyECDSA() 83 | if pub == nil { 84 | return nil, ErrKey 85 | } 86 | priv.PublicKey = *pub 87 | return (*ECDSAPrivateKey)(priv), e 88 | default: 89 | return nil, ErrPrivKey 90 | } 91 | } 92 | 93 | // Read a private key (file) string and create a public key. Return the private key. 94 | func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { 95 | p := new(rsa.PrivateKey) 96 | p.Primes = []*big.Int{nil, nil} 97 | for k, v := range m { 98 | switch k { 99 | case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": 100 | v1, err := fromBase64([]byte(v)) 101 | if err != nil { 102 | return nil, err 103 | } 104 | switch k { 105 | case "modulus": 106 | p.PublicKey.N = big.NewInt(0) 107 | p.PublicKey.N.SetBytes(v1) 108 | case "publicexponent": 109 | i := big.NewInt(0) 110 | i.SetBytes(v1) 111 | p.PublicKey.E = int(i.Int64()) // int64 should be large enough 112 | case "privateexponent": 113 | p.D = big.NewInt(0) 114 | p.D.SetBytes(v1) 115 | case "prime1": 116 | p.Primes[0] = big.NewInt(0) 117 | p.Primes[0].SetBytes(v1) 118 | case "prime2": 119 | p.Primes[1] = big.NewInt(0) 120 | p.Primes[1].SetBytes(v1) 121 | } 122 | case "exponent1", "exponent2", "coefficient": 123 | // not used in Go (yet) 124 | case "created", "publish", "activate": 125 | // not used in Go (yet) 126 | } 127 | } 128 | return p, nil 129 | } 130 | 131 | func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) { 132 | p := new(dsa.PrivateKey) 133 | p.X = big.NewInt(0) 134 | for k, v := range m { 135 | switch k { 136 | case "private_value(x)": 137 | v1, err := fromBase64([]byte(v)) 138 | if err != nil { 139 | return nil, err 140 | } 141 | p.X.SetBytes(v1) 142 | case "created", "publish", "activate": 143 | /* not used in Go (yet) */ 144 | } 145 | } 146 | return p, nil 147 | } 148 | 149 | func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { 150 | p := new(ecdsa.PrivateKey) 151 | p.D = big.NewInt(0) 152 | // TODO: validate that the required flags are present 153 | for k, v := range m { 154 | switch k { 155 | case "privatekey": 156 | v1, err := fromBase64([]byte(v)) 157 | if err != nil { 158 | return nil, err 159 | } 160 | p.D.SetBytes(v1) 161 | case "created", "publish", "activate": 162 | /* not used in Go (yet) */ 163 | } 164 | } 165 | return p, nil 166 | } 167 | 168 | // parseKey reads a private key from r. It returns a map[string]string, 169 | // with the key-value pairs, or an error when the file is not correct. 170 | func parseKey(r io.Reader, file string) (map[string]string, error) { 171 | s := scanInit(r) 172 | m := make(map[string]string) 173 | c := make(chan lex) 174 | k := "" 175 | // Start the lexer 176 | go klexer(s, c) 177 | for l := range c { 178 | // It should alternate 179 | switch l.value { 180 | case zKey: 181 | k = l.token 182 | case zValue: 183 | if k == "" { 184 | return nil, &ParseError{file, "no private key seen", l} 185 | } 186 | //println("Setting", strings.ToLower(k), "to", l.token, "b") 187 | m[strings.ToLower(k)] = l.token 188 | k = "" 189 | } 190 | } 191 | return m, nil 192 | } 193 | 194 | // klexer scans the sourcefile and returns tokens on the channel c. 195 | func klexer(s *scan, c chan lex) { 196 | var l lex 197 | str := "" // Hold the current read text 198 | commt := false 199 | key := true 200 | x, err := s.tokenText() 201 | defer close(c) 202 | for err == nil { 203 | l.column = s.position.Column 204 | l.line = s.position.Line 205 | switch x { 206 | case ':': 207 | if commt { 208 | break 209 | } 210 | l.token = str 211 | if key { 212 | l.value = zKey 213 | c <- l 214 | // Next token is a space, eat it 215 | s.tokenText() 216 | key = false 217 | str = "" 218 | } else { 219 | l.value = zValue 220 | } 221 | case ';': 222 | commt = true 223 | case '\n': 224 | if commt { 225 | // Reset a comment 226 | commt = false 227 | } 228 | l.value = zValue 229 | l.token = str 230 | c <- l 231 | str = "" 232 | commt = false 233 | key = true 234 | default: 235 | if commt { 236 | break 237 | } 238 | str += string(x) 239 | } 240 | x, err = s.tokenText() 241 | } 242 | if len(str) > 0 { 243 | // Send remainder 244 | l.token = str 245 | l.value = zValue 246 | c <- l 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /dnssec_privkey.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "math/big" 10 | "strconv" 11 | ) 12 | 13 | const format = "Private-key-format: v1.3\n" 14 | 15 | // PrivateKey ... TODO(miek) 16 | type PrivateKey interface { 17 | Sign([]byte, uint8) ([]byte, error) 18 | String(uint8) string 19 | } 20 | 21 | // PrivateKeyString converts a PrivateKey to a string. This string has the same 22 | // format as the private-key-file of BIND9 (Private-key-format: v1.3). 23 | // It needs some info from the key (the algorithm), so its a method of the 24 | // DNSKEY and calls PrivateKey.String(alg). 25 | func (r *DNSKEY) PrivateKeyString(p PrivateKey) string { 26 | return p.String(r.Algorithm) 27 | } 28 | 29 | type RSAPrivateKey rsa.PrivateKey 30 | 31 | func (p *RSAPrivateKey) Sign(hashed []byte, alg uint8) ([]byte, error) { 32 | var hash crypto.Hash 33 | switch alg { 34 | case RSASHA1, RSASHA1NSEC3SHA1: 35 | hash = crypto.SHA1 36 | case RSASHA256: 37 | hash = crypto.SHA256 38 | case RSASHA512: 39 | hash = crypto.SHA512 40 | default: 41 | return nil, ErrAlg 42 | } 43 | return rsa.SignPKCS1v15(nil, (*rsa.PrivateKey)(p), hash, hashed) 44 | } 45 | 46 | func (p *RSAPrivateKey) String(alg uint8) string { 47 | algorithm := strconv.Itoa(int(alg)) + " (" + AlgorithmToString[alg] + ")" 48 | modulus := toBase64(p.PublicKey.N.Bytes()) 49 | e := big.NewInt(int64(p.PublicKey.E)) 50 | publicExponent := toBase64(e.Bytes()) 51 | privateExponent := toBase64(p.D.Bytes()) 52 | prime1 := toBase64(p.Primes[0].Bytes()) 53 | prime2 := toBase64(p.Primes[1].Bytes()) 54 | // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm 55 | // and from: http://code.google.com/p/go/issues/detail?id=987 56 | one := big.NewInt(1) 57 | p1 := big.NewInt(0).Sub(p.Primes[0], one) 58 | q1 := big.NewInt(0).Sub(p.Primes[1], one) 59 | exp1 := big.NewInt(0).Mod(p.D, p1) 60 | exp2 := big.NewInt(0).Mod(p.D, q1) 61 | coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0]) 62 | 63 | exponent1 := toBase64(exp1.Bytes()) 64 | exponent2 := toBase64(exp2.Bytes()) 65 | coefficient := toBase64(coeff.Bytes()) 66 | 67 | return format + 68 | "Algorithm: " + algorithm + "\n" + 69 | "Modulus: " + modulus + "\n" + 70 | "PublicExponent: " + publicExponent + "\n" + 71 | "PrivateExponent: " + privateExponent + "\n" + 72 | "Prime1: " + prime1 + "\n" + 73 | "Prime2: " + prime2 + "\n" + 74 | "Exponent1: " + exponent1 + "\n" + 75 | "Exponent2: " + exponent2 + "\n" + 76 | "Coefficient: " + coefficient + "\n" 77 | } 78 | 79 | type ECDSAPrivateKey ecdsa.PrivateKey 80 | 81 | func (p *ECDSAPrivateKey) Sign(hashed []byte, alg uint8) ([]byte, error) { 82 | var intlen int 83 | switch alg { 84 | case ECDSAP256SHA256: 85 | intlen = 32 86 | case ECDSAP384SHA384: 87 | intlen = 48 88 | default: 89 | return nil, ErrAlg 90 | } 91 | r1, s1, err := ecdsa.Sign(rand.Reader, (*ecdsa.PrivateKey)(p), hashed) 92 | if err != nil { 93 | return nil, err 94 | } 95 | signature := intToBytes(r1, intlen) 96 | signature = append(signature, intToBytes(s1, intlen)...) 97 | return signature, nil 98 | } 99 | 100 | func (p *ECDSAPrivateKey) String(alg uint8) string { 101 | algorithm := strconv.Itoa(int(alg)) + " (" + AlgorithmToString[alg] + ")" 102 | var intlen int 103 | switch alg { 104 | case ECDSAP256SHA256: 105 | intlen = 32 106 | case ECDSAP384SHA384: 107 | intlen = 48 108 | } 109 | private := toBase64(intToBytes(p.D, intlen)) 110 | return format + 111 | "Algorithm: " + algorithm + "\n" + 112 | "PrivateKey: " + private + "\n" 113 | } 114 | 115 | type DSAPrivateKey dsa.PrivateKey 116 | 117 | func (p *DSAPrivateKey) Sign(hashed []byte, alg uint8) ([]byte, error) { 118 | r1, s1, err := dsa.Sign(rand.Reader, (*dsa.PrivateKey)(p), hashed) 119 | if err != nil { 120 | return nil, err 121 | } 122 | t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8) 123 | signature := []byte{byte(t)} 124 | signature = append(signature, intToBytes(r1, 20)...) 125 | signature = append(signature, intToBytes(s1, 20)...) 126 | return signature, nil 127 | } 128 | 129 | func (p *DSAPrivateKey) String(alg uint8) string { 130 | algorithm := strconv.Itoa(int(alg)) + " (" + AlgorithmToString[alg] + ")" 131 | T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) 132 | prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) 133 | subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) 134 | base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) 135 | priv := toBase64(intToBytes(p.X, 20)) 136 | pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) 137 | return format + 138 | "Algorithm: " + algorithm + "\n" + 139 | "Prime(p): " + prime + "\n" + 140 | "Subprime(q): " + subprime + "\n" + 141 | "Base(g): " + base + "\n" + 142 | "Private_value(x): " + priv + "\n" + 143 | "Public_value(y): " + pub + "\n" 144 | } 145 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package dns implements a full featured interface to the Domain Name System. 3 | Server- and client-side programming is supported. 4 | The package allows complete control over what is send out to the DNS. The package 5 | API follows the less-is-more principle, by presenting a small, clean interface. 6 | 7 | The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, 8 | TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. 9 | Note that domain names MUST be fully qualified, before sending them, unqualified 10 | names in a message will result in a packing failure. 11 | 12 | Resource records are native types. They are not stored in wire format. 13 | Basic usage pattern for creating a new resource record: 14 | 15 | r := new(dns.MX) 16 | r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600} 17 | r.Preference = 10 18 | r.Mx = "mx.miek.nl." 19 | 20 | Or directly from a string: 21 | 22 | mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") 23 | 24 | Or when the default TTL (3600) and class (IN) suit you: 25 | 26 | mx, err := dns.NewRR("miek.nl. MX 10 mx.miek.nl.") 27 | 28 | Or even: 29 | 30 | mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") 31 | 32 | In the DNS messages are exchanged, these messages contain resource 33 | records (sets). Use pattern for creating a message: 34 | 35 | m := new(dns.Msg) 36 | m.SetQuestion("miek.nl.", dns.TypeMX) 37 | 38 | Or when not certain if the domain name is fully qualified: 39 | 40 | m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) 41 | 42 | The message m is now a message with the question section set to ask 43 | the MX records for the miek.nl. zone. 44 | 45 | The following is slightly more verbose, but more flexible: 46 | 47 | m1 := new(dns.Msg) 48 | m1.Id = dns.Id() 49 | m1.RecursionDesired = true 50 | m1.Question = make([]dns.Question, 1) 51 | m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} 52 | 53 | After creating a message it can be send. 54 | Basic use pattern for synchronous querying the DNS at a 55 | server configured on 127.0.0.1 and port 53: 56 | 57 | c := new(dns.Client) 58 | in, rtt, err := c.Exchange(m1, "127.0.0.1:53") 59 | 60 | Suppressing 61 | multiple outstanding queries (with the same question, type and class) is as easy as setting: 62 | 63 | c.SingleInflight = true 64 | 65 | If these "advanced" features are not needed, a simple UDP query can be send, 66 | with: 67 | 68 | in, err := dns.Exchange(m1, "127.0.0.1:53") 69 | 70 | When this functions returns you will get dns message. A dns message consists 71 | out of four sections. 72 | The question section: in.Question, the answer section: in.Answer, 73 | the authority section: in.Ns and the additional section: in.Extra. 74 | 75 | Each of these sections (except the Question section) contain a []RR. Basic 76 | use pattern for accessing the rdata of a TXT RR as the first RR in 77 | the Answer section: 78 | 79 | if t, ok := in.Answer[0].(*dns.TXT); ok { 80 | // do something with t.Txt 81 | } 82 | 83 | Domain Name and TXT Character String Representations 84 | 85 | Both domain names and TXT character strings are converted to presentation 86 | form both when unpacked and when converted to strings. 87 | 88 | For TXT character strings, tabs, carriage returns and line feeds will be 89 | converted to \t, \r and \n respectively. Back slashes and quotations marks 90 | will be escaped. Bytes below 32 and above 127 will be converted to \DDD 91 | form. 92 | 93 | For domain names, in addition to the above rules brackets, periods, 94 | spaces, semicolons and the at symbol are escaped. 95 | 96 | DNSSEC 97 | 98 | DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It 99 | uses public key cryptography to sign resource records. The 100 | public keys are stored in DNSKEY records and the signatures in RRSIG records. 101 | 102 | Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit 103 | to an request. 104 | 105 | m := new(dns.Msg) 106 | m.SetEdns0(4096, true) 107 | 108 | Signature generation, signature verification and key generation are all supported. 109 | 110 | DYNAMIC UPDATES 111 | 112 | Dynamic updates reuses the DNS message format, but renames three of 113 | the sections. Question is Zone, Answer is Prerequisite, Authority is 114 | Update, only the Additional is not renamed. See RFC 2136 for the gory details. 115 | 116 | You can set a rather complex set of rules for the existence of absence of 117 | certain resource records or names in a zone to specify if resource records 118 | should be added or removed. The table from RFC 2136 supplemented with the Go 119 | DNS function shows which functions exist to specify the prerequisites. 120 | 121 | 3.2.4 - Table Of Metavalues Used In Prerequisite Section 122 | 123 | CLASS TYPE RDATA Meaning Function 124 | -------------------------------------------------------------- 125 | ANY ANY empty Name is in use dns.NameUsed 126 | ANY rrset empty RRset exists (value indep) dns.RRsetUsed 127 | NONE ANY empty Name is not in use dns.NameNotUsed 128 | NONE rrset empty RRset does not exist dns.RRsetNotUsed 129 | zone rrset rr RRset exists (value dep) dns.Used 130 | 131 | The prerequisite section can also be left empty. 132 | If you have decided on the prerequisites you can tell what RRs should 133 | be added or deleted. The next table shows the options you have and 134 | what functions to call. 135 | 136 | 3.4.2.6 - Table Of Metavalues Used In Update Section 137 | 138 | CLASS TYPE RDATA Meaning Function 139 | --------------------------------------------------------------- 140 | ANY ANY empty Delete all RRsets from name dns.RemoveName 141 | ANY rrset empty Delete an RRset dns.RemoveRRset 142 | NONE rrset rr Delete an RR from RRset dns.Remove 143 | zone rrset rr Add to an RRset dns.Insert 144 | 145 | TRANSACTION SIGNATURE 146 | 147 | An TSIG or transaction signature adds a HMAC TSIG record to each message sent. 148 | The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512. 149 | 150 | Basic use pattern when querying with a TSIG name "axfr." (note that these key names 151 | must be fully qualified - as they are domain names) and the base64 secret 152 | "so6ZGir4GPAqINNh9U5c3A==": 153 | 154 | c := new(dns.Client) 155 | c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 156 | m := new(dns.Msg) 157 | m.SetQuestion("miek.nl.", dns.TypeMX) 158 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 159 | ... 160 | // When sending the TSIG RR is calculated and filled in before sending 161 | 162 | When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with 163 | TSIG, this is the basic use pattern. In this example we request an AXFR for 164 | miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A==" 165 | and using the server 176.58.119.54: 166 | 167 | t := new(dns.Transfer) 168 | m := new(dns.Msg) 169 | t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 170 | m.SetAxfr("miek.nl.") 171 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 172 | c, err := t.In(m, "176.58.119.54:53") 173 | for r := range c { ... } 174 | 175 | You can now read the records from the transfer as they come in. Each envelope is checked with TSIG. 176 | If something is not correct an error is returned. 177 | 178 | Basic use pattern validating and replying to a message that has TSIG set. 179 | 180 | server := &dns.Server{Addr: ":53", Net: "udp"} 181 | server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 182 | go server.ListenAndServe() 183 | dns.HandleFunc(".", handleRequest) 184 | 185 | func handleRequest(w dns.ResponseWriter, r *dns.Msg) { 186 | m := new(Msg) 187 | m.SetReply(r) 188 | if r.IsTsig() { 189 | if w.TsigStatus() == nil { 190 | // *Msg r has an TSIG record and it was validated 191 | m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) 192 | } else { 193 | // *Msg r has an TSIG records and it was not valided 194 | } 195 | } 196 | w.WriteMsg(m) 197 | } 198 | 199 | PRIVATE RRS 200 | 201 | RFC 6895 sets aside a range of type codes for private use. This range 202 | is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these 203 | can be used, before requesting an official type code from IANA. 204 | 205 | see http://miek.nl/posts/2014/Sep/21/Private%20RRs%20and%20IDN%20in%20Go%20DNS/ for more 206 | information. 207 | 208 | EDNS0 209 | 210 | EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated 211 | by RFC 6891. It defines an new RR type, the OPT RR, which is then completely 212 | abused. 213 | Basic use pattern for creating an (empty) OPT RR: 214 | 215 | o := new(dns.OPT) 216 | o.Hdr.Name = "." // MUST be the root zone, per definition. 217 | o.Hdr.Rrtype = dns.TypeOPT 218 | 219 | The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) 220 | interfaces. Currently only a few have been standardized: EDNS0_NSID 221 | (RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note 222 | that these options may be combined in an OPT RR. 223 | Basic use pattern for a server to check if (and which) options are set: 224 | 225 | // o is a dns.OPT 226 | for _, s := range o.Option { 227 | switch e := s.(type) { 228 | case *dns.EDNS0_NSID: 229 | // do stuff with e.Nsid 230 | case *dns.EDNS0_SUBNET: 231 | // access e.Family, e.Address, etc. 232 | } 233 | } 234 | 235 | SIG(0) 236 | 237 | From RFC 2931: 238 | 239 | SIG(0) provides protection for DNS transactions and requests .... 240 | ... protection for glue records, DNS requests, protection for message headers 241 | on requests and responses, and protection of the overall integrity of a response. 242 | 243 | It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared 244 | secret approach in TSIG. 245 | Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and 246 | RSASHA512. 247 | 248 | Signing subsequent messages in multi-message sessions is not implemented. 249 | */ 250 | package dns 251 | -------------------------------------------------------------------------------- /dyn_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Find better solution 4 | -------------------------------------------------------------------------------- /edns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | // EDNS0 Option codes. 11 | const ( 12 | EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 13 | EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt 14 | EDNS0NSID = 0x3 // nsid (RFC5001) 15 | EDNS0DAU = 0x5 // DNSSEC Algorithm Understood 16 | EDNS0DHU = 0x6 // DS Hash Understood 17 | EDNS0N3U = 0x7 // NSEC3 Hash Understood 18 | EDNS0SUBNET = 0x8 // client-subnet (RFC6891) 19 | EDNS0EXPIRE = 0x9 // EDNS0 expire 20 | EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET 21 | EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891) 22 | EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891) 23 | _DO = 1 << 15 // dnssec ok 24 | ) 25 | 26 | // OPT is the EDNS0 RR appended to messages to convey extra (meta) information. 27 | // See RFC 6891. 28 | type OPT struct { 29 | Hdr RR_Header 30 | Option []EDNS0 `dns:"opt"` 31 | } 32 | 33 | func (rr *OPT) Header() *RR_Header { 34 | return &rr.Hdr 35 | } 36 | 37 | func (rr *OPT) String() string { 38 | s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; " 39 | if rr.Do() { 40 | s += "flags: do; " 41 | } else { 42 | s += "flags: ; " 43 | } 44 | s += "udp: " + strconv.Itoa(int(rr.UDPSize())) 45 | 46 | for _, o := range rr.Option { 47 | switch o.(type) { 48 | case *EDNS0_NSID: 49 | s += "\n; NSID: " + o.String() 50 | h, e := o.pack() 51 | var r string 52 | if e == nil { 53 | for _, c := range h { 54 | r += "(" + string(c) + ")" 55 | } 56 | s += " " + r 57 | } 58 | case *EDNS0_SUBNET: 59 | s += "\n; SUBNET: " + o.String() 60 | if o.(*EDNS0_SUBNET).DraftOption { 61 | s += " (draft)" 62 | } 63 | case *EDNS0_UL: 64 | s += "\n; UPDATE LEASE: " + o.String() 65 | case *EDNS0_LLQ: 66 | s += "\n; LONG LIVED QUERIES: " + o.String() 67 | case *EDNS0_DAU: 68 | s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String() 69 | case *EDNS0_DHU: 70 | s += "\n; DS HASH UNDERSTOOD: " + o.String() 71 | case *EDNS0_N3U: 72 | s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String() 73 | case *EDNS0_LOCAL: 74 | s += "\n; LOCAL OPT: " + o.String() 75 | } 76 | } 77 | return s 78 | } 79 | 80 | func (rr *OPT) len() int { 81 | l := rr.Hdr.len() 82 | for i := 0; i < len(rr.Option); i++ { 83 | l += 4 // Account for 2-byte option code and 2-byte option length. 84 | lo, _ := rr.Option[i].pack() 85 | l += len(lo) 86 | } 87 | return l 88 | } 89 | 90 | func (rr *OPT) copy() RR { 91 | return &OPT{*rr.Hdr.copyHeader(), rr.Option} 92 | } 93 | 94 | // return the old value -> delete SetVersion? 95 | 96 | // Version returns the EDNS version used. Only zero is defined. 97 | func (rr *OPT) Version() uint8 { 98 | return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16) 99 | } 100 | 101 | // SetVersion sets the version of EDNS. This is usually zero. 102 | func (rr *OPT) SetVersion(v uint8) { 103 | rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16) 104 | } 105 | 106 | // ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL). 107 | func (rr *OPT) ExtendedRcode() uint8 { 108 | return uint8((rr.Hdr.Ttl & 0xFF000000) >> 24) 109 | } 110 | 111 | // SetExtendedRcode sets the EDNS extended RCODE field. 112 | func (rr *OPT) SetExtendedRcode(v uint8) { 113 | rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24) 114 | } 115 | 116 | // UDPSize returns the UDP buffer size. 117 | func (rr *OPT) UDPSize() uint16 { 118 | return rr.Hdr.Class 119 | } 120 | 121 | // SetUDPSize sets the UDP buffer size. 122 | func (rr *OPT) SetUDPSize(size uint16) { 123 | rr.Hdr.Class = size 124 | } 125 | 126 | // Do returns the value of the DO (DNSSEC OK) bit. 127 | func (rr *OPT) Do() bool { 128 | return rr.Hdr.Ttl&_DO == _DO 129 | } 130 | 131 | // SetDo sets the DO (DNSSEC OK) bit. 132 | func (rr *OPT) SetDo() { 133 | rr.Hdr.Ttl |= _DO 134 | } 135 | 136 | // EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to 137 | // it. 138 | type EDNS0 interface { 139 | // Option returns the option code for the option. 140 | Option() uint16 141 | // pack returns the bytes of the option data. 142 | pack() ([]byte, error) 143 | // unpack sets the data as found in the buffer. Is also sets 144 | // the length of the slice as the length of the option data. 145 | unpack([]byte) error 146 | // String returns the string representation of the option. 147 | String() string 148 | } 149 | 150 | // The nsid EDNS0 option is used to retrieve a nameserver 151 | // identifier. When sending a request Nsid must be set to the empty string 152 | // The identifier is an opaque string encoded as hex. 153 | // Basic use pattern for creating an nsid option: 154 | // 155 | // o := new(dns.OPT) 156 | // o.Hdr.Name = "." 157 | // o.Hdr.Rrtype = dns.TypeOPT 158 | // e := new(dns.EDNS0_NSID) 159 | // e.Code = dns.EDNS0NSID 160 | // e.Nsid = "AA" 161 | // o.Option = append(o.Option, e) 162 | type EDNS0_NSID struct { 163 | Code uint16 // Always EDNS0NSID 164 | Nsid string // This string needs to be hex encoded 165 | } 166 | 167 | func (e *EDNS0_NSID) pack() ([]byte, error) { 168 | h, err := hex.DecodeString(e.Nsid) 169 | if err != nil { 170 | return nil, err 171 | } 172 | return h, nil 173 | } 174 | 175 | func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } 176 | func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil } 177 | func (e *EDNS0_NSID) String() string { return string(e.Nsid) } 178 | 179 | // The subnet EDNS0 option is used to give the remote nameserver 180 | // an idea of where the client lives. It can then give back a different 181 | // answer depending on the location or network topology. 182 | // Basic use pattern for creating an subnet option: 183 | // 184 | // o := new(dns.OPT) 185 | // o.Hdr.Name = "." 186 | // o.Hdr.Rrtype = dns.TypeOPT 187 | // e := new(dns.EDNS0_SUBNET) 188 | // e.Code = dns.EDNS0SUBNET 189 | // e.Family = 1 // 1 for IPv4 source address, 2 for IPv6 190 | // e.NetMask = 32 // 32 for IPV4, 128 for IPv6 191 | // e.SourceScope = 0 192 | // e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4 193 | // // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6 194 | // o.Option = append(o.Option, e) 195 | // 196 | // Note: the spec (draft-ietf-dnsop-edns-client-subnet-00) has some insane logic 197 | // for which netmask applies to the address. This code will parse all the 198 | // available bits when unpacking (up to optlen). When packing it will apply 199 | // SourceNetmask. If you need more advanced logic, patches welcome and good luck. 200 | type EDNS0_SUBNET struct { 201 | Code uint16 // Always EDNS0SUBNET 202 | Family uint16 // 1 for IP, 2 for IP6 203 | SourceNetmask uint8 204 | SourceScope uint8 205 | Address net.IP 206 | DraftOption bool // Set to true if using the old (0x50fa) option code 207 | } 208 | 209 | func (e *EDNS0_SUBNET) Option() uint16 { 210 | if e.DraftOption { 211 | return EDNS0SUBNETDRAFT 212 | } 213 | return EDNS0SUBNET 214 | } 215 | 216 | func (e *EDNS0_SUBNET) pack() ([]byte, error) { 217 | b := make([]byte, 4) 218 | b[0], b[1] = packUint16(e.Family) 219 | b[2] = e.SourceNetmask 220 | b[3] = e.SourceScope 221 | switch e.Family { 222 | case 1: 223 | if e.SourceNetmask > net.IPv4len*8 { 224 | return nil, errors.New("dns: bad netmask") 225 | } 226 | if len(e.Address.To4()) != net.IPv4len { 227 | return nil, errors.New("dns: bad address") 228 | } 229 | ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8)) 230 | needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up 231 | b = append(b, ip[:needLength]...) 232 | case 2: 233 | if e.SourceNetmask > net.IPv6len*8 { 234 | return nil, errors.New("dns: bad netmask") 235 | } 236 | if len(e.Address) != net.IPv6len { 237 | return nil, errors.New("dns: bad address") 238 | } 239 | ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8)) 240 | needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up 241 | b = append(b, ip[:needLength]...) 242 | default: 243 | return nil, errors.New("dns: bad address family") 244 | } 245 | return b, nil 246 | } 247 | 248 | func (e *EDNS0_SUBNET) unpack(b []byte) error { 249 | if len(b) < 4 { 250 | return ErrBuf 251 | } 252 | e.Family, _ = unpackUint16(b, 0) 253 | e.SourceNetmask = b[2] 254 | e.SourceScope = b[3] 255 | switch e.Family { 256 | case 1: 257 | if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 { 258 | return errors.New("dns: bad netmask") 259 | } 260 | addr := make([]byte, net.IPv4len) 261 | for i := 0; i < net.IPv4len && 4+i < len(b); i++ { 262 | addr[i] = b[4+i] 263 | } 264 | e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3]) 265 | case 2: 266 | if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 { 267 | return errors.New("dns: bad netmask") 268 | } 269 | addr := make([]byte, net.IPv6len) 270 | for i := 0; i < net.IPv6len && 4+i < len(b); i++ { 271 | addr[i] = b[4+i] 272 | } 273 | e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4], 274 | addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], 275 | addr[11], addr[12], addr[13], addr[14], addr[15]} 276 | default: 277 | return errors.New("dns: bad address family") 278 | } 279 | return nil 280 | } 281 | 282 | func (e *EDNS0_SUBNET) String() (s string) { 283 | if e.Address == nil { 284 | s = "" 285 | } else if e.Address.To4() != nil { 286 | s = e.Address.String() 287 | } else { 288 | s = "[" + e.Address.String() + "]" 289 | } 290 | s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope)) 291 | return 292 | } 293 | 294 | // The UL (Update Lease) EDNS0 (draft RFC) option is used to tell the server to set 295 | // an expiration on an update RR. This is helpful for clients that cannot clean 296 | // up after themselves. This is a draft RFC and more information can be found at 297 | // http://files.dns-sd.org/draft-sekar-dns-ul.txt 298 | // 299 | // o := new(dns.OPT) 300 | // o.Hdr.Name = "." 301 | // o.Hdr.Rrtype = dns.TypeOPT 302 | // e := new(dns.EDNS0_UL) 303 | // e.Code = dns.EDNS0UL 304 | // e.Lease = 120 // in seconds 305 | // o.Option = append(o.Option, e) 306 | type EDNS0_UL struct { 307 | Code uint16 // Always EDNS0UL 308 | Lease uint32 309 | } 310 | 311 | func (e *EDNS0_UL) Option() uint16 { return EDNS0UL } 312 | func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) } 313 | 314 | // Copied: http://golang.org/src/pkg/net/dnsmsg.go 315 | func (e *EDNS0_UL) pack() ([]byte, error) { 316 | b := make([]byte, 4) 317 | b[0] = byte(e.Lease >> 24) 318 | b[1] = byte(e.Lease >> 16) 319 | b[2] = byte(e.Lease >> 8) 320 | b[3] = byte(e.Lease) 321 | return b, nil 322 | } 323 | 324 | func (e *EDNS0_UL) unpack(b []byte) error { 325 | if len(b) < 4 { 326 | return ErrBuf 327 | } 328 | e.Lease = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) 329 | return nil 330 | } 331 | 332 | // Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 333 | // Implemented for completeness, as the EDNS0 type code is assigned. 334 | type EDNS0_LLQ struct { 335 | Code uint16 // Always EDNS0LLQ 336 | Version uint16 337 | Opcode uint16 338 | Error uint16 339 | Id uint64 340 | LeaseLife uint32 341 | } 342 | 343 | func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ } 344 | 345 | func (e *EDNS0_LLQ) pack() ([]byte, error) { 346 | b := make([]byte, 18) 347 | b[0], b[1] = packUint16(e.Version) 348 | b[2], b[3] = packUint16(e.Opcode) 349 | b[4], b[5] = packUint16(e.Error) 350 | b[6] = byte(e.Id >> 56) 351 | b[7] = byte(e.Id >> 48) 352 | b[8] = byte(e.Id >> 40) 353 | b[9] = byte(e.Id >> 32) 354 | b[10] = byte(e.Id >> 24) 355 | b[11] = byte(e.Id >> 16) 356 | b[12] = byte(e.Id >> 8) 357 | b[13] = byte(e.Id) 358 | b[14] = byte(e.LeaseLife >> 24) 359 | b[15] = byte(e.LeaseLife >> 16) 360 | b[16] = byte(e.LeaseLife >> 8) 361 | b[17] = byte(e.LeaseLife) 362 | return b, nil 363 | } 364 | 365 | func (e *EDNS0_LLQ) unpack(b []byte) error { 366 | if len(b) < 18 { 367 | return ErrBuf 368 | } 369 | e.Version, _ = unpackUint16(b, 0) 370 | e.Opcode, _ = unpackUint16(b, 2) 371 | e.Error, _ = unpackUint16(b, 4) 372 | e.Id = uint64(b[6])<<56 | uint64(b[6+1])<<48 | uint64(b[6+2])<<40 | 373 | uint64(b[6+3])<<32 | uint64(b[6+4])<<24 | uint64(b[6+5])<<16 | uint64(b[6+6])<<8 | uint64(b[6+7]) 374 | e.LeaseLife = uint32(b[14])<<24 | uint32(b[14+1])<<16 | uint32(b[14+2])<<8 | uint32(b[14+3]) 375 | return nil 376 | } 377 | 378 | func (e *EDNS0_LLQ) String() string { 379 | s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) + 380 | " " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) + 381 | " " + strconv.FormatUint(uint64(e.LeaseLife), 10) 382 | return s 383 | } 384 | 385 | type EDNS0_DAU struct { 386 | Code uint16 // Always EDNS0DAU 387 | AlgCode []uint8 388 | } 389 | 390 | func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU } 391 | func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil } 392 | func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil } 393 | 394 | func (e *EDNS0_DAU) String() string { 395 | s := "" 396 | for i := 0; i < len(e.AlgCode); i++ { 397 | if a, ok := AlgorithmToString[e.AlgCode[i]]; ok { 398 | s += " " + a 399 | } else { 400 | s += " " + strconv.Itoa(int(e.AlgCode[i])) 401 | } 402 | } 403 | return s 404 | } 405 | 406 | type EDNS0_DHU struct { 407 | Code uint16 // Always EDNS0DHU 408 | AlgCode []uint8 409 | } 410 | 411 | func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU } 412 | func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil } 413 | func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil } 414 | 415 | func (e *EDNS0_DHU) String() string { 416 | s := "" 417 | for i := 0; i < len(e.AlgCode); i++ { 418 | if a, ok := HashToString[e.AlgCode[i]]; ok { 419 | s += " " + a 420 | } else { 421 | s += " " + strconv.Itoa(int(e.AlgCode[i])) 422 | } 423 | } 424 | return s 425 | } 426 | 427 | type EDNS0_N3U struct { 428 | Code uint16 // Always EDNS0N3U 429 | AlgCode []uint8 430 | } 431 | 432 | func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U } 433 | func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil } 434 | func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil } 435 | 436 | func (e *EDNS0_N3U) String() string { 437 | // Re-use the hash map 438 | s := "" 439 | for i := 0; i < len(e.AlgCode); i++ { 440 | if a, ok := HashToString[e.AlgCode[i]]; ok { 441 | s += " " + a 442 | } else { 443 | s += " " + strconv.Itoa(int(e.AlgCode[i])) 444 | } 445 | } 446 | return s 447 | } 448 | 449 | type EDNS0_EXPIRE struct { 450 | Code uint16 // Always EDNS0EXPIRE 451 | Expire uint32 452 | } 453 | 454 | func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } 455 | func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } 456 | 457 | func (e *EDNS0_EXPIRE) pack() ([]byte, error) { 458 | b := make([]byte, 4) 459 | b[0] = byte(e.Expire >> 24) 460 | b[1] = byte(e.Expire >> 16) 461 | b[2] = byte(e.Expire >> 8) 462 | b[3] = byte(e.Expire) 463 | return b, nil 464 | } 465 | 466 | func (e *EDNS0_EXPIRE) unpack(b []byte) error { 467 | if len(b) < 4 { 468 | return ErrBuf 469 | } 470 | e.Expire = uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) 471 | return nil 472 | } 473 | 474 | // The local EDNS0 option is used for local/experimental purposes. The option 475 | // code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] 476 | // (RFC6891), although any unassigned code can actually be used. The content of 477 | // the option is made available in Data, unaltered. 478 | // Basic use pattern for creating a local option: 479 | // 480 | // o := new(dns.OPT) 481 | // o.Hdr.Name = "." 482 | // o.Hdr.Rrtype = dns.TypeOPT 483 | // e := new(dns.EDNS0_LOCAL) 484 | // e.Code = dns.EDNS0LOCALSTART 485 | // e.Data = []byte{72, 82, 74} 486 | // o.Option = append(o.Option, e) 487 | type EDNS0_LOCAL struct { 488 | Code uint16 489 | Data []byte 490 | } 491 | 492 | func (e *EDNS0_LOCAL) Option() uint16 { return e.Code } 493 | func (e *EDNS0_LOCAL) String() string { 494 | return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data) 495 | } 496 | 497 | func (e *EDNS0_LOCAL) pack() ([]byte, error) { 498 | b := make([]byte, len(e.Data)) 499 | copied := copy(b, e.Data) 500 | if copied != len(e.Data) { 501 | return nil, ErrBuf 502 | } 503 | return b, nil 504 | } 505 | 506 | func (e *EDNS0_LOCAL) unpack(b []byte) error { 507 | e.Data = make([]byte, len(b)) 508 | copied := copy(e.Data, b) 509 | if copied != len(b) { 510 | return ErrBuf 511 | } 512 | return nil 513 | } 514 | -------------------------------------------------------------------------------- /edns_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestOPTTtl(t *testing.T) { 6 | e := &OPT{} 7 | e.Hdr.Name = "." 8 | e.Hdr.Rrtype = TypeOPT 9 | 10 | if e.Do() { 11 | t.Fail() 12 | } 13 | 14 | e.SetDo() 15 | if !e.Do() { 16 | t.Fail() 17 | } 18 | 19 | oldTtl := e.Hdr.Ttl 20 | 21 | if e.Version() != 0 { 22 | t.Fail() 23 | } 24 | 25 | e.SetVersion(42) 26 | if e.Version() != 42 { 27 | t.Fail() 28 | } 29 | 30 | e.SetVersion(0) 31 | if e.Hdr.Ttl != oldTtl { 32 | t.Fail() 33 | } 34 | 35 | if e.ExtendedRcode() != 0 { 36 | t.Fail() 37 | } 38 | 39 | e.SetExtendedRcode(42) 40 | if e.ExtendedRcode() != 42 { 41 | t.Fail() 42 | } 43 | 44 | e.SetExtendedRcode(0) 45 | if e.Hdr.Ttl != oldTtl { 46 | t.Fail() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package dns_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/letsencrypt/dns" 10 | ) 11 | 12 | // Retrieve the MX records for miek.nl. 13 | func ExampleMX() { 14 | config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") 15 | c := new(dns.Client) 16 | m := new(dns.Msg) 17 | m.SetQuestion("miek.nl.", dns.TypeMX) 18 | m.RecursionDesired = true 19 | r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) 20 | if err != nil { 21 | return 22 | } 23 | if r.Rcode != dns.RcodeSuccess { 24 | return 25 | } 26 | for _, a := range r.Answer { 27 | if mx, ok := a.(*dns.MX); ok { 28 | fmt.Printf("%s\n", mx.String()) 29 | } 30 | } 31 | } 32 | 33 | // Retrieve the DNSKEY records of a zone and convert them 34 | // to DS records for SHA1, SHA256 and SHA384. 35 | func ExampleDS(zone string) { 36 | config, _ := dns.ClientConfigFromFile("/etc/resolv.conf") 37 | c := new(dns.Client) 38 | m := new(dns.Msg) 39 | if zone == "" { 40 | zone = "miek.nl" 41 | } 42 | m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY) 43 | m.SetEdns0(4096, true) 44 | r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port) 45 | if err != nil { 46 | return 47 | } 48 | if r.Rcode != dns.RcodeSuccess { 49 | return 50 | } 51 | for _, k := range r.Answer { 52 | if key, ok := k.(*dns.DNSKEY); ok { 53 | for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} { 54 | fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags) 55 | } 56 | } 57 | } 58 | } 59 | 60 | const TypeAPAIR = 0x0F99 61 | 62 | type APAIR struct { 63 | addr [2]net.IP 64 | } 65 | 66 | func NewAPAIR() dns.PrivateRdata { return new(APAIR) } 67 | 68 | func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() } 69 | func (rd *APAIR) Parse(txt []string) error { 70 | if len(txt) != 2 { 71 | return errors.New("two addresses required for APAIR") 72 | } 73 | for i, s := range txt { 74 | ip := net.ParseIP(s) 75 | if ip == nil { 76 | return errors.New("invalid IP in APAIR text representation") 77 | } 78 | rd.addr[i] = ip 79 | } 80 | return nil 81 | } 82 | 83 | func (rd *APAIR) Pack(buf []byte) (int, error) { 84 | b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...) 85 | n := copy(buf, b) 86 | if n != len(b) { 87 | return n, dns.ErrBuf 88 | } 89 | return n, nil 90 | } 91 | 92 | func (rd *APAIR) Unpack(buf []byte) (int, error) { 93 | ln := net.IPv4len * 2 94 | if len(buf) != ln { 95 | return 0, errors.New("invalid length of APAIR rdata") 96 | } 97 | cp := make([]byte, ln) 98 | copy(cp, buf) // clone bytes to use them in IPs 99 | 100 | rd.addr[0] = net.IP(cp[:3]) 101 | rd.addr[1] = net.IP(cp[4:]) 102 | 103 | return len(buf), nil 104 | } 105 | 106 | func (rd *APAIR) Copy(dest dns.PrivateRdata) error { 107 | cp := make([]byte, rd.Len()) 108 | _, err := rd.Pack(cp) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | d := dest.(*APAIR) 114 | d.addr[0] = net.IP(cp[:3]) 115 | d.addr[1] = net.IP(cp[4:]) 116 | return nil 117 | } 118 | 119 | func (rd *APAIR) Len() int { 120 | return net.IPv4len * 2 121 | } 122 | 123 | func ExamplePrivateHandle() { 124 | dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR) 125 | defer dns.PrivateHandleRemove(TypeAPAIR) 126 | 127 | rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4 1.2.3.5)") 128 | if err != nil { 129 | log.Fatal("could not parse APAIR record: ", err) 130 | } 131 | fmt.Println(rr) 132 | // Output: miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 133 | 134 | m := new(dns.Msg) 135 | m.Id = 12345 136 | m.SetQuestion("miek.nl.", TypeAPAIR) 137 | m.Answer = append(m.Answer, rr) 138 | 139 | fmt.Println(m) 140 | // ;; opcode: QUERY, status: NOERROR, id: 12345 141 | // ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 142 | // 143 | // ;; QUESTION SECTION: 144 | // ;miek.nl. IN APAIR 145 | // 146 | // ;; ANSWER SECTION: 147 | // miek.nl. 3600 IN APAIR 1.2.3.4 1.2.3.5 148 | } 149 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "strconv" 7 | ) 8 | 9 | // NumField returns the number of rdata fields r has. 10 | func NumField(r RR) int { 11 | return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header 12 | } 13 | 14 | // Field returns the rdata field i as a string. Fields are indexed starting from 1. 15 | // RR types that holds slice data, for instance the NSEC type bitmap will return a single 16 | // string where the types are concatenated using a space. 17 | // Accessing non existing fields will cause a panic. 18 | func Field(r RR, i int) string { 19 | if i == 0 { 20 | return "" 21 | } 22 | d := reflect.ValueOf(r).Elem().Field(i) 23 | switch k := d.Kind(); k { 24 | case reflect.String: 25 | return d.String() 26 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 27 | return strconv.FormatInt(d.Int(), 10) 28 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 29 | return strconv.FormatUint(d.Uint(), 10) 30 | case reflect.Slice: 31 | switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { 32 | case `dns:"a"`: 33 | // TODO(miek): Hmm store this as 16 bytes 34 | if d.Len() < net.IPv6len { 35 | return net.IPv4(byte(d.Index(0).Uint()), 36 | byte(d.Index(1).Uint()), 37 | byte(d.Index(2).Uint()), 38 | byte(d.Index(3).Uint())).String() 39 | } 40 | return net.IPv4(byte(d.Index(12).Uint()), 41 | byte(d.Index(13).Uint()), 42 | byte(d.Index(14).Uint()), 43 | byte(d.Index(15).Uint())).String() 44 | case `dns:"aaaa"`: 45 | return net.IP{ 46 | byte(d.Index(0).Uint()), 47 | byte(d.Index(1).Uint()), 48 | byte(d.Index(2).Uint()), 49 | byte(d.Index(3).Uint()), 50 | byte(d.Index(4).Uint()), 51 | byte(d.Index(5).Uint()), 52 | byte(d.Index(6).Uint()), 53 | byte(d.Index(7).Uint()), 54 | byte(d.Index(8).Uint()), 55 | byte(d.Index(9).Uint()), 56 | byte(d.Index(10).Uint()), 57 | byte(d.Index(11).Uint()), 58 | byte(d.Index(12).Uint()), 59 | byte(d.Index(13).Uint()), 60 | byte(d.Index(14).Uint()), 61 | byte(d.Index(15).Uint()), 62 | }.String() 63 | case `dns:"nsec"`: 64 | if d.Len() == 0 { 65 | return "" 66 | } 67 | s := Type(d.Index(0).Uint()).String() 68 | for i := 1; i < d.Len(); i++ { 69 | s += " " + Type(d.Index(i).Uint()).String() 70 | } 71 | return s 72 | case `dns:"wks"`: 73 | if d.Len() == 0 { 74 | return "" 75 | } 76 | s := strconv.Itoa(int(d.Index(0).Uint())) 77 | for i := 0; i < d.Len(); i++ { 78 | s += " " + strconv.Itoa(int(d.Index(i).Uint())) 79 | } 80 | return s 81 | default: 82 | // if it does not have a tag its a string slice 83 | fallthrough 84 | case `dns:"txt"`: 85 | if d.Len() == 0 { 86 | return "" 87 | } 88 | s := d.Index(0).String() 89 | for i := 1; i < d.Len(); i++ { 90 | s += " " + d.Index(i).String() 91 | } 92 | return s 93 | } 94 | } 95 | return "" 96 | } 97 | -------------------------------------------------------------------------------- /fuzz_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "testing" 4 | 5 | func TestFuzzString(t *testing.T) { 6 | testcases := []string{"", " MINFO ", " RP ", " NSEC 0 0", " \" NSEC 0 0\"", " \" MINFO \"", 7 | ";a ", ";a����������", 8 | " NSAP O ", " NSAP N ", 9 | " TYPE4 TYPE6a789a3bc0045c8a5fb42c7d1bd998f5444 IN 9579b47d46817afbd17273e6", 10 | " TYPE45 3 3 4147994 TYPE\\(\\)\\)\\(\\)\\(\\(\\)\\(\\)\\)\\)\\(\\)\\(\\)\\(\\(\\R 948\"\")\\(\\)\\)\\)\\(\\ ", 11 | "$GENERATE 0-3 ${441189,5039418474430,o}", 12 | "$INCLUDE 00 TYPE00000000000n ", 13 | "$INCLUDE PE4 TYPE061463623/727071511 \\(\\)\\$GENERATE 6-462/0", 14 | } 15 | for i, tc := range testcases { 16 | rr, err := NewRR(tc) 17 | if err == nil { 18 | // rr can be nil because we can (for instance) just parse a comment 19 | if rr == nil { 20 | continue 21 | } 22 | t.Fatalf("parsed mailformed RR %d: %s", i, rr.String()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /idn/example_test.go: -------------------------------------------------------------------------------- 1 | package idn_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/miekg/dns/idn" 6 | ) 7 | 8 | func ExampleToPunycode() { 9 | name := "インターネット.テスト" 10 | fmt.Printf("%s -> %s", name, idn.ToPunycode(name)) 11 | // Output: インターネット.テスト -> xn--eckucmux0ukc.xn--zckzah 12 | } 13 | 14 | func ExampleFromPunycode() { 15 | name := "xn--mgbaja8a1hpac.xn--mgbachtv" 16 | fmt.Printf("%s -> %s", name, idn.FromPunycode(name)) 17 | // Output: xn--mgbaja8a1hpac.xn--mgbachtv -> الانترنت.اختبار 18 | } 19 | -------------------------------------------------------------------------------- /idn/punycode.go: -------------------------------------------------------------------------------- 1 | // Package idn implements encoding from and to punycode as speficied by RFC 3492. 2 | package idn 3 | 4 | import ( 5 | "bytes" 6 | "strings" 7 | "unicode" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | // Implementation idea from RFC itself and from from IDNA::Punycode created by 13 | // Tatsuhiko Miyagawa and released under Perl Artistic 14 | // License in 2002. 15 | 16 | const ( 17 | _MIN rune = 1 18 | _MAX rune = 26 19 | _SKEW rune = 38 20 | _BASE rune = 36 21 | _BIAS rune = 72 22 | _N rune = 128 23 | _DAMP rune = 700 24 | 25 | _DELIMITER = '-' 26 | _PREFIX = "xn--" 27 | ) 28 | 29 | // ToPunycode converts unicode domain names to DNS-appropriate punycode names. 30 | // This function would return an empty string result for domain names with 31 | // invalid unicode strings. This function expects domain names in lowercase. 32 | func ToPunycode(s string) string { 33 | tokens := dns.SplitDomainName(s) 34 | switch { 35 | case s == "": 36 | return "" 37 | case tokens == nil: // s == . 38 | return "." 39 | case s[len(s)-1] == '.': 40 | tokens = append(tokens, "") 41 | } 42 | 43 | for i := range tokens { 44 | t := encode([]byte(tokens[i])) 45 | if t == nil { 46 | return "" 47 | } 48 | tokens[i] = string(t) 49 | } 50 | return strings.Join(tokens, ".") 51 | } 52 | 53 | // FromPunycode returns unicode domain name from provided punycode string. 54 | func FromPunycode(s string) string { 55 | tokens := dns.SplitDomainName(s) 56 | switch { 57 | case s == "": 58 | return "" 59 | case tokens == nil: // s == . 60 | return "." 61 | case s[len(s)-1] == '.': 62 | tokens = append(tokens, "") 63 | } 64 | for i := range tokens { 65 | tokens[i] = string(decode([]byte(tokens[i]))) 66 | } 67 | return strings.Join(tokens, ".") 68 | } 69 | 70 | // digitval converts single byte into meaningful value that's used to calculate decoded unicode character. 71 | const errdigit = 0xffff 72 | 73 | func digitval(code rune) rune { 74 | switch { 75 | case code >= 'A' && code <= 'Z': 76 | return code - 'A' 77 | case code >= 'a' && code <= 'z': 78 | return code - 'a' 79 | case code >= '0' && code <= '9': 80 | return code - '0' + 26 81 | } 82 | return errdigit 83 | } 84 | 85 | // lettercode finds BASE36 byte (a-z0-9) based on calculated number. 86 | func lettercode(digit rune) rune { 87 | switch { 88 | case digit >= 0 && digit <= 25: 89 | return digit + 'a' 90 | case digit >= 26 && digit <= 36: 91 | return digit - 26 + '0' 92 | } 93 | panic("dns: not reached") 94 | } 95 | 96 | // adapt calculates next bias to be used for next iteration delta. 97 | func adapt(delta rune, numpoints int, firsttime bool) rune { 98 | if firsttime { 99 | delta /= _DAMP 100 | } else { 101 | delta /= 2 102 | } 103 | 104 | var k rune 105 | for delta = delta + delta/rune(numpoints); delta > (_BASE-_MIN)*_MAX/2; k += _BASE { 106 | delta /= _BASE - _MIN 107 | } 108 | 109 | return k + ((_BASE-_MIN+1)*delta)/(delta+_SKEW) 110 | } 111 | 112 | // next finds minimal rune (one with lowest codepoint value) that should be equal or above boundary. 113 | func next(b []rune, boundary rune) rune { 114 | if len(b) == 0 { 115 | panic("dns: invalid set of runes to determine next one") 116 | } 117 | m := b[0] 118 | for _, x := range b[1:] { 119 | if x >= boundary && (m < boundary || x < m) { 120 | m = x 121 | } 122 | } 123 | return m 124 | } 125 | 126 | // preprune converts unicode rune to lower case. At this time it's not 127 | // supporting all things described in RFCs. 128 | func preprune(r rune) rune { 129 | if unicode.IsUpper(r) { 130 | r = unicode.ToLower(r) 131 | } 132 | return r 133 | } 134 | 135 | // tfunc is a function that helps calculate each character weight. 136 | func tfunc(k, bias rune) rune { 137 | switch { 138 | case k <= bias: 139 | return _MIN 140 | case k >= bias+_MAX: 141 | return _MAX 142 | } 143 | return k - bias 144 | } 145 | 146 | // encode transforms Unicode input bytes (that represent DNS label) into 147 | // punycode bytestream. This function would return nil if there's an invalid 148 | // character in the label. 149 | func encode(input []byte) []byte { 150 | n, bias := _N, _BIAS 151 | 152 | b := bytes.Runes(input) 153 | for i := range b { 154 | if !isValidRune(b[i]) { 155 | return nil 156 | } 157 | 158 | b[i] = preprune(b[i]) 159 | } 160 | 161 | basic := make([]byte, 0, len(b)) 162 | for _, ltr := range b { 163 | if ltr <= 0x7f { 164 | basic = append(basic, byte(ltr)) 165 | } 166 | } 167 | basiclen := len(basic) 168 | fulllen := len(b) 169 | if basiclen == fulllen { 170 | return basic 171 | } 172 | 173 | var out bytes.Buffer 174 | 175 | out.WriteString(_PREFIX) 176 | if basiclen > 0 { 177 | out.Write(basic) 178 | out.WriteByte(_DELIMITER) 179 | } 180 | 181 | var ( 182 | ltr, nextltr rune 183 | delta, q rune // delta calculation (see rfc) 184 | t, k, cp rune // weight and codepoint calculation 185 | ) 186 | 187 | s := &bytes.Buffer{} 188 | for h := basiclen; h < fulllen; n, delta = n+1, delta+1 { 189 | nextltr = next(b, n) 190 | s.Truncate(0) 191 | s.WriteRune(nextltr) 192 | delta, n = delta+(nextltr-n)*rune(h+1), nextltr 193 | 194 | for _, ltr = range b { 195 | if ltr < n { 196 | delta++ 197 | } 198 | if ltr == n { 199 | q = delta 200 | for k = _BASE; ; k += _BASE { 201 | t = tfunc(k, bias) 202 | if q < t { 203 | break 204 | } 205 | cp = t + ((q - t) % (_BASE - t)) 206 | out.WriteRune(lettercode(cp)) 207 | q = (q - t) / (_BASE - t) 208 | } 209 | 210 | out.WriteRune(lettercode(q)) 211 | 212 | bias = adapt(delta, h+1, h == basiclen) 213 | h, delta = h+1, 0 214 | } 215 | } 216 | } 217 | return out.Bytes() 218 | } 219 | 220 | // decode transforms punycode input bytes (that represent DNS label) into Unicode bytestream. 221 | func decode(b []byte) []byte { 222 | src := b // b would move and we need to keep it 223 | 224 | n, bias := _N, _BIAS 225 | if !bytes.HasPrefix(b, []byte(_PREFIX)) { 226 | return b 227 | } 228 | out := make([]rune, 0, len(b)) 229 | b = b[len(_PREFIX):] 230 | for pos := len(b) - 1; pos >= 0; pos-- { 231 | // only last delimiter is our interest 232 | if b[pos] == _DELIMITER { 233 | out = append(out, bytes.Runes(b[:pos])...) 234 | b = b[pos+1:] // trim source string 235 | break 236 | } 237 | } 238 | if len(b) == 0 { 239 | return src 240 | } 241 | var ( 242 | i, oldi, w rune 243 | ch byte 244 | t, digit rune 245 | ln int 246 | ) 247 | 248 | for i = 0; len(b) > 0; i++ { 249 | oldi, w = i, 1 250 | for k := _BASE; len(b) > 0; k += _BASE { 251 | ch, b = b[0], b[1:] 252 | digit = digitval(rune(ch)) 253 | if digit == errdigit { 254 | return src 255 | } 256 | i += digit * w 257 | if i < 0 { 258 | // safety check for rune overflow 259 | return src 260 | } 261 | 262 | t = tfunc(k, bias) 263 | if digit < t { 264 | break 265 | } 266 | 267 | w *= _BASE - t 268 | } 269 | ln = len(out) + 1 270 | bias = adapt(i-oldi, ln, oldi == 0) 271 | n += i / rune(ln) 272 | i = i % rune(ln) 273 | // insert 274 | out = append(out, 0) 275 | copy(out[i+1:], out[i:]) 276 | out[i] = n 277 | } 278 | 279 | var ret bytes.Buffer 280 | for _, r := range out { 281 | ret.WriteRune(r) 282 | } 283 | return ret.Bytes() 284 | } 285 | 286 | // isValidRune checks if the character is valid. We will look for the 287 | // character property in the code points list. For now we aren't checking special 288 | // rules in case of contextual property 289 | func isValidRune(r rune) bool { 290 | return findProperty(r) == propertyPVALID 291 | } 292 | 293 | // findProperty will try to check the code point property of the given 294 | // character. It will use a binary search algorithm as we have a slice of 295 | // ordered ranges (average case performance O(log n)) 296 | func findProperty(r rune) property { 297 | imin, imax := 0, len(codePoints) 298 | 299 | for imax >= imin { 300 | imid := (imin + imax) / 2 301 | 302 | codePoint := codePoints[imid] 303 | if (codePoint.start == r && codePoint.end == 0) || (codePoint.start <= r && codePoint.end >= r) { 304 | return codePoint.state 305 | } 306 | 307 | if (codePoint.end > 0 && codePoint.end < r) || (codePoint.end == 0 && codePoint.start < r) { 308 | imin = imid + 1 309 | } else { 310 | imax = imid - 1 311 | } 312 | } 313 | 314 | return propertyUnknown 315 | } 316 | -------------------------------------------------------------------------------- /idn/punycode_test.go: -------------------------------------------------------------------------------- 1 | package idn 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var testcases = [][2]string{ 9 | {"", ""}, 10 | {"a", "a"}, 11 | {"A-B", "a-b"}, 12 | {"A-B-C", "a-b-c"}, 13 | {"AbC", "abc"}, 14 | {"я", "xn--41a"}, 15 | {"zя", "xn--z-0ub"}, 16 | {"яZ", "xn--z-zub"}, 17 | {"а-я", "xn----7sb8g"}, 18 | {"إختبار", "xn--kgbechtv"}, 19 | {"آزمایشی", "xn--hgbk6aj7f53bba"}, 20 | {"测试", "xn--0zwm56d"}, 21 | {"測試", "xn--g6w251d"}, 22 | {"испытание", "xn--80akhbyknj4f"}, 23 | {"परीक्षा", "xn--11b5bs3a9aj6g"}, 24 | {"δοκιμή", "xn--jxalpdlp"}, 25 | {"테스트", "xn--9t4b11yi5a"}, 26 | {"טעסט", "xn--deba0ad"}, 27 | {"テスト", "xn--zckzah"}, 28 | {"பரிட்சை", "xn--hlcj6aya9esc7a"}, 29 | {"mamão-com-açúcar", "xn--mamo-com-acar-yeb1e6q"}, 30 | {"σ", "xn--4xa"}, 31 | } 32 | 33 | func TestEncodeDecodePunycode(t *testing.T) { 34 | for _, tst := range testcases { 35 | enc := encode([]byte(tst[0])) 36 | if string(enc) != tst[1] { 37 | t.Errorf("%s encodeded as %s but should be %s", tst[0], enc, tst[1]) 38 | } 39 | dec := decode([]byte(tst[1])) 40 | if string(dec) != strings.ToLower(tst[0]) { 41 | t.Errorf("%s decoded as %s but should be %s", tst[1], dec, strings.ToLower(tst[0])) 42 | } 43 | } 44 | } 45 | 46 | func TestToFromPunycode(t *testing.T) { 47 | for _, tst := range testcases { 48 | // assert unicode.com == punycode.com 49 | full := ToPunycode(tst[0] + ".com") 50 | if full != tst[1]+".com" { 51 | t.Errorf("invalid result from string conversion to punycode, %s and should be %s.com", full, tst[1]) 52 | } 53 | // assert punycode.punycode == unicode.unicode 54 | decoded := FromPunycode(tst[1] + "." + tst[1]) 55 | if decoded != strings.ToLower(tst[0]+"."+tst[0]) { 56 | t.Errorf("invalid result from string conversion to punycode, %s and should be %s.%s", decoded, tst[0], tst[0]) 57 | } 58 | } 59 | } 60 | 61 | func TestEncodeDecodeFinalPeriod(t *testing.T) { 62 | for _, tst := range testcases { 63 | // assert unicode.com. == punycode.com. 64 | full := ToPunycode(tst[0] + ".") 65 | if full != tst[1]+"." { 66 | t.Errorf("invalid result from string conversion to punycode when period added at the end, %#v and should be %#v", full, tst[1]+".") 67 | } 68 | // assert punycode.com. == unicode.com. 69 | decoded := FromPunycode(tst[1] + ".") 70 | if decoded != strings.ToLower(tst[0]+".") { 71 | t.Errorf("invalid result from string conversion to punycode when period added, %#v and should be %#v", decoded, tst[0]+".") 72 | } 73 | full = ToPunycode(tst[0]) 74 | if full != tst[1] { 75 | t.Errorf("invalid result from string conversion to punycode when no period added at the end, %#v and should be %#v", full, tst[1]+".") 76 | } 77 | // assert punycode.com. == unicode.com. 78 | decoded = FromPunycode(tst[1]) 79 | if decoded != strings.ToLower(tst[0]) { 80 | t.Errorf("invalid result from string conversion to punycode when no period added, %#v and should be %#v", decoded, tst[0]+".") 81 | } 82 | } 83 | } 84 | 85 | var invalidACEs = []string{ 86 | "xn--*", 87 | "xn--", 88 | "xn---", 89 | "xn--a000000000", 90 | } 91 | 92 | func TestInvalidPunycode(t *testing.T) { 93 | for _, d := range invalidACEs { 94 | s := FromPunycode(d) 95 | if s != d { 96 | t.Errorf("Changed invalid name %s to %#v", d, s) 97 | } 98 | } 99 | } 100 | 101 | // You can verify the labels that are valid or not comparing to the Verisign 102 | // website: http://mct.verisign-grs.com/ 103 | var invalidUnicodes = []string{ 104 | "Σ", 105 | "ЯZ", 106 | "Испытание", 107 | } 108 | 109 | func TestInvalidUnicodes(t *testing.T) { 110 | for _, d := range invalidUnicodes { 111 | s := ToPunycode(d) 112 | if s != "" { 113 | t.Errorf("Changed invalid name %s to %#v", d, s) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /labels.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Holds a bunch of helper functions for dealing with labels. 4 | 5 | // SplitDomainName splits a name string into it's labels. 6 | // www.miek.nl. returns []string{"www", "miek", "nl"} 7 | // The root label (.) returns nil. Note that using 8 | // strings.Split(s) will work in most cases, but does not handle 9 | // escaped dots (\.) for instance. 10 | func SplitDomainName(s string) (labels []string) { 11 | if len(s) == 0 { 12 | return nil 13 | } 14 | fqdnEnd := 0 // offset of the final '.' or the length of the name 15 | idx := Split(s) 16 | begin := 0 17 | if s[len(s)-1] == '.' { 18 | fqdnEnd = len(s) - 1 19 | } else { 20 | fqdnEnd = len(s) 21 | } 22 | 23 | switch len(idx) { 24 | case 0: 25 | return nil 26 | case 1: 27 | // no-op 28 | default: 29 | end := 0 30 | for i := 1; i < len(idx); i++ { 31 | end = idx[i] 32 | labels = append(labels, s[begin:end-1]) 33 | begin = end 34 | } 35 | } 36 | 37 | labels = append(labels, s[begin:fqdnEnd]) 38 | return labels 39 | } 40 | 41 | // CompareDomainName compares the names s1 and s2 and 42 | // returns how many labels they have in common starting from the *right*. 43 | // The comparison stops at the first inequality. The names are not downcased 44 | // before the comparison. 45 | // 46 | // www.miek.nl. and miek.nl. have two labels in common: miek and nl 47 | // www.miek.nl. and www.bla.nl. have one label in common: nl 48 | func CompareDomainName(s1, s2 string) (n int) { 49 | s1 = Fqdn(s1) 50 | s2 = Fqdn(s2) 51 | l1 := Split(s1) 52 | l2 := Split(s2) 53 | 54 | // the first check: root label 55 | if l1 == nil || l2 == nil { 56 | return 57 | } 58 | 59 | j1 := len(l1) - 1 // end 60 | i1 := len(l1) - 2 // start 61 | j2 := len(l2) - 1 62 | i2 := len(l2) - 2 63 | // the second check can be done here: last/only label 64 | // before we fall through into the for-loop below 65 | if s1[l1[j1]:] == s2[l2[j2]:] { 66 | n++ 67 | } else { 68 | return 69 | } 70 | for { 71 | if i1 < 0 || i2 < 0 { 72 | break 73 | } 74 | if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { 75 | n++ 76 | } else { 77 | break 78 | } 79 | j1-- 80 | i1-- 81 | j2-- 82 | i2-- 83 | } 84 | return 85 | } 86 | 87 | // CountLabel counts the the number of labels in the string s. 88 | func CountLabel(s string) (labels int) { 89 | if s == "." { 90 | return 91 | } 92 | off := 0 93 | end := false 94 | for { 95 | off, end = NextLabel(s, off) 96 | labels++ 97 | if end { 98 | return 99 | } 100 | } 101 | panic("dns: not reached") 102 | } 103 | 104 | // Split splits a name s into its label indexes. 105 | // www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. 106 | // The root name (.) returns nil. Also see dns.SplitDomainName. 107 | func Split(s string) []int { 108 | if s == "." { 109 | return nil 110 | } 111 | idx := make([]int, 1, 3) 112 | off := 0 113 | end := false 114 | 115 | for { 116 | off, end = NextLabel(s, off) 117 | if end { 118 | return idx 119 | } 120 | idx = append(idx, off) 121 | } 122 | panic("dns: not reached") 123 | } 124 | 125 | // NextLabel returns the index of the start of the next label in the 126 | // string s starting at offset. 127 | // The bool end is true when the end of the string has been reached. 128 | func NextLabel(s string, offset int) (i int, end bool) { 129 | quote := false 130 | for i = offset; i < len(s)-1; i++ { 131 | switch s[i] { 132 | case '\\': 133 | quote = !quote 134 | default: 135 | quote = false 136 | case '.': 137 | if quote { 138 | quote = !quote 139 | continue 140 | } 141 | return i + 1, false 142 | } 143 | } 144 | return i + 1, true 145 | } 146 | 147 | // PrevLabel returns the index of the label when starting from the right and 148 | // jumping n labels to the left. 149 | // The bool start is true when the start of the string has been overshot. 150 | func PrevLabel(s string, n int) (i int, start bool) { 151 | if n == 0 { 152 | return len(s), false 153 | } 154 | lab := Split(s) 155 | if lab == nil { 156 | return 0, true 157 | } 158 | if n > len(lab) { 159 | return 0, true 160 | } 161 | return lab[len(lab)-n], false 162 | } 163 | -------------------------------------------------------------------------------- /labels_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCompareDomainName(t *testing.T) { 8 | s1 := "www.miek.nl." 9 | s2 := "miek.nl." 10 | s3 := "www.bla.nl." 11 | s4 := "nl.www.bla." 12 | s5 := "nl" 13 | s6 := "miek.nl" 14 | 15 | if CompareDomainName(s1, s2) != 2 { 16 | t.Errorf("%s with %s should be %d", s1, s2, 2) 17 | } 18 | if CompareDomainName(s1, s3) != 1 { 19 | t.Errorf("%s with %s should be %d", s1, s3, 1) 20 | } 21 | if CompareDomainName(s3, s4) != 0 { 22 | t.Errorf("%s with %s should be %d", s3, s4, 0) 23 | } 24 | // Non qualified tests 25 | if CompareDomainName(s1, s5) != 1 { 26 | t.Errorf("%s with %s should be %d", s1, s5, 1) 27 | } 28 | if CompareDomainName(s1, s6) != 2 { 29 | t.Errorf("%s with %s should be %d", s1, s5, 2) 30 | } 31 | 32 | if CompareDomainName(s1, ".") != 0 { 33 | t.Errorf("%s with %s should be %d", s1, s5, 0) 34 | } 35 | if CompareDomainName(".", ".") != 0 { 36 | t.Errorf("%s with %s should be %d", ".", ".", 0) 37 | } 38 | } 39 | 40 | func TestSplit(t *testing.T) { 41 | splitter := map[string]int{ 42 | "www.miek.nl.": 3, 43 | "www.miek.nl": 3, 44 | "www..miek.nl": 4, 45 | `www\.miek.nl.`: 2, 46 | `www\\.miek.nl.`: 3, 47 | ".": 0, 48 | "nl.": 1, 49 | "nl": 1, 50 | "com.": 1, 51 | ".com.": 2, 52 | } 53 | for s, i := range splitter { 54 | if x := len(Split(s)); x != i { 55 | t.Errorf("labels should be %d, got %d: %s %v", i, x, s, Split(s)) 56 | } else { 57 | t.Logf("%s %v", s, Split(s)) 58 | } 59 | } 60 | } 61 | 62 | func TestSplit2(t *testing.T) { 63 | splitter := map[string][]int{ 64 | "www.miek.nl.": []int{0, 4, 9}, 65 | "www.miek.nl": []int{0, 4, 9}, 66 | "nl": []int{0}, 67 | } 68 | for s, i := range splitter { 69 | x := Split(s) 70 | switch len(i) { 71 | case 1: 72 | if x[0] != i[0] { 73 | t.Errorf("labels should be %v, got %v: %s", i, x, s) 74 | } 75 | default: 76 | if x[0] != i[0] || x[1] != i[1] || x[2] != i[2] { 77 | t.Errorf("labels should be %v, got %v: %s", i, x, s) 78 | } 79 | } 80 | } 81 | } 82 | 83 | func TestPrevLabel(t *testing.T) { 84 | type prev struct { 85 | string 86 | int 87 | } 88 | prever := map[prev]int{ 89 | prev{"www.miek.nl.", 0}: 12, 90 | prev{"www.miek.nl.", 1}: 9, 91 | prev{"www.miek.nl.", 2}: 4, 92 | 93 | prev{"www.miek.nl", 0}: 11, 94 | prev{"www.miek.nl", 1}: 9, 95 | prev{"www.miek.nl", 2}: 4, 96 | 97 | prev{"www.miek.nl.", 5}: 0, 98 | prev{"www.miek.nl", 5}: 0, 99 | 100 | prev{"www.miek.nl.", 3}: 0, 101 | prev{"www.miek.nl", 3}: 0, 102 | } 103 | for s, i := range prever { 104 | x, ok := PrevLabel(s.string, s.int) 105 | if i != x { 106 | t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string) 107 | } 108 | } 109 | } 110 | 111 | func TestCountLabel(t *testing.T) { 112 | splitter := map[string]int{ 113 | "www.miek.nl.": 3, 114 | "www.miek.nl": 3, 115 | "nl": 1, 116 | ".": 0, 117 | } 118 | for s, i := range splitter { 119 | x := CountLabel(s) 120 | if x != i { 121 | t.Errorf("CountLabel should have %d, got %d", i, x) 122 | } 123 | } 124 | } 125 | 126 | func TestSplitDomainName(t *testing.T) { 127 | labels := map[string][]string{ 128 | "miek.nl": []string{"miek", "nl"}, 129 | ".": nil, 130 | "www.miek.nl.": []string{"www", "miek", "nl"}, 131 | "www.miek.nl": []string{"www", "miek", "nl"}, 132 | "www..miek.nl": []string{"www", "", "miek", "nl"}, 133 | `www\.miek.nl`: []string{`www\.miek`, "nl"}, 134 | `www\\.miek.nl`: []string{`www\\`, "miek", "nl"}, 135 | } 136 | domainLoop: 137 | for domain, splits := range labels { 138 | parts := SplitDomainName(domain) 139 | if len(parts) != len(splits) { 140 | t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) 141 | continue domainLoop 142 | } 143 | for i := range parts { 144 | if parts[i] != splits[i] { 145 | t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits) 146 | continue domainLoop 147 | } 148 | } 149 | } 150 | } 151 | 152 | func TestIsDomainName(t *testing.T) { 153 | type ret struct { 154 | ok bool 155 | lab int 156 | } 157 | names := map[string]*ret{ 158 | "..": &ret{false, 1}, 159 | "@.": &ret{true, 1}, 160 | "www.example.com": &ret{true, 3}, 161 | "www.e%ample.com": &ret{true, 3}, 162 | "www.example.com.": &ret{true, 3}, 163 | "mi\\k.nl.": &ret{true, 2}, 164 | "mi\\k.nl": &ret{true, 2}, 165 | } 166 | for d, ok := range names { 167 | l, k := IsDomainName(d) 168 | if ok.ok != k || ok.lab != l { 169 | t.Errorf(" got %v %d for %s ", k, l, d) 170 | t.Errorf("have %v %d for %s ", ok.ok, ok.lab, d) 171 | } 172 | } 173 | } 174 | 175 | func BenchmarkSplitLabels(b *testing.B) { 176 | for i := 0; i < b.N; i++ { 177 | Split("www.example.com") 178 | } 179 | } 180 | 181 | func BenchmarkLenLabels(b *testing.B) { 182 | for i := 0; i < b.N; i++ { 183 | CountLabel("www.example.com") 184 | } 185 | } 186 | 187 | func BenchmarkCompareLabels(b *testing.B) { 188 | for i := 0; i < b.N; i++ { 189 | CompareDomainName("www.example.com", "aa.example.com") 190 | } 191 | } 192 | 193 | func BenchmarkIsSubDomain(b *testing.B) { 194 | for i := 0; i < b.N; i++ { 195 | IsSubDomain("www.example.com", "aa.example.com") 196 | IsSubDomain("example.com", "aa.example.com") 197 | IsSubDomain("miek.nl", "aa.example.com") 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /nsecx.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha1" 5 | "hash" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type saltWireFmt struct { 11 | Salt string `dns:"size-hex"` 12 | } 13 | 14 | // HashName hashes a string (label) according to RFC 5155. It returns the hashed string in 15 | // uppercase. 16 | func HashName(label string, ha uint8, iter uint16, salt string) string { 17 | saltwire := new(saltWireFmt) 18 | saltwire.Salt = salt 19 | wire := make([]byte, DefaultMsgSize) 20 | n, err := PackStruct(saltwire, wire, 0) 21 | if err != nil { 22 | return "" 23 | } 24 | wire = wire[:n] 25 | name := make([]byte, 255) 26 | off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) 27 | if err != nil { 28 | return "" 29 | } 30 | name = name[:off] 31 | var s hash.Hash 32 | switch ha { 33 | case SHA1: 34 | s = sha1.New() 35 | default: 36 | return "" 37 | } 38 | 39 | // k = 0 40 | name = append(name, wire...) 41 | io.WriteString(s, string(name)) 42 | nsec3 := s.Sum(nil) 43 | // k > 0 44 | for k := uint16(0); k < iter; k++ { 45 | s.Reset() 46 | nsec3 = append(nsec3, wire...) 47 | io.WriteString(s, string(nsec3)) 48 | nsec3 = s.Sum(nil) 49 | } 50 | return toBase32(nsec3) 51 | } 52 | 53 | // Denialer is an interface that should be implemented by types that are used to denial 54 | // answers in DNSSEC. 55 | type Denialer interface { 56 | // Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3. 57 | Cover(name string) bool 58 | // Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3. 59 | Match(name string) bool 60 | } 61 | 62 | // Cover implements the Denialer interface. 63 | func (rr *NSEC) Cover(name string) bool { 64 | return true 65 | } 66 | 67 | // Match implements the Denialer interface. 68 | func (rr *NSEC) Match(name string) bool { 69 | return true 70 | } 71 | 72 | // Cover implements the Denialer interface. 73 | func (rr *NSEC3) Cover(name string) bool { 74 | // FIXME(miek): check if the zones match 75 | // FIXME(miek): check if we're not dealing with parent nsec3 76 | hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 77 | labels := Split(rr.Hdr.Name) 78 | if len(labels) < 2 { 79 | return false 80 | } 81 | hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot 82 | if hash == rr.NextDomain { 83 | return false // empty interval 84 | } 85 | if hash > rr.NextDomain { // last name, points to apex 86 | // hname > hash 87 | // hname > rr.NextDomain 88 | // TODO(miek) 89 | } 90 | if hname <= hash { 91 | return false 92 | } 93 | if hname >= rr.NextDomain { 94 | return false 95 | } 96 | return true 97 | } 98 | 99 | // Match implements the Denialer interface. 100 | func (rr *NSEC3) Match(name string) bool { 101 | // FIXME(miek): Check if we are in the same zone 102 | hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 103 | labels := Split(rr.Hdr.Name) 104 | if len(labels) < 2 { 105 | return false 106 | } 107 | hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the . 108 | if hash == hname { 109 | return true 110 | } 111 | return false 112 | } 113 | -------------------------------------------------------------------------------- /nsecx_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPackNsec3(t *testing.T) { 8 | nsec3 := HashName("dnsex.nl.", SHA1, 0, "DEAD") 9 | if nsec3 != "ROCCJAE8BJJU7HN6T7NG3TNM8ACRS87J" { 10 | t.Error(nsec3) 11 | } 12 | 13 | nsec3 = HashName("a.b.c.example.org.", SHA1, 2, "DEAD") 14 | if nsec3 != "6LQ07OAHBTOOEU2R9ANI2AT70K5O0RCG" { 15 | t.Error(nsec3) 16 | } 17 | } 18 | 19 | func TestNsec3(t *testing.T) { 20 | // examples taken from .nl 21 | nsec3, _ := NewRR("39p91242oslggest5e6a7cci4iaeqvnk.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6 NS DS RRSIG") 22 | if !nsec3.(*NSEC3).Cover("snasajsksasasa.nl.") { // 39p94jrinub66hnpem8qdpstrec86pg3 23 | t.Error("39p94jrinub66hnpem8qdpstrec86pg3. should be covered by 39p91242oslggest5e6a7cci4iaeqvnk.nl. - 39P99DCGG0MDLARTCRMCF6OFLLUL7PR6") 24 | } 25 | nsec3, _ = NewRR("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. IN NSEC3 1 1 5 F10E9F7EA83FC8F3 SK4F38CQ0ATIEI8MH3RGD0P5I4II6QAN NS SOA TXT RRSIG DNSKEY NSEC3PARAM") 26 | if !nsec3.(*NSEC3).Match("nl.") { // sk4e8fj94u78smusb40o1n0oltbblu2r.nl. 27 | t.Error("sk4e8fj94u78smusb40o1n0oltbblu2r.nl. should match sk4e8fj94u78smusb40o1n0oltbblu2r.nl.") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /privaterr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // PrivateRdata is an interface used for implementing "Private Use" RR types, see 9 | // RFC 6895. This allows one to experiment with new RR types, without requesting an 10 | // official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. 11 | type PrivateRdata interface { 12 | // String returns the text presentaton of the Rdata of the Private RR. 13 | String() string 14 | // Parse parses the Rdata of the private RR. 15 | Parse([]string) error 16 | // Pack is used when packing a private RR into a buffer. 17 | Pack([]byte) (int, error) 18 | // Unpack is used when unpacking a private RR from a buffer. 19 | // TODO(miek): diff. signature than Pack, see edns0.go for instance. 20 | Unpack([]byte) (int, error) 21 | // Copy copies the Rdata. 22 | Copy(PrivateRdata) error 23 | // Len returns the length in octets of the Rdata. 24 | Len() int 25 | } 26 | 27 | // PrivateRR represents an RR that uses a PrivateRdata user-defined type. 28 | // It mocks normal RRs and implements dns.RR interface. 29 | type PrivateRR struct { 30 | Hdr RR_Header 31 | Data PrivateRdata 32 | } 33 | 34 | func mkPrivateRR(rrtype uint16) *PrivateRR { 35 | // Panics if RR is not an instance of PrivateRR. 36 | rrfunc, ok := typeToRR[rrtype] 37 | if !ok { 38 | panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) 39 | } 40 | 41 | anyrr := rrfunc() 42 | switch rr := anyrr.(type) { 43 | case *PrivateRR: 44 | return rr 45 | } 46 | panic(fmt.Sprintf("dns: RR is not a PrivateRR, typeToRR[%d] generator returned %T", rrtype, anyrr)) 47 | } 48 | 49 | func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } 50 | func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } 51 | 52 | // Private len and copy parts to satisfy RR interface. 53 | func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } 54 | func (r *PrivateRR) copy() RR { 55 | // make new RR like this: 56 | rr := mkPrivateRR(r.Hdr.Rrtype) 57 | newh := r.Hdr.copyHeader() 58 | rr.Hdr = *newh 59 | 60 | err := r.Data.Copy(rr.Data) 61 | if err != nil { 62 | panic("dns: got value that could not be used to copy Private rdata") 63 | } 64 | return rr 65 | } 66 | 67 | // PrivateHandle registers a private resource record type. It requires 68 | // string and numeric representation of private RR type and generator function as argument. 69 | func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { 70 | rtypestr = strings.ToUpper(rtypestr) 71 | 72 | typeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } 73 | TypeToString[rtype] = rtypestr 74 | StringToType[rtypestr] = rtype 75 | 76 | setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { 77 | rr := mkPrivateRR(h.Rrtype) 78 | rr.Hdr = h 79 | 80 | var l lex 81 | text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 82 | FETCH: 83 | for { 84 | // TODO(miek): we could also be returning _QUOTE, this might or might not 85 | // be an issue (basically parsing TXT becomes hard) 86 | switch l = <-c; l.value { 87 | case zNewline, zEOF: 88 | break FETCH 89 | case zString: 90 | text = append(text, l.token) 91 | } 92 | } 93 | 94 | err := rr.Data.Parse(text) 95 | if err != nil { 96 | return nil, &ParseError{f, err.Error(), l}, "" 97 | } 98 | 99 | return rr, nil, "" 100 | } 101 | 102 | typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} 103 | } 104 | 105 | // PrivateHandleRemove removes defenitions required to support private RR type. 106 | func PrivateHandleRemove(rtype uint16) { 107 | rtypestr, ok := TypeToString[rtype] 108 | if ok { 109 | delete(typeToRR, rtype) 110 | delete(TypeToString, rtype) 111 | delete(typeToparserFunc, rtype) 112 | delete(StringToType, rtypestr) 113 | } 114 | return 115 | } 116 | -------------------------------------------------------------------------------- /privaterr_test.go: -------------------------------------------------------------------------------- 1 | package dns_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/letsencrypt/dns" 8 | ) 9 | 10 | const TypeISBN uint16 = 0x0F01 11 | 12 | // A crazy new RR type :) 13 | type ISBN struct { 14 | x string // rdata with 10 or 13 numbers, dashes or spaces allowed 15 | } 16 | 17 | func NewISBN() dns.PrivateRdata { return &ISBN{""} } 18 | 19 | func (rd *ISBN) Len() int { return len([]byte(rd.x)) } 20 | func (rd *ISBN) String() string { return rd.x } 21 | 22 | func (rd *ISBN) Parse(txt []string) error { 23 | rd.x = strings.TrimSpace(strings.Join(txt, " ")) 24 | return nil 25 | } 26 | 27 | func (rd *ISBN) Pack(buf []byte) (int, error) { 28 | b := []byte(rd.x) 29 | n := copy(buf, b) 30 | if n != len(b) { 31 | return n, dns.ErrBuf 32 | } 33 | return n, nil 34 | } 35 | 36 | func (rd *ISBN) Unpack(buf []byte) (int, error) { 37 | rd.x = string(buf) 38 | return len(buf), nil 39 | } 40 | 41 | func (rd *ISBN) Copy(dest dns.PrivateRdata) error { 42 | isbn, ok := dest.(*ISBN) 43 | if !ok { 44 | return dns.ErrRdata 45 | } 46 | isbn.x = rd.x 47 | return nil 48 | } 49 | 50 | var testrecord = strings.Join([]string{"example.org.", "3600", "IN", "ISBN", "12-3 456789-0-123"}, "\t") 51 | 52 | func TestPrivateText(t *testing.T) { 53 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 54 | defer dns.PrivateHandleRemove(TypeISBN) 55 | 56 | rr, err := dns.NewRR(testrecord) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if rr.String() != testrecord { 61 | t.Errorf("record string representation did not match original %#v != %#v", rr.String(), testrecord) 62 | } else { 63 | t.Log(rr.String()) 64 | } 65 | } 66 | 67 | func TestPrivateByteSlice(t *testing.T) { 68 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 69 | defer dns.PrivateHandleRemove(TypeISBN) 70 | 71 | rr, err := dns.NewRR(testrecord) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | buf := make([]byte, 100) 77 | off, err := dns.PackRR(rr, buf, 0, nil, false) 78 | if err != nil { 79 | t.Errorf("got error packing ISBN: %v", err) 80 | } 81 | 82 | custrr := rr.(*dns.PrivateRR) 83 | if ln := custrr.Data.Len() + len(custrr.Header().Name) + 11; ln != off { 84 | t.Errorf("offset is not matching to length of Private RR: %d!=%d", off, ln) 85 | } 86 | 87 | rr1, off1, err := dns.UnpackRR(buf[:off], 0) 88 | if err != nil { 89 | t.Errorf("got error unpacking ISBN: %v", err) 90 | } 91 | 92 | if off1 != off { 93 | t.Errorf("Offset after unpacking differs: %d != %d", off1, off) 94 | } 95 | 96 | if rr1.String() != testrecord { 97 | t.Errorf("Record string representation did not match original %#v != %#v", rr1.String(), testrecord) 98 | } else { 99 | t.Log(rr1.String()) 100 | } 101 | } 102 | 103 | const TypeVERSION uint16 = 0x0F02 104 | 105 | type VERSION struct { 106 | x string 107 | } 108 | 109 | func NewVersion() dns.PrivateRdata { return &VERSION{""} } 110 | 111 | func (rd *VERSION) String() string { return rd.x } 112 | func (rd *VERSION) Parse(txt []string) error { 113 | rd.x = strings.TrimSpace(strings.Join(txt, " ")) 114 | return nil 115 | } 116 | 117 | func (rd *VERSION) Pack(buf []byte) (int, error) { 118 | b := []byte(rd.x) 119 | n := copy(buf, b) 120 | if n != len(b) { 121 | return n, dns.ErrBuf 122 | } 123 | return n, nil 124 | } 125 | 126 | func (rd *VERSION) Unpack(buf []byte) (int, error) { 127 | rd.x = string(buf) 128 | return len(buf), nil 129 | } 130 | 131 | func (rd *VERSION) Copy(dest dns.PrivateRdata) error { 132 | isbn, ok := dest.(*VERSION) 133 | if !ok { 134 | return dns.ErrRdata 135 | } 136 | isbn.x = rd.x 137 | return nil 138 | } 139 | 140 | func (rd *VERSION) Len() int { 141 | return len([]byte(rd.x)) 142 | } 143 | 144 | var smallzone = `$ORIGIN example.org. 145 | @ SOA sns.dns.icann.org. noc.dns.icann.org. ( 146 | 2014091518 7200 3600 1209600 3600 147 | ) 148 | A 1.2.3.4 149 | ok ISBN 1231-92110-12 150 | go VERSION ( 151 | 1.3.1 ; comment 152 | ) 153 | www ISBN 1231-92110-16 154 | * CNAME @ 155 | ` 156 | 157 | func TestPrivateZoneParser(t *testing.T) { 158 | dns.PrivateHandle("ISBN", TypeISBN, NewISBN) 159 | dns.PrivateHandle("VERSION", TypeVERSION, NewVersion) 160 | defer dns.PrivateHandleRemove(TypeISBN) 161 | defer dns.PrivateHandleRemove(TypeVERSION) 162 | 163 | r := strings.NewReader(smallzone) 164 | for x := range dns.ParseZone(r, ".", "") { 165 | if err := x.Error; err != nil { 166 | t.Fatal(err) 167 | } 168 | t.Log(x.RR) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /rawmsg.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // These raw* functions do not use reflection, they directly set the values 4 | // in the buffer. There are faster than their reflection counterparts. 5 | 6 | // RawSetId sets the message id in buf. 7 | func rawSetId(msg []byte, i uint16) bool { 8 | if len(msg) < 2 { 9 | return false 10 | } 11 | msg[0], msg[1] = packUint16(i) 12 | return true 13 | } 14 | 15 | // rawSetQuestionLen sets the length of the question section. 16 | func rawSetQuestionLen(msg []byte, i uint16) bool { 17 | if len(msg) < 6 { 18 | return false 19 | } 20 | msg[4], msg[5] = packUint16(i) 21 | return true 22 | } 23 | 24 | // rawSetAnswerLen sets the lenght of the answer section. 25 | func rawSetAnswerLen(msg []byte, i uint16) bool { 26 | if len(msg) < 8 { 27 | return false 28 | } 29 | msg[6], msg[7] = packUint16(i) 30 | return true 31 | } 32 | 33 | // rawSetsNsLen sets the lenght of the authority section. 34 | func rawSetNsLen(msg []byte, i uint16) bool { 35 | if len(msg) < 10 { 36 | return false 37 | } 38 | msg[8], msg[9] = packUint16(i) 39 | return true 40 | } 41 | 42 | // rawSetExtraLen sets the lenght of the additional section. 43 | func rawSetExtraLen(msg []byte, i uint16) bool { 44 | if len(msg) < 12 { 45 | return false 46 | } 47 | msg[10], msg[11] = packUint16(i) 48 | return true 49 | } 50 | 51 | // rawSetRdlength sets the rdlength in the header of 52 | // the RR. The offset 'off' must be positioned at the 53 | // start of the header of the RR, 'end' must be the 54 | // end of the RR. 55 | func rawSetRdlength(msg []byte, off, end int) bool { 56 | l := len(msg) 57 | Loop: 58 | for { 59 | if off+1 > l { 60 | return false 61 | } 62 | c := int(msg[off]) 63 | off++ 64 | switch c & 0xC0 { 65 | case 0x00: 66 | if c == 0x00 { 67 | // End of the domainname 68 | break Loop 69 | } 70 | if off+c > l { 71 | return false 72 | } 73 | off += c 74 | 75 | case 0xC0: 76 | // pointer, next byte included, ends domainname 77 | off++ 78 | break Loop 79 | } 80 | } 81 | // The domainname has been seen, we at the start of the fixed part in the header. 82 | // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. 83 | off += 2 + 2 + 4 84 | if off+2 > l { 85 | return false 86 | } 87 | //off+1 is the end of the header, 'end' is the end of the rr 88 | //so 'end' - 'off+2' is the length of the rdata 89 | rdatalen := end - (off + 2) 90 | if rdatalen > 0xFFFF { 91 | return false 92 | } 93 | msg[off], msg[off+1] = packUint16(uint16(rdatalen)) 94 | return true 95 | } 96 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Implement a simple scanner, return a byte stream from an io reader. 4 | 5 | import ( 6 | "bufio" 7 | "io" 8 | "text/scanner" 9 | ) 10 | 11 | type scan struct { 12 | src *bufio.Reader 13 | position scanner.Position 14 | eof bool // Have we just seen a eof 15 | } 16 | 17 | func scanInit(r io.Reader) *scan { 18 | s := new(scan) 19 | s.src = bufio.NewReader(r) 20 | s.position.Line = 1 21 | return s 22 | } 23 | 24 | // tokenText returns the next byte from the input 25 | func (s *scan) tokenText() (byte, error) { 26 | c, err := s.src.ReadByte() 27 | if err != nil { 28 | return c, err 29 | } 30 | // delay the newline handling until the next token is delivered, 31 | // fixes off-by-one errors when reporting a parse error. 32 | if s.eof == true { 33 | s.position.Line++ 34 | s.position.Column = 0 35 | s.eof = false 36 | } 37 | if c == '\n' { 38 | s.eof = true 39 | return c, nil 40 | } 41 | s.position.Column++ 42 | return c, nil 43 | } 44 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "runtime" 7 | "sync" 8 | "testing" 9 | ) 10 | 11 | func HelloServer(w ResponseWriter, req *Msg) { 12 | m := new(Msg) 13 | m.SetReply(req) 14 | 15 | m.Extra = make([]RR, 1) 16 | m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} 17 | w.WriteMsg(m) 18 | } 19 | 20 | func HelloServerBadId(w ResponseWriter, req *Msg) { 21 | m := new(Msg) 22 | m.SetReply(req) 23 | m.Id += 1 24 | 25 | m.Extra = make([]RR, 1) 26 | m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} 27 | w.WriteMsg(m) 28 | } 29 | 30 | func AnotherHelloServer(w ResponseWriter, req *Msg) { 31 | m := new(Msg) 32 | m.SetReply(req) 33 | 34 | m.Extra = make([]RR, 1) 35 | m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello example"}} 36 | w.WriteMsg(m) 37 | } 38 | 39 | func RunLocalUDPServer(laddr string) (*Server, string, error) { 40 | pc, err := net.ListenPacket("udp", laddr) 41 | if err != nil { 42 | return nil, "", err 43 | } 44 | server := &Server{PacketConn: pc} 45 | 46 | waitLock := sync.Mutex{} 47 | waitLock.Lock() 48 | server.NotifyStartedFunc = waitLock.Unlock 49 | 50 | go func() { 51 | server.ActivateAndServe() 52 | pc.Close() 53 | }() 54 | 55 | waitLock.Lock() 56 | return server, pc.LocalAddr().String(), nil 57 | } 58 | 59 | func RunLocalUDPServerUnsafe(laddr string) (*Server, string, error) { 60 | pc, err := net.ListenPacket("udp", laddr) 61 | if err != nil { 62 | return nil, "", err 63 | } 64 | server := &Server{PacketConn: pc, Unsafe: true} 65 | 66 | waitLock := sync.Mutex{} 67 | waitLock.Lock() 68 | server.NotifyStartedFunc = waitLock.Unlock 69 | 70 | go func() { 71 | server.ActivateAndServe() 72 | pc.Close() 73 | }() 74 | 75 | waitLock.Lock() 76 | return server, pc.LocalAddr().String(), nil 77 | } 78 | 79 | func RunLocalTCPServer(laddr string) (*Server, string, error) { 80 | l, err := net.Listen("tcp", laddr) 81 | if err != nil { 82 | return nil, "", err 83 | } 84 | 85 | server := &Server{Listener: l} 86 | 87 | waitLock := sync.Mutex{} 88 | waitLock.Lock() 89 | server.NotifyStartedFunc = waitLock.Unlock 90 | 91 | go func() { 92 | server.ActivateAndServe() 93 | l.Close() 94 | }() 95 | 96 | waitLock.Lock() 97 | return server, l.Addr().String(), nil 98 | } 99 | 100 | func TestServing(t *testing.T) { 101 | HandleFunc("miek.nl.", HelloServer) 102 | HandleFunc("example.com.", AnotherHelloServer) 103 | defer HandleRemove("miek.nl.") 104 | defer HandleRemove("example.com.") 105 | 106 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 107 | if err != nil { 108 | t.Fatalf("Unable to run test server: %v", err) 109 | } 110 | defer s.Shutdown() 111 | 112 | c := new(Client) 113 | m := new(Msg) 114 | m.SetQuestion("miek.nl.", TypeTXT) 115 | r, _, err := c.Exchange(m, addrstr) 116 | if err != nil || len(r.Extra) == 0 { 117 | t.Fatal("failed to exchange miek.nl", err) 118 | } 119 | txt := r.Extra[0].(*TXT).Txt[0] 120 | if txt != "Hello world" { 121 | t.Error("Unexpected result for miek.nl", txt, "!= Hello world") 122 | } 123 | 124 | m.SetQuestion("example.com.", TypeTXT) 125 | r, _, err = c.Exchange(m, addrstr) 126 | if err != nil { 127 | t.Fatal("failed to exchange example.com", err) 128 | } 129 | txt = r.Extra[0].(*TXT).Txt[0] 130 | if txt != "Hello example" { 131 | t.Error("Unexpected result for example.com", txt, "!= Hello example") 132 | } 133 | 134 | // Test Mixes cased as noticed by Ask. 135 | m.SetQuestion("eXaMplE.cOm.", TypeTXT) 136 | r, _, err = c.Exchange(m, addrstr) 137 | if err != nil { 138 | t.Error("failed to exchange eXaMplE.cOm", err) 139 | } 140 | txt = r.Extra[0].(*TXT).Txt[0] 141 | if txt != "Hello example" { 142 | t.Error("Unexpected result for example.com", txt, "!= Hello example") 143 | } 144 | } 145 | 146 | func BenchmarkServe(b *testing.B) { 147 | b.StopTimer() 148 | HandleFunc("miek.nl.", HelloServer) 149 | defer HandleRemove("miek.nl.") 150 | a := runtime.GOMAXPROCS(4) 151 | 152 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 153 | if err != nil { 154 | b.Fatalf("Unable to run test server: %v", err) 155 | } 156 | defer s.Shutdown() 157 | 158 | c := new(Client) 159 | m := new(Msg) 160 | m.SetQuestion("miek.nl", TypeSOA) 161 | 162 | b.StartTimer() 163 | for i := 0; i < b.N; i++ { 164 | c.Exchange(m, addrstr) 165 | } 166 | runtime.GOMAXPROCS(a) 167 | } 168 | 169 | func benchmarkServe6(b *testing.B) { 170 | b.StopTimer() 171 | HandleFunc("miek.nl.", HelloServer) 172 | defer HandleRemove("miek.nl.") 173 | a := runtime.GOMAXPROCS(4) 174 | s, addrstr, err := RunLocalUDPServer("[::1]:0") 175 | if err != nil { 176 | b.Fatalf("Unable to run test server: %v", err) 177 | } 178 | defer s.Shutdown() 179 | 180 | c := new(Client) 181 | m := new(Msg) 182 | m.SetQuestion("miek.nl", TypeSOA) 183 | 184 | b.StartTimer() 185 | for i := 0; i < b.N; i++ { 186 | c.Exchange(m, addrstr) 187 | } 188 | runtime.GOMAXPROCS(a) 189 | } 190 | 191 | func HelloServerCompress(w ResponseWriter, req *Msg) { 192 | m := new(Msg) 193 | m.SetReply(req) 194 | m.Extra = make([]RR, 1) 195 | m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}} 196 | m.Compress = true 197 | w.WriteMsg(m) 198 | } 199 | 200 | func BenchmarkServeCompress(b *testing.B) { 201 | b.StopTimer() 202 | HandleFunc("miek.nl.", HelloServerCompress) 203 | defer HandleRemove("miek.nl.") 204 | a := runtime.GOMAXPROCS(4) 205 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 206 | if err != nil { 207 | b.Fatalf("Unable to run test server: %v", err) 208 | } 209 | defer s.Shutdown() 210 | 211 | c := new(Client) 212 | m := new(Msg) 213 | m.SetQuestion("miek.nl", TypeSOA) 214 | b.StartTimer() 215 | for i := 0; i < b.N; i++ { 216 | c.Exchange(m, addrstr) 217 | } 218 | runtime.GOMAXPROCS(a) 219 | } 220 | 221 | func TestDotAsCatchAllWildcard(t *testing.T) { 222 | mux := NewServeMux() 223 | mux.Handle(".", HandlerFunc(HelloServer)) 224 | mux.Handle("example.com.", HandlerFunc(AnotherHelloServer)) 225 | 226 | handler := mux.match("www.miek.nl.", TypeTXT) 227 | if handler == nil { 228 | t.Error("wildcard match failed") 229 | } 230 | 231 | handler = mux.match("www.example.com.", TypeTXT) 232 | if handler == nil { 233 | t.Error("example.com match failed") 234 | } 235 | 236 | handler = mux.match("a.www.example.com.", TypeTXT) 237 | if handler == nil { 238 | t.Error("a.www.example.com match failed") 239 | } 240 | 241 | handler = mux.match("boe.", TypeTXT) 242 | if handler == nil { 243 | t.Error("boe. match failed") 244 | } 245 | } 246 | 247 | func TestCaseFolding(t *testing.T) { 248 | mux := NewServeMux() 249 | mux.Handle("_udp.example.com.", HandlerFunc(HelloServer)) 250 | 251 | handler := mux.match("_dns._udp.example.com.", TypeSRV) 252 | if handler == nil { 253 | t.Error("case sensitive characters folded") 254 | } 255 | 256 | handler = mux.match("_DNS._UDP.EXAMPLE.COM.", TypeSRV) 257 | if handler == nil { 258 | t.Error("case insensitive characters not folded") 259 | } 260 | } 261 | 262 | func TestRootServer(t *testing.T) { 263 | mux := NewServeMux() 264 | mux.Handle(".", HandlerFunc(HelloServer)) 265 | 266 | handler := mux.match(".", TypeNS) 267 | if handler == nil { 268 | t.Error("root match failed") 269 | } 270 | } 271 | 272 | type maxRec struct { 273 | max int 274 | sync.RWMutex 275 | } 276 | 277 | var M = new(maxRec) 278 | 279 | func HelloServerLargeResponse(resp ResponseWriter, req *Msg) { 280 | m := new(Msg) 281 | m.SetReply(req) 282 | m.Authoritative = true 283 | m1 := 0 284 | M.RLock() 285 | m1 = M.max 286 | M.RUnlock() 287 | for i := 0; i < m1; i++ { 288 | aRec := &A{ 289 | Hdr: RR_Header{ 290 | Name: req.Question[0].Name, 291 | Rrtype: TypeA, 292 | Class: ClassINET, 293 | Ttl: 0, 294 | }, 295 | A: net.ParseIP(fmt.Sprintf("127.0.0.%d", i+1)).To4(), 296 | } 297 | m.Answer = append(m.Answer, aRec) 298 | } 299 | resp.WriteMsg(m) 300 | } 301 | 302 | func TestServingLargeResponses(t *testing.T) { 303 | HandleFunc("example.", HelloServerLargeResponse) 304 | defer HandleRemove("example.") 305 | 306 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 307 | if err != nil { 308 | t.Fatalf("Unable to run test server: %v", err) 309 | } 310 | defer s.Shutdown() 311 | 312 | // Create request 313 | m := new(Msg) 314 | m.SetQuestion("web.service.example.", TypeANY) 315 | 316 | c := new(Client) 317 | c.Net = "udp" 318 | M.Lock() 319 | M.max = 2 320 | M.Unlock() 321 | _, _, err = c.Exchange(m, addrstr) 322 | if err != nil { 323 | t.Errorf("failed to exchange: %v", err) 324 | } 325 | // This must fail 326 | M.Lock() 327 | M.max = 20 328 | M.Unlock() 329 | _, _, err = c.Exchange(m, addrstr) 330 | if err == nil { 331 | t.Error("failed to fail exchange, this should generate packet error") 332 | } 333 | // But this must work again 334 | c.UDPSize = 7000 335 | _, _, err = c.Exchange(m, addrstr) 336 | if err != nil { 337 | t.Errorf("failed to exchange: %v", err) 338 | } 339 | } 340 | 341 | func TestServingResponse(t *testing.T) { 342 | if testing.Short() { 343 | t.Skip("skipping test in short mode.") 344 | } 345 | HandleFunc("miek.nl.", HelloServer) 346 | s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") 347 | if err != nil { 348 | t.Fatalf("Unable to run test server: %v", err) 349 | } 350 | 351 | c := new(Client) 352 | m := new(Msg) 353 | m.SetQuestion("miek.nl.", TypeTXT) 354 | m.Response = false 355 | _, _, err = c.Exchange(m, addrstr) 356 | if err != nil { 357 | t.Fatal("failed to exchange", err) 358 | } 359 | m.Response = true 360 | _, _, err = c.Exchange(m, addrstr) 361 | if err == nil { 362 | t.Fatal("exchanged response message") 363 | } 364 | 365 | s.Shutdown() 366 | s, addrstr, err = RunLocalUDPServerUnsafe("127.0.0.1:0") 367 | if err != nil { 368 | t.Fatalf("Unable to run test server: %v", err) 369 | } 370 | defer s.Shutdown() 371 | 372 | m.Response = true 373 | _, _, err = c.Exchange(m, addrstr) 374 | if err != nil { 375 | t.Fatal("could exchanged response message in Unsafe mode") 376 | } 377 | } 378 | 379 | func TestShutdownTCP(t *testing.T) { 380 | s, _, err := RunLocalTCPServer("127.0.0.1:0") 381 | if err != nil { 382 | t.Fatalf("Unable to run test server: %v", err) 383 | } 384 | err = s.Shutdown() 385 | if err != nil { 386 | t.Errorf("Could not shutdown test TCP server, %v", err) 387 | } 388 | } 389 | 390 | func TestShutdownUDP(t *testing.T) { 391 | s, _, err := RunLocalUDPServer("127.0.0.1:0") 392 | if err != nil { 393 | t.Fatalf("Unable to run test server: %v", err) 394 | } 395 | err = s.Shutdown() 396 | if err != nil { 397 | t.Errorf("Could not shutdown test UDP server, %v", err) 398 | } 399 | } 400 | 401 | type ExampleFrameLengthWriter struct { 402 | Writer 403 | } 404 | 405 | func (e *ExampleFrameLengthWriter) Write(m []byte) (int, error) { 406 | fmt.Println("writing raw DNS message of length", len(m)) 407 | return e.Writer.Write(m) 408 | } 409 | 410 | func ExampleDecorateWriter() { 411 | // instrument raw DNS message writing 412 | wf := DecorateWriter(func(w Writer) Writer { 413 | return &ExampleFrameLengthWriter{w} 414 | }) 415 | 416 | // simple UDP server 417 | pc, err := net.ListenPacket("udp", "127.0.0.1:0") 418 | if err != nil { 419 | fmt.Println(err.Error()) 420 | return 421 | } 422 | server := &Server{ 423 | PacketConn: pc, 424 | DecorateWriter: wf, 425 | } 426 | 427 | waitLock := sync.Mutex{} 428 | waitLock.Lock() 429 | server.NotifyStartedFunc = waitLock.Unlock 430 | defer server.Shutdown() 431 | 432 | go func() { 433 | server.ActivateAndServe() 434 | pc.Close() 435 | }() 436 | 437 | waitLock.Lock() 438 | 439 | HandleFunc("miek.nl.", HelloServer) 440 | 441 | c := new(Client) 442 | m := new(Msg) 443 | m.SetQuestion("miek.nl.", TypeTXT) 444 | _, _, err = c.Exchange(m, pc.LocalAddr().String()) 445 | if err != nil { 446 | fmt.Println("failed to exchange", err.Error()) 447 | return 448 | } 449 | // Output: writing raw DNS message of length 56 450 | } 451 | -------------------------------------------------------------------------------- /sig0.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "math/big" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // Sign signs a dns.Msg. It fills the signature with the appropriate data. 14 | // The SIG record should have the SignerName, KeyTag, Algorithm, Inception 15 | // and Expiration set. 16 | func (rr *SIG) Sign(k PrivateKey, m *Msg) ([]byte, error) { 17 | if k == nil { 18 | return nil, ErrPrivKey 19 | } 20 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 21 | return nil, ErrKey 22 | } 23 | rr.Header().Rrtype = TypeSIG 24 | rr.Header().Class = ClassANY 25 | rr.Header().Ttl = 0 26 | rr.Header().Name = "." 27 | rr.OrigTtl = 0 28 | rr.TypeCovered = 0 29 | rr.Labels = 0 30 | 31 | buf := make([]byte, m.Len()+rr.len()) 32 | mbuf, err := m.PackBuffer(buf) 33 | if err != nil { 34 | return nil, err 35 | } 36 | if &buf[0] != &mbuf[0] { 37 | return nil, ErrBuf 38 | } 39 | off, err := PackRR(rr, buf, len(mbuf), nil, false) 40 | if err != nil { 41 | return nil, err 42 | } 43 | buf = buf[:off:cap(buf)] 44 | var hash crypto.Hash 45 | switch rr.Algorithm { 46 | case DSA, RSASHA1: 47 | hash = crypto.SHA1 48 | case RSASHA256, ECDSAP256SHA256: 49 | hash = crypto.SHA256 50 | case ECDSAP384SHA384: 51 | hash = crypto.SHA384 52 | case RSASHA512: 53 | hash = crypto.SHA512 54 | default: 55 | return nil, ErrAlg 56 | } 57 | hasher := hash.New() 58 | // Write SIG rdata 59 | hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) 60 | // Write message 61 | hasher.Write(buf[:len(mbuf)]) 62 | hashed := hasher.Sum(nil) 63 | 64 | sig, err := k.Sign(hashed, rr.Algorithm) 65 | if err != nil { 66 | return nil, err 67 | } 68 | rr.Signature = toBase64(sig) 69 | buf = append(buf, sig...) 70 | if len(buf) > int(^uint16(0)) { 71 | return nil, ErrBuf 72 | } 73 | // Adjust sig data length 74 | rdoff := len(mbuf) + 1 + 2 + 2 + 4 75 | rdlen, _ := unpackUint16(buf, rdoff) 76 | rdlen += uint16(len(sig)) 77 | buf[rdoff], buf[rdoff+1] = packUint16(rdlen) 78 | // Adjust additional count 79 | adc, _ := unpackUint16(buf, 10) 80 | adc++ 81 | buf[10], buf[11] = packUint16(adc) 82 | return buf, nil 83 | } 84 | 85 | // Verify validates the message buf using the key k. 86 | // It's assumed that buf is a valid message from which rr was unpacked. 87 | func (rr *SIG) Verify(k *KEY, buf []byte) error { 88 | if k == nil { 89 | return ErrKey 90 | } 91 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 92 | return ErrKey 93 | } 94 | 95 | var hash crypto.Hash 96 | switch rr.Algorithm { 97 | case DSA, RSASHA1: 98 | hash = crypto.SHA1 99 | case RSASHA256, ECDSAP256SHA256: 100 | hash = crypto.SHA256 101 | case ECDSAP384SHA384: 102 | hash = crypto.SHA384 103 | case RSASHA512: 104 | hash = crypto.SHA512 105 | default: 106 | return ErrAlg 107 | } 108 | hasher := hash.New() 109 | 110 | buflen := len(buf) 111 | qdc, _ := unpackUint16(buf, 4) 112 | anc, _ := unpackUint16(buf, 6) 113 | auc, _ := unpackUint16(buf, 8) 114 | adc, offset := unpackUint16(buf, 10) 115 | var err error 116 | for i := uint16(0); i < qdc && offset < buflen; i++ { 117 | _, offset, err = UnpackDomainName(buf, offset) 118 | if err != nil { 119 | return err 120 | } 121 | // Skip past Type and Class 122 | offset += 2 + 2 123 | } 124 | for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { 125 | _, offset, err = UnpackDomainName(buf, offset) 126 | if err != nil { 127 | return err 128 | } 129 | // Skip past Type, Class and TTL 130 | offset += 2 + 2 + 4 131 | if offset+1 >= buflen { 132 | continue 133 | } 134 | var rdlen uint16 135 | rdlen, offset = unpackUint16(buf, offset) 136 | offset += int(rdlen) 137 | } 138 | if offset >= buflen { 139 | return &Error{err: "overflowing unpacking signed message"} 140 | } 141 | 142 | // offset should be just prior to SIG 143 | bodyend := offset 144 | // owner name SHOULD be root 145 | _, offset, err = UnpackDomainName(buf, offset) 146 | if err != nil { 147 | return err 148 | } 149 | // Skip Type, Class, TTL, RDLen 150 | offset += 2 + 2 + 4 + 2 151 | sigstart := offset 152 | // Skip Type Covered, Algorithm, Labels, Original TTL 153 | offset += 2 + 1 + 1 + 4 154 | if offset+4+4 >= buflen { 155 | return &Error{err: "overflow unpacking signed message"} 156 | } 157 | expire := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) 158 | offset += 4 159 | incept := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) 160 | offset += 4 161 | now := uint32(time.Now().Unix()) 162 | if now < incept || now > expire { 163 | return ErrTime 164 | } 165 | // Skip key tag 166 | offset += 2 167 | var signername string 168 | signername, offset, err = UnpackDomainName(buf, offset) 169 | if err != nil { 170 | return err 171 | } 172 | // If key has come from the DNS name compression might 173 | // have mangled the case of the name 174 | if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { 175 | return &Error{err: "signer name doesn't match key name"} 176 | } 177 | sigend := offset 178 | hasher.Write(buf[sigstart:sigend]) 179 | hasher.Write(buf[:10]) 180 | hasher.Write([]byte{ 181 | byte((adc - 1) << 8), 182 | byte(adc - 1), 183 | }) 184 | hasher.Write(buf[12:bodyend]) 185 | 186 | hashed := hasher.Sum(nil) 187 | sig := buf[sigend:] 188 | switch k.Algorithm { 189 | case DSA: 190 | pk := k.publicKeyDSA() 191 | sig = sig[1:] 192 | r := big.NewInt(0) 193 | r.SetBytes(sig[:len(sig)/2]) 194 | s := big.NewInt(0) 195 | s.SetBytes(sig[len(sig)/2:]) 196 | if pk != nil { 197 | if dsa.Verify(pk, hashed, r, s) { 198 | return nil 199 | } 200 | return ErrSig 201 | } 202 | case RSASHA1, RSASHA256, RSASHA512: 203 | pk := k.publicKeyRSA() 204 | if pk != nil { 205 | return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) 206 | } 207 | case ECDSAP256SHA256, ECDSAP384SHA384: 208 | pk := k.publicKeyECDSA() 209 | r := big.NewInt(0) 210 | r.SetBytes(sig[:len(sig)/2]) 211 | s := big.NewInt(0) 212 | s.SetBytes(sig[len(sig)/2:]) 213 | if pk != nil { 214 | if ecdsa.Verify(pk, hashed, r, s) { 215 | return nil 216 | } 217 | return ErrSig 218 | } 219 | } 220 | return ErrKeyAlg 221 | } 222 | -------------------------------------------------------------------------------- /sig0_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestSIG0(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skipping test in short mode.") 11 | } 12 | m := new(Msg) 13 | m.SetQuestion("example.org.", TypeSOA) 14 | for _, alg := range []uint8{DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256, RSASHA512} { 15 | algstr := AlgorithmToString[alg] 16 | keyrr := new(KEY) 17 | keyrr.Hdr.Name = algstr + "." 18 | keyrr.Hdr.Rrtype = TypeKEY 19 | keyrr.Hdr.Class = ClassINET 20 | keyrr.Algorithm = alg 21 | keysize := 1024 22 | switch alg { 23 | case ECDSAP256SHA256: 24 | keysize = 256 25 | case ECDSAP384SHA384: 26 | keysize = 384 27 | } 28 | pk, err := keyrr.Generate(keysize) 29 | if err != nil { 30 | t.Errorf("Failed to generate key for “%s”: %v", algstr, err) 31 | continue 32 | } 33 | now := uint32(time.Now().Unix()) 34 | sigrr := new(SIG) 35 | sigrr.Hdr.Name = "." 36 | sigrr.Hdr.Rrtype = TypeSIG 37 | sigrr.Hdr.Class = ClassANY 38 | sigrr.Algorithm = alg 39 | sigrr.Expiration = now + 300 40 | sigrr.Inception = now - 300 41 | sigrr.KeyTag = keyrr.KeyTag() 42 | sigrr.SignerName = keyrr.Hdr.Name 43 | mb, err := sigrr.Sign(pk, m) 44 | if err != nil { 45 | t.Errorf("Failed to sign message using “%s”: %v", algstr, err) 46 | continue 47 | } 48 | m := new(Msg) 49 | if err := m.Unpack(mb); err != nil { 50 | t.Errorf("Failed to unpack message signed using “%s”: %v", algstr, err) 51 | continue 52 | } 53 | if len(m.Extra) != 1 { 54 | t.Errorf("Missing SIG for message signed using “%s”", algstr) 55 | continue 56 | } 57 | var sigrrwire *SIG 58 | switch rr := m.Extra[0].(type) { 59 | case *SIG: 60 | sigrrwire = rr 61 | default: 62 | t.Errorf("Expected SIG RR, instead: %v", rr) 63 | continue 64 | } 65 | for _, rr := range []*SIG{sigrr, sigrrwire} { 66 | id := "sigrr" 67 | if rr == sigrrwire { 68 | id = "sigrrwire" 69 | } 70 | if err := rr.Verify(keyrr, mb); err != nil { 71 | t.Errorf("Failed to verify “%s” signed SIG(%s): %v", algstr, id, err) 72 | continue 73 | } 74 | } 75 | mb[13]++ 76 | if err := sigrr.Verify(keyrr, mb); err == nil { 77 | t.Errorf("Verify succeeded on an altered message using “%s”", algstr) 78 | continue 79 | } 80 | sigrr.Expiration = 2 81 | sigrr.Inception = 1 82 | mb, _ = sigrr.Sign(pk, m) 83 | if err := sigrr.Verify(keyrr, mb); err == nil { 84 | t.Errorf("Verify succeeded on an expired message using “%s”", algstr) 85 | continue 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /singleinflight.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Adapted for dns package usage by Miek Gieben. 6 | 7 | package dns 8 | 9 | import "sync" 10 | import "time" 11 | 12 | // call is an in-flight or completed singleflight.Do call 13 | type call struct { 14 | wg sync.WaitGroup 15 | val *Msg 16 | rtt time.Duration 17 | err error 18 | dups int 19 | } 20 | 21 | // singleflight represents a class of work and forms a namespace in 22 | // which units of work can be executed with duplicate suppression. 23 | type singleflight struct { 24 | sync.Mutex // protects m 25 | m map[string]*call // lazily initialized 26 | } 27 | 28 | // Do executes and returns the results of the given function, making 29 | // sure that only one execution is in-flight for a given key at a 30 | // time. If a duplicate comes in, the duplicate caller waits for the 31 | // original to complete and receives the same results. 32 | // The return value shared indicates whether v was given to multiple callers. 33 | func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { 34 | g.Lock() 35 | if g.m == nil { 36 | g.m = make(map[string]*call) 37 | } 38 | if c, ok := g.m[key]; ok { 39 | c.dups++ 40 | g.Unlock() 41 | c.wg.Wait() 42 | return c.val, c.rtt, c.err, true 43 | } 44 | c := new(call) 45 | c.wg.Add(1) 46 | g.m[key] = c 47 | g.Unlock() 48 | 49 | c.val, c.rtt, c.err = fn() 50 | c.wg.Done() 51 | 52 | g.Lock() 53 | delete(g.m, key) 54 | g.Unlock() 55 | 56 | return c.val, c.rtt, c.err, c.dups > 0 57 | } 58 | -------------------------------------------------------------------------------- /tlsa.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/sha512" 6 | "crypto/x509" 7 | "encoding/hex" 8 | "errors" 9 | "io" 10 | "net" 11 | "strconv" 12 | ) 13 | 14 | // CertificateToDANE converts a certificate to a hex string as used in the TLSA record. 15 | func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { 16 | switch matchingType { 17 | case 0: 18 | switch selector { 19 | case 0: 20 | return hex.EncodeToString(cert.Raw), nil 21 | case 1: 22 | return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil 23 | } 24 | case 1: 25 | h := sha256.New() 26 | switch selector { 27 | case 0: 28 | io.WriteString(h, string(cert.Raw)) 29 | return hex.EncodeToString(h.Sum(nil)), nil 30 | case 1: 31 | io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) 32 | return hex.EncodeToString(h.Sum(nil)), nil 33 | } 34 | case 2: 35 | h := sha512.New() 36 | switch selector { 37 | case 0: 38 | io.WriteString(h, string(cert.Raw)) 39 | return hex.EncodeToString(h.Sum(nil)), nil 40 | case 1: 41 | io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) 42 | return hex.EncodeToString(h.Sum(nil)), nil 43 | } 44 | } 45 | return "", errors.New("dns: bad TLSA MatchingType or TLSA Selector") 46 | } 47 | 48 | // Sign creates a TLSA record from an SSL certificate. 49 | func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { 50 | r.Hdr.Rrtype = TypeTLSA 51 | r.Usage = uint8(usage) 52 | r.Selector = uint8(selector) 53 | r.MatchingType = uint8(matchingType) 54 | 55 | r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) 56 | if err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | // Verify verifies a TLSA record against an SSL certificate. If it is OK 63 | // a nil error is returned. 64 | func (r *TLSA) Verify(cert *x509.Certificate) error { 65 | c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) 66 | if err != nil { 67 | return err // Not also ErrSig? 68 | } 69 | if r.Certificate == c { 70 | return nil 71 | } 72 | return ErrSig // ErrSig, really? 73 | } 74 | 75 | // TLSAName returns the ownername of a TLSA resource record as per the 76 | // rules specified in RFC 6698, Section 3. 77 | func TLSAName(name, service, network string) (string, error) { 78 | if !IsFqdn(name) { 79 | return "", ErrFqdn 80 | } 81 | p, e := net.LookupPort(network, service) 82 | if e != nil { 83 | return "", e 84 | } 85 | return "_" + strconv.Itoa(p) + "_" + network + "." + name, nil 86 | } 87 | -------------------------------------------------------------------------------- /tsig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/hex" 10 | "hash" 11 | "io" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // HMAC hashing codes. These are transmitted as domain names. 18 | const ( 19 | HmacMD5 = "hmac-md5.sig-alg.reg.int." 20 | HmacSHA1 = "hmac-sha1." 21 | HmacSHA256 = "hmac-sha256." 22 | HmacSHA512 = "hmac-sha512." 23 | ) 24 | 25 | // TSIG is the RR the holds the transaction signature of a message. 26 | // See RFC 2845 and RFC 4635. 27 | type TSIG struct { 28 | Hdr RR_Header 29 | Algorithm string `dns:"domain-name"` 30 | TimeSigned uint64 `dns:"uint48"` 31 | Fudge uint16 32 | MACSize uint16 33 | MAC string `dns:"size-hex"` 34 | OrigId uint16 35 | Error uint16 36 | OtherLen uint16 37 | OtherData string `dns:"size-hex"` 38 | } 39 | 40 | func (rr *TSIG) Header() *RR_Header { 41 | return &rr.Hdr 42 | } 43 | 44 | // TSIG has no official presentation format, but this will suffice. 45 | 46 | func (rr *TSIG) String() string { 47 | s := "\n;; TSIG PSEUDOSECTION:\n" 48 | s += rr.Hdr.String() + 49 | " " + rr.Algorithm + 50 | " " + tsigTimeToString(rr.TimeSigned) + 51 | " " + strconv.Itoa(int(rr.Fudge)) + 52 | " " + strconv.Itoa(int(rr.MACSize)) + 53 | " " + strings.ToUpper(rr.MAC) + 54 | " " + strconv.Itoa(int(rr.OrigId)) + 55 | " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR 56 | " " + strconv.Itoa(int(rr.OtherLen)) + 57 | " " + rr.OtherData 58 | return s 59 | } 60 | 61 | func (rr *TSIG) len() int { 62 | return rr.Hdr.len() + len(rr.Algorithm) + 1 + 6 + 63 | 4 + len(rr.MAC)/2 + 1 + 6 + len(rr.OtherData)/2 + 1 64 | } 65 | 66 | func (rr *TSIG) copy() RR { 67 | return &TSIG{*rr.Hdr.copyHeader(), rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData} 68 | } 69 | 70 | // The following values must be put in wireformat, so that the MAC can be calculated. 71 | // RFC 2845, section 3.4.2. TSIG Variables. 72 | type tsigWireFmt struct { 73 | // From RR_Header 74 | Name string `dns:"domain-name"` 75 | Class uint16 76 | Ttl uint32 77 | // Rdata of the TSIG 78 | Algorithm string `dns:"domain-name"` 79 | TimeSigned uint64 `dns:"uint48"` 80 | Fudge uint16 81 | // MACSize, MAC and OrigId excluded 82 | Error uint16 83 | OtherLen uint16 84 | OtherData string `dns:"size-hex"` 85 | } 86 | 87 | // If we have the MAC use this type to convert it to wiredata. 88 | // Section 3.4.3. Request MAC 89 | type macWireFmt struct { 90 | MACSize uint16 91 | MAC string `dns:"size-hex"` 92 | } 93 | 94 | // 3.3. Time values used in TSIG calculations 95 | type timerWireFmt struct { 96 | TimeSigned uint64 `dns:"uint48"` 97 | Fudge uint16 98 | } 99 | 100 | // TsigGenerate fills out the TSIG record attached to the message. 101 | // The message should contain 102 | // a "stub" TSIG RR with the algorithm, key name (owner name of the RR), 103 | // time fudge (defaults to 300 seconds) and the current time 104 | // The TSIG MAC is saved in that Tsig RR. 105 | // When TsigGenerate is called for the first time requestMAC is set to the empty string and 106 | // timersOnly is false. 107 | // If something goes wrong an error is returned, otherwise it is nil. 108 | func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { 109 | if m.IsTsig() == nil { 110 | panic("dns: TSIG not last RR in additional") 111 | } 112 | // If we barf here, the caller is to blame 113 | rawsecret, err := fromBase64([]byte(secret)) 114 | if err != nil { 115 | return nil, "", err 116 | } 117 | 118 | rr := m.Extra[len(m.Extra)-1].(*TSIG) 119 | m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg 120 | mbuf, err := m.Pack() 121 | if err != nil { 122 | return nil, "", err 123 | } 124 | buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) 125 | 126 | t := new(TSIG) 127 | var h hash.Hash 128 | switch rr.Algorithm { 129 | case HmacMD5: 130 | h = hmac.New(md5.New, []byte(rawsecret)) 131 | case HmacSHA1: 132 | h = hmac.New(sha1.New, []byte(rawsecret)) 133 | case HmacSHA256: 134 | h = hmac.New(sha256.New, []byte(rawsecret)) 135 | case HmacSHA512: 136 | h = hmac.New(sha512.New, []byte(rawsecret)) 137 | default: 138 | return nil, "", ErrKeyAlg 139 | } 140 | io.WriteString(h, string(buf)) 141 | t.MAC = hex.EncodeToString(h.Sum(nil)) 142 | t.MACSize = uint16(len(t.MAC) / 2) // Size is half! 143 | 144 | t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} 145 | t.Fudge = rr.Fudge 146 | t.TimeSigned = rr.TimeSigned 147 | t.Algorithm = rr.Algorithm 148 | t.OrigId = m.Id 149 | 150 | tbuf := make([]byte, t.len()) 151 | if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { 152 | tbuf = tbuf[:off] // reset to actual size used 153 | } else { 154 | return nil, "", err 155 | } 156 | mbuf = append(mbuf, tbuf...) 157 | rawSetExtraLen(mbuf, uint16(len(m.Extra)+1)) 158 | return mbuf, t.MAC, nil 159 | } 160 | 161 | // TsigVerify verifies the TSIG on a message. 162 | // If the signature does not validate err contains the 163 | // error, otherwise it is nil. 164 | func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { 165 | rawsecret, err := fromBase64([]byte(secret)) 166 | if err != nil { 167 | return err 168 | } 169 | // Strip the TSIG from the incoming msg 170 | stripped, tsig, err := stripTsig(msg) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | msgMAC, err := hex.DecodeString(tsig.MAC) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) 181 | 182 | // Fudge factor works both ways. A message can arrive before it was signed because 183 | // of clock skew. 184 | now := uint64(time.Now().Unix()) 185 | ti := now - tsig.TimeSigned 186 | if now < tsig.TimeSigned { 187 | ti = tsig.TimeSigned - now 188 | } 189 | if uint64(tsig.Fudge) < ti { 190 | return ErrTime 191 | } 192 | 193 | var h hash.Hash 194 | switch tsig.Algorithm { 195 | case HmacMD5: 196 | h = hmac.New(md5.New, rawsecret) 197 | case HmacSHA1: 198 | h = hmac.New(sha1.New, rawsecret) 199 | case HmacSHA256: 200 | h = hmac.New(sha256.New, rawsecret) 201 | case HmacSHA512: 202 | h = hmac.New(sha512.New, rawsecret) 203 | default: 204 | return ErrKeyAlg 205 | } 206 | h.Write(buf) 207 | if !hmac.Equal(h.Sum(nil), msgMAC) { 208 | return ErrSig 209 | } 210 | return nil 211 | } 212 | 213 | // Create a wiredata buffer for the MAC calculation. 214 | func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { 215 | var buf []byte 216 | if rr.TimeSigned == 0 { 217 | rr.TimeSigned = uint64(time.Now().Unix()) 218 | } 219 | if rr.Fudge == 0 { 220 | rr.Fudge = 300 // Standard (RFC) default. 221 | } 222 | 223 | if requestMAC != "" { 224 | m := new(macWireFmt) 225 | m.MACSize = uint16(len(requestMAC) / 2) 226 | m.MAC = requestMAC 227 | buf = make([]byte, len(requestMAC)) // long enough 228 | n, _ := PackStruct(m, buf, 0) 229 | buf = buf[:n] 230 | } 231 | 232 | tsigvar := make([]byte, DefaultMsgSize) 233 | if timersOnly { 234 | tsig := new(timerWireFmt) 235 | tsig.TimeSigned = rr.TimeSigned 236 | tsig.Fudge = rr.Fudge 237 | n, _ := PackStruct(tsig, tsigvar, 0) 238 | tsigvar = tsigvar[:n] 239 | } else { 240 | tsig := new(tsigWireFmt) 241 | tsig.Name = strings.ToLower(rr.Hdr.Name) 242 | tsig.Class = ClassANY 243 | tsig.Ttl = rr.Hdr.Ttl 244 | tsig.Algorithm = strings.ToLower(rr.Algorithm) 245 | tsig.TimeSigned = rr.TimeSigned 246 | tsig.Fudge = rr.Fudge 247 | tsig.Error = rr.Error 248 | tsig.OtherLen = rr.OtherLen 249 | tsig.OtherData = rr.OtherData 250 | n, _ := PackStruct(tsig, tsigvar, 0) 251 | tsigvar = tsigvar[:n] 252 | } 253 | 254 | if requestMAC != "" { 255 | x := append(buf, msgbuf...) 256 | buf = append(x, tsigvar...) 257 | } else { 258 | buf = append(msgbuf, tsigvar...) 259 | } 260 | return buf 261 | } 262 | 263 | // Strip the TSIG from the raw message. 264 | func stripTsig(msg []byte) ([]byte, *TSIG, error) { 265 | // Copied from msg.go's Unpack() 266 | // Header. 267 | var dh Header 268 | var err error 269 | dns := new(Msg) 270 | rr := new(TSIG) 271 | off := 0 272 | tsigoff := 0 273 | if off, err = UnpackStruct(&dh, msg, off); err != nil { 274 | return nil, nil, err 275 | } 276 | if dh.Arcount == 0 { 277 | return nil, nil, ErrNoSig 278 | } 279 | // Rcode, see msg.go Unpack() 280 | if int(dh.Bits&0xF) == RcodeNotAuth { 281 | return nil, nil, ErrAuth 282 | } 283 | 284 | // Arrays. 285 | dns.Question = make([]Question, dh.Qdcount) 286 | dns.Answer = make([]RR, dh.Ancount) 287 | dns.Ns = make([]RR, dh.Nscount) 288 | dns.Extra = make([]RR, dh.Arcount) 289 | 290 | for i := 0; i < len(dns.Question); i++ { 291 | off, err = UnpackStruct(&dns.Question[i], msg, off) 292 | if err != nil { 293 | return nil, nil, err 294 | } 295 | } 296 | for i := 0; i < len(dns.Answer); i++ { 297 | dns.Answer[i], off, err = UnpackRR(msg, off) 298 | if err != nil { 299 | return nil, nil, err 300 | } 301 | } 302 | for i := 0; i < len(dns.Ns); i++ { 303 | dns.Ns[i], off, err = UnpackRR(msg, off) 304 | if err != nil { 305 | return nil, nil, err 306 | } 307 | } 308 | for i := 0; i < len(dns.Extra); i++ { 309 | tsigoff = off 310 | dns.Extra[i], off, err = UnpackRR(msg, off) 311 | if err != nil { 312 | return nil, nil, err 313 | } 314 | if dns.Extra[i].Header().Rrtype == TypeTSIG { 315 | rr = dns.Extra[i].(*TSIG) 316 | // Adjust Arcount. 317 | arcount, _ := unpackUint16(msg, 10) 318 | msg[10], msg[11] = packUint16(arcount - 1) 319 | break 320 | } 321 | } 322 | if rr == nil { 323 | return nil, nil, ErrNoSig 324 | } 325 | return msg[:tsigoff], rr, nil 326 | } 327 | 328 | // Translate the TSIG time signed into a date. There is no 329 | // need for RFC1982 calculations as this date is 48 bits. 330 | func tsigTimeToString(t uint64) string { 331 | ti := time.Unix(int64(t), 0).UTC() 332 | return ti.Format("20060102150405") 333 | } 334 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCmToM(t *testing.T) { 8 | s := cmToM(0, 0) 9 | if s != "0.00" { 10 | t.Error("0, 0") 11 | } 12 | 13 | s = cmToM(1, 0) 14 | if s != "0.01" { 15 | t.Error("1, 0") 16 | } 17 | 18 | s = cmToM(3, 1) 19 | if s != "0.30" { 20 | t.Error("3, 1") 21 | } 22 | 23 | s = cmToM(4, 2) 24 | if s != "4" { 25 | t.Error("4, 2") 26 | } 27 | 28 | s = cmToM(5, 3) 29 | if s != "50" { 30 | t.Error("5, 3") 31 | } 32 | 33 | s = cmToM(7, 5) 34 | if s != "7000" { 35 | t.Error("7, 5") 36 | } 37 | 38 | s = cmToM(9, 9) 39 | if s != "90000000" { 40 | t.Error("9, 9") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /udp.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | type SessionUDP struct { 11 | raddr *net.UDPAddr 12 | context []byte 13 | } 14 | 15 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 16 | 17 | // setUDPSocketOptions sets the UDP socket options. 18 | // This function is implemented on a per platform basis. See udp_*.go for more details 19 | func setUDPSocketOptions(conn *net.UDPConn) error { 20 | sa, err := getUDPSocketName(conn) 21 | if err != nil { 22 | return err 23 | } 24 | switch sa.(type) { 25 | case *syscall.SockaddrInet6: 26 | v6only, err := getUDPSocketOptions6Only(conn) 27 | if err != nil { 28 | return err 29 | } 30 | setUDPSocketOptions6(conn) 31 | if !v6only { 32 | setUDPSocketOptions4(conn) 33 | } 34 | case *syscall.SockaddrInet4: 35 | setUDPSocketOptions4(conn) 36 | } 37 | return nil 38 | } 39 | 40 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 41 | // net.UDPAddr. 42 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 43 | oob := make([]byte, 40) 44 | n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) 45 | if err != nil { 46 | return n, nil, err 47 | } 48 | return n, &SessionUDP{raddr, oob[:oobn]}, err 49 | } 50 | 51 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 52 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 53 | n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) 54 | return n, err 55 | } 56 | -------------------------------------------------------------------------------- /udp_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package dns 4 | 5 | // See: 6 | // * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and 7 | // * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ 8 | // 9 | // Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing 10 | // interface, this might not always be the correct one. This code will make sure the egress 11 | // packet's interface matched the ingress' one. 12 | 13 | import ( 14 | "net" 15 | "syscall" 16 | ) 17 | 18 | // setUDPSocketOptions4 prepares the v4 socket for sessions. 19 | func setUDPSocketOptions4(conn *net.UDPConn) error { 20 | file, err := conn.File() 21 | if err != nil { 22 | return err 23 | } 24 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | // setUDPSocketOptions6 prepares the v6 socket for sessions. 31 | func setUDPSocketOptions6(conn *net.UDPConn) error { 32 | file, err := conn.File() 33 | if err != nil { 34 | return err 35 | } 36 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | // getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined 43 | // (dualstack). 44 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { 45 | file, err := conn.File() 46 | if err != nil { 47 | return false, err 48 | } 49 | // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections 50 | v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) 51 | if err != nil { 52 | return false, err 53 | } 54 | return v6only == 1, nil 55 | } 56 | 57 | func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { 58 | file, err := conn.File() 59 | if err != nil { 60 | return nil, err 61 | } 62 | return syscall.Getsockname(int(file.Fd())) 63 | } 64 | -------------------------------------------------------------------------------- /udp_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | // These do nothing. See udp_linux.go for an example of how to implement this. 11 | 12 | // We tried to adhire to some kind of naming scheme. 13 | 14 | func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } 15 | func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } 16 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } 17 | func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil } 18 | -------------------------------------------------------------------------------- /udp_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package dns 4 | 5 | import "net" 6 | 7 | type SessionUDP struct { 8 | raddr *net.UDPAddr 9 | } 10 | 11 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 12 | // net.UDPAddr. 13 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 14 | n, raddr, err := conn.ReadFrom(b) 15 | if err != nil { 16 | return n, nil, err 17 | } 18 | session := &SessionUDP{raddr.(*net.UDPAddr)} 19 | return n, session, err 20 | } 21 | 22 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 23 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 24 | n, err := conn.WriteTo(b, session.raddr) 25 | return n, err 26 | } 27 | 28 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 29 | 30 | // setUDPSocketOptions sets the UDP socket options. 31 | // This function is implemented on a per platform basis. See udp_*.go for more details 32 | func setUDPSocketOptions(conn *net.UDPConn) error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // NameUsed sets the RRs in the prereq section to 4 | // "Name is in use" RRs. RFC 2136 section 2.4.4. 5 | func (u *Msg) NameUsed(rr []RR) { 6 | u.Answer = make([]RR, len(rr)) 7 | for i, r := range rr { 8 | u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} 9 | } 10 | } 11 | 12 | // NameNotUsed sets the RRs in the prereq section to 13 | // "Name is in not use" RRs. RFC 2136 section 2.4.5. 14 | func (u *Msg) NameNotUsed(rr []RR) { 15 | u.Answer = make([]RR, len(rr)) 16 | for i, r := range rr { 17 | u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}} 18 | } 19 | } 20 | 21 | // Used sets the RRs in the prereq section to 22 | // "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. 23 | func (u *Msg) Used(rr []RR) { 24 | if len(u.Question) == 0 { 25 | panic("dns: empty question section") 26 | } 27 | u.Answer = make([]RR, len(rr)) 28 | for i, r := range rr { 29 | u.Answer[i] = r 30 | u.Answer[i].Header().Class = u.Question[0].Qclass 31 | } 32 | } 33 | 34 | // RRsetUsed sets the RRs in the prereq section to 35 | // "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. 36 | func (u *Msg) RRsetUsed(rr []RR) { 37 | u.Answer = make([]RR, len(rr)) 38 | for i, r := range rr { 39 | u.Answer[i] = r 40 | u.Answer[i].Header().Class = ClassANY 41 | u.Answer[i].Header().Ttl = 0 42 | u.Answer[i].Header().Rdlength = 0 43 | } 44 | } 45 | 46 | // RRsetNotUsed sets the RRs in the prereq section to 47 | // "RRset does not exist" RRs. RFC 2136 section 2.4.3. 48 | func (u *Msg) RRsetNotUsed(rr []RR) { 49 | u.Answer = make([]RR, len(rr)) 50 | for i, r := range rr { 51 | u.Answer[i] = r 52 | u.Answer[i].Header().Class = ClassNONE 53 | u.Answer[i].Header().Rdlength = 0 54 | u.Answer[i].Header().Ttl = 0 55 | } 56 | } 57 | 58 | // Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. 59 | func (u *Msg) Insert(rr []RR) { 60 | if len(u.Question) == 0 { 61 | panic("dns: empty question section") 62 | } 63 | u.Ns = make([]RR, len(rr)) 64 | for i, r := range rr { 65 | u.Ns[i] = r 66 | u.Ns[i].Header().Class = u.Question[0].Qclass 67 | } 68 | } 69 | 70 | // RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. 71 | func (u *Msg) RemoveRRset(rr []RR) { 72 | u.Ns = make([]RR, len(rr)) 73 | for i, r := range rr { 74 | u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}} 75 | } 76 | } 77 | 78 | // RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 79 | func (u *Msg) RemoveName(rr []RR) { 80 | u.Ns = make([]RR, len(rr)) 81 | for i, r := range rr { 82 | u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} 83 | } 84 | } 85 | 86 | // Remove creates a dynamic update packet deletes RR from the RRSset, see RFC 2136 section 2.5.4 87 | func (u *Msg) Remove(rr []RR) { 88 | u.Ns = make([]RR, len(rr)) 89 | for i, r := range rr { 90 | u.Ns[i] = r 91 | u.Ns[i].Header().Class = ClassNONE 92 | u.Ns[i].Header().Ttl = 0 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /update_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestDynamicUpdateParsing(t *testing.T) { 9 | prefix := "example.com. IN " 10 | for _, typ := range TypeToString { 11 | if typ == "OPT" || typ == "AXFR" || typ == "IXFR" || typ == "ANY" || typ == "TKEY" || 12 | typ == "TSIG" || typ == "ISDN" || typ == "UNSPEC" || typ == "NULL" || typ == "ATMA" { 13 | continue 14 | } 15 | r, err := NewRR(prefix + typ) 16 | if err != nil { 17 | t.Errorf("failure to parse: %s %s: %v", prefix, typ, err) 18 | } else { 19 | t.Logf("parsed: %s", r.String()) 20 | } 21 | } 22 | } 23 | 24 | func TestDynamicUpdateUnpack(t *testing.T) { 25 | // From https://github.com/miekg/dns/issues/150#issuecomment-62296803 26 | // It should be an update message for the zone "example.", 27 | // deleting the A RRset "example." and then adding an A record at "example.". 28 | // class ANY, TYPE A 29 | 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} 30 | msg := new(Msg) 31 | err := msg.Unpack(buf) 32 | if err != nil { 33 | t.Errorf("failed to unpack: %v\n%s", err, msg.String()) 34 | } 35 | } 36 | 37 | func TestDynamicUpdateZeroRdataUnpack(t *testing.T) { 38 | m := new(Msg) 39 | rr := &RR_Header{Name: ".", Rrtype: 0, Class: 1, Ttl: ^uint32(0), Rdlength: 0} 40 | m.Answer = []RR{rr, rr, rr, rr, rr} 41 | m.Ns = m.Answer 42 | for n, s := range TypeToString { 43 | rr.Rrtype = n 44 | bytes, err := m.Pack() 45 | if err != nil { 46 | t.Errorf("failed to pack %s: %v", s, err) 47 | continue 48 | } 49 | if err := new(Msg).Unpack(bytes); err != nil { 50 | t.Errorf("failed to unpack %s: %v", s, err) 51 | } 52 | } 53 | } 54 | 55 | func TestRemoveRRset(t *testing.T) { 56 | // Should add a zero data RR in Class ANY with a TTL of 0 57 | // for each set mentioned in the RRs provided to it. 58 | rr, err := NewRR(". 100 IN A 127.0.0.1") 59 | if err != nil { 60 | t.Fatalf("Error constructing RR: %v", err) 61 | } 62 | m := new(Msg) 63 | m.Ns = []RR{&RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY, Ttl: 0, Rdlength: 0}} 64 | expectstr := m.String() 65 | expect, err := m.Pack() 66 | if err != nil { 67 | t.Fatalf("Error packing expected msg: %v", err) 68 | } 69 | 70 | m.Ns = nil 71 | m.RemoveRRset([]RR{rr}) 72 | actual, err := m.Pack() 73 | if err != nil { 74 | t.Fatalf("Error packing actual msg: %v", err) 75 | } 76 | if !bytes.Equal(actual, expect) { 77 | tmp := new(Msg) 78 | if err := tmp.Unpack(actual); err != nil { 79 | t.Fatalf("Error unpacking actual msg: %v", err) 80 | } 81 | t.Errorf("Expected msg:\n%s", expectstr) 82 | t.Errorf("Actual msg:\n%v", tmp) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /xfr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Envelope is used when doing a zone transfer with a remote server. 8 | type Envelope struct { 9 | RR []RR // The set of RRs in the answer section of the xfr reply message. 10 | Error error // If something went wrong, this contains the error. 11 | } 12 | 13 | // A Transfer defines parameters that are used during a zone transfer. 14 | type Transfer struct { 15 | *Conn 16 | DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds 17 | ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds 18 | WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds 19 | TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be fully qualified 20 | tsigTimersOnly bool 21 | } 22 | 23 | // Think we need to away to stop the transfer 24 | 25 | // In performs an incoming transfer with the server in a. 26 | func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { 27 | timeout := dnsTimeout 28 | if t.DialTimeout != 0 { 29 | timeout = t.DialTimeout 30 | } 31 | t.Conn, err = DialTimeout("tcp", a, timeout) 32 | if err != nil { 33 | return nil, err 34 | } 35 | if err := t.WriteMsg(q); err != nil { 36 | return nil, err 37 | } 38 | env = make(chan *Envelope) 39 | go func() { 40 | if q.Question[0].Qtype == TypeAXFR { 41 | go t.inAxfr(q.Id, env) 42 | return 43 | } 44 | if q.Question[0].Qtype == TypeIXFR { 45 | go t.inIxfr(q.Id, env) 46 | return 47 | } 48 | }() 49 | return env, nil 50 | } 51 | 52 | func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { 53 | first := true 54 | defer t.Close() 55 | defer close(c) 56 | timeout := dnsTimeout 57 | if t.ReadTimeout != 0 { 58 | timeout = t.ReadTimeout 59 | } 60 | for { 61 | t.Conn.SetReadDeadline(time.Now().Add(timeout)) 62 | in, err := t.ReadMsg() 63 | if err != nil { 64 | c <- &Envelope{nil, err} 65 | return 66 | } 67 | if id != in.Id { 68 | c <- &Envelope{in.Answer, ErrId} 69 | return 70 | } 71 | if first { 72 | if !isSOAFirst(in) { 73 | c <- &Envelope{in.Answer, ErrSoa} 74 | return 75 | } 76 | first = !first 77 | // only one answer that is SOA, receive more 78 | if len(in.Answer) == 1 { 79 | t.tsigTimersOnly = true 80 | c <- &Envelope{in.Answer, nil} 81 | continue 82 | } 83 | } 84 | 85 | if !first { 86 | t.tsigTimersOnly = true // Subsequent envelopes use this. 87 | if isSOALast(in) { 88 | c <- &Envelope{in.Answer, nil} 89 | return 90 | } 91 | c <- &Envelope{in.Answer, nil} 92 | } 93 | } 94 | panic("dns: not reached") 95 | } 96 | 97 | func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { 98 | serial := uint32(0) // The first serial seen is the current server serial 99 | first := true 100 | defer t.Close() 101 | defer close(c) 102 | timeout := dnsTimeout 103 | if t.ReadTimeout != 0 { 104 | timeout = t.ReadTimeout 105 | } 106 | for { 107 | t.SetReadDeadline(time.Now().Add(timeout)) 108 | in, err := t.ReadMsg() 109 | if err != nil { 110 | c <- &Envelope{nil, err} 111 | return 112 | } 113 | if id != in.Id { 114 | c <- &Envelope{in.Answer, ErrId} 115 | return 116 | } 117 | if first { 118 | // A single SOA RR signals "no changes" 119 | if len(in.Answer) == 1 && isSOAFirst(in) { 120 | c <- &Envelope{in.Answer, nil} 121 | return 122 | } 123 | 124 | // Check if the returned answer is ok 125 | if !isSOAFirst(in) { 126 | c <- &Envelope{in.Answer, ErrSoa} 127 | return 128 | } 129 | // This serial is important 130 | serial = in.Answer[0].(*SOA).Serial 131 | first = !first 132 | } 133 | 134 | // Now we need to check each message for SOA records, to see what we need to do 135 | if !first { 136 | t.tsigTimersOnly = true 137 | // If the last record in the IXFR contains the servers' SOA, we should quit 138 | if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { 139 | if v.Serial == serial { 140 | c <- &Envelope{in.Answer, nil} 141 | return 142 | } 143 | } 144 | c <- &Envelope{in.Answer, nil} 145 | } 146 | } 147 | } 148 | 149 | // Out performs an outgoing transfer with the client connecting in w. 150 | // Basic use pattern: 151 | // 152 | // ch := make(chan *dns.Envelope) 153 | // tr := new(dns.Transfer) 154 | // tr.Out(w, r, ch) 155 | // c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} 156 | // close(ch) 157 | // w.Hijack() 158 | // // w.Close() // Client closes connection 159 | // 160 | // The server is responsible for sending the correct sequence of RRs through the 161 | // channel ch. 162 | func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { 163 | for x := range ch { 164 | r := new(Msg) 165 | // Compress? 166 | r.SetReply(q) 167 | r.Authoritative = true 168 | // assume it fits TODO(miek): fix 169 | r.Answer = append(r.Answer, x.RR...) 170 | if err := w.WriteMsg(r); err != nil { 171 | return err 172 | } 173 | } 174 | w.TsigTimersOnly(true) 175 | return nil 176 | } 177 | 178 | // ReadMsg reads a message from the transfer connection t. 179 | func (t *Transfer) ReadMsg() (*Msg, error) { 180 | m := new(Msg) 181 | p := make([]byte, MaxMsgSize) 182 | n, err := t.Read(p) 183 | if err != nil && n == 0 { 184 | return nil, err 185 | } 186 | p = p[:n] 187 | if err := m.Unpack(p); err != nil { 188 | return nil, err 189 | } 190 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 191 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 192 | return m, ErrSecret 193 | } 194 | // Need to work on the original message p, as that was used to calculate the tsig. 195 | err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 196 | t.tsigRequestMAC = ts.MAC 197 | } 198 | return m, err 199 | } 200 | 201 | // WriteMsg writes a message through the transfer connection t. 202 | func (t *Transfer) WriteMsg(m *Msg) (err error) { 203 | var out []byte 204 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 205 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 206 | return ErrSecret 207 | } 208 | out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 209 | } else { 210 | out, err = m.Pack() 211 | } 212 | if err != nil { 213 | return err 214 | } 215 | if _, err = t.Write(out); err != nil { 216 | return err 217 | } 218 | return nil 219 | } 220 | 221 | func isSOAFirst(in *Msg) bool { 222 | if len(in.Answer) > 0 { 223 | return in.Answer[0].Header().Rrtype == TypeSOA 224 | } 225 | return false 226 | } 227 | 228 | func isSOALast(in *Msg) bool { 229 | if len(in.Answer) > 0 { 230 | return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA 231 | } 232 | return false 233 | } 234 | -------------------------------------------------------------------------------- /xfr_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func getIP(s string) string { 10 | a, err := net.LookupAddr(s) 11 | if err != nil { 12 | return "" 13 | } 14 | return a[0] 15 | } 16 | 17 | // flaky, need to setup local server and test from 18 | // that. 19 | func testClientAXFR(t *testing.T) { 20 | if testing.Short() { 21 | return 22 | } 23 | m := new(Msg) 24 | m.SetAxfr("miek.nl.") 25 | 26 | server := getIP("linode.atoom.net") 27 | 28 | tr := new(Transfer) 29 | 30 | if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { 31 | t.Fatal("failed to setup axfr: ", err) 32 | } else { 33 | for ex := range a { 34 | if ex.Error != nil { 35 | t.Errorf("error %v", ex.Error) 36 | break 37 | } 38 | for _, rr := range ex.RR { 39 | t.Log(rr.String()) 40 | } 41 | } 42 | } 43 | } 44 | 45 | // fails. 46 | func testClientAXFRMultipleEnvelopes(t *testing.T) { 47 | if testing.Short() { 48 | return 49 | } 50 | m := new(Msg) 51 | m.SetAxfr("nlnetlabs.nl.") 52 | 53 | server := getIP("open.nlnetlabs.nl.") 54 | 55 | tr := new(Transfer) 56 | if a, err := tr.In(m, net.JoinHostPort(server, "53")); err != nil { 57 | t.Fatalf("Failed to setup axfr %v for server: %v", err, server) 58 | } else { 59 | for ex := range a { 60 | if ex.Error != nil { 61 | t.Errorf("Error %v", ex.Error) 62 | break 63 | } 64 | } 65 | } 66 | } 67 | 68 | func testClientTsigAXFR(t *testing.T) { 69 | if testing.Short() { 70 | return 71 | } 72 | m := new(Msg) 73 | m.SetAxfr("example.nl.") 74 | m.SetTsig("axfr.", HmacMD5, 300, time.Now().Unix()) 75 | 76 | tr := new(Transfer) 77 | tr.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} 78 | 79 | if a, err := tr.In(m, "176.58.119.54:53"); err != nil { 80 | t.Fatal("failed to setup axfr: ", err) 81 | } else { 82 | for ex := range a { 83 | if ex.Error != nil { 84 | t.Errorf("error %v", ex.Error) 85 | break 86 | } 87 | for _, rr := range ex.RR { 88 | t.Log(rr.String()) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /zgenerate.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // Parse the $GENERATE statement as used in BIND9 zones. 11 | // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. 12 | // We are called after '$GENERATE '. After which we expect: 13 | // * the range (12-24/2) 14 | // * lhs (ownername) 15 | // * [[ttl][class]] 16 | // * type 17 | // * rhs (rdata) 18 | // But we are lazy here, only the range is parsed *all* occurences 19 | // of $ after that are interpreted. 20 | // Any error are returned as a string value, the empty string signals 21 | // "no error". 22 | func generate(l lex, c chan lex, t chan *Token, o string) string { 23 | step := 1 24 | if i := strings.IndexAny(l.token, "/"); i != -1 { 25 | if i+1 == len(l.token) { 26 | return "bad step in $GENERATE range" 27 | } 28 | if s, e := strconv.Atoi(l.token[i+1:]); e == nil { 29 | if s < 0 { 30 | return "bad step in $GENERATE range" 31 | } 32 | step = s 33 | } else { 34 | return "bad step in $GENERATE range" 35 | } 36 | l.token = l.token[:i] 37 | } 38 | sx := strings.SplitN(l.token, "-", 2) 39 | if len(sx) != 2 { 40 | return "bad start-stop in $GENERATE range" 41 | } 42 | start, err := strconv.Atoi(sx[0]) 43 | if err != nil { 44 | return "bad start in $GENERATE range" 45 | } 46 | end, err := strconv.Atoi(sx[1]) 47 | if err != nil { 48 | return "bad stop in $GENERATE range" 49 | } 50 | if end < 0 || start < 0 || end < start { 51 | return "bad range in $GENERATE range" 52 | } 53 | 54 | <-c // _BLANK 55 | // Create a complete new string, which we then parse again. 56 | s := "" 57 | BuildRR: 58 | l = <-c 59 | if l.value != zNewline && l.value != zEOF { 60 | s += l.token 61 | goto BuildRR 62 | } 63 | for i := start; i <= end; i += step { 64 | var ( 65 | escape bool 66 | dom bytes.Buffer 67 | mod string 68 | err string 69 | offset int 70 | ) 71 | 72 | for j := 0; j < len(s); j++ { // No 'range' because we need to jump around 73 | switch s[j] { 74 | case '\\': 75 | if escape { 76 | dom.WriteByte('\\') 77 | escape = false 78 | continue 79 | } 80 | escape = true 81 | case '$': 82 | mod = "%d" 83 | offset = 0 84 | if escape { 85 | dom.WriteByte('$') 86 | escape = false 87 | continue 88 | } 89 | escape = false 90 | if j+1 >= len(s) { // End of the string 91 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 92 | continue 93 | } else { 94 | if s[j+1] == '$' { 95 | dom.WriteByte('$') 96 | j++ 97 | continue 98 | } 99 | } 100 | // Search for { and } 101 | if s[j+1] == '{' { // Modifier block 102 | sep := strings.Index(s[j+2:], "}") 103 | if sep == -1 { 104 | return "bad modifier in $GENERATE" 105 | } 106 | mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) 107 | if err != "" { 108 | return err 109 | } 110 | j += 2 + sep // Jump to it 111 | } 112 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 113 | default: 114 | if escape { // Pretty useless here 115 | escape = false 116 | continue 117 | } 118 | dom.WriteByte(s[j]) 119 | } 120 | } 121 | // Re-parse the RR and send it on the current channel t 122 | rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String()) 123 | if e != nil { 124 | return e.(*ParseError).err 125 | } 126 | t <- &Token{RR: rx} 127 | // Its more efficient to first built the rrlist and then parse it in 128 | // one go! But is this a problem? 129 | } 130 | return "" 131 | } 132 | 133 | // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. 134 | func modToPrintf(s string) (string, int, string) { 135 | xs := strings.SplitN(s, ",", 3) 136 | if len(xs) != 3 { 137 | return "", 0, "bad modifier in $GENERATE" 138 | } 139 | // xs[0] is offset, xs[1] is width, xs[2] is base 140 | if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { 141 | return "", 0, "bad base in $GENERATE" 142 | } 143 | offset, err := strconv.Atoi(xs[0]) 144 | if err != nil || offset > 255 { 145 | return "", 0, "bad offset in $GENERATE" 146 | } 147 | width, err := strconv.Atoi(xs[1]) 148 | if err != nil || width > 255 { 149 | return "", offset, "bad width in $GENERATE" 150 | } 151 | switch { 152 | case width < 0: 153 | return "", offset, "bad width in $GENERATE" 154 | case width == 0: 155 | return "%" + xs[1] + xs[2], offset, "" 156 | } 157 | return "%0" + xs[1] + xs[2], offset, "" 158 | } 159 | --------------------------------------------------------------------------------