shadowsocks/shadowsocks-rust · error
source and destination type unmatch
Error message
source and destination type unmatch
What it means
In the tun UDP relay's send_to, when the peer/source address is IPv4 but the resolved destination is a non-IPv4-mapped IPv6 address, the code cannot build a consistent IP packet, so it returns InvalidData 'source and destination type unmatch'. The tun device requires source and destination families to agree.
Source
Thrown at crates/shadowsocks-service/src/local/tun/udp.rs:107
impl UdpTunInboundWriter {
fn new(tun_tx: mpsc::Sender<BytesMut>) -> Self {
Self { tun_tx }
}
}
impl UdpInboundWrite for UdpTunInboundWriter {
async fn send_to(&self, peer_addr: SocketAddr, remote_addr: &Address, data: &[u8]) -> io::Result<()> {
let addr = match *remote_addr {
Address::SocketAddress(sa) => {
// Try to convert IPv4 mapped IPv6 address if server is running on dual-stack mode
match (peer_addr, sa) {
(SocketAddr::V4(..), SocketAddr::V4(..)) | (SocketAddr::V6(..), SocketAddr::V6(..)) => sa,
(SocketAddr::V4(..), SocketAddr::V6(v6)) => {
// If peer is IPv4, then remote_addr can only be IPv4-mapped-IPv6
match to_ipv4_mapped(v6.ip()) {
Some(v4) => SocketAddr::new(IpAddr::from(v4), v6.port()),
None => {
return Err(io::Error::new(
ErrorKind::InvalidData,
"source and destination type unmatch",
));
}
}
}
(SocketAddr::V6(..), SocketAddr::V4(v4)) => {
// Convert remote_addr to IPv4-mapped-IPv6
SocketAddr::new(IpAddr::from(v4.ip().to_ipv6_mapped()), v4.port())
}
}
}
Address::DomainNameAddress(..) => {
let err = io::Error::new(
ErrorKind::InvalidInput,
"tun destination must not be an domain name address",
);
return Err(err);View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Ensure the destination resolves to an IPv4 or IPv4-mapped-IPv6 address when the source is IPv4
- Force IPv4 resolution (prefer A records) for remotes accessed from the IPv4 side of the tun
- Configure the tun address pool to include IPv6 if you need to proxy native IPv6 destinations
- Reject or skip such UDP flows earlier in the relay with a clearer log message
Example fix
// before: destination is 2001:db8::1, source is IPv4 -> error // after: resolve/convert destination to IPv4-mapped or use an IPv6-capable source let dst = SocketAddr::new(IpAddr::V6(to_ipv6_mapped(v4.ip())), v4.port()); // align families
Defensive patterns
Strategy: type-guard
Validate before calling
fn families_compatible(src: &SocketAddr, dst: &Address) -> bool {
match dst { Address::DomainNameAddress(..) => false,
Address::SocketAddr(d) => matches!((src.is_ipv4(), d.is_ipv4()), (true,true)|(false,false)) || d.ip().to_ipv4_mapped().is_some() } } Type guard
fn as_v4_mapped(sa: SocketAddr) -> Option<SocketAddr> {
match sa { SocketAddr::V6(v6) => v6.ip().to_ipv4_mapped().map(|v4| SocketAddr::new(v4.into(), v6.port())), v4 => Some(v4) } } Try / catch
match relay.send_to(dest, src, &data).await { Err(e) if e.kind()==InvalidData => log::debug!("family mismatch, dropped"), r => r? } Prevention
- Keep tun v4 and v6 source pools consistent with expected destinations
- Prefer IPv4-mapped conversion early in the pipeline
- Pre-resolve AAAA vs A based on the peer family
- Log family mismatches to tune resolution policy
When it happens
Trigger: send_to where remote_addr (source) is SocketAddr::V4 and the destination Address resolves to an IPv6 address that is not IPv4-mapped (::ffff:x.x.x.x). The to_ipv4_mapped conversion returns None and the error is returned.
Common situations: Proxying a remote whose resolved address is a native IPv6 address while the local client/peer is on the IPv4 tun subnet; dual-stack misconfiguration on the tun interface.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- tun destination must not be an domain name address
- unexpected response from 8.8.8.8:53
- client addr must be ipv6
- redir destination must not be an domain name address
- missing destination address in msghdr
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/5d1ca5c50ab676fc.
Report an issue: GitHub.