mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
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.
240 lines
6.6 KiB
Go
240 lines
6.6 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"strconv"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
|
"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
|
|
}
|
|
e.setFileDropTunnel()
|
|
e.fileDrop.SetReceivingChangeHandler(e.onFileDropPolicyChange)
|
|
|
|
if e.config.BlockInbound {
|
|
log.Info("file drop receiver is disabled because inbound connections are blocked")
|
|
return
|
|
}
|
|
if !e.fileDrop.ReceivingEnabled() {
|
|
log.Info("file drop receiver is not started because receiving is turned off")
|
|
return
|
|
}
|
|
|
|
wgAddr := e.wgInterface.Address()
|
|
addr := netip.AddrPortFrom(wgAddr.IP, fileDropListenPort())
|
|
resolver := filedropResolver{status: e.statusRecorder}
|
|
|
|
netstackNet := e.wgInterface.GetNet()
|
|
if err := e.fileDrop.StartReceiver(e.ctx, addr, netstackNet, resolver, fileDropListenControl(e.wgInterface)); err != nil {
|
|
log.Errorf("failed to start file drop receiver: %v", err)
|
|
return
|
|
}
|
|
|
|
bound := e.fileDrop.ReceiverPort()
|
|
if bound == 0 {
|
|
bound = addr.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)
|
|
}
|
|
}
|
|
|
|
e.setupFileDropPortRedirection(bound)
|
|
|
|
e.fileDropRunning = true
|
|
}
|
|
|
|
// setupFileDropPortRedirection keeps the tunnel-side port fixed when the receiver
|
|
// could not bind it, so senders always reach the well-known port.
|
|
func (e *Engine) setupFileDropPortRedirection(bound uint16) {
|
|
if e.firewall == nil || bound == filedrop.Port {
|
|
return
|
|
}
|
|
|
|
for _, addr := range e.fileDropLocalAddrs() {
|
|
if err := e.firewall.AddInboundDNAT(addr, firewallManager.ProtocolTCP, filedrop.Port, bound); err != nil {
|
|
log.Warnf("failed to add file drop port redirection on %s: %v", addr, err)
|
|
continue
|
|
}
|
|
log.Infof("file drop port redirection enabled: %s:%d -> %s:%d", addr, filedrop.Port, addr, bound)
|
|
}
|
|
}
|
|
|
|
func (e *Engine) removeFileDropPortRedirection(bound uint16) {
|
|
if e.firewall == nil || bound == 0 || bound == filedrop.Port {
|
|
return
|
|
}
|
|
|
|
for _, addr := range e.fileDropLocalAddrs() {
|
|
if err := e.firewall.RemoveInboundDNAT(addr, firewallManager.ProtocolTCP, filedrop.Port, bound); err != nil {
|
|
log.Warnf("failed to remove file drop port redirection on %s: %v", addr, err)
|
|
continue
|
|
}
|
|
log.Debugf("file drop port redirection removed: %s:%d -> %s:%d", addr, filedrop.Port, addr, bound)
|
|
}
|
|
}
|
|
|
|
func (e *Engine) fileDropLocalAddrs() []netip.Addr {
|
|
if e.wgInterface == nil {
|
|
return nil
|
|
}
|
|
|
|
wgAddr := e.wgInterface.Address()
|
|
var addrs []netip.Addr
|
|
if wgAddr.IP.IsValid() {
|
|
addrs = append(addrs, wgAddr.IP)
|
|
}
|
|
if wgAddr.IPv6.IsValid() {
|
|
addrs = append(addrs, wgAddr.IPv6)
|
|
}
|
|
return addrs
|
|
}
|
|
|
|
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 {
|
|
dial = fileDropOSDial(e.wgInterface)
|
|
}
|
|
|
|
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.
|
|
// No-op when it is not running. See Engine.rebindOverlayListeners.
|
|
func (e *Engine) restartFileDrop() error {
|
|
if e.fileDrop == nil || !e.fileDropRunning || e.wgInterface == nil {
|
|
return nil
|
|
}
|
|
|
|
e.stopFileDrop()
|
|
e.startFileDrop()
|
|
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 {
|
|
e.unbindFileDropReceiver()
|
|
}
|
|
|
|
if err := e.fileDrop.StopReceiver(); err != nil {
|
|
log.Warnf("failed to stop file drop receiver: %v", err)
|
|
}
|
|
e.fileDropRunning = false
|
|
e.fileDropPort = 0
|
|
}
|
|
|
|
// fileDropListenPort is the port the receiver tries to bind locally; the
|
|
// tunnel-side port stays filedrop.Port whatever this resolves to.
|
|
func fileDropListenPort() uint16 {
|
|
raw := os.Getenv(filedrop.EnvPort)
|
|
if raw == "" {
|
|
return filedrop.Port
|
|
}
|
|
|
|
port, err := strconv.ParseUint(raw, 10, 16)
|
|
if err != nil {
|
|
log.Warnf("invalid %s value %q, using %d", filedrop.EnvPort, raw, filedrop.Port)
|
|
return filedrop.Port
|
|
}
|
|
return uint16(port)
|
|
}
|