Skip to content

fix(apple): Only use connlib sessions that are connected#8104

Merged
jamilbk merged 3 commits into
mainfrom
fix/setDns
Feb 12, 2025
Merged

fix(apple): Only use connlib sessions that are connected#8104
jamilbk merged 3 commits into
mainfrom
fix/setDns

Conversation

@jamilbk
Copy link
Copy Markdown
Member

@jamilbk jamilbk commented Feb 11, 2025

In the window of time between we check AdapterState == .tunnelStarted and we call setDns in the Apple pathUpdateHandler, it's possible that connlib disconnected. This window of time could potentially be non-trivial since we read system resolvers in there, which hits the disk.

As such, we should always check the session pointer is valid just before use.

The AdapterState enum tracks two states: tunnelStopped and tunnelStarted. In the tunnelStarted state, we populate a WrappedSession object. This is redundant - connlib is either connected and we have a WrappedSession, or it is not. Therefore we can remove the AdapterState abstraction completely (which was leftover from a previous developer) and directly use a WrappedSession? object to issue calls to connlib with.

We set this to a valid WrappedSession upon connecting, and back to nil as soon as connlib either onDisconnects us, or the user disconnects the tunnel.

Lastly, we avoid early-returning from queued workItems because we now call connlib with session? which will no-op if there is no session, allowing whatever IPC call running at the time (such as fetchResources) to complete successfully, even though they'll see a "snapshotted" state of the Adapter/PacketTunnelProvider. In other words, we no longer enforce the session pointer to be valid for things that don't depend on its state.

Fixes #7882

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
firezone ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 12, 2025 7:11pm

@sentry
Copy link
Copy Markdown

sentry Bot commented Feb 11, 2025

Sentry Issue: APPLE-CLIENT-2A

@sentry
Copy link
Copy Markdown

sentry Bot commented Feb 11, 2025

Sentry Issue: APPLE-CLIENT-28

@sentry
Copy link
Copy Markdown

sentry Bot commented Feb 11, 2025

Sentry Issue: APPLE-CLIENT-27

@sentry
Copy link
Copy Markdown

sentry Bot commented Feb 11, 2025

Sentry Issue: APPLE-CLIENT-1N

@jamilbk
Copy link
Copy Markdown
Member Author

jamilbk commented Feb 11, 2025

Using the session pointer in this mimics the pattern we have in Android's TunnelService.

}
session?.disconnect()

session = nil
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 believe this is the important fix. When we call onDisconnect, the pointer to the session is still valid. It is when you call disconnect that we free the memory.

Arguably, this is a shortcoming of swift-bridge I think where it doesn't model correctly, that we capture self entirely in the disconnect call.

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.

Comment on lines +396 to +397
// Immediately invalidate our session pointer to prevent workQueue items from trying to use it
session = nil
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 to be sure we don't leak memory, can you insert a call to disconnect here? Otherwise I think you'd have to free the memory.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, is that the case? So connlib calls our on_disconnect and then expects us to issue a final disconnect() afterward?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We'll also need to update the Android client if so

Copy link
Copy Markdown
Member

@thomaseizinger thomaseizinger Feb 12, 2025

Choose a reason for hiding this comment

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

Oh, is that the case? So connlib calls our on_disconnect and then expects us to issue a final disconnect() afterward?

I didn't think about it until now to be honest. However, I can't see how Swift would be able to correctly free the memory and we can't free it from within the callback. From Swift's perspective, all it has is a pointer and it is discarding it on the call to nil so I believe this is a memory leak.

It is only in disconnect that we take full ownership of self and therefore Rust will drop it at the end of the function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍

@jamilbk jamilbk enabled auto-merge February 12, 2025 19:20
@jamilbk jamilbk added this pull request to the merge queue Feb 12, 2025
Merged via the queue into main with commit cf1b74c Feb 12, 2025
@jamilbk jamilbk deleted the fix/setDns branch February 12, 2025 19:49
github-merge-queue Bot pushed a commit that referenced this pull request Feb 12, 2025
We need to call `disconnect()` in `onDisconnect` to free the memory
associated with the connlib session.

Related: #8104
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.

EXC_BAD_ACCESS: Exception 1, Code 1, Subcode 456 >

2 participants