hscontrol: extract more magic numbers to named constants

- PolicyVersion for policy manager version
- portRangeParts for port range parsing
- invalidStringRandomLength for random string generation
- hostIPRegexGroups for regex match validation
- minTracerouteHeaderMatch for traceroute parsing
- nodeKeyPrefixLen for node key truncation
- minNameLength for username/hostname validation
This commit is contained in:
Kristoffer Dalby 2026-01-20 16:14:05 +00:00
parent 1462847878
commit b1463dff1e
5 changed files with 33 additions and 13 deletions

View file

@ -21,6 +21,9 @@ import (
"tailscale.com/util/deephash"
)
// PolicyVersion is the version number of this policy implementation.
const PolicyVersion = 2
// ErrInvalidTagOwner is returned when a tag owner is not an Alias type.
var ErrInvalidTagOwner = errors.New("tag owner is not an Alias")
@ -739,7 +742,7 @@ func (pm *PolicyManager) NodeCanApproveRoute(node types.NodeView, route netip.Pr
}
func (pm *PolicyManager) Version() int {
return 2
return PolicyVersion
}
func (pm *PolicyManager) DebugString() string {

View file

@ -9,6 +9,9 @@ import (
"tailscale.com/tailcfg"
)
// portRangeParts is the expected number of parts in a port range (start-end).
const portRangeParts = 2
// Sentinel errors for port and destination parsing.
var (
ErrInputMissingColon = errors.New("input must contain a colon character separating destination and port")
@ -63,7 +66,7 @@ func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
rangeParts = slices.DeleteFunc(rangeParts, func(e string) bool {
return e == ""
})
if len(rangeParts) != 2 {
if len(rangeParts) != portRangeParts {
return nil, ErrInvalidPortRange
}

View file

@ -20,6 +20,9 @@ const (
// value related to RFC 1123 and 952.
LabelHostnameLength = 63
// minNameLength is the minimum length for usernames and hostnames.
minNameLength = 2
)
var invalidDNSRegex = regexp.MustCompile("[^a-z0-9-.]+")
@ -48,7 +51,7 @@ var (
// It cannot contain invalid characters.
func ValidateUsername(username string) error {
// Ensure the username meets the minimum length requirement
if len(username) < 2 {
if len(username) < minNameLength {
return ErrUsernameTooShort
}
@ -84,7 +87,7 @@ func ValidateUsername(username string) error {
// This function does NOT modify the input - it only validates.
// The hostname must already be lowercase and contain only valid characters.
func ValidateHostname(name string) error {
if len(name) < 2 {
if len(name) < minNameLength {
return fmt.Errorf("%w: %q", ErrHostnameTooShort, name)
}
if len(name) > LabelHostnameLength {

View file

@ -9,6 +9,9 @@ import (
"tailscale.com/tailcfg"
)
// invalidStringRandomLength is the length of random bytes for invalid string generation.
const invalidStringRandomLength = 8
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
@ -68,7 +71,7 @@ func MustGenerateRandomStringDNSSafe(size int) string {
}
func InvalidString() string {
hash, _ := GenerateRandomStringDNSSafe(8)
hash, _ := GenerateRandomStringDNSSafe(invalidStringRandomLength)
return "invalid-" + hash
}

View file

@ -24,9 +24,17 @@ var (
// Sentinel errors for traceroute parsing.
var (
ErrTracerouteEmpty = errors.New("empty traceroute output")
ErrTracerouteHeader = errors.New("parsing traceroute header")
ErrTracerouteNotReached = errors.New("traceroute did not reach target")
ErrTracerouteEmpty = errors.New("empty traceroute output")
ErrTracerouteHeader = errors.New("parsing traceroute header")
ErrTracerouteNotReached = errors.New("traceroute did not reach target")
)
// Regex match group constants for traceroute parsing.
// The regexes capture hostname (group 1) and IP (group 2), plus the full match (group 0).
const (
hostIPRegexGroups = 3
nodeKeyPrefixLen = 8
minTracerouteHeaderMatch = 2 // full match + hostname
)
func TailscaleVersionNewerOrEqual(minimum, toCheck string) bool {
@ -111,7 +119,7 @@ func ParseTraceroute(output string) (Traceroute, error) {
headerRegex := regexp.MustCompile(`(?i)(?:traceroute|tracing route) to ([^ ]+) (?:\[([^\]]+)\]|\(([^)]+)\))`)
headerMatches := headerRegex.FindStringSubmatch(lines[0])
if len(headerMatches) < 2 {
if len(headerMatches) < minTracerouteHeaderMatch {
return Traceroute{}, fmt.Errorf("%w: %s", ErrTracerouteHeader, lines[0])
}
@ -210,12 +218,12 @@ func ParseTraceroute(output string) (Traceroute, error) {
hopHostname = "*"
// Skip any remaining asterisks
remainder = strings.TrimLeft(remainder, "* ")
} else if hostMatch := hostIPRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 {
} else if hostMatch := hostIPRegex.FindStringSubmatch(remainder); len(hostMatch) >= hostIPRegexGroups {
// Format: hostname (IP)
hopHostname = hostMatch[1]
hopIP, _ = netip.ParseAddr(hostMatch[2])
remainder = strings.TrimSpace(remainder[len(hostMatch[0]):])
} else if hostMatch := hostIPBracketRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 {
} else if hostMatch := hostIPBracketRegex.FindStringSubmatch(remainder); len(hostMatch) >= hostIPRegexGroups {
// Format: hostname [IP] (Windows)
hopHostname = hostMatch[1]
hopIP, _ = netip.ParseAddr(hostMatch[2])
@ -307,8 +315,8 @@ func EnsureHostname(hostinfo *tailcfg.Hostinfo, machineKey, nodeKey string) stri
}
keyPrefix := key
if len(key) > 8 {
keyPrefix = key[:8]
if len(key) > nodeKeyPrefixLen {
keyPrefix = key[:nodeKeyPrefixLen]
}
return "node-" + keyPrefix