mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
cmd/tailscale: refactor shared utility methods
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>
This commit is contained in:
parent
b3618c23bf
commit
69f1324c9e
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
|
"tailscale.com/licenses"
|
||||||
)
|
)
|
||||||
|
|
||||||
var licensesCmd = &ffcli.Command{
|
var licensesCmd = &ffcli.Command{
|
||||||
@ -18,27 +18,13 @@
|
|||||||
Exec: runLicenses,
|
Exec: runLicenses,
|
||||||
}
|
}
|
||||||
|
|
||||||
// licensesURL returns the absolute URL containing open source license information for the current platform.
|
|
||||||
func licensesURL() string {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "android":
|
|
||||||
return "https://tailscale.com/licenses/android"
|
|
||||||
case "darwin", "ios":
|
|
||||||
return "https://tailscale.com/licenses/apple"
|
|
||||||
case "windows":
|
|
||||||
return "https://tailscale.com/licenses/windows"
|
|
||||||
default:
|
|
||||||
return "https://tailscale.com/licenses/tailscale"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLicenses(ctx context.Context, args []string) error {
|
func runLicenses(ctx context.Context, args []string) error {
|
||||||
licenses := licensesURL()
|
url := licenses.LicensesURL()
|
||||||
outln(`
|
outln(`
|
||||||
Tailscale wouldn't be possible without the contributions of thousands of open
|
Tailscale wouldn't be possible without the contributions of thousands of open
|
||||||
source developers. To see the open source packages included in Tailscale and
|
source developers. To see the open source packages included in Tailscale and
|
||||||
their respective license information, visit:
|
their respective license information, visit:
|
||||||
|
|
||||||
` + licenses)
|
` + url)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
|
"tailscale.com/net/netutil"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
)
|
)
|
||||||
@ -159,11 +160,11 @@ func runSet(ctx context.Context, args []string) (retErr error) {
|
|||||||
// setArgs is the parsed command-line arguments.
|
// setArgs is the parsed command-line arguments.
|
||||||
func calcAdvertiseRoutesForSet(advertiseExitNodeSet, advertiseRoutesSet bool, curPrefs *ipn.Prefs, setArgs setArgsT) (routes []netip.Prefix, err error) {
|
func calcAdvertiseRoutesForSet(advertiseExitNodeSet, advertiseRoutesSet bool, curPrefs *ipn.Prefs, setArgs setArgsT) (routes []netip.Prefix, err error) {
|
||||||
if advertiseExitNodeSet && advertiseRoutesSet {
|
if advertiseExitNodeSet && advertiseRoutesSet {
|
||||||
return calcAdvertiseRoutes(setArgs.advertiseRoutes, setArgs.advertiseDefaultRoute)
|
return netutil.CalcAdvertiseRoutes(setArgs.advertiseRoutes, setArgs.advertiseDefaultRoute)
|
||||||
|
|
||||||
}
|
}
|
||||||
if advertiseRoutesSet {
|
if advertiseRoutesSet {
|
||||||
return calcAdvertiseRoutes(setArgs.advertiseRoutes, curPrefs.AdvertisesExitNode())
|
return netutil.CalcAdvertiseRoutes(setArgs.advertiseRoutes, curPrefs.AdvertisesExitNode())
|
||||||
}
|
}
|
||||||
if advertiseExitNodeSet {
|
if advertiseExitNodeSet {
|
||||||
alreadyAdvertisesExitNode := curPrefs.AdvertisesExitNode()
|
alreadyAdvertisesExitNode := curPrefs.AdvertisesExitNode()
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
@ -33,7 +32,7 @@
|
|||||||
"tailscale.com/health/healthmsg"
|
"tailscale.com/health/healthmsg"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/netutil"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -220,82 +219,6 @@ func warnf(format string, args ...any) {
|
|||||||
printf("Warning: "+format+"\n", args...)
|
printf("Warning: "+format+"\n", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefsFromUpArgs returns the ipn.Prefs for the provided args.
|
// prefsFromUpArgs returns the ipn.Prefs for the provided args.
|
||||||
//
|
//
|
||||||
// Note that the parameters upArgs and warnf are named intentionally
|
// Note that the parameters upArgs and warnf are named intentionally
|
||||||
@ -303,7 +226,7 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
|
|||||||
// function exists for testing and should have no side effects or
|
// function exists for testing and should have no side effects or
|
||||||
// outside interactions (e.g. no making Tailscale LocalAPI calls).
|
// outside interactions (e.g. no making Tailscale LocalAPI calls).
|
||||||
func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goos string) (*ipn.Prefs, error) {
|
func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goos string) (*ipn.Prefs, error) {
|
||||||
routes, err := calcAdvertiseRoutes(upArgs.advertiseRoutes, upArgs.advertiseDefaultRoute)
|
routes, err := netutil.CalcAdvertiseRoutes(upArgs.advertiseRoutes, upArgs.advertiseDefaultRoute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
"tailscale.com/licenses"
|
||||||
|
"tailscale.com/net/netutil"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/cmpx"
|
"tailscale.com/util/cmpx"
|
||||||
"tailscale.com/util/groupmember"
|
"tailscale.com/util/groupmember"
|
||||||
@ -385,7 +387,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := calcAdvertiseRoutes(postData.AdvertiseRoutes, postData.AdvertiseExitNode)
|
routes, err := netutil.CalcAdvertiseRoutes(postData.AdvertiseRoutes, postData.AdvertiseExitNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
||||||
@ -437,7 +439,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Profile: profile,
|
Profile: profile,
|
||||||
Status: st.BackendState,
|
Status: st.BackendState,
|
||||||
DeviceName: deviceName,
|
DeviceName: deviceName,
|
||||||
LicensesURL: licensesURL(),
|
LicensesURL: licenses.LicensesURL(),
|
||||||
TUNMode: st.TUN,
|
TUNMode: st.TUN,
|
||||||
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
||||||
DSMVersion: distro.DSMVersion(),
|
DSMVersion: distro.DSMVersion(),
|
||||||
|
@ -81,6 +81,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
||||||
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
||||||
|
tailscale.com/licenses from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/metrics from tailscale.com/derp
|
tailscale.com/metrics from tailscale.com/derp
|
||||||
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
|
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
|
||||||
tailscale.com/net/dnscache from tailscale.com/derp/derphttp+
|
tailscale.com/net/dnscache from tailscale.com/derp/derphttp+
|
||||||
|
21
licenses/licenses.go
Normal file
21
licenses/licenses.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package licenses provides utilities for working with open source licenses.
|
||||||
|
package licenses
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
// LicensesURL returns the absolute URL containing open source license information for the current platform.
|
||||||
|
func LicensesURL() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "android":
|
||||||
|
return "https://tailscale.com/licenses/android"
|
||||||
|
case "darwin", "ios":
|
||||||
|
return "https://tailscale.com/licenses/apple"
|
||||||
|
case "windows":
|
||||||
|
return "https://tailscale.com/licenses/windows"
|
||||||
|
default:
|
||||||
|
return "https://tailscale.com/licenses/tailscale"
|
||||||
|
}
|
||||||
|
}
|
93
net/netutil/routes.go
Normal file
93
net/netutil/routes.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user