shadowsocks/shadowsocks-rust · error
tun destination must not be an domain name address
Error message
tun destination must not be an domain name address
What it means
The tun device routes raw IP packets, so a UDP destination must be a concrete IP address. If the destination Address is a domain name that was never resolved, send_to returns InvalidInput 'tun destination must not be an domain name address'.
Source
Thrown at crates/shadowsocks-service/src/local/tun/udp.rs:121
// 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);
}
};
let packet = match (peer_addr, addr) {
(SocketAddr::V4(peer), SocketAddr::V4(remote)) => {
let builder =
PacketBuilder::ipv4(remote.ip().octets(), peer.ip().octets(), 20).udp(remote.port(), peer.port());
let packet = BytesMut::with_capacity(builder.size(data.len()));
let mut packet_writer = packet.writer();
builder.write(&mut packet_writer, data).expect("PacketBuilder::write");
packet_writer.into_inner()
}View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Resolve the domain to an IP address before calling send_to (use a DNS resolver and pass SocketAddr/Address::Ip)
- Enable local address resolution so destinations are IPs by the time they reach the tun relay
- If the remote returns a domain in the UDP reply, map it back through your DNS cache to the queried IP
- Reject such packets upstream with a clear log instead of reaching send_to
Example fix
// before
let dest = Address::DomainNameAddress("example.com".into(), 53);
relay.send_to(dest, ...).await?;
// after
let ip = resolver.resolve("example.com").await?;
let dest = Address::SocketAddr(SocketAddr::new(ip, 53));
relay.send_to(dest, ...).await?; Defensive patterns
Strategy: validation
Validate before calling
if let Address::DomainNameAddress(..) = dest {
return Err("resolve domain before tun UDP send_to");
} Type guard
fn is_ip_address(addr: &Address) -> bool { !matches!(addr, Address::DomainNameAddress(..)) } Try / catch
match relay.send_to(dest, src, &data).await { Err(e) if e.kind()==InvalidInput => { let ip = resolve(dest).await?; retry_with(ip).await? }, r => r? } Prevention
- Resolve all destinations to IPs before the tun relay
- Enable DNS resolution in the local pipeline when using tun mode
- Cache domain->IP mappings from upstream queries
- Validate Address kind at relay entry points
When it happens
Trigger: Calling send_to on the tun UDP relay with a destination Address::DomainNameAddress — i.e. DNS resolution was skipped or the address came from a path that did not resolve hostnames (e.g. remote returned a domain in its reply header).
Common situations: Remote shadowsocks server replies with a domain-name address for UDP associate replies; local resolution disabled (remote DNS) while using tun mode, which cannot send un-resolved names.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- unexpected response from 8.8.8.8:53
- redir destination must not be an domain name address
- source and destination type unmatch
- `local_udp_port` cannot be 0
- `udp_redir` invalid
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/790f55fce128ef60.
Report an issue: GitHub.