[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
@@ -7,6 +7,7 @@ import (
"fmt"
"net"
"net/netip"
"strings"
"syscall"
"golang.org/x/sys/unix"
@@ -14,6 +15,31 @@ import (
"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
// Extension's own unscoped sockets bypass its tunnel and leave on the
// physical interface.