Skip to content

From<u32> for Ipv4Addr is not performed in network byte order #48819

Closed
@VilleHallivuori

Description

@VilleHallivuori

Documentation for u32 -> Ipv4Addr conversion (https://doc.rust-lang.org/1.21.0/std/net/struct.Ipv4Addr.html) and recent bug discussion (#40118) claim that conversion is performed in network byte order. This is clearly not the cases.

For example IP address 10.11.12.13 in IP header would be hex bytes 0x0a 0x0b 0x0c 0x0d and Inter CPU (little endian) would interpret this value as u32 0x0d0c0b0a. Conversion to Ipv4Addr results with current library implementation IP address 13.12.11.10.

The problem is clear from the fact that implementation (https://doc.rust-lang.org/1.21.0/src/std/net/ip.rs.html#745-747) does not contain conditional code based on endianness and operates using shift operations. Hence it can only work on a processor that is natively in network byte order. Current implementation is:

#[stable(feature = "ip_u32", since = "1.1.0")]
impl From<u32> for Ipv4Addr {
    /// It performs the conversion in network order (big-endian).
    fn from(ip: u32) -> Ipv4Addr {
        Ipv4Addr::new((ip >> 24) as u8, (ip >> 16) as u8, (ip >> 8) as u8, ip as u8)
    }
}

Conversion without conditional code would only be possible if code would cast u32 to u8[4] (not sure if that is possible in Rust... on C that would work), not using arithmetic operations on u32 where host byte order comes into play.

A suitable fix might be along the lines of (I am assuming Rust compiler optimises htonl away.. if not, more optimal approach would just be to make conversion trait itself conditional on host byte order):

#[cfg(target_endian = "little")]
fn htonl(val : u32) -> u32 {
    let o3 = (val >> 24) as u8;
    let o2 = (val >> 16) as u8;
    let o1 = (val >> 8)  as u8;
    let o0 =  val        as u8;
    ((o0 as u32) << 24 | (o1 as u32) << 16 | (o2 as u32) << 8 | (o3 as u32))
}

#[cfg(target_endian = "big")]
fn htonl(val : u32) -> u32 {
    val
}

#[cfg(target_endian = "little")]
#[test]
fn test_htonl() {
    assert_eq!(0x12345678, htonl(0x78563412));
}

impl From<u32> for Ipv4Addr {
    /// It performs the conversion in network order (big-endian).
    fn from(ip_hb: u32) -> Ipv4Addr {
        let ip = htonl(ip_hb);
        Ipv4Addr::new((ip >> 24) as u8, (ip >> 16) as u8, (ip >> 8) as u8, ip as u8)
    }
}

Fixing the issue in code (instead of just fixing the documentation) does have down side. If any code writes IP addresses in hex in host byte order (that is a convenient way to do it), fixing this breaks such code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-docsArea: Documentation for any part of the project, including the compiler, standard library, and toolsC-bugCategory: This is a bug.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions