mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-08 00:50:20 +09:00
util: consolidate parsing and encoding helpers
This commit is contained in:
@@ -120,12 +120,5 @@ func (m *Match) DestsIsTheInternet() bool {
|
||||
|
||||
// Superset-of-[util.TheInternet] check handles merged filter rules
|
||||
// where the internet prefixes are combined with other dests.
|
||||
theInternet := util.TheInternet()
|
||||
for _, prefix := range theInternet.Prefixes() {
|
||||
if !m.dests.ContainsPrefix(prefix) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return util.IPSetSubsetOf(util.TheInternet(), m.dests)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
@@ -66,7 +65,7 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||
// Exit-route advertisers need rules targeting the
|
||||
// public internet so the kernel filter accepts
|
||||
// traffic forwarded by autogroup:internet sources.
|
||||
if hasExitRoutes && ipSetSubsetOf(expanded, util.TheInternet()) {
|
||||
if hasExitRoutes && util.IPSetSubsetOf(expanded, util.TheInternet()) {
|
||||
dests = append(dests, dest)
|
||||
}
|
||||
}
|
||||
@@ -83,20 +82,6 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||
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
|
||||
// [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
|
||||
|
||||
+3
-11
@@ -12,10 +12,6 @@ import (
|
||||
// This is borrowed from, and updated to use [netipx.IPSet]
|
||||
// https://github.com/tailscale/tailscale/blob/71029cea2ddf82007b80f465b256d027eab0f02d/wgengine/filter/tailcfg.go#L97-L162
|
||||
// TODO(kradalby): contribute upstream and make public.
|
||||
var (
|
||||
zeroIP4 = netip.AddrFrom4([4]byte{})
|
||||
zeroIP6 = netip.AddrFrom16([16]byte{})
|
||||
)
|
||||
|
||||
// parseIPSet parses arg as one:
|
||||
//
|
||||
@@ -30,8 +26,8 @@ var (
|
||||
func ParseIPSet(arg string, bits *int) (*netipx.IPSet, error) {
|
||||
var ipSet netipx.IPSetBuilder
|
||||
if arg == "*" {
|
||||
ipSet.AddPrefix(netip.PrefixFrom(zeroIP4, 0))
|
||||
ipSet.AddPrefix(netip.PrefixFrom(zeroIP6, 0))
|
||||
ipSet.AddPrefix(netip.PrefixFrom(netip.IPv4Unspecified(), 0))
|
||||
ipSet.AddPrefix(netip.PrefixFrom(netip.IPv6Unspecified(), 0))
|
||||
|
||||
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) {
|
||||
var network, broadcast netip.Addr
|
||||
|
||||
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) {
|
||||
|
||||
+6
-13
@@ -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.)
|
||||
rdnsSlice := []string{}
|
||||
for i := lastOctet - 1; i >= 0; i-- {
|
||||
rdnsSlice = append(rdnsSlice, strconv.FormatUint(uint64(netRange.IP[i]), 10))
|
||||
for _, b := range slices.Backward(netRange.IP[:lastOctet]) {
|
||||
rdnsSlice = append(rdnsSlice, strconv.FormatUint(uint64(b), 10))
|
||||
}
|
||||
|
||||
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
|
||||
@@ -158,13 +158,7 @@ func GenerateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||
|
||||
maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size()
|
||||
expanded := ipPrefix.Addr().StringExpanded()
|
||||
nibbleStr := strings.Map(func(r rune) rune {
|
||||
if r == ':' {
|
||||
return -1
|
||||
}
|
||||
|
||||
return r
|
||||
}, expanded)
|
||||
nibbleStr := strings.ReplaceAll(expanded, ":", "")
|
||||
|
||||
// TODO?: that does not look the most efficient implementation,
|
||||
// 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.
|
||||
prefixConstantParts := []string{}
|
||||
for i := range maskBits / nibbleLen {
|
||||
prefixConstantParts = append(
|
||||
[]string{string(nibbleStr[i])},
|
||||
prefixConstantParts...,
|
||||
)
|
||||
prefixConstantParts = append(prefixConstantParts, string(nibbleStr[i]))
|
||||
}
|
||||
|
||||
slices.Reverse(prefixConstantParts)
|
||||
|
||||
makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) {
|
||||
prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".")
|
||||
|
||||
|
||||
+20
-5
@@ -26,13 +26,12 @@ func PrefixesToString(prefixes []netip.Prefix) []string {
|
||||
}
|
||||
|
||||
func MustStringsToPrefixes(strings []string) []netip.Prefix {
|
||||
ret := make([]netip.Prefix, 0, len(strings))
|
||||
for _, str := range strings {
|
||||
prefix := netip.MustParsePrefix(str)
|
||||
ret = append(ret, prefix)
|
||||
prefixes, err := StringToIPPrefix(strings)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ret
|
||||
return prefixes
|
||||
}
|
||||
|
||||
// TheInternet returns the [netipx.IPSet] for the Internet.
|
||||
@@ -61,3 +60,19 @@ var TheInternet = sync.OnceValue(func() *netipx.IPSet {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+11
-11
@@ -21,20 +21,20 @@ var AddrPortComparer = cmp.Comparer(func(x, y netip.AddrPort) bool {
|
||||
return x == y
|
||||
})
|
||||
|
||||
var MkeyComparer = cmp.Comparer(func(x, y key.MachinePublic) bool {
|
||||
return x.String() == y.String()
|
||||
})
|
||||
func strComparer[T interface{ String() string }]() cmp.Option {
|
||||
return cmp.Comparer(func(x, y T) bool {
|
||||
return x.String() == y.String()
|
||||
})
|
||||
}
|
||||
|
||||
var NkeyComparer = cmp.Comparer(func(x, y key.NodePublic) bool {
|
||||
return x.String() == y.String()
|
||||
})
|
||||
|
||||
var DkeyComparer = cmp.Comparer(func(x, y key.DiscoPublic) bool {
|
||||
return x.String() == y.String()
|
||||
})
|
||||
var (
|
||||
MkeyComparer = strComparer[key.MachinePublic]()
|
||||
NkeyComparer = strComparer[key.NodePublic]()
|
||||
DkeyComparer = strComparer[key.DiscoPublic]()
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
+22
-29
@@ -24,13 +24,9 @@ var (
|
||||
)
|
||||
|
||||
func TailscaleVersionNewerOrEqual(minimum, toCheck string) bool {
|
||||
if cmpver.Compare(minimum, toCheck) <= 0 ||
|
||||
return cmpver.Compare(minimum, toCheck) <= 0 ||
|
||||
toCheck == "unstable" ||
|
||||
toCheck == "head" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
toCheck == "head"
|
||||
}
|
||||
|
||||
// ParseLoginURLFromCLILogin parses the output of the tailscale up command to extract the login URL.
|
||||
@@ -94,6 +90,19 @@ type Traceroute struct {
|
||||
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.
|
||||
func ParseTraceroute(output string) (Traceroute, error) {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
@@ -185,13 +194,8 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||
break
|
||||
}
|
||||
// Extract and remove the latency from the beginning
|
||||
latStr := strings.TrimPrefix(remainder[latMatch[2]:latMatch[3]], "<")
|
||||
|
||||
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))
|
||||
if d, ok := parseLatency(remainder[latMatch[2]:latMatch[3]]); ok {
|
||||
latencies = append(latencies, d)
|
||||
}
|
||||
|
||||
remainder = strings.TrimSpace(remainder[latMatch[1]:])
|
||||
@@ -232,14 +236,8 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||
latencyMatches := latencyRegex.FindAllStringSubmatch(remainder, -1)
|
||||
for _, match := range latencyMatches {
|
||||
if len(match) > 1 {
|
||||
// Remove '<' prefix if present (e.g., "<1 ms")
|
||||
latStr := strings.TrimPrefix(match[1], "<")
|
||||
|
||||
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))
|
||||
if d, ok := parseLatency(match[1]); ok {
|
||||
latencies = append(latencies, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,15 +267,10 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||
}
|
||||
|
||||
func IsCI() bool {
|
||||
if _, ok := os.LookupEnv("CI"); ok {
|
||||
return true
|
||||
}
|
||||
_, ci := os.LookupEnv("CI")
|
||||
_, gh := os.LookupEnv("GITHUB_RUN_ID")
|
||||
|
||||
if _, ok := os.LookupEnv("GITHUB_RUN_ID"); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return ci || gh
|
||||
}
|
||||
|
||||
// GenerateRegistrationKey generates a vanity key for tracking web authentication
|
||||
|
||||
Reference in New Issue
Block a user