mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-17 16:36:02 +00:00
util: consolidate parsing and encoding helpers
This commit is contained in:
parent
45e6e90d11
commit
fdc7e26c8a
7 changed files with 64 additions and 93 deletions
|
|
@ -120,12 +120,5 @@ func (m *Match) DestsIsTheInternet() bool {
|
||||||
|
|
||||||
// Superset-of-[util.TheInternet] check handles merged filter rules
|
// Superset-of-[util.TheInternet] check handles merged filter rules
|
||||||
// where the internet prefixes are combined with other dests.
|
// where the internet prefixes are combined with other dests.
|
||||||
theInternet := util.TheInternet()
|
return util.IPSetSubsetOf(util.TheInternet(), m.dests)
|
||||||
for _, prefix := range theInternet.Prefixes() {
|
|
||||||
if !m.dests.ContainsPrefix(prefix) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/juanfont/headscale/hscontrol/types"
|
"github.com/juanfont/headscale/hscontrol/types"
|
||||||
"github.com/juanfont/headscale/hscontrol/util"
|
"github.com/juanfont/headscale/hscontrol/util"
|
||||||
"go4.org/netipx"
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,7 +65,7 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||||
// Exit-route advertisers need rules targeting the
|
// Exit-route advertisers need rules targeting the
|
||||||
// public internet so the kernel filter accepts
|
// public internet so the kernel filter accepts
|
||||||
// traffic forwarded by autogroup:internet sources.
|
// traffic forwarded by autogroup:internet sources.
|
||||||
if hasExitRoutes && ipSetSubsetOf(expanded, util.TheInternet()) {
|
if hasExitRoutes && util.IPSetSubsetOf(expanded, util.TheInternet()) {
|
||||||
dests = append(dests, dest)
|
dests = append(dests, dest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,20 +82,6 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func ipSetSubsetOf(candidate, container *netipx.IPSet) bool {
|
|
||||||
if candidate == nil || container == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pref := range candidate.Prefixes() {
|
|
||||||
if !container.ContainsPrefix(pref) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduceCapGrantRule filters a [tailcfg.CapGrant] rule to only include
|
// reduceCapGrantRule filters a [tailcfg.CapGrant] rule to only include
|
||||||
// [tailcfg.CapGrant] entries whose Dsts match the given node's IPs. When a
|
// [tailcfg.CapGrant] entries whose Dsts match the given node's IPs. When a
|
||||||
// broad prefix (e.g. 100.64.0.0/10 from dst:*) contains a node's IP, it is
|
// broad prefix (e.g. 100.64.0.0/10 from dst:*) contains a node's IP, it is
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,6 @@ import (
|
||||||
// This is borrowed from, and updated to use [netipx.IPSet]
|
// This is borrowed from, and updated to use [netipx.IPSet]
|
||||||
// https://github.com/tailscale/tailscale/blob/71029cea2ddf82007b80f465b256d027eab0f02d/wgengine/filter/tailcfg.go#L97-L162
|
// https://github.com/tailscale/tailscale/blob/71029cea2ddf82007b80f465b256d027eab0f02d/wgengine/filter/tailcfg.go#L97-L162
|
||||||
// TODO(kradalby): contribute upstream and make public.
|
// TODO(kradalby): contribute upstream and make public.
|
||||||
var (
|
|
||||||
zeroIP4 = netip.AddrFrom4([4]byte{})
|
|
||||||
zeroIP6 = netip.AddrFrom16([16]byte{})
|
|
||||||
)
|
|
||||||
|
|
||||||
// parseIPSet parses arg as one:
|
// parseIPSet parses arg as one:
|
||||||
//
|
//
|
||||||
|
|
@ -30,8 +26,8 @@ var (
|
||||||
func ParseIPSet(arg string, bits *int) (*netipx.IPSet, error) {
|
func ParseIPSet(arg string, bits *int) (*netipx.IPSet, error) {
|
||||||
var ipSet netipx.IPSetBuilder
|
var ipSet netipx.IPSetBuilder
|
||||||
if arg == "*" {
|
if arg == "*" {
|
||||||
ipSet.AddPrefix(netip.PrefixFrom(zeroIP4, 0))
|
ipSet.AddPrefix(netip.PrefixFrom(netip.IPv4Unspecified(), 0))
|
||||||
ipSet.AddPrefix(netip.PrefixFrom(zeroIP6, 0))
|
ipSet.AddPrefix(netip.PrefixFrom(netip.IPv6Unspecified(), 0))
|
||||||
|
|
||||||
return ipSet.IPSet()
|
return ipSet.IPSet()
|
||||||
}
|
}
|
||||||
|
|
@ -90,13 +86,9 @@ func ParseIPSet(arg string, bits *int) (*netipx.IPSet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
|
func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
|
||||||
var network, broadcast netip.Addr
|
|
||||||
|
|
||||||
ipRange := netipx.RangeOfPrefix(na)
|
ipRange := netipx.RangeOfPrefix(na)
|
||||||
network = ipRange.From()
|
|
||||||
broadcast = ipRange.To()
|
|
||||||
|
|
||||||
return network, broadcast
|
return ipRange.From(), ipRange.To()
|
||||||
}
|
}
|
||||||
|
|
||||||
func StringToIPPrefix(prefixes []string) ([]netip.Prefix, error) {
|
func StringToIPPrefix(prefixes []string) ([]netip.Prefix, error) {
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ func GenerateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||||
|
|
||||||
// here we generate the base domain (e.g., 100.in-addr.arpa., 16.172.in-addr.arpa., etc.)
|
// here we generate the base domain (e.g., 100.in-addr.arpa., 16.172.in-addr.arpa., etc.)
|
||||||
rdnsSlice := []string{}
|
rdnsSlice := []string{}
|
||||||
for i := lastOctet - 1; i >= 0; i-- {
|
for _, b := range slices.Backward(netRange.IP[:lastOctet]) {
|
||||||
rdnsSlice = append(rdnsSlice, strconv.FormatUint(uint64(netRange.IP[i]), 10))
|
rdnsSlice = append(rdnsSlice, strconv.FormatUint(uint64(b), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
|
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
|
||||||
|
|
@ -158,13 +158,7 @@ func GenerateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||||
|
|
||||||
maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size()
|
maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size()
|
||||||
expanded := ipPrefix.Addr().StringExpanded()
|
expanded := ipPrefix.Addr().StringExpanded()
|
||||||
nibbleStr := strings.Map(func(r rune) rune {
|
nibbleStr := strings.ReplaceAll(expanded, ":", "")
|
||||||
if r == ':' {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}, expanded)
|
|
||||||
|
|
||||||
// TODO?: that does not look the most efficient implementation,
|
// TODO?: that does not look the most efficient implementation,
|
||||||
// but the inputs are not so long as to cause problems,
|
// but the inputs are not so long as to cause problems,
|
||||||
|
|
@ -172,12 +166,11 @@ func GenerateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||||
// function is called only once over the lifetime of a server process.
|
// function is called only once over the lifetime of a server process.
|
||||||
prefixConstantParts := []string{}
|
prefixConstantParts := []string{}
|
||||||
for i := range maskBits / nibbleLen {
|
for i := range maskBits / nibbleLen {
|
||||||
prefixConstantParts = append(
|
prefixConstantParts = append(prefixConstantParts, string(nibbleStr[i]))
|
||||||
[]string{string(nibbleStr[i])},
|
|
||||||
prefixConstantParts...,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slices.Reverse(prefixConstantParts)
|
||||||
|
|
||||||
makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) {
|
makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) {
|
||||||
prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".")
|
prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,12 @@ func PrefixesToString(prefixes []netip.Prefix) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustStringsToPrefixes(strings []string) []netip.Prefix {
|
func MustStringsToPrefixes(strings []string) []netip.Prefix {
|
||||||
ret := make([]netip.Prefix, 0, len(strings))
|
prefixes, err := StringToIPPrefix(strings)
|
||||||
for _, str := range strings {
|
if err != nil {
|
||||||
prefix := netip.MustParsePrefix(str)
|
panic(err)
|
||||||
ret = append(ret, prefix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return prefixes
|
||||||
}
|
}
|
||||||
|
|
||||||
// TheInternet returns the [netipx.IPSet] for the Internet.
|
// TheInternet returns the [netipx.IPSet] for the Internet.
|
||||||
|
|
@ -61,3 +60,19 @@ var TheInternet = sync.OnceValue(func() *netipx.IPSet {
|
||||||
|
|
||||||
return theInternetSet
|
return theInternetSet
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// IPSetSubsetOf reports whether every prefix of candidate is contained in
|
||||||
|
// container. A nil candidate or container returns false.
|
||||||
|
func IPSetSubsetOf(candidate, container *netipx.IPSet) bool {
|
||||||
|
if candidate == nil || container == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pref := range candidate.Prefixes() {
|
||||||
|
if !container.ContainsPrefix(pref) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,20 @@ var AddrPortComparer = cmp.Comparer(func(x, y netip.AddrPort) bool {
|
||||||
return x == y
|
return x == y
|
||||||
})
|
})
|
||||||
|
|
||||||
var MkeyComparer = cmp.Comparer(func(x, y key.MachinePublic) bool {
|
func strComparer[T interface{ String() string }]() cmp.Option {
|
||||||
return x.String() == y.String()
|
return cmp.Comparer(func(x, y T) bool {
|
||||||
})
|
return x.String() == y.String()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var NkeyComparer = cmp.Comparer(func(x, y key.NodePublic) bool {
|
var (
|
||||||
return x.String() == y.String()
|
MkeyComparer = strComparer[key.MachinePublic]()
|
||||||
})
|
NkeyComparer = strComparer[key.NodePublic]()
|
||||||
|
DkeyComparer = strComparer[key.DiscoPublic]()
|
||||||
var DkeyComparer = cmp.Comparer(func(x, y key.DiscoPublic) bool {
|
)
|
||||||
return x.String() == y.String()
|
|
||||||
})
|
|
||||||
|
|
||||||
var ViewSliceIPProtoComparer = cmp.Comparer(views.SliceEqual[ipproto.Proto])
|
var ViewSliceIPProtoComparer = cmp.Comparer(views.SliceEqual[ipproto.Proto])
|
||||||
|
|
||||||
var Comparers []cmp.Option = []cmp.Option{
|
var Comparers = []cmp.Option{
|
||||||
IPComparer, PrefixComparer, AddrPortComparer, MkeyComparer, NkeyComparer, DkeyComparer, ViewSliceIPProtoComparer,
|
IPComparer, PrefixComparer, AddrPortComparer, MkeyComparer, NkeyComparer, DkeyComparer, ViewSliceIPProtoComparer,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TailscaleVersionNewerOrEqual(minimum, toCheck string) bool {
|
func TailscaleVersionNewerOrEqual(minimum, toCheck string) bool {
|
||||||
if cmpver.Compare(minimum, toCheck) <= 0 ||
|
return cmpver.Compare(minimum, toCheck) <= 0 ||
|
||||||
toCheck == "unstable" ||
|
toCheck == "unstable" ||
|
||||||
toCheck == "head" {
|
toCheck == "head"
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseLoginURLFromCLILogin parses the output of the tailscale up command to extract the login URL.
|
// ParseLoginURLFromCLILogin parses the output of the tailscale up command to extract the login URL.
|
||||||
|
|
@ -94,6 +90,19 @@ type Traceroute struct {
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseLatency parses a traceroute latency token such as "1.5" or "<1",
|
||||||
|
// returning the duration rounded to the nearest microsecond. The second
|
||||||
|
// return value reports whether the token was a valid number.
|
||||||
|
func parseLatency(tok string) (time.Duration, bool) {
|
||||||
|
ms, err := strconv.ParseFloat(strings.TrimPrefix(tok, "<"), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round to nearest microsecond to avoid floating point precision issues.
|
||||||
|
return time.Duration(ms * float64(time.Millisecond)).Round(time.Microsecond), true
|
||||||
|
}
|
||||||
|
|
||||||
// ParseTraceroute parses the output of the traceroute command and returns a [Traceroute] struct.
|
// ParseTraceroute parses the output of the traceroute command and returns a [Traceroute] struct.
|
||||||
func ParseTraceroute(output string) (Traceroute, error) {
|
func ParseTraceroute(output string) (Traceroute, error) {
|
||||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
|
@ -185,13 +194,8 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Extract and remove the latency from the beginning
|
// Extract and remove the latency from the beginning
|
||||||
latStr := strings.TrimPrefix(remainder[latMatch[2]:latMatch[3]], "<")
|
if d, ok := parseLatency(remainder[latMatch[2]:latMatch[3]]); ok {
|
||||||
|
latencies = append(latencies, d)
|
||||||
ms, err := strconv.ParseFloat(latStr, 64)
|
|
||||||
if err == nil {
|
|
||||||
// Round to nearest microsecond to avoid floating point precision issues
|
|
||||||
duration := time.Duration(ms * float64(time.Millisecond))
|
|
||||||
latencies = append(latencies, duration.Round(time.Microsecond))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remainder = strings.TrimSpace(remainder[latMatch[1]:])
|
remainder = strings.TrimSpace(remainder[latMatch[1]:])
|
||||||
|
|
@ -232,14 +236,8 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||||
latencyMatches := latencyRegex.FindAllStringSubmatch(remainder, -1)
|
latencyMatches := latencyRegex.FindAllStringSubmatch(remainder, -1)
|
||||||
for _, match := range latencyMatches {
|
for _, match := range latencyMatches {
|
||||||
if len(match) > 1 {
|
if len(match) > 1 {
|
||||||
// Remove '<' prefix if present (e.g., "<1 ms")
|
if d, ok := parseLatency(match[1]); ok {
|
||||||
latStr := strings.TrimPrefix(match[1], "<")
|
latencies = append(latencies, d)
|
||||||
|
|
||||||
ms, err := strconv.ParseFloat(latStr, 64)
|
|
||||||
if err == nil {
|
|
||||||
// Round to nearest microsecond to avoid floating point precision issues
|
|
||||||
duration := time.Duration(ms * float64(time.Millisecond))
|
|
||||||
latencies = append(latencies, duration.Round(time.Microsecond))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,15 +267,10 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCI() bool {
|
func IsCI() bool {
|
||||||
if _, ok := os.LookupEnv("CI"); ok {
|
_, ci := os.LookupEnv("CI")
|
||||||
return true
|
_, gh := os.LookupEnv("GITHUB_RUN_ID")
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := os.LookupEnv("GITHUB_RUN_ID"); ok {
|
return ci || gh
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRegistrationKey generates a vanity key for tracking web authentication
|
// GenerateRegistrationKey generates a vanity key for tracking web authentication
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue