| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package main
- import (
- "expvar"
- "log"
- "net"
- "net/netip"
- "sync"
- "time"
- "golang.org/x/net/dns/dnsmessage"
- "tailscale.com/metrics"
- "tailscale.com/tailcfg"
- "tailscale.com/types/appctype"
- "tailscale.com/types/ipproto"
- "tailscale.com/types/nettype"
- "tailscale.com/util/clientmetric"
- "tailscale.com/util/mak"
- )
- var tsMBox = dnsmessage.MustNewName("support.tailscale.com.")
- // target describes the predicates which route some inbound
- // traffic to the app connector to a specific handler.
- type target struct {
- Dest netip.Prefix
- Matching tailcfg.ProtoPortRange
- }
- // Server implements an App Connector as expressed in sniproxy.
- type Server struct {
- mu sync.RWMutex // mu guards following fields
- connectors map[appctype.ConfigID]connector
- }
- type appcMetrics struct {
- dnsResponses expvar.Int
- dnsFailures expvar.Int
- tcpConns expvar.Int
- sniConns expvar.Int
- unhandledConns expvar.Int
- }
- var getMetrics = sync.OnceValue[*appcMetrics](func() *appcMetrics {
- m := appcMetrics{}
- stats := new(metrics.Set)
- stats.Set("tls_sessions", &m.sniConns)
- clientmetric.NewCounterFunc("sniproxy_tls_sessions", m.sniConns.Value)
- stats.Set("tcp_sessions", &m.tcpConns)
- clientmetric.NewCounterFunc("sniproxy_tcp_sessions", m.tcpConns.Value)
- stats.Set("dns_responses", &m.dnsResponses)
- clientmetric.NewCounterFunc("sniproxy_dns_responses", m.dnsResponses.Value)
- stats.Set("dns_failed", &m.dnsFailures)
- clientmetric.NewCounterFunc("sniproxy_dns_failed", m.dnsFailures.Value)
- expvar.Publish("sniproxy", stats)
- return &m
- })
- // Configure applies the provided configuration to the app connector.
- func (s *Server) Configure(cfg *appctype.AppConnectorConfig) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.connectors = makeConnectorsFromConfig(cfg)
- log.Printf("installed app connector config: %+v", s.connectors)
- }
- // HandleTCPFlow implements tsnet.FallbackTCPHandler.
- func (s *Server) HandleTCPFlow(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
- m := getMetrics()
- s.mu.RLock()
- defer s.mu.RUnlock()
- for _, c := range s.connectors {
- if handler, intercept := c.handleTCPFlow(src, dst, m); intercept {
- return handler, intercept
- }
- }
- return nil, false
- }
- // HandleDNS handles a DNS request to the app connector.
- func (s *Server) HandleDNS(c nettype.ConnPacketConn) {
- defer c.Close()
- c.SetReadDeadline(time.Now().Add(5 * time.Second))
- m := getMetrics()
- buf := make([]byte, 1500)
- n, err := c.Read(buf)
- if err != nil {
- log.Printf("HandleDNS: read failed: %v\n ", err)
- m.dnsFailures.Add(1)
- return
- }
- addrPortStr := c.LocalAddr().String()
- host, _, err := net.SplitHostPort(addrPortStr)
- if err != nil {
- log.Printf("HandleDNS: bogus addrPort %q", addrPortStr)
- m.dnsFailures.Add(1)
- return
- }
- localAddr, err := netip.ParseAddr(host)
- if err != nil {
- log.Printf("HandleDNS: bogus local address %q", host)
- m.dnsFailures.Add(1)
- return
- }
- var msg dnsmessage.Message
- err = msg.Unpack(buf[:n])
- if err != nil {
- log.Printf("HandleDNS: dnsmessage unpack failed: %v\n ", err)
- m.dnsFailures.Add(1)
- return
- }
- s.mu.RLock()
- defer s.mu.RUnlock()
- for _, connector := range s.connectors {
- resp, err := connector.handleDNS(&msg, localAddr)
- if err != nil {
- log.Printf("HandleDNS: connector handling failed: %v\n", err)
- m.dnsFailures.Add(1)
- return
- }
- if len(resp) > 0 {
- // This connector handled the DNS request
- _, err = c.Write(resp)
- if err != nil {
- log.Printf("HandleDNS: write failed: %v\n", err)
- m.dnsFailures.Add(1)
- return
- }
- m.dnsResponses.Add(1)
- return
- }
- }
- }
- // connector describes a logical collection of
- // services which need to be proxied.
- type connector struct {
- Handlers map[target]handler
- }
- // handleTCPFlow implements tsnet.FallbackTCPHandler.
- func (c *connector) handleTCPFlow(src, dst netip.AddrPort, m *appcMetrics) (handler func(net.Conn), intercept bool) {
- for t, h := range c.Handlers {
- if t.Matching.Proto != 0 && t.Matching.Proto != int(ipproto.TCP) {
- continue
- }
- if !t.Dest.Contains(dst.Addr()) {
- continue
- }
- if !t.Matching.Ports.Contains(dst.Port()) {
- continue
- }
- switch h.(type) {
- case *tcpSNIHandler:
- m.sniConns.Add(1)
- case *tcpRoundRobinHandler:
- m.tcpConns.Add(1)
- default:
- log.Printf("handleTCPFlow: unhandled handler type %T", h)
- }
- return h.Handle, true
- }
- m.unhandledConns.Add(1)
- return nil, false
- }
- // handleDNS returns the DNS response to the given query. If this
- // connector is unable to handle the request, nil is returned.
- func (c *connector) handleDNS(req *dnsmessage.Message, localAddr netip.Addr) (response []byte, err error) {
- for t, h := range c.Handlers {
- if t.Dest.Contains(localAddr) {
- return makeDNSResponse(req, h.ReachableOn())
- }
- }
- // Did not match, signal 'not handled' to caller
- return nil, nil
- }
- func makeDNSResponse(req *dnsmessage.Message, reachableIPs []netip.Addr) (response []byte, err error) {
- resp := dnsmessage.NewBuilder(response,
- dnsmessage.Header{
- ID: req.Header.ID,
- Response: true,
- Authoritative: true,
- })
- resp.EnableCompression()
- if len(req.Questions) == 0 {
- response, _ = resp.Finish()
- return response, nil
- }
- q := req.Questions[0]
- err = resp.StartQuestions()
- if err != nil {
- return
- }
- resp.Question(q)
- err = resp.StartAnswers()
- if err != nil {
- return
- }
- switch q.Type {
- case dnsmessage.TypeAAAA:
- for _, ip := range reachableIPs {
- if ip.Is6() {
- err = resp.AAAAResource(
- dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
- dnsmessage.AAAAResource{AAAA: ip.As16()},
- )
- }
- }
- case dnsmessage.TypeA:
- for _, ip := range reachableIPs {
- if ip.Is4() {
- err = resp.AResource(
- dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
- dnsmessage.AResource{A: ip.As4()},
- )
- }
- }
- case dnsmessage.TypeSOA:
- err = resp.SOAResource(
- dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
- dnsmessage.SOAResource{NS: q.Name, MBox: tsMBox, Serial: 2023030600,
- Refresh: 120, Retry: 120, Expire: 120, MinTTL: 60},
- )
- case dnsmessage.TypeNS:
- err = resp.NSResource(
- dnsmessage.ResourceHeader{Name: q.Name, Class: q.Class, TTL: 120},
- dnsmessage.NSResource{NS: tsMBox},
- )
- }
- if err != nil {
- return nil, err
- }
- return resp.Finish()
- }
- type handler interface {
- // Handle handles the given socket.
- Handle(c net.Conn)
- // ReachableOn returns the IP addresses this handler is reachable on.
- ReachableOn() []netip.Addr
- }
- func installDNATHandler(d *appctype.DNATConfig, out *connector) {
- // These handlers don't actually do DNAT, they just
- // proxy the data over the connection.
- var dialer net.Dialer
- dialer.Timeout = 5 * time.Second
- h := tcpRoundRobinHandler{
- To: d.To,
- DialContext: dialer.DialContext,
- ReachableIPs: d.Addrs,
- }
- for _, addr := range d.Addrs {
- for _, protoPort := range d.IP {
- t := target{
- Dest: netip.PrefixFrom(addr, addr.BitLen()),
- Matching: protoPort,
- }
- mak.Set(&out.Handlers, t, handler(&h))
- }
- }
- }
- func installSNIHandler(c *appctype.SNIProxyConfig, out *connector) {
- var dialer net.Dialer
- dialer.Timeout = 5 * time.Second
- h := tcpSNIHandler{
- Allowlist: c.AllowedDomains,
- DialContext: dialer.DialContext,
- ReachableIPs: c.Addrs,
- }
- for _, addr := range c.Addrs {
- for _, protoPort := range c.IP {
- t := target{
- Dest: netip.PrefixFrom(addr, addr.BitLen()),
- Matching: protoPort,
- }
- mak.Set(&out.Handlers, t, handler(&h))
- }
- }
- }
- func makeConnectorsFromConfig(cfg *appctype.AppConnectorConfig) map[appctype.ConfigID]connector {
- var connectors map[appctype.ConfigID]connector
- for cID, d := range cfg.DNAT {
- c := connectors[cID]
- installDNATHandler(&d, &c)
- mak.Set(&connectors, cID, c)
- }
- for cID, d := range cfg.SNIProxy {
- c := connectors[cID]
- installSNIHandler(&d, &c)
- mak.Set(&connectors, cID, c)
- }
- return connectors
- }
|