[client] Bind the file drop receiver only while receiving is enabled

The receiver was bound on every engine start regardless of the profile's
receiving mode, and a failed bind also left the manager without a tunnel
dialer, which took sending down with it.

Set the tunnel dialer before the bind so sending no longer depends on it,
and gate the bind on the receiving mode. A mode change now notifies the
engine, which binds or unbinds accordingly: turning receiving off releases
the listeners and the offer store, empties the spool and settles unfinished
incoming transfers as cancelled.

Also log file drop send and receive failures with the peer and the step that
broke, so a failure can be read from the client log alone.
This commit is contained in:
Zoltan Papp
2026-09-05 19:58:08 +02:00
parent 895a1dde57
commit ec0659cbf0
5 changed files with 183 additions and 26 deletions
+57 -10
View File
@@ -32,9 +32,15 @@ func (e *Engine) startFileDrop() {
if e.fileDrop == nil || e.fileDropRunning || e.wgInterface == nil {
return
}
e.setFileDropTunnel()
e.fileDrop.SetReceivingChangeHandler(e.onFileDropPolicyChange)
if e.config.BlockInbound {
log.Info("file drop receiver is disabled because inbound connections are blocked")
e.setFileDropTunnel()
return
}
if !e.fileDrop.ReceivingEnabled() {
log.Info("file drop receiver is not started because receiving is turned off")
return
}
@@ -70,7 +76,6 @@ func (e *Engine) startFileDrop() {
e.setupFileDropPortRedirection(bound)
e.setFileDropTunnel()
e.fileDropRunning = true
}
@@ -152,20 +157,62 @@ func (e *Engine) restartFileDrop() error {
return nil
}
// onFileDropPolicyChange binds or unbinds the receiver after the profile turned
// receiving on or off. The work is handed to a goroutine because it needs
// syncMsgMux, which the caller (a settings RPC) must not wait on and which the
// engine may itself hold while calling into the manager.
func (e *Engine) onFileDropPolicyChange() {
if e.ctx.Err() != nil {
return
}
e.shutdownWg.Add(1)
go func() {
defer e.shutdownWg.Done()
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
if e.fileDrop == nil || e.ctx.Err() != nil {
return
}
if e.fileDrop.ReceivingEnabled() {
e.startFileDrop()
return
}
if e.fileDropRunning {
e.unbindFileDropReceiver()
if err := e.fileDrop.DisableReceiving(); err != nil {
log.Warnf("failed to stop file drop receiver: %v", err)
}
e.fileDropRunning = false
e.fileDropPort = 0
}
}()
}
// unbindFileDropReceiver releases what the bind claimed outside the receiver
// itself. The caller must hold syncMsgMux.
func (e *Engine) unbindFileDropReceiver() {
if netstackNet := e.wgInterface.GetNet(); netstackNet != nil {
if registrar, ok := e.firewall.(interface {
UnregisterNetstackService(protocol nftypes.Protocol, port uint16)
}); ok {
registrar.UnregisterNetstackService(nftypes.TCP, e.fileDropPort)
}
}
e.removeFileDropPortRedirection(e.fileDropPort)
}
func (e *Engine) stopFileDrop() {
if e.fileDrop == nil {
return
}
e.fileDrop.SetReceivingChangeHandler(nil)
e.fileDrop.ClearTunnel()
if e.fileDropRunning {
if netstackNet := e.wgInterface.GetNet(); netstackNet != nil {
if registrar, ok := e.firewall.(interface {
UnregisterNetstackService(protocol nftypes.Protocol, port uint16)
}); ok {
registrar.UnregisterNetstackService(nftypes.TCP, e.fileDropPort)
}
}
e.removeFileDropPortRedirection(e.fileDropPort)
e.unbindFileDropReceiver()
}
if err := e.fileDrop.StopReceiver(); err != nil {