all: apply godoc [Name] link conventions across comments

Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.

Pattern applied across the tree:
  In-package         [Foo], [Foo.Bar]
  Cross-package      [pkg.Foo], [pkg.Foo.Bar]
  Stdlib             [netip.Prefix], [errors.Is], [context.Context]
  Tailscale          [tailcfg.MapResponse], [tailcfg.Node.CapMap],
                     [tailcfg.NodeAttrSuggestExitNode]

Skip rules:
  - File:line refs left as plain text
  - HuJSON wire keys inside backtick raw strings untouched
  - ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
    symbols, left as plain text
  - JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
    link tags, decorative dividers — all left as-is
This commit is contained in:
Kristoffer Dalby 2026-05-18 18:35:53 +00:00
parent 17236fd284
commit 4cca63155d
124 changed files with 1037 additions and 1011 deletions

View file

@ -18,7 +18,7 @@ type grantCategory int
const (
// grantCategoryRegular requires no per-node work. The pre-compiled
// rules are complete and only need ReduceFilterRules.
// rules are complete and only need [policyutil.ReduceFilterRules].
grantCategoryRegular grantCategory = iota
// grantCategorySelf has autogroup:self destinations that must be
@ -80,7 +80,7 @@ type viaGrantData struct {
// resolveViaDestinations splits a via grant's destinations into the
// flat list of IP prefixes they resolve to plus a flag for
// autogroup:internet. Every alias kind goes through Alias.Resolve so
// autogroup:internet. Every alias kind goes through [Alias.Resolve] so
// adding a new alias type to the policy parser does not silently
// disappear from the via path. Non-IP alias kinds (tag, user, group,
// wildcard) resolve to /32 host IPs that never overlap with subnet
@ -116,7 +116,7 @@ func resolveViaDestinations(
// userNodeIndex maps user IDs to their untagged nodes. Built once per
// policy or node-set change and read from many goroutines under
// PolicyManager.mu; readers must hold the lock (or the snapshot
// [PolicyManager.mu]; readers must hold the lock (or the snapshot
// returned to them).
type userNodeIndex map[uint][]types.NodeView
@ -136,7 +136,7 @@ func buildUserNodeIndex(
}
// compileNodeAttrs returns the per-node CapMap derived from policy
// nodeAttrs plus the tailnet-wide RandomizeClientPort flag.
// nodeAttrs plus the tailnet-wide [Policy.RandomizeClientPort] flag.
//
// Returns an error when a target alias fails to resolve so the caller
// surfaces a corrupt policy instead of silently granting a partial set
@ -163,18 +163,18 @@ func (pol *Policy) compileNodeAttrs(
result[id] = capMap
}
// nil RawMessage matches the wire format from a Tailscale-hosted
// control plane: capabilities without companion data marshal as
// `null` rather than `[]`. Storing nil keeps the merge stable
// and lets the compat test diff cleanly against captured
// netmaps.
// nil [tailcfg.RawMessage] matches the wire format from a
// Tailscale-hosted control plane: capabilities without companion
// data marshal as null rather than []. Storing nil keeps the
// merge stable and lets the compat test diff cleanly against
// captured netmaps.
if _, exists := capMap[attr]; !exists {
capMap[attr] = nil
}
}
// Cache each node's IPs once per call. Without the cache, the
// node-attr inner loop would call NodeView.IPs() once per attr
// node-attr inner loop would call [types.NodeView.IPs] once per attr
// per node — O(grants × nodes) allocations of a 2-element slice
// for what is invariant per node within a single policy compile.
type nodeIPs struct {
@ -221,10 +221,11 @@ func (pol *Policy) compileNodeAttrs(
return result, nil
}
// compileGrants resolves all policy grants into compiledGrant structs.
// compileGrants resolves all policy grants into [compiledGrant] structs.
// Source resolution and non-self destination resolution happens once
// here. This is the single resolution path that replaces the
// duplicated work in compileFilterRules and compileGrantWithAutogroupSelf.
// duplicated work in [Policy.compileFilterRules] and the autogroup:self
// expansion.
func (pol *Policy) compileGrants(
users types.Users,
nodes views.Slice[types.NodeView],
@ -256,7 +257,7 @@ func (pol *Policy) compileGrants(
return compiled
}
// compileOneGrant resolves a single grant into a compiledGrant.
// compileOneGrant resolves a single grant into a [compiledGrant].
// All source resolution happens here. Non-self, non-via destination
// resolution also happens here. Per-node data (self dests, via
// matching) is stored for deferred compilation.
@ -341,7 +342,7 @@ func (pol *Policy) compileOneGrant(
// compileOneViaGrant resolves sources for a via grant and stores the
// deferred per-node data. The actual via-node matching and route
// intersection happens in compileViaForNode.
// intersection happens in [compileViaForNode].
func (pol *Policy) compileOneViaGrant(
grant Grant,
users types.Users,
@ -404,8 +405,8 @@ func (pol *Policy) compileOneViaGrant(
// resolveSources resolves grant sources per-alias, returning the
// resolved addresses and a separate slice of non-wildcard sources.
// This is the canonical source-resolution path. Its output lands in
// compiledGrant.srcIPStrings (among other places) and callers on the
// hot path should prefer reading that over calling Resolve again.
// [compiledGrant.srcIPStrings] (among other places) and callers on the
// hot path should prefer reading that over calling [Alias.Resolve] again.
func resolveSources(
pol *Policy,
sources Aliases,
@ -490,8 +491,9 @@ func buildSrcIPStrings(
}
// compileOtherDests compiles filter rules for non-self, non-via
// destinations. This produces both DstPorts rules (from
// InternetProtocols) and CapGrant rules (from App).
// destinations. This produces both [tailcfg.FilterRule.DstPorts] rules
// (from [Grant.InternetProtocols]) and [tailcfg.CapGrant] rules (from
// [Grant.App]).
func (pol *Policy) compileOtherDests(
users types.Users,
nodes views.Slice[types.NodeView],
@ -580,7 +582,7 @@ func (pol *Policy) compileOtherDests(
return rules
}
// hasPerNodeGrants reports whether any compiled grant requires
// hasPerNodeGrants reports whether any [compiledGrant] requires
// per-node filter compilation (via grants or autogroup:self).
func hasPerNodeGrants(grants []compiledGrant) bool {
for i := range grants {
@ -592,10 +594,10 @@ func hasPerNodeGrants(grants []compiledGrant) bool {
return false
}
// globalFilterRules extracts global filter rules from compiled
// grants. Via grants produce no global rules (they are per-node
// only); regular grants contribute their full pre-compiled ruleset;
// self grants contribute their non-self portion.
// globalFilterRules extracts global filter rules from [compiledGrant]s.
// Via grants produce no global rules (they are per-node only); regular
// grants contribute their full pre-compiled ruleset; self grants
// contribute their non-self portion.
func globalFilterRules(grants []compiledGrant) []tailcfg.FilterRule {
var rules []tailcfg.FilterRule
@ -804,10 +806,11 @@ func compileViaForNode(
return nil
}
// SubnetRoutes excludes exit routes, so the overlap gate below sees
// only subnet advertisements. autogroup:internet on a via-tagged
// exit advertiser is handled separately because its eligibility is
// per-node (IsExitNode) rather than per-prefix overlap.
// [types.NodeView.SubnetRoutes] excludes exit routes, so the overlap
// gate below sees only subnet advertisements. autogroup:internet on
// a via-tagged exit advertiser is handled separately because its
// eligibility is per-node ([types.NodeView.IsExitNode]) rather than
// per-prefix overlap.
nodeSubnetRoutes := node.SubnetRoutes()
var viaDstPrefixes []netip.Prefix
@ -826,11 +829,11 @@ func compileViaForNode(
}
// autogroup:internet on a via-tagged exit advertiser becomes a rule
// whose DstPorts enumerate util.TheInternet(). The matchers derived
// from this rule let Node.CanAccess surface the exit node to the
// grant source via DestsIsTheInternet. ReduceFilterRules strips the
// rule from the wire format on non-exit advertisers, preserving
// SaaS PacketFilter encoding.
// whose DstPorts enumerate [util.TheInternet]. The matchers derived
// from this rule let [types.NodeView.CanAccess] surface the exit node
// to the grant source via [matcher.Match.DestsIsTheInternet].
// [policyutil.ReduceFilterRules] strips the rule from the wire format
// on non-exit advertisers, preserving SaaS PacketFilter encoding.
if cg.via.hasAutoGroupInternet && node.IsExitNode() {
viaDstPrefixes = append(
viaDstPrefixes,

View file

@ -735,10 +735,11 @@ func TestTagPropagationToPeerMap(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, matchersForUser2, "MatchersForNode should return non-empty matchers (at least self-access rule)")
// Test ReduceNodes logic with the updated nodes and matchers
// This is what buildTailPeers does - it takes peers from ListPeers (which might include user1)
// and filters them using ReduceNodes with the updated matchers
// Inline the ReduceNodes logic to avoid import cycle
// Test [policy.ReduceNodes] logic with the updated nodes and matchers
// This is what [mapper.MapResponseBuilder.buildTailPeers] does - it takes peers from
// [state.State.ListPeers] (which might include user1) and filters them using
// [policy.ReduceNodes] with the updated matchers
// Inline the [policy.ReduceNodes] logic to avoid import cycle
user2View := user2Node.View()
user1UpdatedView := user1NodeUpdated.View()

View file

@ -22,7 +22,7 @@ import (
// - check: every listed user reaches every dst via a check-action
// rule specifically (accept-only matches fail the assertion).
// SSHPolicyTestResult is the outcome of a single SSHPolicyTest.
// SSHPolicyTestResult is the outcome of a single [SSHPolicyTest].
type SSHPolicyTestResult struct {
Src string `json:"src"`
Passed bool `json:"passed"`
@ -122,7 +122,7 @@ func checkFailReason(res SSHPolicyTestResult, user, dst string) string {
}
// RunSSHTests evaluates the live policy's sshTests block and wraps any
// failure in errSSHPolicyTestsFailed.
// failure in [errSSHPolicyTestsFailed].
func (pm *PolicyManager) RunSSHTests() error {
if pm == nil || pm.pol == nil || len(pm.pol.SSHTests) == 0 {
return nil
@ -162,7 +162,7 @@ func evaluateSSHTests(
}
// runSSHPolicyTests evaluates every sshTests entry. The cache is keyed
// by dst NodeID so repeat destinations only compile once per pass.
// by dst [types.NodeID] so repeat destinations only compile once per pass.
func runSSHPolicyTests(
pol *Policy,
users []types.User,
@ -389,7 +389,7 @@ func appendUserDst(m map[string][]string, user, dst string) map[string][]string
// resolveSSHTestSource returns the src's principal addresses and, for
// user-shaped sources, the user ID (so autogroup:self can scope to it).
// Tag, host, and IP sources return userID 0.
// [Tag], [Host], and IP sources return userID 0.
func resolveSSHTestSource(
src Alias,
pol *Policy,
@ -428,9 +428,10 @@ func resolveSSHTestSource(
}
// resolveSSHTestDestNodes maps each dst alias to its destination
// NodeViews. autogroup:self needs special handling: it cannot resolve
// without per-node context, so it walks the node set keyed on src's
// owning user. Other aliases resolve to an IPSet and match via InIPSet.
// [types.NodeView]s. autogroup:self needs special handling: it cannot
// resolve without per-node context, so it walks the node set keyed on
// src's owning user. Other aliases resolve to an [netipx.IPSet] and match
// via [types.NodeView.InIPSet].
func resolveSSHTestDestNodes(
dsts SSHTestDestinations,
pol *Policy,
@ -527,8 +528,8 @@ func resolveSSHTestDestNodes(
return out, emptyDsts, nil
}
// prefixesToIPSet builds the IPSet that InIPSet expects on the node
// side.
// prefixesToIPSet builds the [netipx.IPSet] that [types.NodeView.InIPSet]
// expects on the node side.
func prefixesToIPSet(prefixes []netip.Prefix) (*netipx.IPSet, error) {
var b netipx.IPSetBuilder
@ -539,9 +540,9 @@ func prefixesToIPSet(prefixes []netip.Prefix) (*netipx.IPSet, error) {
return b.IPSet()
}
// compiledSSHPolicy returns the per-node compiled SSH policy, caching
// compiledSSHPolicy returns the per-node compiled [tailcfg.SSHPolicy], caching
// on miss. baseURL is empty because reachability only checks for the
// presence of HoldAndDelegate, not its value.
// presence of [tailcfg.SSHAction.HoldAndDelegate], not its value.
func compiledSSHPolicy(
pol *Policy,
users []types.User,
@ -607,8 +608,8 @@ func reachability(
return acceptHit, checkHit
}
// principalContainsAddr reports whether any principal's NodeIP matches
// srcAddr exactly (the SSH compiler emits one principal per source IP).
// principalContainsAddr reports whether any principal's [tailcfg.SSHPrincipal.NodeIP]
// matches srcAddr exactly (the SSH compiler emits one principal per source IP).
func principalContainsAddr(
principals []*tailcfg.SSHPrincipal,
srcAddr netip.Addr,
@ -635,7 +636,7 @@ func principalContainsAddr(
return false
}
// sshUserMapAllows reports whether SSHUsers permits user. The SSHUsers
// sshUserMapAllows reports whether [SSHUsers] permits user. The [SSHUsers]
// wire shape (see filter.go compileSSHPolicy):
//
// - SSHUsers["root"] == "root" allows root; == "" disallows it.

View file

@ -4,7 +4,7 @@ package v2
// Tailscale-hosted control plane emits where headscale has no
// equivalent concept yet. The compat test in
// tailscale_nodeattrs_compat_test.go builds the self-view CapMap via
// [types.NodeView.TailNode] -- the same call the mapper makes -- and
// [types.Node.TailNode] -- the same call the mapper makes -- and
// strips these from BOTH sides before [cmp.Diff]; every other cap is
// compared in full as it lands on the wire.
//
@ -30,8 +30,9 @@ import (
// (suggest-exit-node, dns-subdomain-resolve — see
// ipn/ipnlocal/local.go:7534 and node_backend.go:745) are emitted only
// when the peer satisfies the cap's emission condition. This function
// encodes those conditions; the mapper calls it from buildTailPeers and
// the compat test calls it to compute the expected per-peer wire shape.
// encodes those conditions; the mapper calls it from
// [mapper.MapResponseBuilder.buildTailPeers] and the compat test calls
// it to compute the expected per-peer wire shape.
func PeerCapMap(peer types.NodeView, peerSelfCaps tailcfg.NodeCapMap) tailcfg.NodeCapMap {
if len(peerSelfCaps) == 0 {
return nil

View file

@ -27,7 +27,7 @@ import (
// errPolicyTestsFailed and errSSHPolicyTestsFailed share the
// "test(s) failed" prefix but stay distinct so callers can use
// errors.Is to tell ACL-test and SSH-test failures apart.
// [errors.Is] to tell ACL-test and SSH-test failures apart.
var (
errPolicyTestsFailed = errors.New("test(s) failed")
errSSHPolicyTestsFailed = errors.New("test(s) failed")
@ -55,7 +55,7 @@ type PolicyTest struct {
// SSHPolicyTest is one entry in the policy's `sshTests` block. The
// accept/deny/check arrays carry usernames, not destinations — every
// listed user is asserted against every entry in Dst.
// listed user is asserted against every entry in [SSHPolicyTest.Dst].
type SSHPolicyTest struct {
// Src is a single source alias (user, group, tag, host, or IP).
Src Alias `json:"src"`
@ -78,7 +78,7 @@ type SSHPolicyTest struct {
}
// SSHTestDestinations is the typed list of destination aliases an
// sshTests entry targets. validateSSHTestDestination enforces the
// sshTests entry targets. [validateSSHTestDestination] enforces the
// SSH-specific shape rules (no :port, no CIDR, no autogroup:internet,
// known tag).
type SSHTestDestinations []Alias
@ -100,7 +100,7 @@ func (d *SSHTestDestinations) UnmarshalJSON(b []byte) error {
}
// UnmarshalJSON parses each typed field. An empty src lands as a nil
// Alias so validation surfaces ErrSSHTestEmptySrc rather than a parser
// [Alias] so validation surfaces [ErrSSHTestEmptySrc] rather than a parser
// failure.
func (t *SSHPolicyTest) UnmarshalJSON(b []byte) error {
var raw struct {
@ -134,7 +134,7 @@ func (t *SSHPolicyTest) UnmarshalJSON(b []byte) error {
return nil
}
// PolicyTestResult is the outcome of a single PolicyTest.
// PolicyTestResult is the outcome of a single [PolicyTest].
type PolicyTestResult struct {
Src string `json:"src"`
Proto Protocol `json:"proto,omitempty"`
@ -216,7 +216,7 @@ func (pm *PolicyManager) RunTests() error {
}
// evaluateTests runs the `tests` block against a fresh compilation of pol.
// It is the user-write sandbox: the live PolicyManager state is left
// It is the user-write sandbox: the live [PolicyManager] state is left
// untouched, so a failing test rejects the write without side effects.
func evaluateTests(pol *Policy, users []types.User, nodes views.Slice[types.NodeView]) error {
if pol == nil || len(pol.Tests) == 0 {
@ -262,7 +262,7 @@ func runPolicyTests(pol *Policy, filter []tailcfg.FilterRule, users []types.User
return results
}
// runPolicyTest evaluates one PolicyTest.
// runPolicyTest evaluates one [PolicyTest].
func runPolicyTest(test PolicyTest, pol *Policy, filter []tailcfg.FilterRule, users []types.User, nodes views.Slice[types.NodeView]) PolicyTestResult {
res := PolicyTestResult{
Src: test.Src,
@ -322,8 +322,8 @@ func runPolicyTest(test PolicyTest, pol *Policy, filter []tailcfg.FilterRule, us
return res
}
// resolveTestSource resolves the Src alias of a PolicyTest into a slice of
// netip.Prefix. parseAlias + Alias.Resolve cover every alias type the rest
// resolveTestSource resolves the Src alias of a [PolicyTest] into a slice of
// [netip.Prefix]. [parseAlias] + [Alias.Resolve] cover every alias type the rest
// of the policy engine supports, so tests inherit alias semantics for free.
func resolveTestSource(src string, pol *Policy, users []types.User, nodes views.Slice[types.NodeView]) ([]netip.Prefix, error) {
alias, err := parseAlias(src)
@ -377,13 +377,13 @@ func evalReachability(srcPrefixes []netip.Prefix, dst string, proto Protocol, po
return true, nil
}
// parseDestinationAlias is a thin wrapper over AliasWithPorts.UnmarshalJSON
// parseDestinationAlias is a thin wrapper over [AliasWithPorts.UnmarshalJSON]
// so callers can hand it a bare `"host:port"` string without re-implementing
// the parse logic.
func parseDestinationAlias(dst string) (*AliasWithPorts, error) {
var awp AliasWithPorts
// AliasWithPorts.UnmarshalJSON expects a quoted JSON string, so wrap.
// [AliasWithPorts.UnmarshalJSON] expects a quoted JSON string, so wrap.
err := awp.UnmarshalJSON([]byte(`"` + dst + `"`))
if err != nil {
return nil, err
@ -425,9 +425,9 @@ func srcReachesDst(src netip.Prefix, dstPrefixes []netip.Prefix, ports []tailcfg
}
// ruleMatchesSource reports whether the rule's source list contains src.
// SrcIPs may be CIDR, single addresses, IP ranges (`a-b`), or `*`; we use
// util.ParseIPSet to cover all of those uniformly. Unparseable entries
// are skipped (the rule compiler emits well-formed strings, so this is
// [tailcfg.FilterRule.SrcIPs] may be CIDR, single addresses, IP ranges (`a-b`),
// or `*`; we use [util.ParseIPSet] to cover all of those uniformly. Unparseable
// entries are skipped (the rule compiler emits well-formed strings, so this is
// defence-in-depth, not error handling).
func ruleMatchesSource(rule tailcfg.FilterRule, src netip.Prefix) bool {
for _, raw := range rule.SrcIPs {
@ -445,9 +445,10 @@ func ruleMatchesSource(rule tailcfg.FilterRule, src netip.Prefix) bool {
}
// ruleMatchesProto reports whether the rule permits any of requestedProtos.
// An unset rule.IPProto means "any protocol" and matches everything.
// requestedProtos is the per-test protocol set: a single proto for an
// explicit test.Proto, or the default set when test.Proto is empty.
// An unset [tailcfg.FilterRule.IPProto] means "any protocol" and matches
// everything. requestedProtos is the per-test protocol set: a single proto
// for an explicit [PolicyTest.Proto], or the default set when
// [PolicyTest.Proto] is empty.
func ruleMatchesProto(rule tailcfg.FilterRule, requestedProtos []int) bool {
if len(rule.IPProto) == 0 {
return true
@ -479,7 +480,7 @@ func ruleAllowsAnyDest(rule tailcfg.FilterRule, dstPrefixes []netip.Prefix, port
return false
}
// destEntryMatchesPrefixes reports whether the rule's NetPortRange.IP
// destEntryMatchesPrefixes reports whether the rule's [tailcfg.NetPortRange.IP]
// (CIDR, single IP, IP range, or "*") covers any prefix in dstPrefixes.
func destEntryMatchesPrefixes(dp tailcfg.NetPortRange, dstPrefixes []netip.Prefix) bool {
set, err := util.ParseIPSet(dp.IP, nil)

View file

@ -31,8 +31,8 @@ var (
//
// Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types.
// Bracket stripping reduces both forms to bare "addr:port" or "addr/prefix:port",
// which the normal LastIndex(":") split handles correctly because port strings
// never contain colons.
// which the normal [strings.LastIndex] of ":" split handles correctly because
// port strings never contain colons.
func splitDestinationAndPort(input string) (string, string, error) {
// Handle RFC 3986 bracketed IPv6 (e.g. "[::1]:80" or "[fd7a::1]/128:80,443").
// Strip brackets after validation and fall through to normal parsing.
@ -82,7 +82,7 @@ func splitDestinationAndPort(input string) (string, string, error) {
return destination, port, nil
}
// parsePortRange parses a port definition string and returns a slice of PortRange structs.
// parsePortRange parses a port definition string and returns a slice of [tailcfg.PortRange] structs.
func parsePortRange(portDef string) ([]tailcfg.PortRange, error) {
if portDef == "*" {
return []tailcfg.PortRange{tailcfg.PortRangeAny}, nil