sslocal keeps crashing
Describe the bug
Keeps crashing after a while on Android 16. @zonyitoo Any ideas?
Crash dump: https://pastesio.com/tokay16-crashlog
Configuration
Put an x inside the [ ] that applies.
- [x] IPv4 server address
- [ ] IPv6 server address
- [x] Client IPv4 availability
- [ ] Client IPv6 availability
- Encrypt method:
- Route
- [ ] All
- [ ] Bypass LAN
- [ ] Bypass China
- [ ] Bypass LAN & China
- [ ] GFW List
- [ ] China List
- [ ] Custom rules
- [ ] IPv6 route
- [ ] Apps VPN mode
- [ ] Bypass mode
- Remote DNS: 8.8.8.8
- [ ] DNS over UDP
- Plugin configuration (if applicable): no plugin
- [ ] Auto Connect
- [ ] TCP Fast Open
- If you're not using VPN mode, please supply more details here:
Additional context Add any other context about the problem here.
From Gemini: After reviewing the code in conjunction with the original crash log, I can identify the specific location and the nature of the bug.
The Bug is Here:
The bug is a subtle but critical issue in the Drop implementation for UdpAssociation:
impl<W> Drop for UdpAssociation<W>
where
W: UdpInboundWrite + Send + Sync + Unpin + 'static,
{
fn drop(&mut self) {
self.assoc_handle.abort();
}
}
Explanation: Why This is a Bug
-
What
dropmeans: When anUdpAssociationis removed from theLruCache(because it expired), itsdropmethod is called. This is the cleanup crew coming in. -
What
.abort()does: The lineself.assoc_handle.abort()sends a cancellation request to the asynchronous task that is running thedispatch_packetloop insideUdpAssociationContext. This is a very abrupt and immediate termination. The task will be cancelled at the next.awaitpoint it hits, regardless of what it's doing. -
The Race Condition: The
dispatch_packetloop is a complex state machine that juggles multiple asynchronous operations:receiver.recv()receive_from_bypassed_opt()receive_from_proxied_opt()keepalive_interval.tick()- Multiple
.awaitpoints insidedispatch_received_packet()
When
.abort()is called from another thread (the one runningcleanup_expired), thedispatch_packettask can be terminated at any one of these.awaitpoints. -
The Crash Scenario: The original backtrace showed a
panichappening inside thedropofUdpAssociationContext. This is the key. TheUdpAssociationContextis the state that the aborted task owns. When the task is aborted, Rust still tries to clean up its owned data by droppingUdpAssociationContext.However, because the task was cancelled at an arbitrary point, the
UdpAssociationContextcan be in an inconsistent state. For example:- It might be holding a lock that never gets released.
- It might have partially sent data to a channel.
- It could be in the middle of re-establishing a socket.
The most likely culprit is a resource that is being used by another part of the system when the
dropoccurs. When thedropimplementation forUdpAssociationContextruns, it might try to perform an operation that is no longer valid because the task was killed mid-stride, leading to the panic.The provided
Dropimplementation forUdpAssociationContextis simple:impl<W> Drop for UdpAssociationContext<W> where W: UdpInboundWrite + Send + Sync + Unpin + 'static, { fn drop(&mut self) { debug!("udp association for {} is closed", self.peer_addr); } }While this specific
dropimplementation itself doesn't do much that could panic, the resources it owns (likebypassed_ipv4_socket,proxied_socket, and thekeepalive_txchannel sender) will also be dropped. If one of these resources is in a bad state due to the.abort(), its owndropimplementation could be the source of the panic.
How to Fix It
The standard way to fix this in Tokio is to avoid .abort() for graceful shutdown. Instead, you should use a cooperative cancellation mechanism.
- Introduce a "Shutdown" Channel: Add a channel (like a
tokio::sync::watchortokio::sync::oneshotchannel) to theUdpAssociationContext. - Modify the
DropImplementation: Thedropimplementation ofUdpAssociationshould send a "shutdown" signal on this new channel. It should not call.abort(). - Modify the
dispatch_packetLoop: Thetokio::select!loop indispatch_packetshould be modified to listen for a message on the shutdown channel. - Graceful Exit: When the shutdown signal is received, the loop should
breakcleanly. This allows the task to finish its current operation, clean up its state properly, and exit gracefully.
This cooperative approach ensures that the UdpAssociationContext is always in a consistent state when it is eventually dropped, preventing the "panic during drop" that is causing your application to crash.
#08 pc 000000000119ed70 /data/app/~~YNtuU6BX-4Di9oI8Qa2KgQ==/com.github.shadowsocks-y0WpsbERN4j9350M4QW-lg==/lib/arm64/libsslocal.so (core::panicking::panic_in_cleanup::h38ce2cd36b69578e+20)
#09 pc 0000000000645304 /data/app/~~YNtuU6BX-4Di9oI8Qa2KgQ==/com.github.shadowsocks-y0WpsbERN4j9350M4QW-lg==/lib/arm64/libsslocal.so (core::ptr::drop_in_place$LT$shadowsocks_service..local..net..udp..association..UdpAssociationContext$LT$shadowsocks_service..local..socks..server..socks5..udprelay..Socks5UdpInboundWriter$GT$$GT$::hc22e9602ebb94b88+408)
The Socks5UdpInboundWriter somehow triggered a panic.
My phone updated to Android 16 today. And sslocal doesn't crash. Is this still reproducible with latest android updates?