1package dns 2 3// A client implementation. 4 5import ( 6 "context" 7 "crypto/tls" 8 "encoding/binary" 9 "fmt" 10 "io" 11 "net" 12 "strings" 13 "time" 14) 15 16const ( 17 dnsTimeout time.Duration = 2 * time.Second 18 tcpIdleTimeout time.Duration = 8 * time.Second 19) 20 21// A Conn represents a connection to a DNS server. 22type Conn struct { 23 net.Conn // a net.Conn holding the connection 24 UDPSize uint16 // minimum receive buffer for UDP messages 25 TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) 26 TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations. 27 tsigRequestMAC string 28} 29 30// A Client defines parameters for a DNS client. 31type Client struct { 32 Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) 33 UDPSize uint16 // minimum receive buffer for UDP messages 34 TLSConfig *tls.Config // TLS connection configuration 35 Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more 36 // Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout, 37 // WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and 38 // Client.Dialer) or context.Context.Deadline (see ExchangeContext) 39 Timeout time.Duration 40 DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero 41 ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero 42 WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero 43 TsigSecret map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) 44 TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations. 45 SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass 46 group singleflight 47} 48 49// Exchange performs a synchronous UDP query. It sends the message m to the address 50// contained in a and waits for a reply. Exchange does not retry a failed query, nor 51// will it fall back to TCP in case of truncation. 52// See client.Exchange for more information on setting larger buffer sizes. 53func Exchange(m *Msg, a string) (r *Msg, err error) { 54 client := Client{Net: "udp"} 55 r, _, err = client.Exchange(m, a) 56 return r, err 57} 58 59func (c *Client) dialTimeout() time.Duration { 60 if c.Timeout != 0 { 61 return c.Timeout 62 } 63 if c.DialTimeout != 0 { 64 return c.DialTimeout 65 } 66 return dnsTimeout 67} 68 69func (c *Client) readTimeout() time.Duration { 70 if c.ReadTimeout != 0 { 71 return c.ReadTimeout 72 } 73 return dnsTimeout 74} 75 76func (c *Client) writeTimeout() time.Duration { 77 if c.WriteTimeout != 0 { 78 return c.WriteTimeout 79 } 80 return dnsTimeout 81} 82 83// Dial connects to the address on the named network. 84func (c *Client) Dial(address string) (conn *Conn, err error) { 85 // create a new dialer with the appropriate timeout 86 var d net.Dialer 87 if c.Dialer == nil { 88 d = net.Dialer{Timeout: c.getTimeoutForRequest(c.dialTimeout())} 89 } else { 90 d = *c.Dialer 91 } 92 93 network := c.Net 94 if network == "" { 95 network = "udp" 96 } 97 98 useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls") 99 100 conn = new(Conn) 101 if useTLS { 102 network = strings.TrimSuffix(network, "-tls") 103 104 conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig) 105 } else { 106 conn.Conn, err = d.Dial(network, address) 107 } 108 if err != nil { 109 return nil, err 110 } 111 conn.UDPSize = c.UDPSize 112 return conn, nil 113} 114 115// Exchange performs a synchronous query. It sends the message m to the address 116// contained in a and waits for a reply. Basic use pattern with a *dns.Client: 117// 118// c := new(dns.Client) 119// in, rtt, err := c.Exchange(message, "127.0.0.1:53") 120// 121// Exchange does not retry a failed query, nor will it fall back to TCP in 122// case of truncation. 123// It is up to the caller to create a message that allows for larger responses to be 124// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger 125// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit 126// of 512 bytes 127// To specify a local address or a timeout, the caller has to set the `Client.Dialer` 128// attribute appropriately 129func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) { 130 co, err := c.Dial(address) 131 132 if err != nil { 133 return nil, 0, err 134 } 135 defer co.Close() 136 return c.ExchangeWithConn(m, co) 137} 138 139// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection 140// that will be used instead of creating a new one. 141// Usage pattern with a *dns.Client: 142// c := new(dns.Client) 143// // connection management logic goes here 144// 145// conn := c.Dial(address) 146// in, rtt, err := c.ExchangeWithConn(message, conn) 147// 148// This allows users of the library to implement their own connection management, 149// as opposed to Exchange, which will always use new connections and incur the added overhead 150// that entails when using "tcp" and especially "tcp-tls" clients. 151func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) { 152 if !c.SingleInflight { 153 return c.exchange(m, conn) 154 } 155 156 q := m.Question[0] 157 key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass) 158 r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) { 159 return c.exchange(m, conn) 160 }) 161 if r != nil && shared { 162 r = r.Copy() 163 } 164 165 return r, rtt, err 166} 167 168func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) { 169 170 opt := m.IsEdns0() 171 // If EDNS0 is used use that for size. 172 if opt != nil && opt.UDPSize() >= MinMsgSize { 173 co.UDPSize = opt.UDPSize() 174 } 175 // Otherwise use the client's configured UDP size. 176 if opt == nil && c.UDPSize >= MinMsgSize { 177 co.UDPSize = c.UDPSize 178 } 179 180 co.TsigSecret, co.TsigProvider = c.TsigSecret, c.TsigProvider 181 t := time.Now() 182 // write with the appropriate write timeout 183 co.SetWriteDeadline(t.Add(c.getTimeoutForRequest(c.writeTimeout()))) 184 if err = co.WriteMsg(m); err != nil { 185 return nil, 0, err 186 } 187 188 co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout()))) 189 if _, ok := co.Conn.(net.PacketConn); ok { 190 for { 191 r, err = co.ReadMsg() 192 // Ignore replies with mismatched IDs because they might be 193 // responses to earlier queries that timed out. 194 if err != nil || r.Id == m.Id { 195 break 196 } 197 } 198 } else { 199 r, err = co.ReadMsg() 200 if err == nil && r.Id != m.Id { 201 err = ErrId 202 } 203 } 204 rtt = time.Since(t) 205 return r, rtt, err 206} 207 208// ReadMsg reads a message from the connection co. 209// If the received message contains a TSIG record the transaction signature 210// is verified. This method always tries to return the message, however if an 211// error is returned there are no guarantees that the returned message is a 212// valid representation of the packet read. 213func (co *Conn) ReadMsg() (*Msg, error) { 214 p, err := co.ReadMsgHeader(nil) 215 if err != nil { 216 return nil, err 217 } 218 219 m := new(Msg) 220 if err := m.Unpack(p); err != nil { 221 // If an error was returned, we still want to allow the user to use 222 // the message, but naively they can just check err if they don't want 223 // to use an erroneous message 224 return m, err 225 } 226 if t := m.IsTsig(); t != nil { 227 if co.TsigProvider != nil { 228 err = tsigVerifyProvider(p, co.TsigProvider, co.tsigRequestMAC, false) 229 } else { 230 if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { 231 return m, ErrSecret 232 } 233 // Need to work on the original message p, as that was used to calculate the tsig. 234 err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) 235 } 236 } 237 return m, err 238} 239 240// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil). 241// Returns message as a byte slice to be parsed with Msg.Unpack later on. 242// Note that error handling on the message body is not possible as only the header is parsed. 243func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { 244 var ( 245 p []byte 246 n int 247 err error 248 ) 249 250 if _, ok := co.Conn.(net.PacketConn); ok { 251 if co.UDPSize > MinMsgSize { 252 p = make([]byte, co.UDPSize) 253 } else { 254 p = make([]byte, MinMsgSize) 255 } 256 n, err = co.Read(p) 257 } else { 258 var length uint16 259 if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil { 260 return nil, err 261 } 262 263 p = make([]byte, length) 264 n, err = io.ReadFull(co.Conn, p) 265 } 266 267 if err != nil { 268 return nil, err 269 } else if n < headerSize { 270 return nil, ErrShortRead 271 } 272 273 p = p[:n] 274 if hdr != nil { 275 dh, _, err := unpackMsgHdr(p, 0) 276 if err != nil { 277 return nil, err 278 } 279 *hdr = dh 280 } 281 return p, err 282} 283 284// Read implements the net.Conn read method. 285func (co *Conn) Read(p []byte) (n int, err error) { 286 if co.Conn == nil { 287 return 0, ErrConnEmpty 288 } 289 290 if _, ok := co.Conn.(net.PacketConn); ok { 291 // UDP connection 292 return co.Conn.Read(p) 293 } 294 295 var length uint16 296 if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil { 297 return 0, err 298 } 299 if int(length) > len(p) { 300 return 0, io.ErrShortBuffer 301 } 302 303 return io.ReadFull(co.Conn, p[:length]) 304} 305 306// WriteMsg sends a message through the connection co. 307// If the message m contains a TSIG record the transaction 308// signature is calculated. 309func (co *Conn) WriteMsg(m *Msg) (err error) { 310 var out []byte 311 if t := m.IsTsig(); t != nil { 312 mac := "" 313 if co.TsigProvider != nil { 314 out, mac, err = tsigGenerateProvider(m, co.TsigProvider, co.tsigRequestMAC, false) 315 } else { 316 if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { 317 return ErrSecret 318 } 319 out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) 320 } 321 // Set for the next read, although only used in zone transfers 322 co.tsigRequestMAC = mac 323 } else { 324 out, err = m.Pack() 325 } 326 if err != nil { 327 return err 328 } 329 _, err = co.Write(out) 330 return err 331} 332 333// Write implements the net.Conn Write method. 334func (co *Conn) Write(p []byte) (int, error) { 335 if len(p) > MaxMsgSize { 336 return 0, &Error{err: "message too large"} 337 } 338 339 if _, ok := co.Conn.(net.PacketConn); ok { 340 return co.Conn.Write(p) 341 } 342 343 msg := make([]byte, 2+len(p)) 344 binary.BigEndian.PutUint16(msg, uint16(len(p))) 345 copy(msg[2:], p) 346 return co.Conn.Write(msg) 347} 348 349// Return the appropriate timeout for a specific request 350func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration { 351 var requestTimeout time.Duration 352 if c.Timeout != 0 { 353 requestTimeout = c.Timeout 354 } else { 355 requestTimeout = timeout 356 } 357 // net.Dialer.Timeout has priority if smaller than the timeouts computed so 358 // far 359 if c.Dialer != nil && c.Dialer.Timeout != 0 { 360 if c.Dialer.Timeout < requestTimeout { 361 requestTimeout = c.Dialer.Timeout 362 } 363 } 364 return requestTimeout 365} 366 367// Dial connects to the address on the named network. 368func Dial(network, address string) (conn *Conn, err error) { 369 conn = new(Conn) 370 conn.Conn, err = net.Dial(network, address) 371 if err != nil { 372 return nil, err 373 } 374 return conn, nil 375} 376 377// ExchangeContext performs a synchronous UDP query, like Exchange. It 378// additionally obeys deadlines from the passed Context. 379func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { 380 client := Client{Net: "udp"} 381 r, _, err = client.ExchangeContext(ctx, m, a) 382 // ignoring rtt to leave the original ExchangeContext API unchanged, but 383 // this function will go away 384 return r, err 385} 386 387// ExchangeConn performs a synchronous query. It sends the message m via the connection 388// c and waits for a reply. The connection c is not closed by ExchangeConn. 389// Deprecated: This function is going away, but can easily be mimicked: 390// 391// co := &dns.Conn{Conn: c} // c is your net.Conn 392// co.WriteMsg(m) 393// in, _ := co.ReadMsg() 394// co.Close() 395// 396func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { 397 println("dns: ExchangeConn: this function is deprecated") 398 co := new(Conn) 399 co.Conn = c 400 if err = co.WriteMsg(m); err != nil { 401 return nil, err 402 } 403 r, err = co.ReadMsg() 404 if err == nil && r.Id != m.Id { 405 err = ErrId 406 } 407 return r, err 408} 409 410// DialTimeout acts like Dial but takes a timeout. 411func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { 412 client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}} 413 return client.Dial(address) 414} 415 416// DialWithTLS connects to the address on the named network with TLS. 417func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) { 418 if !strings.HasSuffix(network, "-tls") { 419 network += "-tls" 420 } 421 client := Client{Net: network, TLSConfig: tlsConfig} 422 return client.Dial(address) 423} 424 425// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout. 426func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) { 427 if !strings.HasSuffix(network, "-tls") { 428 network += "-tls" 429 } 430 client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig} 431 return client.Dial(address) 432} 433 434// ExchangeContext acts like Exchange, but honors the deadline on the provided 435// context, if present. If there is both a context deadline and a configured 436// timeout on the client, the earliest of the two takes effect. 437func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) { 438 var timeout time.Duration 439 if deadline, ok := ctx.Deadline(); !ok { 440 timeout = 0 441 } else { 442 timeout = time.Until(deadline) 443 } 444 // not passing the context to the underlying calls, as the API does not support 445 // context. For timeouts you should set up Client.Dialer and call Client.Exchange. 446 // TODO(tmthrgd,miekg): this is a race condition. 447 c.Dialer = &net.Dialer{Timeout: timeout} 448 return c.Exchange(m, a) 449} 450