[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:
Zoltán Papp
2026-08-27 17:58:25 +02:00
parent 898539381c
commit fa47b32b92
5 changed files with 51 additions and 4 deletions
+6 -2
View File
@@ -147,8 +147,9 @@ func (m *Manager) SetDestinationDir(dir string) error {
return m.policy.SetDestinationDir(dir)
}
// StartReceiver binds the receiving server on addr.
func (m *Manager) StartReceiver(ctx context.Context, addr netip.AddrPort, netstackNet *netstack.Net, resolver PeerResolver) error {
// StartReceiver binds the receiving server on addr. A non-nil control is
// 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()
if m.server != nil {
m.mu.Unlock()
@@ -170,6 +171,9 @@ func (m *Manager) StartReceiver(ctx context.Context, addr netip.AddrPort, netsta
if netstackNet != nil {
server.SetNetstackNet(netstackNet)
}
if control != nil {
server.SetListenControl(control)
}
if err := server.Start(ctx, addr); err != nil {
return fmt.Errorf("start receiver: %w", err)
+14 -1
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"net/netip"
"sync"
"syscall"
"time"
log "github.com/sirupsen/logrus"
@@ -48,6 +49,10 @@ type ServerConfig struct {
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
// listener and janitor lifecycle; the protocol logic itself lives in receiver.
type Server struct {
@@ -56,6 +61,7 @@ type Server struct {
listener net.Listener
extraListeners []net.Listener
netstackNet *netstack.Net
listenControl ListenControl
recv *receiver
boundPort uint16
@@ -90,6 +96,13 @@ func NewServer(cfg ServerConfig) (*Server, error) {
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.
func (s *Server) SetNetstackNet(n *netstack.Net) {
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
}
var lc net.ListenConfig
lc := net.ListenConfig{Control: s.listenControl}
ln, err := lc.Listen(ctx, "tcp", net.TCPAddrFromAddrPort(addr).String())
if err != nil {
return nil, "", fmt.Errorf("listen: %w", err)