Files
tailscale/net/netutil/routes.go
BeckyPauley 98f1ac0880 cmd/k8s-operator, net/netutil: revert 4via6 changes (#19990)
Reverts support 4via6 in egress proxy and connector (#19863)

Updates #19334

Signed-off-by: Becky Pauley <becky@tailscale.com>
2026-06-03 20:20:36 +01:00

94 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package netutil
import (
"encoding/binary"
"fmt"
"net/netip"
"sort"
"strings"
"tailscale.com/net/tsaddr"
)
// ValidateViaPrefix checks that the IP prefix is a valid 4via6 route.
// It verifies that the prefix is in the Tailscale via range, has a prefix
// length between /96 and /128, and that the embedded site ID is in the
// range 065535.
func validateViaPrefix(ipp netip.Prefix) error {
if !tsaddr.IsViaPrefix(ipp) {
return fmt.Errorf("%v is not a 4-in-6 prefix", ipp)
}
if ipp.Bits() < (128 - 32) {
return fmt.Errorf("%v 4-in-6 prefix must be at least a /%v", ipp, 128-32)
}
a := ipp.Addr().As16()
// The first 64 bits of a are the via prefix.
// The next 32 bits are the "site ID".
// The last 32 bits are the IPv4.
//
// We used to only allow advertising site IDs from 0-255, but we have
// since relaxed this (as of 2024-01) to allow IDs from 0-65535.
siteID := binary.BigEndian.Uint32(a[8:12])
if siteID > 0xFFFF {
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xffff or less", ipp, siteID)
}
return nil
}
// CalcAdvertiseRoutes calculates the requested routes to be advertised by a node.
// advertiseRoutes is the user-provided, comma-separated list of routes (IP addresses or CIDR prefixes) to advertise.
// advertiseDefaultRoute indicates whether the node should act as an exit node and advertise default routes.
func CalcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netip.Prefix, error) {
routeMap := map[netip.Prefix]bool{}
if advertiseRoutes != "" {
var default4, default6 bool
advroutes := strings.SplitSeq(advertiseRoutes, ",")
for s := range advroutes {
ipp, err := netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("%q is not a valid IP address or CIDR prefix", s)
}
if ipp != ipp.Masked() {
return nil, fmt.Errorf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
}
if tsaddr.IsViaPrefix(ipp) {
if err := validateViaPrefix(ipp); err != nil {
return nil, err
}
}
if ipp == tsaddr.AllIPv4() {
default4 = true
} else if ipp == tsaddr.AllIPv6() {
default6 = true
}
routeMap[ipp] = true
}
if default4 && !default6 {
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", tsaddr.AllIPv4(), tsaddr.AllIPv6())
} else if default6 && !default4 {
return nil, fmt.Errorf("%s advertised without its IPv4 counterpart, please also advertise %s", tsaddr.AllIPv6(), tsaddr.AllIPv4())
}
}
if advertiseDefaultRoute {
routeMap[tsaddr.AllIPv4()] = true
routeMap[tsaddr.AllIPv6()] = true
}
if len(routeMap) == 0 {
return nil, nil
}
routes := make([]netip.Prefix, 0, len(routeMap))
for r := range routeMap {
routes = append(routes, r)
}
sort.Slice(routes, func(i, j int) bool {
if routes[i].Bits() != routes[j].Bits() {
return routes[i].Bits() < routes[j].Bits()
}
return routes[i].Addr().Less(routes[j].Addr())
})
return routes, nil
}