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