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

  1. Ensure the destination resolves to an IPv4 or IPv4-mapped-IPv6 address when the source is IPv4
  2. Force IPv4 resolution (prefer A records) for remotes accessed from the IPv4 side of the tun
  3. Configure the tun address pool to include IPv6 if you need to proxy native IPv6 destinations
  4. 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

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


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