Skip to content

Better support for multi-loopback scenarios #512

@frs3

Description

@frs3

Describe the feature

For loopback output destinations I would like to propose to bind the socket to the destination loopback addresses so that the originating address is the same as the destination, and not always 127.0.0.1 or ::1.

Describe the reason for such feature

Currently, when wstunnel connects to a loopback service e.g. on 127.0.0.2, the connection always originates from 127.0.0.1 or ::1, which breaks scenarios where the destination service relies on the source IP for routing decisions, callbacks, or isolation. This is particularly important in containerized environments and development setups where different services bind to distinct loopback addresses (127.0.0.1, 127.0.0.2, etc.) to avoid port conflicts or implement IP-based access control.

Describe alternatives you've considered

I did not find an alternative solutions except from patching and building wstunnel myself.

Example implementation

Call configure_socket_with_bind(&socket, &addr) in wstunnel/src/protocols/tcp/server.rs after calling configure_socket (or merge to it). Same could be applied for outgoing UDP connections. The code was tested with a local build. The change is minimal and non-breaking: it only affects connections to loopback addresses.

fn configure_socket_with_bind(
    socket: &TcpSocket,
    addr: &SocketAddr,
) -> Result<(), anyhow::Error> {
    // Bind to the host IP if it's a loopback address
    // This ensures outbound connections to loopback addresses originate
    // from the same loopback address they're connecting to.
    match addr {
        SocketAddr::V4(addr_v4) => {
            let ip = addr_v4.ip();
            if ip.is_loopback() {
                socket
                    .bind(SocketAddr::V4(SocketAddrV4::new(*ip, 0)))
                    .context("cannot bind socket to loopback address")?;
            }
        }
        SocketAddr::V6(addr_v6) => {
            let ip = addr_v6.ip();
            if ip.is_loopback() {
                socket
                    .bind(SocketAddr::V6(SocketAddrV6::new(*ip, 0, 0, 0)))
                    .context("cannot bind socket to loopback address")?;
            }
        }
    }

    Ok(())
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions