mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 02:37:38 +00:00
ipn/ipnlocal: start adding DoH DNS server to peerapi when exit node
Updates #1713 Change-Id: I8d9c488f779e7acc811a9bc18166a2726198a429 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
6fd6fe11f2
commit
283ae702c1
@ -310,6 +310,9 @@ func run() error {
|
|||||||
logf("wgengine.New: %v", err)
|
logf("wgengine.New: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, ok := e.(wgengine.ResolvingEngine).GetResolver(); !ok {
|
||||||
|
panic("internal error: exit node resolver not wired up")
|
||||||
|
}
|
||||||
|
|
||||||
ns, err := newNetstack(logf, e)
|
ns, err := newNetstack(logf, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2142,6 +2142,11 @@ func (b *LocalBackend) initPeerAPIListener() {
|
|||||||
selfNode: selfNode,
|
selfNode: selfNode,
|
||||||
directFileMode: b.directFileRoot != "",
|
directFileMode: b.directFileRoot != "",
|
||||||
}
|
}
|
||||||
|
if re, ok := b.e.(wgengine.ResolvingEngine); ok {
|
||||||
|
if r, ok := re.GetResolver(); ok {
|
||||||
|
ps.resolver = r
|
||||||
|
}
|
||||||
|
}
|
||||||
b.peerAPIServer = ps
|
b.peerAPIServer = ps
|
||||||
|
|
||||||
isNetstack := wgengine.IsNetstack(b.e)
|
isNetstack := wgengine.IsNetstack(b.e)
|
||||||
@ -2947,3 +2952,25 @@ func (b *LocalBackend) DERPMap() *tailcfg.DERPMap {
|
|||||||
}
|
}
|
||||||
return b.netMap.DERPMap
|
return b.netMap.DERPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OfferingExitNode reports whether b is currently offering exit node
|
||||||
|
// access.
|
||||||
|
func (b *LocalBackend) OfferingExitNode() bool {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.prefs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var def4, def6 bool
|
||||||
|
for _, r := range b.prefs.AdvertiseRoutes {
|
||||||
|
if r.Bits() != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.IP().Is4() {
|
||||||
|
def4 = true
|
||||||
|
} else if r.IP().Is6() {
|
||||||
|
def6 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def4 && def6
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package ipnlocal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -33,6 +34,7 @@ import (
|
|||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/logtail/backoff"
|
"tailscale.com/logtail/backoff"
|
||||||
|
"tailscale.com/net/dns/resolver"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -48,6 +50,7 @@ type peerAPIServer struct {
|
|||||||
tunName string
|
tunName string
|
||||||
selfNode *tailcfg.Node
|
selfNode *tailcfg.Node
|
||||||
knownEmpty syncs.AtomicBool
|
knownEmpty syncs.AtomicBool
|
||||||
|
resolver *resolver.Resolver
|
||||||
|
|
||||||
// directFileMode is whether we're writing files directly to a
|
// directFileMode is whether we're writing files directly to a
|
||||||
// download directory (as *.partial files), rather than making
|
// download directory (as *.partial files), rather than making
|
||||||
@ -503,6 +506,10 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.handlePeerPut(w, r)
|
h.handlePeerPut(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/dns-query") {
|
||||||
|
h.handleDNSQuery(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/v0/goroutines":
|
case "/v0/goroutines":
|
||||||
h.handleServeGoroutines(w, r)
|
h.handleServeGoroutines(w, r)
|
||||||
@ -749,3 +756,64 @@ func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Reque
|
|||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
clientmetric.WritePrometheusExpositionFormat(w)
|
clientmetric.WritePrometheusExpositionFormat(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *peerAPIHandler) replyToDNSQueries() bool {
|
||||||
|
// TODO(bradfitz): maybe lock this down more? what if we're an
|
||||||
|
// exit node but ACLs don't permit autogroup:internet access
|
||||||
|
// from h.peerNode via this node? peerapi bypasses ACL checks,
|
||||||
|
// so we should do additional checks here; but on what? this
|
||||||
|
// node's UDP port 53? our upstream DNS forwarder IP(s)?
|
||||||
|
// For now just offer DNS to any peer if we're an exit node.
|
||||||
|
return h.isSelf || h.ps.b.OfferingExitNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *peerAPIHandler) handleDNSQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.ps.resolver == nil {
|
||||||
|
http.Error(w, "DNS not wired up", http.StatusNotImplemented)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !h.replyToDNSQueries() {
|
||||||
|
http.Error(w, "DNS access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q, publicError := dohQuery(r)
|
||||||
|
if publicError != "" {
|
||||||
|
http.Error(w, publicError, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(bradfitz): owl.
|
||||||
|
fmt.Fprintf(w, "## TODO: got %d bytes of DNS query", len(q))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dohQuery(r *http.Request) (dnsQuery []byte, publicErr string) {
|
||||||
|
const maxQueryLen = 256 << 10
|
||||||
|
switch r.Method {
|
||||||
|
default:
|
||||||
|
return nil, "bad HTTP method"
|
||||||
|
case "GET":
|
||||||
|
q64 := r.FormValue("dns")
|
||||||
|
if q64 == "" {
|
||||||
|
return nil, "missing 'dns' parameter"
|
||||||
|
}
|
||||||
|
if base64.RawURLEncoding.DecodedLen(len(q64)) > maxQueryLen {
|
||||||
|
return nil, "query too large"
|
||||||
|
}
|
||||||
|
q, err := base64.RawURLEncoding.DecodeString(q64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "invalid 'dns' base64 encoding"
|
||||||
|
}
|
||||||
|
return q, ""
|
||||||
|
case "POST":
|
||||||
|
if r.Header.Get("Content-Type") != "application/dns-message" {
|
||||||
|
return nil, "unexpected Content-Type"
|
||||||
|
}
|
||||||
|
q, err := io.ReadAll(io.LimitReader(r.Body, maxQueryLen+1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "error reading post body with DNS query"
|
||||||
|
}
|
||||||
|
if len(q) > maxQueryLen {
|
||||||
|
return nil, "query too large"
|
||||||
|
}
|
||||||
|
return q, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,6 +50,9 @@ func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, li
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolver returns the Manager's DNS Resolver.
|
||||||
|
func (m *Manager) Resolver() *resolver.Resolver { return m.resolver }
|
||||||
|
|
||||||
func (m *Manager) Set(cfg Config) error {
|
func (m *Manager) Set(cfg Config) error {
|
||||||
m.logf("Set: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
m.logf("Set: %v", logger.ArgWriter(func(w *bufio.Writer) {
|
||||||
cfg.WriteToBufioWriter(w)
|
cfg.WriteToBufioWriter(w)
|
||||||
|
@ -147,6 +147,20 @@ func (e *userspaceEngine) GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, o
|
|||||||
return e.tundev, e.magicConn, true
|
return e.tundev, e.magicConn, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolvingEngine is implemented by Engines that have DNS resolvers.
|
||||||
|
type ResolvingEngine interface {
|
||||||
|
GetResolver() (_ *resolver.Resolver, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ ResolvingEngine = (*userspaceEngine)(nil)
|
||||||
|
_ ResolvingEngine = (*watchdogEngine)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *userspaceEngine) GetResolver() (r *resolver.Resolver, ok bool) {
|
||||||
|
return e.dns.Resolver(), true
|
||||||
|
}
|
||||||
|
|
||||||
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
|
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
|
||||||
type BIRDClient interface {
|
type BIRDClient interface {
|
||||||
EnableProtocol(proto string) error
|
EnableProtocol(proto string) error
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/dns"
|
"tailscale.com/net/dns"
|
||||||
|
"tailscale.com/net/dns/resolver"
|
||||||
"tailscale.com/net/tstun"
|
"tailscale.com/net/tstun"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@ -139,6 +140,12 @@ func (e *watchdogEngine) GetInternals() (tw *tstun.Wrapper, c *magicsock.Conn, o
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
func (e *watchdogEngine) GetResolver() (r *resolver.Resolver, ok bool) {
|
||||||
|
if re, ok := e.wrap.(ResolvingEngine); ok {
|
||||||
|
return re.GetResolver()
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
func (e *watchdogEngine) Wait() {
|
func (e *watchdogEngine) Wait() {
|
||||||
e.wrap.Wait()
|
e.wrap.Wait()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user