mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-04 23:45:34 +00:00
69f1324c9e
Refactor two shared functions used by the tailscale cli, calcAdvertiseRoutes and licensesURL. These are used by the web client as well as other tailscale subcommands. The web client is being moved out of the cli package, so move these two functions to new locations. Updates tailscale/corp#13775 Signed-off-by: Will Norris <will@tailscale.com>
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package netutil
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net/netip"
|
|
"sort"
|
|
"strings"
|
|
|
|
"tailscale.com/net/tsaddr"
|
|
)
|
|
|
|
var (
|
|
ipv4default = netip.MustParsePrefix("0.0.0.0/0")
|
|
ipv6default = netip.MustParsePrefix("::/0")
|
|
)
|
|
|
|
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.
|
|
// For now, we reserve the top 3 bytes of the site ID,
|
|
// and only allow users to use site IDs 0-255.
|
|
siteID := binary.BigEndian.Uint32(a[8:12])
|
|
if siteID > 0xFF {
|
|
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xff 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.Split(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 == ipv4default {
|
|
default4 = true
|
|
} else if ipp == ipv6default {
|
|
default6 = true
|
|
}
|
|
routeMap[ipp] = true
|
|
}
|
|
if default4 && !default6 {
|
|
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv4default, ipv6default)
|
|
} else if default6 && !default4 {
|
|
return nil, fmt.Errorf("%s advertised without its IPv4 counterpart, please also advertise %s", ipv6default, ipv4default)
|
|
}
|
|
}
|
|
if advertiseDefaultRoute {
|
|
routeMap[netip.MustParsePrefix("0.0.0.0/0")] = true
|
|
routeMap[netip.MustParsePrefix("::/0")] = 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
|
|
}
|