Skip to content

tagpeers, lansport, rendezvous: add LAN peering among tagged tailnet nodes - #1

Merged
bradfitz merged 1 commit into
mainfrom
bradfitz/macshare
Sep 26, 2026
Merged

bradfitz merged 1 commit into
mainfrom
bradfitz/macshare

Conversation

@bradfitz

Copy link
Copy Markdown
Member

These are the reusable layers under gocached's upcoming cache pooling for
the colo Macs, moved here so anything with the same shape can use them:
a set of tailnet nodes on one LAN that want to exchange bulk traffic
directly, without pushing every byte through magicsock/wireguard, while
still getting their trust from the tailnet.

tagpeers watches tailscaled's IPN bus and keeps the set of nodes
carrying a tag, as the control plane sees it; no
reachability analysis.
lansport turns those nodes into working connections: each process
serves a plain-HTTP advert over the tailnet naming its LAN
TLS address and per-process certificate hash, fetches its
peers' adverts, pins their certificates, and maintains a
pinned *http.Transport to each reachable peer. Requests
from peers arrive on a TLS listener that admits only
pinned certificates.
rendezvous weighted rendezvous hashing over a changing member set,
and a router that picks which lansport peer, if any,
should handle a key.

tagpeerstest has a fake tailscaled serving the IPN bus watch endpoint
for testing code built on these.

Updates tailscale/corp#48514

@bradfitz
bradfitz requested a review from a team September 24, 2026 22:05

@tomhjp tomhjp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the rendevous package reviewed so far. I'll probably split my review for the other 2 packages too to break it into manageable chunks.

Comment thread rendezvous/rendezvous.go Outdated
}

type member struct {
key string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny type for key? Reading this file, I'm getting a bit confused between member and key, because I think this key is referenced as a member in Score, and key is another thing altogether in that function signature?

Comment thread rendezvous/rendezvous.go Outdated
Comment on lines +66 to +67
// the given name and weight: -weight / ln(u) for u uniform in (0, 1) derived
// from hashing member and key together. The member with the highest score

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the exact implementation doesn't mean much to the casual reader, and the non-casual reader can read the code. Maybe here or elsewhere we should add a reference for details of where the equations came from though?

Suggested change
// the given name and weight: -weight / ln(u) for u uniform in (0, 1) derived
// from hashing member and key together. The member with the highest score
// the given name and weight. The member with the highest score

Comment thread rendezvous/router.go Outdated
Comment on lines +85 to +86
r.table.mu.RLock()
defer r.table.mu.RUnlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should reach in and use table's mutex here - let's make a method for it

Comment thread rendezvous/router.go Outdated
Comment on lines +65 to +66
r.table.Set(members)
r.peers.Store(&peers)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks racy, and maybe it should be protected by a Router mutex?

@tomhjp tomhjp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tagpeers LGTM, just nits

Comment thread tagpeers/tracker.go Outdated
Comment on lines +441 to +444
t.mu.Unlock()
if changed {
t.signal()
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer this chunk higher up? The big gap after lock makes it kinda vulnerable to future bugs or panics

Comment thread tagpeers/tracker.go Outdated
Comment on lines +384 to +387
t.mu.Unlock()
if changed {
t.signal()
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as below

Comment thread tagpeers/tracker.go Outdated
type Tracker struct {
cfg Config
lc *local.Client
logf logger.Logf

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// never nil ?

Comment thread tagpeers/tracker.go
cancel context.CancelFunc
done chan struct{}

mu sync.Mutex

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this never composes with the subsMu lock, maybe document that?

Comment thread lansport/lansport_test.go Outdated
"github.com/tailscale/tb/tagpeers/tagpeerstest"
)

func waitFor(t testing.TB, what string, f func() bool) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked in detail, but does this exist because of needing to use real network? Go 1.27 had this nifty looking release note I've been meaning to check out, which might be relevant:

The new NewTestServer function creates a Server configured to use an in-memory fake network suitable for use with the testing/synctest package.

Comment thread lansport/source.go Outdated
TrustedAddr(ip netip.Addr) bool

// SelfKey returns this node's own routing key, if known.
SelfKey() (string, bool)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny type or more documentation on key?

Comment thread lansport/lansport.go Outdated

// probe fetches p's advert over the source's trusted path, rebuilds its
// transport if the pin or address changed, and checks the LAN path.
func (s *Server) probe(ctx context.Context, p *peerState) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function signature could be clearer if it took a value peerState and returned another value with all the mutations it made. As it is, it's not clear whose state we're mutating when we update this pointer in-place in the function, and it looks like it complicates the locking semantics too.

…nodes

These are the reusable layers under gocached's upcoming cache pooling for
the colo Macs, moved here so anything with the same shape can use them:
a set of tailnet nodes on one LAN that want to exchange bulk traffic
directly, without pushing every byte through magicsock/wireguard, while
still getting their trust from the tailnet.

  tagpeers    watches tailscaled's IPN bus and keeps the set of nodes
              carrying a tag, as the control plane sees it; no
              reachability analysis.
  lansport    turns those nodes into working connections: each process
              serves a plain-HTTP advert over the tailnet naming its LAN
              TLS address and per-process certificate hash, fetches its
              peers' adverts, pins their certificates, and maintains a
              pinned *http.Transport to each reachable peer. Requests
              from peers arrive on a TLS listener that admits only
              pinned certificates.
  rendezvous  weighted rendezvous hashing over a changing member set,
              and a router that picks which lansport peer, if any,
              should handle a key.

tagpeerstest has a fake tailscaled serving the IPN bus watch endpoint
for testing code built on these.

Updates tailscale/corp#48514

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I7b2f9c41d8e3a65f0c2d4b8e1a9f3c7d5e6b0a12
@bradfitz

Copy link
Copy Markdown
Member Author

All done.

@bradfitz
bradfitz merged commit 3e358bb into main Sep 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants