Saves 45 KB. Updates #12614 Change-Id: Iaeb73e69633878ce0a0f58c986024784bbe218f1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
9386a101d8
commit
6c6a1d8341
@ -0,0 +1,132 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_appconnectors
|
||||
|
||||
package appc |
||||
|
||||
import ( |
||||
"net/netip" |
||||
"strings" |
||||
|
||||
"golang.org/x/net/dns/dnsmessage" |
||||
"tailscale.com/util/mak" |
||||
) |
||||
|
||||
// ObserveDNSResponse is a callback invoked by the DNS resolver when a DNS
|
||||
// response is being returned over the PeerAPI. The response is parsed and
|
||||
// matched against the configured domains, if matched the routeAdvertiser is
|
||||
// advised to advertise the discovered route.
|
||||
func (e *AppConnector) ObserveDNSResponse(res []byte) error { |
||||
var p dnsmessage.Parser |
||||
if _, err := p.Start(res); err != nil { |
||||
return err |
||||
} |
||||
if err := p.SkipAllQuestions(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// cnameChain tracks a chain of CNAMEs for a given query in order to reverse
|
||||
// a CNAME chain back to the original query for flattening. The keys are
|
||||
// CNAME record targets, and the value is the name the record answers, so
|
||||
// for www.example.com CNAME example.com, the map would contain
|
||||
// ["example.com"] = "www.example.com".
|
||||
var cnameChain map[string]string |
||||
|
||||
// addressRecords is a list of address records found in the response.
|
||||
var addressRecords map[string][]netip.Addr |
||||
|
||||
for { |
||||
h, err := p.AnswerHeader() |
||||
if err == dnsmessage.ErrSectionDone { |
||||
break |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if h.Class != dnsmessage.ClassINET { |
||||
if err := p.SkipAnswer(); err != nil { |
||||
return err |
||||
} |
||||
continue |
||||
} |
||||
|
||||
switch h.Type { |
||||
case dnsmessage.TypeCNAME, dnsmessage.TypeA, dnsmessage.TypeAAAA: |
||||
default: |
||||
if err := p.SkipAnswer(); err != nil { |
||||
return err |
||||
} |
||||
continue |
||||
|
||||
} |
||||
|
||||
domain := strings.TrimSuffix(strings.ToLower(h.Name.String()), ".") |
||||
if len(domain) == 0 { |
||||
continue |
||||
} |
||||
|
||||
if h.Type == dnsmessage.TypeCNAME { |
||||
res, err := p.CNAMEResource() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
cname := strings.TrimSuffix(strings.ToLower(res.CNAME.String()), ".") |
||||
if len(cname) == 0 { |
||||
continue |
||||
} |
||||
mak.Set(&cnameChain, cname, domain) |
||||
continue |
||||
} |
||||
|
||||
switch h.Type { |
||||
case dnsmessage.TypeA: |
||||
r, err := p.AResource() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
addr := netip.AddrFrom4(r.A) |
||||
mak.Set(&addressRecords, domain, append(addressRecords[domain], addr)) |
||||
case dnsmessage.TypeAAAA: |
||||
r, err := p.AAAAResource() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
addr := netip.AddrFrom16(r.AAAA) |
||||
mak.Set(&addressRecords, domain, append(addressRecords[domain], addr)) |
||||
default: |
||||
if err := p.SkipAnswer(); err != nil { |
||||
return err |
||||
} |
||||
continue |
||||
} |
||||
} |
||||
|
||||
e.mu.Lock() |
||||
defer e.mu.Unlock() |
||||
|
||||
for domain, addrs := range addressRecords { |
||||
domain, isRouted := e.findRoutedDomainLocked(domain, cnameChain) |
||||
|
||||
// domain and none of the CNAMEs in the chain are routed
|
||||
if !isRouted { |
||||
continue |
||||
} |
||||
|
||||
// advertise each address we have learned for the routed domain, that
|
||||
// was not already known.
|
||||
var toAdvertise []netip.Prefix |
||||
for _, addr := range addrs { |
||||
if !e.isAddrKnownLocked(domain, addr) { |
||||
toAdvertise = append(toAdvertise, netip.PrefixFrom(addr, addr.BitLen())) |
||||
} |
||||
} |
||||
|
||||
if len(toAdvertise) > 0 { |
||||
e.logf("[v2] observed new routes for %s: %s", domain, toAdvertise) |
||||
e.scheduleAdvertisement(domain, toAdvertise...) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,8 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ts_omit_appconnectors
|
||||
|
||||
package appc |
||||
|
||||
func (e *AppConnector) ObserveDNSResponse(res []byte) error { return nil } |
||||
@ -0,0 +1,39 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package appconnectors registers support for Tailscale App Connectors.
|
||||
package appconnectors |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"net/http" |
||||
|
||||
"tailscale.com/ipn/ipnlocal" |
||||
"tailscale.com/tailcfg" |
||||
) |
||||
|
||||
func init() { |
||||
ipnlocal.RegisterC2N("GET /appconnector/routes", handleC2NAppConnectorDomainRoutesGet) |
||||
} |
||||
|
||||
// handleC2NAppConnectorDomainRoutesGet handles returning the domains
|
||||
// that the app connector is responsible for, as well as the resolved
|
||||
// IP addresses for each domain. If the node is not configured as
|
||||
// an app connector, an empty map is returned.
|
||||
func handleC2NAppConnectorDomainRoutesGet(b *ipnlocal.LocalBackend, w http.ResponseWriter, r *http.Request) { |
||||
logf := b.Logger() |
||||
logf("c2n: GET /appconnector/routes received") |
||||
|
||||
var res tailcfg.C2NAppConnectorDomainRoutesResponse |
||||
appConnector := b.AppConnector() |
||||
if appConnector == nil { |
||||
w.Header().Set("Content-Type", "application/json") |
||||
json.NewEncoder(w).Encode(res) |
||||
return |
||||
} |
||||
|
||||
res.Domains = appConnector.DomainRoutes() |
||||
|
||||
w.Header().Set("Content-Type", "application/json") |
||||
json.NewEncoder(w).Encode(res) |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_appconnectors
|
||||
|
||||
package buildfeatures |
||||
|
||||
// HasAppConnectors is whether the binary was built with support for modular feature "App Connectors support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_appconnectors" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAppConnectors = false |
||||
@ -0,0 +1,13 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_appconnectors
|
||||
|
||||
package buildfeatures |
||||
|
||||
// HasAppConnectors is whether the binary was built with support for modular feature "App Connectors support".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_appconnectors" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasAppConnectors = true |
||||
@ -0,0 +1,8 @@ |
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_appconnectors
|
||||
|
||||
package condregister |
||||
|
||||
import _ "tailscale.com/feature/appconnectors" |
||||
Loading…
Reference in new issue