Skip to content

feature/conn25: expire connector state - #20841

Open
franbull wants to merge 2 commits into
mainfrom
fran/conn25-connector-state-expiry
Open

feature/conn25: expire connector state#20841
franbull wants to merge 2 commits into
mainfrom
fran/conn25-connector-state-expiry

Conversation

@franbull

Copy link
Copy Markdown
Contributor

The connector struct holds a map of peer+transitIP -> destinationIP that it uses for routing traffic. The client registers new entries in the map over the peer API.

Stop the transitIPs map from growing indefinitely by expiring entries after 1 hour.

Updates tailscale/corp#38261

@franbull
franbull marked this pull request as ready for review August 12, 2026 17:25
The connector struct holds a map of peer+transitIP -> destinationIP that
it uses for routing traffic. The client registers new entries in the map
over the peer API.

Stop the transitIPs map from growing indefinitely by expiring entries
after 1 hour.

Updates tailscale/corp#38261

Signed-off-by: Fran Bull <fran@tailscale.com>
@franbull
franbull force-pushed the fran/conn25-connector-state-expiry branch from 12fc34d to 1ef6a84 Compare August 12, 2026 17:31
@franbull
franbull requested a review from mzbenami August 12, 2026 19:21

@mzbenami mzbenami left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The important thing to me is lowering the maximum per sweep and storing the value in a constant.

I'd also like to not use list.List.Init() in the reset().

The others are typos or explicitly marked as optional.

Comment thread feature/conn25/conn25.go
// handle at most 100000 entries, don't just keep going if we have an
// unexpectedly large number of expiries, give up the lock and expect we will
// handle the backlog over time.
for i := 0; i < 100000; i++ {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

c.mu is technically on the packet path, because the per-packet filter checks packetFilterAllow. Claude did some benchmarking and a full sweep added 20ms, while a limit of 1000 added 76 microseconds.

I'd prefer starting at 1000, which matches the flowtable (other place a sweeper is used).

DefaultMaxRemovedFlowsPerSweep = 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I also think this should be a named constant, not only for style consistency, but also because it can then be referenced from tests, and the tests are self-updating if/when the value changes.

}
c.connector.mu.Lock()
if c.connector.expiryQueue.Len() != 3 {
t.Fatalf("expected 2 items remaining in queue")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s/2/3


c.connector.mu.Lock()
if _, ok := c.connector.transitIPs[peerA][tipOne]; ok {
t.Fatalf("expected oldTip %v to be removed from the map", tipOne)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s/oldTip/tipOne

t.Fatalf("expected oldTip %v to be removed from the map", tipOne)
}
if _, ok := c.connector.transitIPs[peerA][tipTwo]; !ok {
t.Fatalf("expected freshTip %v to remain in the map", tipTwo)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

s/freshTip/tipTwo

return netip.AddrFrom4(buf)
}
var i uint32
for i = 0; i < 100003; i++ {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

making this a named constant, e.g. maxPerSweep and referencing it here, e.g. maxPerSweep+3, would make tests self-updating when the value changes.

Comment thread feature/conn25/conn25.go
defer c.mu.Unlock()

c.transitIPs = make(map[netip.Addr]map[netip.Addr]appAddr)
c.expiryQueue.Init()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude told me that making this c.expiryQueue = list.New() would be provide a little extra safety here if somehow a list.Element survives the reset. If code tries to remove the old element on a re-initialized list, funky things can happen like like the list having a length of -1.

You can see the relevant code here:
https://github.com/golang/go/blob/master/src/container/list/list.go#L134-L141

Comment thread feature/conn25/conn25.go
type transitIPExpiryEntry struct {
peerIP netip.Addr
transitIP netip.Addr
createdAt time.Time

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

optional: mono.Time provides the same use cases at a smaller memory footprint. It's what we use on the flowtable side. We could wait until we observe some actual memory problems before making the change, or we can just do it now while we're here.

Comment thread feature/conn25/conn25.go
return v, ok
}

// expireTransitIPs expires entries in the connectors transitIPs map that are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"connector's"

Comment thread feature/conn25/conn25.go
c.mu.Lock()
defer c.mu.Unlock()
removed := 0
// handle at most 100000 entries, don't just keep going if we have an

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

double space between "keep" and "going"

Comment thread feature/conn25/conn25.go
}
e := front.Value.(*transitIPExpiryEntry)
if now.Sub(e.createdAt) < connectorTransitIPExpiry {
// the list is ordered by createdAt there will be no entries to expire after this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

punctuation between "createdAt" and "there will be" would be helpful for readability.

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