mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-28 11:17:33 +00:00

In this PR, we add a generic views.ValuePointer type that can be used as a view for pointers to basic types and struct types that do not require deep cloning and do not have corresponding view types. Its Get/GetOk methods return stack-allocated shallow copies of the underlying value. We then update the cmd/viewer codegen to produce getters that return either concrete views when available or ValuePointer views when not, for pointer fields in generated view types. This allows us to avoid unnecessary allocations compared to returning pointers to newly allocated shallow copies. Updates #14570 Signed-off-by: Nick Khyl <nickk@tailscale.com>
149 lines
4.5 KiB
Go
149 lines
4.5 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package nmcfg converts a controlclient.NetMap into a wgcfg config.
|
|
package nmcfg
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/types/logid"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/wgengine/wgcfg"
|
|
)
|
|
|
|
func nodeDebugName(n tailcfg.NodeView) string {
|
|
name := n.Name()
|
|
if name == "" {
|
|
name = n.Hostinfo().Hostname()
|
|
}
|
|
if i := strings.Index(name, "."); i != -1 {
|
|
name = name[:i]
|
|
}
|
|
if name == "" && n.Addresses().Len() != 0 {
|
|
return n.Addresses().At(0).String()
|
|
}
|
|
return name
|
|
}
|
|
|
|
// cidrIsSubnet reports whether cidr is a non-default-route subnet
|
|
// exported by node that is not one of its own self addresses.
|
|
func cidrIsSubnet(node tailcfg.NodeView, cidr netip.Prefix) bool {
|
|
if cidr.Bits() == 0 {
|
|
return false
|
|
}
|
|
if !cidr.IsSingleIP() {
|
|
return true
|
|
}
|
|
for _, selfCIDR := range node.Addresses().All() {
|
|
if cidr == selfCIDR {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// WGCfg returns the NetworkMaps's WireGuard configuration.
|
|
func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, exitNode tailcfg.StableNodeID) (*wgcfg.Config, error) {
|
|
cfg := &wgcfg.Config{
|
|
Name: "tailscale",
|
|
PrivateKey: nm.PrivateKey,
|
|
Addresses: nm.GetAddresses().AsSlice(),
|
|
Peers: make([]wgcfg.Peer, 0, len(nm.Peers)),
|
|
}
|
|
|
|
// Setup log IDs for data plane audit logging.
|
|
if nm.SelfNode.Valid() {
|
|
cfg.NodeID = nm.SelfNode.StableID()
|
|
canNetworkLog := nm.SelfNode.HasCap(tailcfg.CapabilityDataPlaneAuditLogs)
|
|
logExitFlowEnabled := nm.SelfNode.HasCap(tailcfg.NodeAttrLogExitFlows)
|
|
if canNetworkLog && nm.SelfNode.DataPlaneAuditLogID() != "" && nm.DomainAuditLogID != "" {
|
|
nodeID, errNode := logid.ParsePrivateID(nm.SelfNode.DataPlaneAuditLogID())
|
|
if errNode != nil {
|
|
logf("[v1] wgcfg: unable to parse node audit log ID: %v", errNode)
|
|
}
|
|
domainID, errDomain := logid.ParsePrivateID(nm.DomainAuditLogID)
|
|
if errDomain != nil {
|
|
logf("[v1] wgcfg: unable to parse domain audit log ID: %v", errDomain)
|
|
}
|
|
if errNode == nil && errDomain == nil {
|
|
cfg.NetworkLogging.NodeID = nodeID
|
|
cfg.NetworkLogging.DomainID = domainID
|
|
cfg.NetworkLogging.LogExitFlowEnabled = logExitFlowEnabled
|
|
}
|
|
}
|
|
}
|
|
|
|
// Logging buffers
|
|
skippedUnselected := new(bytes.Buffer)
|
|
skippedIPs := new(bytes.Buffer)
|
|
skippedSubnets := new(bytes.Buffer)
|
|
|
|
for _, peer := range nm.Peers {
|
|
if peer.DiscoKey().IsZero() && peer.DERP() == "" && !peer.IsWireGuardOnly() {
|
|
// Peer predates both DERP and active discovery, we cannot
|
|
// communicate with it.
|
|
logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key().ShortString())
|
|
continue
|
|
}
|
|
// Skip expired peers; we'll end up failing to connect to them
|
|
// anyway, since control intentionally breaks node keys for
|
|
// expired peers so that we can't discover endpoints via DERP.
|
|
if peer.Expired() {
|
|
logf("[v1] wgcfg: skipped expired peer %s", peer.Key().ShortString())
|
|
continue
|
|
}
|
|
|
|
cfg.Peers = append(cfg.Peers, wgcfg.Peer{
|
|
PublicKey: peer.Key(),
|
|
DiscoKey: peer.DiscoKey(),
|
|
})
|
|
cpeer := &cfg.Peers[len(cfg.Peers)-1]
|
|
|
|
didExitNodeWarn := false
|
|
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer().Clone()
|
|
cpeer.V6MasqAddr = peer.SelfNodeV6MasqAddrForThisPeer().Clone()
|
|
cpeer.IsJailed = peer.IsJailed()
|
|
for _, allowedIP := range peer.AllowedIPs().All() {
|
|
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
|
|
if didExitNodeWarn {
|
|
// Don't log about both the IPv4 /0 and IPv6 /0.
|
|
continue
|
|
}
|
|
didExitNodeWarn = true
|
|
if skippedUnselected.Len() > 0 {
|
|
skippedUnselected.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(skippedUnselected, "%q (%v)", nodeDebugName(peer), peer.Key().ShortString())
|
|
continue
|
|
} else if cidrIsSubnet(peer, allowedIP) {
|
|
if (flags & netmap.AllowSubnetRoutes) == 0 {
|
|
if skippedSubnets.Len() > 0 {
|
|
skippedSubnets.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(skippedSubnets, "%v from %q (%v)", allowedIP, nodeDebugName(peer), peer.Key().ShortString())
|
|
continue
|
|
}
|
|
}
|
|
cpeer.AllowedIPs = append(cpeer.AllowedIPs, allowedIP)
|
|
}
|
|
}
|
|
|
|
if skippedUnselected.Len() > 0 {
|
|
logf("[v1] wgcfg: skipped unselected default routes from: %s", skippedUnselected.Bytes())
|
|
}
|
|
if skippedIPs.Len() > 0 {
|
|
logf("[v1] wgcfg: skipped node IPs: %s", skippedIPs)
|
|
}
|
|
if skippedSubnets.Len() > 0 {
|
|
logf("[v1] wgcfg: did not accept subnet routes: %s", skippedSubnets)
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|