shadowsocks/shadowsocks-rust · error

bind_local_addr is not a valid IPv4-mapped IPv6 address

Error message

bind_local_addr is not a valid IPv4-mapped IPv6 address

What it means

set_common_sockopt_for_connect binds the socket to bind_local_addr before connecting. When the remote target is IPv4 but bind_local_addr is an IPv6 address, shadowsocks converts it to an IPv4-mapped form; if the IPv6 address is not actually of the ::ffff:a.b.c.d mapped form, the bind cannot proceed and ErrorKind::InvalidInput is returned.

Source

Thrown at crates/shadowsocks/src/net/sys/mod.rs:40

}

fn set_common_sockopt_for_connect(addr: SocketAddr, socket: &TcpSocket, opts: &ConnectOpts) -> io::Result<()> {
    // Binds to IP address
    if let Some(baddr) = opts.bind_local_addr {
        match (baddr, addr) {
            (SocketAddr::V4(..), SocketAddr::V4(..)) => {
                socket.bind(baddr)?;
            }
            (SocketAddr::V4(v4baddr), SocketAddr::V6(..)) => {
                socket.bind(SocketAddr::new(v4baddr.ip().to_ipv6_mapped().into(), v4baddr.port()))?;
            }
            (SocketAddr::V6(..), SocketAddr::V6(..)) => {
                socket.bind(baddr)?;
            }
            (SocketAddr::V6(v6baddr), SocketAddr::V4(..)) => match v6baddr.ip().to_ipv4_mapped() {
                Some(v4baddr) => socket.bind(SocketAddr::new(v4baddr.into(), v6baddr.port()))?,
                None => {
                    return Err(io::Error::new(
                        ErrorKind::InvalidInput,
                        "bind_local_addr is not a valid IPv4-mapped IPv6 address",
                    ));
                }
            },
        }
    }

    // Set `SO_SNDBUF`
    if let Some(buf_size) = opts.tcp.send_buffer_size {
        socket.set_send_buffer_size(buf_size)?;
    }

    // Set `SO_RCVBUF`
    if let Some(buf_size) = opts.tcp.recv_buffer_size {
        socket.set_recv_buffer_size(buf_size)?;
    }

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Set bind_local_addr to an IPv4 address (e.g. 0.0.0.0 or a specific local IPv4) when the destination is IPv4
  2. Use an IPv4-mapped IPv6 address (::ffff:x.x.x.x) if an IPv6-form bind address is required
  3. Remove bind_local_addr from the config so the OS picks the local address automatically
  4. Match bind_local_addr family to the server address family (check DNS resolution result first)

Example fix

// before
bind_local_addr = "::1:1080"  // plain IPv6, destination is IPv4
// after
bind_local_addr = "0.0.0.0:1080"  // IPv4 bind for IPv4 destination
Defensive patterns

Strategy: validation

Validate before calling

if let SocketAddr::V6(v6) = bind_local_addr {
    if v6.ip().to_ipv4_mapped().is_none() && dst.is_ipv4() {
        bail!("bind_local_addr must be IPv4 or IPv4-mapped for IPv4 destinations");
    }
}

Prevention

When it happens

Trigger: Calling connect or connect_with_socket with a config whose bind_local_addr is an IPv6 address that is not IPv4-mapped (e.g. ::1 or 2001:db8::1) while the resolved destination address is IPv4.

Common situations: Users set bind_local_addr to a plain IPv6 loopback or global IPv6 address while the server address resolves to IPv4 (A record); dual-stack confusion where the outbound connection goes over IPv4.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09). Data as JSON: /api/errors/d1f81a95a830d9f5. Report an issue: GitHub.