Skip to content

SO_BINDTODEVICE makes a loopback SOCKS5 proxy unreachable with Linux auto-route #2

Description

@exelents

Environment

  • TinyTun: v0.0.3-alpha.2 (8c57833dba9a7614262e90121d3e1fae61fed203)
  • v2rayA: 2.4.6
  • OS: Arch Linux
  • Kernel: Linux 7.1
  • Physical interface: bond0
  • TUN interface: tun0

Configuration

The SOCKS5 server is a local listener created by v2rayA:

tun:
  name: tun0
  ip: 198.18.0.1
  netmask: 255.255.255.255
  auto_route: true
socks5:
  name: proxy
  address: 127.0.0.1:52345
route:
  auto_detect_interface: true

TinyTun correctly detects bond0 and installs the two default-route halves:

0.0.0.0/1 dev tun0 proto static
128.0.0.0/1 dev tun0 proto static

Steps to reproduce

  1. Start a SOCKS5 server on 127.0.0.1:52345.
  2. Start TinyTun with the configuration above on Linux.
  3. Send TCP traffic through tun0.

Expected behavior

TinyTun connects to the local SOCKS5 listener through the loopback device and forwards the captured flow.

Actual behavior

Every captured connection eventually times out:

[Warning] SOCKS5 connect timed out for FlowKey { src: 198.18.0.1:40040, dst: 149.154.175.100:443 }

Proxied DNS fails for the same reason because its SOCKS5 UDP-associate control channel also uses open_tcp_to_proxy:

[Warning] DNS forwarding failed: SOCKS5 handshake timed out for DNS upstream 1.1.1.1:53

The local SOCKS5 listener itself works when contacted directly:

curl --socks5-hostname 127.0.0.1:52345 https://cloudflare.com
# succeeds

However, sockets opened by TinyTun remain in this state:

SYN-SENT 192.168.88.75%bond0:<ephemeral> 127.0.0.1:52345

Root cause

Socks5Client::open_tcp_to_proxy applies SO_BINDTODEVICE whenever an outbound interface was detected. It also applies it when the configured proxy address is loopback.

Binding a socket whose destination is 127.0.0.1 to bond0 prevents the SYN from reaching the loopback listener. This causes both TCP forwarding and proxied DNS to hang even though the SOCKS5 server is healthy.

SO_BINDTODEVICE is still needed for a remote SOCKS5 server to prevent routing loops, but it should be skipped when config.address.ip().is_loopback() is true. The same applies to IPv6 loopback (::1).

I have a patch with IPv4/IPv6 loopback regression tests and will link the pull request here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions