SocketAddrV6 should be able to parse IPv4 addresses

Every IPv4 address is technically a valid IPv6 address. There's even a (relatively) standardized mapping between them: https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses

This wouldn't be breaking for SocketAddr parsing, because every string could be first attempted to be parsed as IPv4 address, resulting in a SocketAddr::V4 variant, then as IPv6.

If however, you want to parse any legitimate IP address into an IPv6 address, you should be able to. There is a use-case for this, as IPv6 sockets on many OSes accept IPv4 mapped addresses. However, regardless of the address family of the socket.bind(...) address your socket is bound to, all the send and receive methods take a SocketAddr type. Which is fine, if you could pass in a SocketAddr::V6(<IPv4:port literal>.parse()). But you can't, you have to manually attempt to parse the literal as SocketAddrV4, then re-encode it as V6 through the SocketAddrV6::new which requires you to specify the flowinfo and scope_id, without providing any defaults (which are obviously used in the string parsing).

Alternatively, this would be also simply solved if there was a From<SocketAddrV4> for SocketAddrV6 implementation in the std, or a similar method if the mapping standard isn't definite enough.

#[cfg(test)]
mod tests {
    use std::net::SocketAddrV6;
    type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

    #[test]
    fn parse_ipv4_mapped_socket_address() -> Result<()> {
        // succeeds
        let _sock1: SocketAddrV6 = "[::1]:45454".parse()?;
        let _sock2: SocketAddrV6 = "[::192.168.1.1]:45454".parse()?;
        let _sock3: SocketAddrV6 = "[::ffff:192.168.1.1]:45454".parse()?;
        // fails
        let _sock4: SocketAddrV6 = "192.168.1.1:45454".parse()?;
        Ok(())
    }
}

Current workaround I use:

fn workaround(s: &str) -> std::result::Result<SocketAddrV6, std::net::AddrParseError> {
    match s.parse::<SocketAddrV6>() {
        Err(_) => match s.parse::<SocketAddrV4>() {
            Ok(addr_v4) => 
                format!("[::ffff:{}]:{}", addr_v4.ip(), addr_v4.port()).parse::<SocketAddrV6>(),
            Err(e) => Err(e)
        },
        any => any
    }
}

I should note I had a bug in my code, which prompted this issue in the first place. It seems sockets bound to an IPv6 address correctly work even with SocketAddr::V4 variants.

That being said, this conversion should still be valid, even if my use-case falls off.

You can use Ipv4Addr::to_ipv6_mapped like this:

fn workaround(s: &str) -> Result<SocketAddrV6, AddrParseError> {
    match s.parse::<SocketAddr>()? {
        SocketAddr::V4(socket_addr_v4) => {
            Ok(SocketAddrV6::new(
                socket_addr_v4.ip().to_ipv6_mapped(),
                socket_addr_v4.port(),
                0,
                0,
            ))
        },
        SocketAddr::V6(socket_addr_v6) => Ok(socket_addr_v6),
    }
}

It would be convenient if that method also existed on IpAddr, SocketAddrV4 and SocketAddr though.

Edit: (Because then you could just use s.parse::<SocketAddr>()?.to_ipv6_mapped())

4 Likes