shadowsocks/shadowsocks-rust · error

client addr must be ipv6

Error message

client addr must be ipv6

What it means

Mirror of the IPv4 case: tcp_natlook checks that when the client peer address is an IPv6 SocketAddr, the pf natlook state's address family is AF_INET6. A mismatch means the pf state doesn't correspond to an IPv6 flow and the original destination cannot be recovered, so InvalidInput is thrown.

Source

Thrown at crates/shadowsocks-service/src/local/redir/sys/unix/bsd_pf.rs:133

                    let sockaddr = SockAddr::from(*v4);
                    let sockaddr = sockaddr.as_ptr() as *const sockaddr_in;

                    let addr: *const in_addr = ptr::addr_of!((*sockaddr).sin_addr) as *const _;
                    let port: libc::in_port_t = (*sockaddr).sin_port;

                    ptr::write_unaligned::<in_addr>(ptr::addr_of_mut!(pnl.saddr.pfa) as *mut _, *addr);

                    cfg_if! {
                        if #[cfg(any(target_os = "macos", target_os = "ios"))] {
                            pnl.sxport.port = port;
                        } else {
                            pnl.sport = port;
                        }
                    }
                }
                SocketAddr::V6(ref v6) => {
                    if pnl.af != libc::AF_INET6 as libc::sa_family_t {
                        return Err(Error::new(ErrorKind::InvalidInput, "client addr must be ipv6"));
                    }

                    let sockaddr = SockAddr::from(*v6);
                    let sockaddr = sockaddr.as_ptr() as *const sockaddr_in6;

                    let addr: *const in6_addr = ptr::addr_of!((*sockaddr).sin6_addr) as *const _;
                    let port: libc::in_port_t = (*sockaddr).sin6_port;

                    ptr::write_unaligned::<in6_addr>(ptr::addr_of_mut!(pnl.saddr.pfa) as *mut _, *addr);

                    cfg_if! {
                        if #[cfg(any(target_os = "macos", target_os = "ios"))] {
                            pnl.sxport.port = port;
                        } else {
                            pnl.sport = port;
                        }
                    }
                }

View on GitHub (pinned to 8eb0f0a65b)

Solutions

  1. Add matching pf redirect rules for the IPv6 family (pass in ... rdr-to with AF_INET6).
  2. Bind the redir listener to an explicit IPv6 address so states are AF_INET6.
  3. Verify no NAT64 translation is rewriting the flow family mid-path.
  4. Check `pfctl -ss` for the actual state family of the connection.
Defensive patterns

Strategy: try-catch

Validate before calling

if !peer_addr.is_ipv6() { /* route to the v4 natlook path instead */ }

Type guard

fn as_v6(addr: &SocketAddr) -> Option<std::net::SocketAddrV6> {
    match addr { SocketAddr::V6(v6) => Some(*v6), _ => None }
}

Try / catch

match natlook(fd, peer_addr, local_addr).await {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
        tracing::warn!("pf state family mismatch for {peer_addr}; rejecting connection");
        // drop or redirect to an IPv6-capable redir path
    }
    r => r?,
}

Prevention

When it happens

Trigger: natlook called with an IPv6 peer address while the pf state table entry carries a non-AF_INET6 family — stale/replaced states, or IPv6 traffic actually being NATed through an IPv4 state.

Common situations: Dual-stack redirect setups where ip6 rules are missing, NAT64/DNS64 translation changing the family, or pf rules only created for the IPv4 family.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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