Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trait to reduce duplicated code between socket address types, implement netlink addresses #1004

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

kevinmehall
Copy link
Contributor

@kevinmehall kevinmehall commented Jan 29, 2024

#918

This adds a trait SocketAddress, and implements it for SocketAddrV4, SocketAddrV6, SocketAddrUnix, SocketAddrXdp, SocketAddr, SocketAddrAny, and the newly-added SocketAddrNetlink. The syscalls that take a sockaddr are reimplemented in terms of this trait, removing hundreds of lines of duplicated code. It also simplifies the public interface, allowing rustix::net::{bind, connect, sendto, sendmsg_addr} to take an <A: SocketAddress>(addr: &A), rather than needing separate functions bind_v4, bind_v6, bind_unix, bind_xdp, bind_any etc. All of those are left as wrappers around the generic version, but could be deprecated and removed.

Open questions

  • Naming: I went with SocketAddress for the trait. The rest of the code consistently uses SocketAddrX rather than Address, but SocketAddr is already taken by the std enum of V4 and V6. Could maybe just be rustix::net::Addr?
  • SocketAddrAny: So far this only changes the syscalls that take a socket address as an in-pointer. Those that return an address via an out-pointer currently return SocketAddrAny. If it is desirable to allow types outside of rustix to implement the SocketAddress trait, it would be ideal for SocketAddrAny to wrap sockaddr_storage instead of being a Rust enum. This would allow SocketAddrAny be fully generic and allow these syscalls to be used with any address type not known to rustix. Address types could use .into() / .try_into() to convert to / from SocketAddrAny. This would require a breaking change.
  • libc types in trait: if you want to keep libc types out of the publlic interface, the trait methods could use *mut c_void and usize instead of the underlying sockaddr_* types.

@sunfishcode
Copy link
Member

As a quick update here, I am looking forward to reviewing this; I've just been busy.

Copy link

@niklaswimmer niklaswimmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach, it allows me to very easily define a socket address not yet present in this crate, like so

#[derive(Debug, Clone, Copy)]
#[allow(dead_code)] // fields are never read directly because they get memcp'd into another struct
#[repr(C)]
struct SocketAddressHci {
    hci_family: rustix::net::AddressFamily,
    hci_dev: u16,
    hci_channel: u16,
}

unsafe impl SocketAddress for SocketAddressHci {
    /// SAFETY: `SocketAddressHci` has the same layout as the type in the kernel and can therefore safely be passed in a syscall
    type CSockAddr = Self;

    fn encode(&self) -> Self::CSockAddr {
        self.clone()
    }
}

I left some comment about the code structure, most of it more nit than anything else. I am interested to here your opinion on using something like a Bow for the return value of encode tho. It would allow impl's to avoid the clone without having to reimplement write_sockaddr and with_sockaddr again :)

@@ -6,4 +6,3 @@ pub(crate) mod read_sockaddr;
pub(crate) mod send_recv;
pub(crate) mod sockopt;
pub(crate) mod syscalls;
pub(crate) mod write_sockaddr;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the only reason for this removal was because write_sockaddr* were no longer needed we should remove those functions instead. We can then keep this mod and use the encode_sockaddr* functions in src/net/socket_address.rs to define one common impl of SocketAddress for SockAddrV4 and SockAddrV6 (instead of defining separate impls in src/backend/*/net/addr.rs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, replaced the duplicated implementations with ones shared between linux_raw and libc that call into the existing write_sockaddr functions.

src/backend/linux_raw/net/addr.rs Outdated Show resolved Hide resolved
src/backend/linux_raw/net/addr.rs Outdated Show resolved Hide resolved
src/backend/linux_raw/net/addr.rs Outdated Show resolved Hide resolved
src/backend/libc/net/msghdr.rs Outdated Show resolved Hide resolved
/// address.
unsafe fn write_sockaddr(&self, storage: *mut SocketAddrStorage) -> usize {
let encoded = self.encode();
core::ptr::write(storage.cast(), encoded);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future Rust moves to enabling the unsafe_fn_in_fn lint by default, so we should probably mark this operation explicitly as unsafe, even though the compiler does not require it for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this method entirely, but fixed an unsafe_fn_in_fn elsewhere.

type CSockAddr;

/// Convert to the C type.
fn encode(&self) -> Self::CSockAddr;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it a bit annoying/confusing that this trait uses default fns. We could instead utilize a small custom enum that stores either a reference to or an owned CSockAddr. Kinda like a Cow but for nostd, something along the lines of

/// Borrowed Or oWned
pub enum Bow<'a, B> {
    /// Stores a reference to the value.
    Borrowed(&'a B),
    /// Owns the value.
    Owned(B),
}

impl<'a, B> Bow<'a, B> {
    /// Get a reference to the value stored in this Bow.
    pub fn as_ref(&self) -> &B {
        match self {
            Bow::Borrowed(val) => val,
            Bow::Owned(val) => &val,
        }
    }
}

Then only encode would be enough and the rest could be defined as helper functions or similar.

Let me know what you think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the write_sockaddr method, which had a default implementation that was not overridden for any of the types. It was only used in one place, for SocketAddrAny::write.

The purpose of the with_sockaddr method is to avoid the copying that you mention, and is overridden in the implementation for SocketAddrUnix which contains a sockaddr_un.

Before simplifying this trait further, I'd like to hear what @sunfishcode thinks about the CSockAddr associated type. If it's undesirable to expose the underlying C types, I think the way to go would be to remove CSockAddr and encode, leaving only with_sockaddr. This is a messier interface than simply returning a value, but a private helper function could be used to implement this in terms of the encode_sockaddr_* functions for the majority of the address types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants