Files
netbird/client/internal/engine_filedrop_dial_ios.go
Zoltán Papp fa47b32b92 [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.
2026-08-27 17:58:25 +02:00

84 lines
2.2 KiB
Go

//go:build ios
package internal
import (
"context"
"fmt"
"net"
"net/netip"
"strings"
"syscall"
"golang.org/x/sys/unix"
"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.
func fileDropOSDial(wgIface WGIface) filedrop.DialFunc {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
addrPort, err := netip.ParseAddrPort(addr)
if err != nil {
return nil, err
}
osIface, err := net.InterfaceByName(wgIface.Name())
if err != nil {
return nil, fmt.Errorf("lookup interface %q: %w", wgIface.Name(), err)
}
wgAddr := wgIface.Address()
bindIP := wgAddr.IP
proto, opt := unix.IPPROTO_IP, unix.IP_BOUND_IF
if addrPort.Addr().Is6() {
if !wgAddr.HasIPv6() {
return nil, fmt.Errorf("no IPv6 address on %s", wgIface.Name())
}
bindIP = wgAddr.IPv6
proto, opt = unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
}
dialer := &net.Dialer{
LocalAddr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(bindIP, 0)),
Control: func(_, _ string, c syscall.RawConn) error {
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
},
}
return dialer.DialContext(ctx, network, addr)
}
}