mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-27 23:09:08 +01:00
[client] Scope the iOS file drop listener to the tunnel interface
The receiver's host listener is a socket of the Network Extension, so the SYN-ACK of an accepted connection followed the extension's own-traffic bypass onto the physical interface and the sender never got an answer. Bind the listeners to the tunnel interface index the same way the dial already is; accepted sockets inherit the scope. Other platforms pass a nil control and keep the current behavior.
This commit is contained in:
@@ -43,7 +43,7 @@ func (e *Engine) startFileDrop() {
|
|||||||
resolver := filedropResolver{status: e.statusRecorder}
|
resolver := filedropResolver{status: e.statusRecorder}
|
||||||
|
|
||||||
netstackNet := e.wgInterface.GetNet()
|
netstackNet := e.wgInterface.GetNet()
|
||||||
if err := e.fileDrop.StartReceiver(e.ctx, addr, netstackNet, resolver); err != nil {
|
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)
|
log.Errorf("failed to start file drop receiver: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,7 @@ func fileDropOSDial(WGIface) filedrop.DialFunc {
|
|||||||
dialer := &net.Dialer{}
|
dialer := &net.Dialer{}
|
||||||
return dialer.DialContext
|
return dialer.DialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fileDropListenControl(WGIface) filedrop.ListenControl {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -14,6 +15,31 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/filedrop"
|
"github.com/netbirdio/netbird/client/internal/filedrop"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fileDropListenControl scopes the receiver's listeners to the tunnel
|
||||||
|
// interface, so replies on accepted connections leave through the tunnel
|
||||||
|
// instead of following the Network Extension's own-traffic bypass.
|
||||||
|
func fileDropListenControl(wgIface WGIface) filedrop.ListenControl {
|
||||||
|
return func(network, _ string, c syscall.RawConn) error {
|
||||||
|
osIface, err := net.InterfaceByName(wgIface.Name())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("lookup interface %q: %w", wgIface.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proto, opt := unix.IPPROTO_IP, unix.IP_BOUND_IF
|
||||||
|
if strings.HasSuffix(network, "6") {
|
||||||
|
proto, opt = unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
|
||||||
|
}
|
||||||
|
|
||||||
|
var operr error
|
||||||
|
if err := c.Control(func(s uintptr) {
|
||||||
|
operr = unix.SetsockoptInt(int(s), proto, opt, osIface.Index)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return operr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fileDropOSDial scopes the dial to the tunnel interface, since a Network
|
// fileDropOSDial scopes the dial to the tunnel interface, since a Network
|
||||||
// Extension's own unscoped sockets bypass its tunnel and leave on the
|
// Extension's own unscoped sockets bypass its tunnel and leave on the
|
||||||
// physical interface.
|
// physical interface.
|
||||||
|
|||||||
@@ -147,8 +147,9 @@ func (m *Manager) SetDestinationDir(dir string) error {
|
|||||||
return m.policy.SetDestinationDir(dir)
|
return m.policy.SetDestinationDir(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartReceiver binds the receiving server on addr.
|
// StartReceiver binds the receiving server on addr. A non-nil control is
|
||||||
func (m *Manager) StartReceiver(ctx context.Context, addr netip.AddrPort, netstackNet *netstack.Net, resolver PeerResolver) error {
|
// applied to host listeners before bind.
|
||||||
|
func (m *Manager) StartReceiver(ctx context.Context, addr netip.AddrPort, netstackNet *netstack.Net, resolver PeerResolver, control ListenControl) error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
if m.server != nil {
|
if m.server != nil {
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
@@ -170,6 +171,9 @@ func (m *Manager) StartReceiver(ctx context.Context, addr netip.AddrPort, netsta
|
|||||||
if netstackNet != nil {
|
if netstackNet != nil {
|
||||||
server.SetNetstackNet(netstackNet)
|
server.SetNetstackNet(netstackNet)
|
||||||
}
|
}
|
||||||
|
if control != nil {
|
||||||
|
server.SetListenControl(control)
|
||||||
|
}
|
||||||
|
|
||||||
if err := server.Start(ctx, addr); err != nil {
|
if err := server.Start(ctx, addr); err != nil {
|
||||||
return fmt.Errorf("start receiver: %w", err)
|
return fmt.Errorf("start receiver: %w", err)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -48,6 +49,10 @@ type ServerConfig struct {
|
|||||||
SpoolMaxAge time.Duration
|
SpoolMaxAge time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenControl is a raw-socket hook applied to host listeners before bind,
|
||||||
|
// mirroring net.ListenConfig.Control.
|
||||||
|
type ListenControl func(network, address string, c syscall.RawConn) error
|
||||||
|
|
||||||
// Server serves the receiver over HTTP on the overlay address and owns the
|
// Server serves the receiver over HTTP on the overlay address and owns the
|
||||||
// listener and janitor lifecycle; the protocol logic itself lives in receiver.
|
// listener and janitor lifecycle; the protocol logic itself lives in receiver.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -56,6 +61,7 @@ type Server struct {
|
|||||||
listener net.Listener
|
listener net.Listener
|
||||||
extraListeners []net.Listener
|
extraListeners []net.Listener
|
||||||
netstackNet *netstack.Net
|
netstackNet *netstack.Net
|
||||||
|
listenControl ListenControl
|
||||||
|
|
||||||
recv *receiver
|
recv *receiver
|
||||||
boundPort uint16
|
boundPort uint16
|
||||||
@@ -90,6 +96,13 @@ func NewServer(cfg ServerConfig) (*Server, error) {
|
|||||||
return &Server{recv: newReceiver(cfg, spool, maxAge)}, nil
|
return &Server{recv: newReceiver(cfg, spool, maxAge)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetListenControl installs a raw-socket hook applied to host listeners.
|
||||||
|
func (s *Server) SetListenControl(control ListenControl) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.listenControl = control
|
||||||
|
}
|
||||||
|
|
||||||
// SetNetstackNet routes listeners through the gVisor netstack instead of host sockets.
|
// SetNetstackNet routes listeners through the gVisor netstack instead of host sockets.
|
||||||
func (s *Server) SetNetstackNet(n *netstack.Net) {
|
func (s *Server) SetNetstackNet(n *netstack.Net) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -244,7 +257,7 @@ func (s *Server) createListener(ctx context.Context, addr netip.AddrPort) (net.L
|
|||||||
return ln, fmt.Sprintf("netstack %s", addr), nil
|
return ln, fmt.Sprintf("netstack %s", addr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lc net.ListenConfig
|
lc := net.ListenConfig{Control: s.listenControl}
|
||||||
ln, err := lc.Listen(ctx, "tcp", net.TCPAddrFromAddrPort(addr).String())
|
ln, err := lc.Listen(ctx, "tcp", net.TCPAddrFromAddrPort(addr).String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("listen: %w", err)
|
return nil, "", fmt.Errorf("listen: %w", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user