From aade52d68e0bf400ae55afd3adadffce3b027043 Mon Sep 17 00:00:00 2001 From: insomniac Date: Fri, 29 Sep 2017 02:52:01 -0700 Subject: [PATCH] Redesigned Client API around net.Dialer (#511) * Redesigned Client API around net.Dialer * Moved Dialer under Client; reverted msgHash; removed unneeded deprecation * Reverted client_compat.go into client.go and renamed dialer variable * Shortened comment lines * Erroneously removed pointer declaration --- client.go | 340 +++++++++++++++++++++++-------------------------- client_test.go | 168 ++++++++++++++++++++++-- doc.go | 26 +++- server_test.go | 10 ++ 4 files changed, 347 insertions(+), 197 deletions(-) diff --git a/client.go b/client.go index 1c14a19d..359a0ab8 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "io" "net" + "strings" "time" ) @@ -27,11 +28,15 @@ type Conn struct { // A Client defines parameters for a DNS client. type Client struct { - Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) - UDPSize uint16 // minimum receive buffer for UDP messages - TLSConfig *tls.Config // TLS connection configuration - Timeout time.Duration // a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout and WriteTimeout when non-zero - DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds - overridden by Timeout when that value is non-zero + Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) + UDPSize uint16 // minimum receive buffer for UDP messages + TLSConfig *tls.Config // TLS connection configuration + Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more + // Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout, + // WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and + // Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext) + Timeout time.Duration + 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 ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be fully qualified @@ -44,140 +49,11 @@ type Client struct { // will it fall back to TCP in case of truncation. // See client.Exchange for more information on setting larger buffer sizes. func Exchange(m *Msg, a string) (r *Msg, err error) { - var co *Conn - co, err = DialTimeout("udp", a, dnsTimeout) - if err != nil { - return nil, err - } - - defer co.Close() - - opt := m.IsEdns0() - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= MinMsgSize { - co.UDPSize = opt.UDPSize() - } - - co.SetWriteDeadline(time.Now().Add(dnsTimeout)) - if err = co.WriteMsg(m); err != nil { - return nil, err - } - - co.SetReadDeadline(time.Now().Add(dnsTimeout)) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } + client := Client{Net: "udp"} + r, _, err = client.Exchange(m, a) return r, err } -// ExchangeContext performs a synchronous UDP query, like Exchange. It -// additionally obeys deadlines from the passed Context. -func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { - // Combine context deadline with built-in timeout. Context chooses whichever - // is sooner. - timeoutCtx, cancel := context.WithTimeout(ctx, dnsTimeout) - defer cancel() - deadline, _ := timeoutCtx.Deadline() - - co := new(Conn) - dialer := net.Dialer{} - co.Conn, err = dialer.DialContext(timeoutCtx, "udp", a) - if err != nil { - return nil, err - } - - defer co.Conn.Close() - - opt := m.IsEdns0() - // If EDNS0 is used use that for size. - if opt != nil && opt.UDPSize() >= MinMsgSize { - co.UDPSize = opt.UDPSize() - } - - co.SetWriteDeadline(deadline) - if err = co.WriteMsg(m); err != nil { - return nil, err - } - - co.SetReadDeadline(deadline) - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, err -} - -// ExchangeConn performs a synchronous query. It sends the message m via the connection -// c and waits for a reply. The connection c is not closed by ExchangeConn. -// This function is going away, but can easily be mimicked: -// -// co := &dns.Conn{Conn: c} // c is your net.Conn -// co.WriteMsg(m) -// in, _ := co.ReadMsg() -// co.Close() -// -func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { - println("dns: this function is deprecated") - co := new(Conn) - co.Conn = c - if err = co.WriteMsg(m); err != nil { - return nil, err - } - r, err = co.ReadMsg() - if err == nil && r.Id != m.Id { - err = ErrId - } - return r, err -} - -// Exchange performs a synchronous query. It sends the message m to the address -// contained in a and waits for a reply. Basic use pattern with a *dns.Client: -// -// c := new(dns.Client) -// in, rtt, err := c.Exchange(message, "127.0.0.1:53") -// -// Exchange does not retry a failed query, nor will it fall back to TCP in -// case of truncation. -// It is up to the caller to create a message that allows for larger responses to be -// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger -// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit -// of 512 bytes. -func (c *Client) Exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { - return c.ExchangeContext(context.Background(), m, a) -} - -// ExchangeContext acts like Exchange, but honors the deadline on the provided -// context, if present. If there is both a context deadline and a configured -// timeout on the client, the earliest of the two takes effect. -func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) ( - r *Msg, - rtt time.Duration, - err error) { - if !c.SingleInflight { - return c.exchange(ctx, m, a) - } - // This adds a bunch of garbage, TODO(miek). - t := "nop" - if t1, ok := TypeToString[m.Question[0].Qtype]; ok { - t = t1 - } - cl := "nop" - if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { - cl = cl1 - } - r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { - return c.exchange(ctx, m, a) - }) - if r != nil && shared { - r = r.Copy() - } - if err != nil { - return r, rtt, err - } - return r, rtt, nil -} - func (c *Client) dialTimeout() time.Duration { if c.Timeout != 0 { return c.Timeout @@ -202,40 +78,87 @@ func (c *Client) writeTimeout() time.Duration { return dnsTimeout } -func (c *Client) exchange(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) { - var co *Conn +func (c *Client) Dial(address string) (conn *Conn, err error) { + // create a new dialer with the appropriate timeout + var d net.Dialer + if c.Dialer == nil { + d = net.Dialer{} + } else { + d = net.Dialer(*c.Dialer) + } + d.Timeout = c.getTimeoutForRequest(c.writeTimeout()) + network := "udp" - tls := false + useTLS := false switch c.Net { case "tcp-tls": network = "tcp" - tls = true + useTLS = true case "tcp4-tls": network = "tcp4" - tls = true + useTLS = true case "tcp6-tls": network = "tcp6" - tls = true + useTLS = true default: if c.Net != "" { network = c.Net } } - var deadline time.Time - if c.Timeout != 0 { - deadline = time.Now().Add(c.Timeout) - } - - dialDeadline := deadlineOrTimeoutOrCtx(ctx, deadline, c.dialTimeout()) - dialTimeout := dialDeadline.Sub(time.Now()) - - if tls { - co, err = DialTimeoutWithTLS(network, a, c.TLSConfig, dialTimeout) + conn = new(Conn) + if useTLS { + conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig) } else { - co, err = DialTimeout(network, a, dialTimeout) + conn.Conn, err = d.Dial(network, address) } + if err != nil { + return nil, err + } + return conn, nil +} + +// Exchange performs a synchronous query. It sends the message m to the address +// contained in a and waits for a reply. Basic use pattern with a *dns.Client: +// +// c := new(dns.Client) +// in, rtt, err := c.Exchange(message, "127.0.0.1:53") +// +// Exchange does not retry a failed query, nor will it fall back to TCP in +// case of truncation. +// It is up to the caller to create a message that allows for larger responses to be +// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger +// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit +// of 512 bytes +// To specify a local address or a timeout, the caller has to set the `Client.Dialer` +// attribute appropriately +func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) { + if !c.SingleInflight { + return c.exchange(m, address) + } + + t := "nop" + if t1, ok := TypeToString[m.Question[0].Qtype]; ok { + t = t1 + } + cl := "nop" + if cl1, ok := ClassToString[m.Question[0].Qclass]; ok { + cl = cl1 + } + r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) { + return c.exchange(m, address) + }) + if r != nil && shared { + r = r.Copy() + } + return r, rtt, err +} + +func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) { + var co *Conn + + co, err = c.Dial(a) if err != nil { return nil, 0, err @@ -253,12 +176,13 @@ func (c *Client) exchange(ctx context.Context, m *Msg, a string) (r *Msg, rtt ti } co.TsigSecret = c.TsigSecret - co.SetWriteDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.writeTimeout())) + // write with the appropriate write timeout + co.SetWriteDeadline(time.Now().Add(c.getTimeoutForRequest(c.writeTimeout()))) if err = co.WriteMsg(m); err != nil { return nil, 0, err } - co.SetReadDeadline(deadlineOrTimeoutOrCtx(ctx, deadline, c.readTimeout())) + co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout()))) r, err = co.ReadMsg() if err == nil && r.Id != m.Id { err = ErrId @@ -352,7 +276,7 @@ func tcpMsgLen(t io.Reader) (int, error) { return 0, err } - // As seen with my local router/switch, retursn 1 byte on the above read, + // As seen with my local router/switch, returns 1 byte on the above read, // resulting a a ShortRead. Just write it out (instead of loop) and read the // other byte. if n == 1 { @@ -467,6 +391,24 @@ func (co *Conn) Write(p []byte) (n int, err error) { return n, err } +// Return the appropriate timeout for a specific request +func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration { + var requestTimeout time.Duration + if c.Timeout != 0 { + requestTimeout = c.Timeout + } else { + requestTimeout = timeout + } + // net.Dialer.Timeout has priority if smaller than the timeouts computed so + // far + if c.Dialer != nil && c.Dialer.Timeout != 0 { + if c.Dialer.Timeout < requestTimeout { + requestTimeout = c.Dialer.Timeout + } + } + return requestTimeout +} + // Dial connects to the address on the named network. func Dial(network, address string) (conn *Conn, err error) { conn = new(Conn) @@ -477,10 +419,44 @@ func Dial(network, address string) (conn *Conn, err error) { return conn, nil } +// ExchangeContext performs a synchronous UDP query, like Exchange. It +// additionally obeys deadlines from the passed Context. +func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { + client := Client{Net: "udp"} + r, _, err = client.ExchangeContext(ctx, m, a) + // ignorint rtt to leave the original ExchangeContext API unchanged, but + // this function will go away + return r, err +} + +// ExchangeConn performs a synchronous query. It sends the message m via the connection +// c and waits for a reply. The connection c is not closed by ExchangeConn. +// This function is going away, but can easily be mimicked: +// +// co := &dns.Conn{Conn: c} // c is your net.Conn +// co.WriteMsg(m) +// in, _ := co.ReadMsg() +// co.Close() +// +func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { + println("dns: ExchangeConn: this function is deprecated") + co := new(Conn) + co.Conn = c + if err = co.WriteMsg(m); err != nil { + return nil, err + } + r, err = co.ReadMsg() + if err == nil && r.Id != m.Id { + err = ErrId + } + return r, err +} + // DialTimeout acts like Dial but takes a timeout. func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { - conn = new(Conn) - conn.Conn, err = net.DialTimeout(network, address, timeout) + + client := Client{Net: "udp", Dialer: &net.Dialer{Timeout: timeout}} + conn, err = client.Dial(address) if err != nil { return nil, err } @@ -489,8 +465,12 @@ func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, er // DialWithTLS connects to the address on the named network with TLS. func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) { - conn = new(Conn) - conn.Conn, err = tls.Dial(network, address, tlsConfig) + if !strings.HasSuffix(network, "-tls") { + network += "-tls" + } + client := Client{Net: network, TLSConfig: tlsConfig} + conn, err = client.Dial(address) + if err != nil { return nil, err } @@ -499,33 +479,29 @@ func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, er // DialTimeoutWithTLS acts like DialWithTLS but takes a timeout. func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) { - var dialer net.Dialer - dialer.Timeout = timeout - - conn = new(Conn) - conn.Conn, err = tls.DialWithDialer(&dialer, network, address, tlsConfig) + if !strings.HasSuffix(network, "-tls") { + network += "-tls" + } + client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig} + conn, err = client.Dial(address) if err != nil { return nil, err } return conn, nil } -// deadlineOrTimeout chooses between the provided deadline and timeout -// by always preferring the deadline so long as it's non-zero (regardless -// of which is bigger), and returns the equivalent deadline value. -func deadlineOrTimeout(deadline time.Time, timeout time.Duration) time.Time { - if deadline.IsZero() { - return time.Now().Add(timeout) +// ExchangeContext acts like Exchange, but honors the deadline on the provided +// context, if present. If there is both a context deadline and a configured +// timeout on the client, the earliest of the two takes effect. +func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) { + var timeout time.Duration + if deadline, ok := ctx.Deadline(); !ok { + timeout = 0 + } else { + timeout = deadline.Sub(time.Now()) } - return deadline -} - -// deadlineOrTimeoutOrCtx returns the earliest of: a context deadline, or the -// output of deadlineOrtimeout. -func deadlineOrTimeoutOrCtx(ctx context.Context, deadline time.Time, timeout time.Duration) time.Time { - result := deadlineOrTimeout(deadline, timeout) - if ctxDeadline, ok := ctx.Deadline(); ok && ctxDeadline.Before(result) { - result = ctxDeadline - } - return result + // not passing the context to the underlying calls, as the API does not support + // context. For timeouts you should set up Client.Dialer and call Client.Exchange. + c.Dialer = &net.Dialer{Timeout: timeout} + return c.Exchange(m, a) } diff --git a/client_test.go b/client_test.go index 73083dba..3ff619cf 100644 --- a/client_test.go +++ b/client_test.go @@ -11,6 +11,29 @@ import ( "time" ) +func TestDialUDP(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + s, addrstr, err := RunLocalUDPServer("[::1]:0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := new(Client) + conn, err := c.Dial(addrstr) + if err != nil { + t.Fatalf("failed to dial: %v", err) + } + if conn == nil { + t.Fatalf("conn is nil") + } +} + func TestClientSync(t *testing.T) { HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") @@ -27,9 +50,12 @@ func TestClientSync(t *testing.T) { c := new(Client) r, _, err := c.Exchange(m, addrstr) if err != nil { - t.Errorf("failed to exchange: %v", err) + t.Fatalf("failed to exchange: %v", err) } - if r != nil && r.Rcode != RcodeSuccess { + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { t.Errorf("failed to get an valid answer\n%v", r) } // And now with plain Exchange(). @@ -42,7 +68,42 @@ func TestClientSync(t *testing.T) { } } -func TestClientTLSSync(t *testing.T) { +func TestClientLocalAddress(t *testing.T) { + HandleFunc("miek.nl.", HelloServerEchoAddrPort) + defer HandleRemove("miek.nl.") + + s, addrstr, err := RunLocalUDPServer("127.0.0.1:0") + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := new(Client) + laddr := net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345, Zone: ""} + c.Dialer = &net.Dialer{LocalAddr: &laddr} + r, _, err := c.Exchange(m, addrstr) + if err != nil { + t.Errorf("failed to exchange: %v", err) + } + if r != nil && r.Rcode != RcodeSuccess { + t.Errorf("failed to get an valid answer\n%v", r) + } + if len(r.Extra) != 1 { + t.Errorf("failed to get additional answers\n%v", r) + } + txt := r.Extra[0].(*TXT) + if txt == nil { + t.Errorf("invalid TXT response\n%v", txt) + } + if len(txt.Txt) != 1 || txt.Txt[0] != "127.0.0.1:12345" { + t.Errorf("invalid TXT response\n%v", txt.Txt) + } +} + +func TestClientTLSSyncV4(t *testing.T) { HandleFunc("miek.nl.", HelloServer) defer HandleRemove("miek.nl.") @@ -65,6 +126,8 @@ func TestClientTLSSync(t *testing.T) { m.SetQuestion("miek.nl.", TypeSOA) c := new(Client) + + // test tcp-tls c.Net = "tcp-tls" c.TLSConfig = &tls.Config{ InsecureSkipVerify: true, @@ -72,9 +135,88 @@ func TestClientTLSSync(t *testing.T) { r, _, err := c.Exchange(m, addrstr) if err != nil { - t.Errorf("failed to exchange: %v", err) + t.Fatalf("failed to exchange: %v", err) } - if r != nil && r.Rcode != RcodeSuccess { + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { + t.Errorf("failed to get an valid answer\n%v", r) + } + + // test tcp4-tls + c.Net = "tcp4-tls" + c.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Fatalf("failed to exchange: %v", err) + } + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { + t.Errorf("failed to get an valid answer\n%v", r) + } +} + +func TestClientTLSSyncV6(t *testing.T) { + HandleFunc("miek.nl.", HelloServer) + defer HandleRemove("miek.nl.") + + cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock) + if err != nil { + t.Fatalf("unable to build certificate: %v", err) + } + + config := tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + s, addrstr, err := RunLocalTLSServer("[::1]:0", &config) + if err != nil { + t.Fatalf("unable to run test server: %v", err) + } + defer s.Shutdown() + + m := new(Msg) + m.SetQuestion("miek.nl.", TypeSOA) + + c := new(Client) + + // test tcp-tls + c.Net = "tcp-tls" + c.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + r, _, err := c.Exchange(m, addrstr) + if err != nil { + t.Fatalf("failed to exchange: %v", err) + } + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { + t.Errorf("failed to get an valid answer\n%v", r) + } + + // test tcp6-tls + c.Net = "tcp6-tls" + c.TLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + r, _, err = c.Exchange(m, addrstr) + if err != nil { + t.Fatalf("failed to exchange: %v", err) + } + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { t.Errorf("failed to get an valid answer\n%v", r) } } @@ -120,11 +262,11 @@ func TestClientEDNS0(t *testing.T) { c := new(Client) r, _, err := c.Exchange(m, addrstr) if err != nil { - t.Errorf("failed to exchange: %v", err) + t.Fatalf("failed to exchange: %v", err) } if r != nil && r.Rcode != RcodeSuccess { - t.Errorf("failed to get an valid answer\n%v", r) + t.Errorf("failed to get a valid answer\n%v", r) } } @@ -171,11 +313,14 @@ func TestClientEDNS0Local(t *testing.T) { c := new(Client) r, _, err := c.Exchange(m, addrstr) if err != nil { - t.Errorf("failed to exchange: %s", err) + t.Fatalf("failed to exchange: %s", err) } - if r != nil && r.Rcode != RcodeSuccess { - t.Error("failed to get a valid answer") + if r == nil { + t.Fatal("response is nil") + } + if r.Rcode != RcodeSuccess { + t.Fatal("failed to get a valid answer") t.Logf("%v\n", r) } @@ -513,6 +658,9 @@ func TestConcurrentExchanges(t *testing.T) { for i := 0; i < len(r); i++ { go func(i int) { r[i], _, _ = c.Exchange(m.Copy(), addrstr) + if r[i] == nil { + t.Fatalf("response %d is nil", i) + } wg.Done() }(i) } diff --git a/doc.go b/doc.go index 6c92329d..ceabd24c 100644 --- a/doc.go +++ b/doc.go @@ -1,7 +1,7 @@ /* Package dns implements a full featured interface to the Domain Name System. Server- and client-side programming is supported. -The package allows complete control over what is send out to the DNS. The package +The package allows complete control over what is sent out to the DNS. The package API follows the less-is-more principle, by presenting a small, clean interface. The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers, @@ -14,7 +14,7 @@ Basic usage pattern for creating a new resource record: r := new(dns.MX) r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, - Class: dns.ClassINET, Ttl: 3600} + Class: dns.ClassINET, Ttl: 3600} r.Preference = 10 r.Mx = "mx.miek.nl." @@ -31,7 +31,7 @@ Or even: mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") In the DNS messages are exchanged, these messages contain resource -records (sets). Use pattern for creating a message: +records (sets). Use pattern for creating a message: m := new(dns.Msg) m.SetQuestion("miek.nl.", dns.TypeMX) @@ -51,7 +51,7 @@ The following is slightly more verbose, but more flexible: m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} -After creating a message it can be send. +After creating a message it can be sent. Basic use pattern for synchronous querying the DNS at a server configured on 127.0.0.1 and port 53: @@ -63,7 +63,23 @@ class) is as easy as setting: c.SingleInflight = true -If these "advanced" features are not needed, a simple UDP query can be send, +More advanced options are availabe using a net.Dialer and the corresponding API. +For example it is possible to set a timeout, or to specify a source IP address +and port to use for the connection: + + c := new(dns.Client) + laddr := net.UDPAddr{ + IP: net.ParseIP("[::1]"), + Port: 12345, + Zone: "", + } + d := net.Dialer{ + Timeout: 200 * time.Millisecond, + LocalAddr: &laddr, + } + in, rtt, err := c.ExchangeWithDialer(&d, m1, "8.8.8.8:53") + +If these "advanced" features are not needed, a simple UDP query can be sent, with: in, err := dns.Exchange(m1, "127.0.0.1:53") diff --git a/server_test.go b/server_test.go index f17a2f90..b74f2f1a 100644 --- a/server_test.go +++ b/server_test.go @@ -30,6 +30,16 @@ func HelloServerBadID(w ResponseWriter, req *Msg) { w.WriteMsg(m) } +func HelloServerEchoAddrPort(w ResponseWriter, req *Msg) { + m := new(Msg) + m.SetReply(req) + + remoteAddr := w.RemoteAddr().String() + m.Extra = make([]RR, 1) + m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{remoteAddr}} + w.WriteMsg(m) +} + func AnotherHelloServer(w ResponseWriter, req *Msg) { m := new(Msg) m.SetReply(req)