And simplify, unexport some tsdial/netstack stuff in the the process. Fixes #3475 Change-Id: I186a5a5cbd8958e25c075b4676f7f6e70f3ff76e Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
9f6249b26d
commit
9c5c9d0a50
@ -0,0 +1,86 @@ |
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tsdial |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
// dohConn is a net.PacketConn suitable for returning from
|
||||
// net.Dialer.Dial to send DNS queries over PeerAPI to exit nodes'
|
||||
// ExitDNS DoH proxy service.
|
||||
type dohConn struct { |
||||
ctx context.Context |
||||
baseURL string |
||||
hc *http.Client // if nil, default is used
|
||||
|
||||
rbuf bytes.Buffer |
||||
} |
||||
|
||||
var ( |
||||
_ net.Conn = (*dohConn)(nil) |
||||
_ net.PacketConn = (*dohConn)(nil) // be a PacketConn to change net.Resolver semantics
|
||||
) |
||||
|
||||
func (*dohConn) Close() error { return nil } |
||||
func (*dohConn) LocalAddr() net.Addr { return todoAddr{} } |
||||
func (*dohConn) RemoteAddr() net.Addr { return todoAddr{} } |
||||
func (*dohConn) SetDeadline(t time.Time) error { return nil } |
||||
func (*dohConn) SetReadDeadline(t time.Time) error { return nil } |
||||
func (*dohConn) SetWriteDeadline(t time.Time) error { return nil } |
||||
|
||||
func (c *dohConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { |
||||
return c.Write(p) |
||||
} |
||||
|
||||
func (c *dohConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { |
||||
n, err = c.Read(p) |
||||
return n, todoAddr{}, err |
||||
} |
||||
|
||||
func (c *dohConn) Read(p []byte) (n int, err error) { |
||||
return c.rbuf.Read(p) |
||||
} |
||||
|
||||
func (c *dohConn) Write(packet []byte) (n int, err error) { |
||||
req, err := http.NewRequestWithContext(c.ctx, "POST", c.baseURL, bytes.NewReader(packet)) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
const dohType = "application/dns-message" |
||||
req.Header.Set("Content-Type", dohType) |
||||
hc := c.hc |
||||
if hc == nil { |
||||
hc = http.DefaultClient |
||||
} |
||||
hres, err := hc.Do(req) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
defer hres.Body.Close() |
||||
if hres.StatusCode != 200 { |
||||
return 0, errors.New(hres.Status) |
||||
} |
||||
if ct := hres.Header.Get("Content-Type"); ct != dohType { |
||||
return 0, fmt.Errorf("unexpected response Content-Type %q", ct) |
||||
} |
||||
_, err = io.Copy(&c.rbuf, hres.Body) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return len(packet), nil |
||||
} |
||||
|
||||
type todoAddr struct{} |
||||
|
||||
func (todoAddr) Network() string { return "unused" } |
||||
func (todoAddr) String() string { return "unused-todoAddr" } |
||||
@ -0,0 +1,32 @@ |
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tsdial |
||||
|
||||
import ( |
||||
"context" |
||||
"flag" |
||||
"net" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
var dohBase = flag.String("doh-base", "", "DoH base URL for manual DoH tests; e.g. \"http://100.68.82.120:47830/dns-query\"") |
||||
|
||||
func TestDoHResolve(t *testing.T) { |
||||
if *dohBase == "" { |
||||
t.Skip("skipping manual test without --doh-base= set") |
||||
} |
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
||||
defer cancel() |
||||
var r net.Resolver |
||||
r.Dial = func(ctx context.Context, network, address string) (net.Conn, error) { |
||||
return &dohConn{ctx: ctx, baseURL: *dohBase}, nil |
||||
} |
||||
addrs, err := r.LookupIP(ctx, "ip4", "google.com.") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
t.Logf("Got: %q", addrs) |
||||
} |
||||
Loading…
Reference in new issue