Files
netbird/client/android/client_filedrop.go
Zoltán Papp 73cffdb702 Add peer-to-peer file drop
Files move directly between peers over the overlay, with no server in the
path. The receiver listens on the WireGuard address only, so the port is
unreachable from outside the tunnel, and every offer is matched to a known
peer before anything is read.

Consent is the default: an offer carries metadata alone, and no payload
moves until the receiver accepts. Policy is per profile and device-local —
off, ask, or auto-accept, with per-sender exceptions on top.

Policy and history live in the profile's preferences, so removing a profile
takes its file drop state with it. Transfers interrupted by a restart are
settled on load; nothing survives to finish them, and left alone they would
sit in the log as permanently pending.

The Android bindings pull payload bytes through a chunk-returning stream:
gomobile copies a []byte argument into a fresh Java array and never copies
it back, so a fill-my-buffer method would hand back the right length with
no data.
2026-08-16 22:04:38 +02:00

79 lines
2.2 KiB
Go

//go:build android
package android
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal"
)
// FileDrop returns the handle of the active profile, creating it on first use.
// The UI calls this to list transfers and change settings while disconnected.
func (c *Client) FileDrop(configDir string) (*FileDrop, error) {
profile, err := NewProfileManager(configDir).GetActiveProfile()
if err != nil {
return nil, fmt.Errorf("get active profile: %w", err)
}
return c.fileDropFor(configDir, profile.ID)
}
// fileDropFor returns the handle of one profile, replacing the cached one when
// the profile changed. The listener is carried over so a profile switch does not
// silence the UI.
func (c *Client) fileDropFor(configDir, profileID string) (*FileDrop, error) {
c.fileDropMu.Lock()
if c.fileDrop != nil && c.fileDrop.ProfileID() == profileID {
fd := c.fileDrop
c.fileDropMu.Unlock()
return fd, nil
}
fd, err := NewFileDrop(configDir, profileID)
if err != nil {
c.fileDropMu.Unlock()
return nil, err
}
ensureFileDropDestination(fd)
old := c.fileDrop
if old != nil {
fd.SetListener(old.Listener())
}
c.fileDrop = fd
c.fileDropMu.Unlock()
// Closing waits out the in-flight uploads of the profile being left, which is
// far too long to hold the lock every caller of this goes through.
if old != nil {
if err := old.Close(); err != nil {
log.Warnf("failed to close previous file drop manager: %v", err)
}
}
return fd, nil
}
// attachFileDrop hands the connect client the file drop manager of the profile
// the engine is starting for. The profile is derived from the config path rather
// than read from the active profile state, so a switch racing the startup cannot
// pair one profile's engine with another's transfers. A failure is not fatal:
// the tunnel is worth more than the feature, so the engine runs on without it.
func (c *Client) attachFileDrop(cc *internal.ConnectClient, cfgFile string) {
configDir, profileID, err := profileLocationFor(cfgFile)
if err != nil {
log.Warnf("file drop is unavailable: %v", err)
return
}
fd, err := c.fileDropFor(configDir, profileID)
if err != nil {
log.Warnf("file drop is unavailable: %v", err)
return
}
cc.SetFileDropManager(fd.manager)
}