Files
netbird/client/internal/engine_filedrop.go
Zoltan Papp faf8619a83 [client] Rebind the file drop receiver when the tun is renewed
Android re-establishes the VpnService interface on every route change, which
replaces tun0 with a fresh device. The file drop listeners are bound to the
overlay address of the interface being swapped out: the IPv4 one dies with
accept4: invalid argument and never comes back, so a peer dialing the overlay
IPv4 address gets an RST. Restart the receiver once the new device is in place.
2026-08-17 00:23:09 +02:00

144 lines
3.7 KiB
Go

package internal
import (
"context"
"net"
"net/netip"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/filedrop"
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
"github.com/netbirdio/netbird/client/internal/peer"
)
type filedropResolver struct {
status *peer.Status
}
// ResolvePeer implements filedrop.PeerResolver.
func (r filedropResolver) ResolvePeer(addr netip.Addr) (filedrop.PeerKey, string, bool) {
state, ok := r.status.PeerStateByIP(addr.String())
if !ok {
return "", "", false
}
return filedrop.PeerKey(state.PubKey), state.FQDN, true
}
func (e *Engine) startFileDrop() {
if e.fileDrop == nil || e.fileDropRunning || e.wgInterface == nil {
return
}
if e.config.BlockInbound {
log.Info("file drop receiver is disabled because inbound connections are blocked")
e.setFileDropTunnel()
return
}
wgAddr := e.wgInterface.Address()
addr := netip.AddrPortFrom(wgAddr.IP, filedrop.Port)
resolver := filedropResolver{status: e.statusRecorder}
netstackNet := e.wgInterface.GetNet()
if err := e.fileDrop.StartReceiver(e.ctx, addr, netstackNet, resolver); err != nil {
log.Errorf("failed to start file drop receiver: %v", err)
return
}
bound := e.fileDrop.ReceiverPort()
if bound == 0 {
bound = filedrop.Port
}
e.fileDropPort = bound
if v6 := wgAddr.IPv6; v6.IsValid() {
if err := e.fileDrop.AddReceiverListener(e.ctx, netip.AddrPortFrom(v6, bound)); err != nil {
log.Warnf("failed to add IPv6 file drop listener: %v", err)
}
}
if netstackNet != nil {
if registrar, ok := e.firewall.(interface {
RegisterNetstackService(protocol nftypes.Protocol, port uint16)
}); ok {
registrar.RegisterNetstackService(nftypes.TCP, bound)
}
}
if bound != filedrop.Port {
e.signaler.SetFiledropPort(bound)
}
e.setFileDropTunnel()
e.fileDropRunning = true
}
// recordFiledropPort stores the file drop port a peer advertised over signaling;
// a value that does not fit a port is treated as the default.
func (e *Engine) recordFiledropPort(peerKey string, port uint32) {
if e.fileDrop == nil {
return
}
if port > 65535 {
port = 0
}
e.fileDrop.Ports().Set(filedrop.PeerKey(peerKey), uint16(port))
}
func (e *Engine) setFileDropTunnel() {
var dial filedrop.DialFunc
if netstackNet := e.wgInterface.GetNet(); netstackNet != nil {
dial = func(ctx context.Context, _, addr string) (net.Conn, error) {
addrPort, err := netip.ParseAddrPort(addr)
if err != nil {
return nil, err
}
return netstackNet.DialContextTCPAddrPort(ctx, addrPort)
}
} else {
dialer := &net.Dialer{}
dial = dialer.DialContext
}
e.fileDrop.SetTunnel(dial, e.statusRecorder.GetLocalPeerState().FQDN)
}
// restartFileDrop rebinds the receiver after the platform replaced the tunnel
// device. The listeners are bound to the overlay address of the interface being
// swapped out and do not survive it: Android renews the tun on every route
// change, which leaves the IPv4 listener dead with accept4: invalid argument.
func (e *Engine) restartFileDrop() {
e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock()
if e.fileDrop == nil || !e.fileDropRunning || e.wgInterface == nil {
return
}
e.stopFileDrop()
e.startFileDrop()
}
func (e *Engine) stopFileDrop() {
if e.fileDrop == nil {
return
}
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.signaler.SetFiledropPort(0)
}
if err := e.fileDrop.StopReceiver(); err != nil {
log.Warnf("failed to stop file drop receiver: %v", err)
}
e.fileDropRunning = false
e.fileDropPort = 0
}