Skip to content

No sound way to zero-copy from socketaddr_in #323

Open
@jeff-hiner

Description

@jeff-hiner

This is most painful on Windows, where SOCKADDR_STORAGE is 132 bytes long.

Consider the following naive example code, where the SOCKADDR pointer here is a pointer to an ipv4/ipv6 address obtained via syscall:

unsafe fn sock_addr_to_ip(ptr: *const SOCKADDR, length: i32) -> Option<IpAddr> {
    let sa = socket2::SockAddr::new(*(ptr as *const SOCKADDR_STORAGE), length);
    sa.as_socket().map(|s| s.ip())
}

The issue is that SOCKADDR_STORAGE is larger than SOCKADDR, the pointer is only guaranteed to be valid through length bytes, and so casting it to *const SOCKADDR_STORAGE and dereferencing immediately introduces UB.

The correct code looks more like this:

unsafe fn sock_addr_to_ip(ptr: *const SOCKADDR, length: i32) -> Option<IpAddr> {
    assert!(length as usize <= std::mem::size_of::<SOCKADDR_STORAGE>());
    let mut storage = std::mem::MaybeUninit::<SOCKADDR_STORAGE>::uninit();
    std::ptr::copy_nonoverlapping(ptr, &mut storage as *mut _ as *mut _, length as _);
    let sa = socket2::SockAddr::new(storage.assume_init(), length);
    sa.as_socket().map(|s| s.ip())
}

Since the backing storage for SockAddr is owning, we have to make a copy. It's incredibly awkward and a bit slower. And that's just so we can safely invoke the nice parsing logic in as_socket().

To fix this we'd probably have to add a SockAddrRef<'a> type that doesn't own its backing storage. If that's not immediately feasible, providing a constructor that performs this unsafe copy inside socket2 would also solve the immediate issue.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions