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
- Add matching pf redirect rules for the IPv6 family (pass in ... rdr-to with AF_INET6).
- Bind the redir listener to an explicit IPv6 address so states are AF_INET6.
- Verify no NAT64 translation is rewriting the flow family mid-path.
- 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
- Create pf rdr rules for both inet and inet6 families
- Avoid NAT64/DNS64 in front of transparent redirect hosts
- Verify with `pfctl -ss` that v6 flows carry AF_INET6 states
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
- client addr must be ipv4
- not supported tcp transparent proxy type
- missing destination address in msghdr
- not supported udp transparent proxy type
- missing destination address in msghdr
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/9cc4cb38502cb5ab.
Report an issue: GitHub.