summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--net/dns/manager.go99
-rw-r--r--wgengine/netstack/netstack.go19
2 files changed, 107 insertions, 11 deletions
diff --git a/net/dns/manager.go b/net/dns/manager.go
index 59bcdcf17..f7559cc50 100644
--- a/net/dns/manager.go
+++ b/net/dns/manager.go
@@ -6,12 +6,22 @@ package dns
import (
"bufio"
+ "bytes"
"context"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
"encoding/binary"
+ "encoding/pem"
"errors"
"io"
+ "math/big"
"net"
"runtime"
+ "sync"
"sync/atomic"
"time"
@@ -83,6 +93,11 @@ type Manager struct {
responses chan response
activeQueriesAtomic int32
+ // DNS-over-TLS cached value.
+ dotCertMu sync.Mutex
+ dotCertLast time.Time
+ dotCertVal tls.Certificate
+
ctx context.Context // good until Down
ctxCancel context.CancelFunc // closes ctx
@@ -367,8 +382,8 @@ type dnsTCPSession struct {
conn net.Conn
srcAddr netaddr.IPPort
- readClosing chan struct{}
- responses chan []byte // DNS replies pending writing
+ readClosing chan struct{}
+ responses chan []byte // DNS replies pending writing
ctx context.Context
closeCtx context.CancelFunc
@@ -454,17 +469,87 @@ func (s *dnsTCPSession) handleReads() {
// servicing DNS requests sent down it.
func (m *Manager) HandleTCPConn(conn net.Conn, srcAddr netaddr.IPPort) {
s := dnsTCPSession{
- m: m,
- conn: conn,
- srcAddr: srcAddr,
- responses: make(chan []byte),
- readClosing: make(chan struct{}),
+ m: m,
+ conn: conn,
+ srcAddr: srcAddr,
+ responses: make(chan []byte),
+ readClosing: make(chan struct{}),
}
s.ctx, s.closeCtx = context.WithCancel(context.Background())
go s.handleReads()
s.handleWrites()
}
+const dotCertValidity = time.Hour * 24 * 30 // arbitrary; LetsEncrypt-ish
+
+func (m *Manager) dotCert() (tls.Certificate, error) {
+ m.dotCertMu.Lock()
+ defer m.dotCertMu.Unlock()
+
+ if !m.dotCertLast.IsZero() && time.Since(m.dotCertLast) < dotCertValidity {
+ return m.dotCertVal, nil
+ }
+
+ cert, err := genSelfSignedDoTCert()
+ if err == nil {
+ m.dotCertVal = cert
+ m.dotCertLast = time.Now()
+ }
+ return cert, err
+}
+
+// genSelfSignedDoTCert generates a self-signed certificate for DNS-over-TLS
+// (DoT) queries on 100.100.100.100.
+//
+// This exists for Android Private DNS, which in "Automatic" (aka opportunistic)
+// mode doesn't verify certs.
+//
+// See https://github.com/tailscale/tailscale/issues/915.
+func genSelfSignedDoTCert() (tls.Certificate, error) {
+ privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ template := x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{
+ Organization: []string{"Tailscale MagicDNS"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(dotCertValidity),
+
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ }
+ derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ privKeyBytes, _ := x509.MarshalECPrivateKey(privKey)
+ pemCert := new(bytes.Buffer)
+ pemKey := new(bytes.Buffer)
+ pem.Encode(pemCert, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+ pem.Encode(pemKey, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privKeyBytes})
+ return tls.X509KeyPair(pemCert.Bytes(), pemKey.Bytes())
+}
+
+// HandleDNSoverTLSConn implements magicDNS over DNS-over-TLS, taking a
+// connection and servicing DNS requests sent down it.
+//
+// It uses a self-signed cert; see genSelfSignedDoTCert for backbground.
+func (m *Manager) HandleDNSoverTLSConn(conn net.Conn, srcAddr netaddr.IPPort) {
+ tlsCert, err := m.dotCert()
+ if err != nil {
+ m.logf("[unexpected] HandleDNSoverTLSConn.dotCert: %v", err)
+ conn.Close()
+ }
+ tlsConn := tls.Server(conn, &tls.Config{
+ Certificates: []tls.Certificate{tlsCert},
+ })
+ m.HandleTCPConn(tlsConn, srcAddr)
+}
+
func (m *Manager) Down() error {
m.ctxCancel()
if err := m.os.Close(); err != nil {
diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go
index 7e6067b99..d8ebd579d 100644
--- a/wgengine/netstack/netstack.go
+++ b/wgengine/netstack/netstack.go
@@ -377,7 +377,10 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
// on port 80 & 53.
switch p.IPProto {
case ipproto.TCP:
- if port := p.Dst.Port(); port != 53 && port != 80 {
+ switch p.Dst.Port() {
+ case 80, 53, 853:
+ // Handle below.
+ default:
return filter.Accept
}
case ipproto.UDP:
@@ -386,7 +389,6 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
}
}
-
var pn tcpip.NetworkProtocolNumber
switch p.IPVersion {
case 4:
@@ -771,8 +773,17 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
// block until the TCP handshake is complete.
c := gonet.NewTCPConn(&wq, ep)
- if reqDetails.LocalPort == 53 && (dialIP == magicDNSIP || dialIP == magicDNSIPv6) {
- go ns.dns.HandleTCPConn(c, netaddr.IPPortFrom(clientRemoteIP, reqDetails.RemotePort))
+ if dialIP == magicDNSIP || dialIP == magicDNSIPv6 {
+ src := netaddr.IPPortFrom(clientRemoteIP, reqDetails.RemotePort)
+ switch reqDetails.LocalPort {
+ case 53:
+ go ns.dns.HandleTCPConn(c, src)
+ case 853:
+ go ns.dns.HandleDNSoverTLSConn(c, src)
+ default:
+ ns.logf("[unexpected] TCP connection to service IP on port %d", reqDetails.LocalPort)
+ c.Close() // should be unreachable
+ }
return
}