-
Notifications
You must be signed in to change notification settings - Fork 430
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
Abstractions of socket and related network entities #1066
base: staging/rust-net
Are you sure you want to change the base?
Abstractions of socket and related network entities #1066
Conversation
Create `net` module files and network headers in `bindings_helper.h`. Add `IpProtocol`, `AddressFamily` and `Namespace`. The wrappers added with this patch are shared across the whole network subsystem. For this reason, they are placed in the `net.rs` module file. The enum `IpProtocol`, however, is placed in an individual `ip.rs` submodule, allowing to place together all the ip-related structures, such as wrappers for `iphdr`, `ip_auth_hdr`, etc. Signed-off-by: Michele Dalle Rive <[email protected]>
Create structures to handle addresses: `Ipv4Addr`, `Ipv6Addr`, `SocketAddr`, `SocketAddrV4` and `SocketAddrV6`. These structures are meant to be as similar as possible to the ones in Rust `std::net`, while, at the same time, providing functionalities available in the kernel. Some extra structures are added, compared to `std`: - `SocketAddrStorage`: wraps `struct sockaddr_storage` and is used to interact with the kernel functions when the type of socket address is unknown. Since it is only used for FFI, it is crate-public. - `GenericSocketAddr`: trait that defines shared functions and traits amont all socket addresses. Signed-off-by: Michele Dalle Rive <[email protected]>
Add enums representing flags related to sockets: - `ReceiveFlag` to modify the behaviour of the socket receive operation. - `SendFlag` to modify the behaviour of the socket send operation. - `MessageFlag` to represent the flags in a `msghdr`. - `SocketFlag` to represent the flags in the `socket` struct. Introduce a `FlagSet` structure to offer a convenient way to handle the flags. Having an abstraction over the "raw" numerical value of the flags offers many advantages: - A `FlagSet` can be created in different ways: from an `IntoIterator`, a value, a single flag or using the defined macro `flag_set!(...)`. - Custom operations can be defined, such as the bitwise or. - Flags in the set can be set, tested, unset through functions instead of using bitwise operations. - FlagSet implements the IntoIterator trait, allowing for iteration over the flags contained. Signed-off-by: Michele Dalle Rive <[email protected]>
Create a `Socket` abstraction, which provides a Rust API to the kernel socket functionalities. The Socket structures tries to keep the same function signatures of the Rust standard library; at the same time, functions are added or modified in order to provide as much as possible of the C kernel functionalities. Most of the internals of the C socket is not accessible by Rust, because those structures are still to be wrapped. However, sockets are mainly managed through the functions provided by the kernel; thus, even if some fields are not accessible, since the functions are wrapped, most of the kernel functionality should be available in Rust as well. Specifically, the usage of `msghdr` is mostly abstracted away in the Rust interface, because using it would mean having to deal, both in the kernel and in modules, with Pinned instances (msghdr is self-referencing), which would be a struggle that provides no particular advantage. A `MessageHeader` object is actually created and returned when a message is received, because at that point the structure is not really self-referencing, as long as the source address is copied. The wrapper is not used when a message is sent. Anyways, some useful functionalities of `msghdr`, like `cmsghdr`s, are missing and should be implemented in the future to provide a complete API. Signed-off-by: Michele Dalle Rive <[email protected]>
Create socket `Option`s and `set_option` function in the `Socket` abstraction. These changes introduce wrappers and functions to handle socket options in Rust, with compilation-time advantages compared to the C API: - Type safety: A specific option accepts only a value of the correct type. - Read/write safety: A read-only option cannot be set. - Coherence safety: An option of, for example, IP level cannot be set by specifying another level. The downside of using options in the kernel is the lack of functions to get the value of an option. For this reason, in Rust, kernel options can only be set, but not retrieved. Everything that can be done by socket options can actually be done through helper functions, or by accessing directly the specific fields. However, since the Rust-wrapped structures are few, it can be useful to have options in order to still be able to modify the behaviour of the socket. As specified in the documentation of `opts.rs`, options could (and should) be removed when the Rust API will be developed enough. Signed-off-by: Michele Dalle Rive <[email protected]>
Add `TcpListener` and `TcpStream` wrappers around the Rust Socket. They provide a convenient way to handle TCP sockets. This interface is intended to be as close as possible to the one in `std::net`. Signed-off-by: Michele Dalle Rive <[email protected]>
Add a UDP socket wrapper, which allows to handle UDP sockets conveniently. This interface is intended to be as close as possible to the one in `std::net`. Signed-off-by: Michele Dalle Rive <[email protected]>
Hi Michele, thanks a lot for posting this work! So far I've only had time to look at patches 01 and 02, but here are some things that I noticed. Some general things:
Please note that:
Finally, some questions where I'd be interested in the opinion of you and others:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! Didn't get the chance to review everything but made a few overall comments.
We will get Rust 1.77 in 3 weeks which has the ip and socket types in core which would mean a lot of the changes to rust/kernel/net/ip.rs
and rust/kernel/net/socket.rs
could be dropped. Miguel actually already posted the version bump patch https://lore.kernel.org/rust-for-linux/CANiq72nbr6qy1otJpdh3FVUN5cUfrkUPYEHJFm_QqfKvYEj-Xw@mail.gmail.com/T/#m654659d25b00821ef3c51941b1791398bf0602e0, maybe we could apply that and just say you need to build with beta until it merges.
|
||
/// The address family. | ||
/// | ||
/// See [`man 7 address families`](https://man7.org/linux/man-pages/man7/address_families.7.html) for more information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line wrap
|
||
fn try_from(value: isize) -> Result<Self, Self::Error> { | ||
let val = value as u32; | ||
match val { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably better to make this impl TryFrom<u32>
rather than casting
/// Wraps a `struct in_addr`. | ||
#[derive(Default, Copy, Clone)] | ||
#[repr(transparent)] | ||
pub struct Ipv4Addr(pub(crate) bindings::in_addr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ip_in_core
will be available in 1.77 (upcoming release March 21) and add core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}
, so it probably makes sense to update as soon as it is available. That would just need a conversion to/from bindings::in_addr
, rather than reimplementing the methods here.
Any additional constants could be in kernel::net
or in kernel::net::{ipv4, ipv6}
modules. Worth noting that Rust's Ipv4Addr
is not ABI-compatible with in_addr
, align 1 vs. align 4.
pub(crate) fn into<T: GenericSocketAddr>(self) -> T { | ||
// SAFETY: The `self.0` field is a `struct sockaddr_storage` which is guaranteed to be large enough to hold any socket address. | ||
unsafe { *(&self.0 as *const _ as *const T) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't safe unless GenericSocketAddr
is also unsafe or you do something like assert on size_of:::<T>
. But what is the purpose of GenericSocketAddr
anyway? I think that SocketAddr
could be used without a trait, calling .into()
is trivial if you have a SocketAddrV4
or V6
pub mod opts; | ||
|
||
/// The socket type. | ||
pub enum SockType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the most confusing things in the kernel has to be that sock
and socket
are different things. Unfortunately I don't have any better suggestions, but we probably want to make a more clear distinction on the Rust side.
unsafe { | ||
(*self.0).flags = flags; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsafe { | |
(*self.0).flags = flags; | |
} | |
unsafe { (*self.0).flags = flags }; |
Semi outside braces formats nicer. Also safety comment
/// ``` | ||
pub fn has_flag(&self, flag: SocketFlag) -> bool { | ||
bindings::__BindgenBitfieldUnit::<[u8; 8], u8>::new(self.flags().to_be_bytes()) | ||
.get_bit(flag as _) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type should be specified for as
casts to make the conversion clear. Quite a few cases below too. Maybe some could be converted to .into
/ .cast
/// Consumes the socket and returns the underlying pointer. | ||
/// | ||
/// The pointer is valid for the lifetime of the wrapper. | ||
/// | ||
/// # Safety | ||
/// The caller must ensure that the pointer is not used after the wrapper is dropped. | ||
pub unsafe fn into_inner(self) -> *mut bindings::socket { | ||
self.0 | ||
} | ||
|
||
/// Returns the underlying pointer. | ||
/// | ||
/// The pointer is valid for the lifetime of the wrapper. | ||
/// | ||
/// # Safety | ||
/// The caller must ensure that the pointer is not used after the wrapper is dropped. | ||
pub unsafe fn as_inner(&self) -> *mut bindings::socket { | ||
self.0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a pointer isn't unsafe
(e.g. str::as_ptr
). This will clean up nicer after a lifetime is added to the type.
($(#[$meta:meta])* | ||
$opt:ident = $value:expr, | ||
$level:expr, | ||
unimplemented, | ||
$($tr:ty),*) => {}; | ||
|
||
($(#[$meta:meta])* | ||
$opt:ident = $value:expr, | ||
$level:expr, | ||
$rtyp:ty, | ||
$($tr:ty),*) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
($(#[$meta:meta])* | |
$opt:ident = $value:expr, | |
$level:expr, | |
unimplemented, | |
$($tr:ty),*) => {}; | |
($(#[$meta:meta])* | |
$opt:ident = $value:expr, | |
$level:expr, | |
$rtyp:ty, | |
$($tr:ty),*) => { | |
( | |
$(#[$meta:meta])* | |
$opt:ident = $value:expr, | |
$level:expr, | |
unimplemented, | |
$($tr:ty),* | |
) => {}; | |
( | |
$(#[$meta:meta])* | |
$opt:ident = $value:expr, | |
$level:expr, | |
$rtyp:ty, | |
$($tr:ty),* | |
) => { |
The macro headers are easier to read when indented. I don't think there is any point of having a specific unimplemented
matcher that does nothing.
Named fields would make these macros more clear, and you could use repetition:
impl_ip_opts!{
/// Don't reserve a port when binding with port number 0.
///
/// C value type: `int`
BindAddressNoPort {
value: bindings::IP_BIND_ADDRESS_NO_PORT,
ctype: bool,
options: [ReadableOption, WritableOption]
},
BlockSource {
// ..
}
}
All that being said, the RFL team sometimes prefer having the code written out than using macros. I could see this going either way :)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] | ||
pub struct FlagSet<T: Flag> { | ||
value: isize, | ||
_phantom: core::marker::PhantomData<T>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is isize
correct here? This would be ssize_t
in C, and it looks like every call to .value()
is being casted.
We might want to rethink FlagSet
a bit anyway
@tgross35 @vobst Thanks for the comments, I'm working on the required changes. I am not sure how to commit the changes tho; should I simply commit and then when the PR is ready rebase everything into some patch-like commits or should I already integrate the changes in the previous commits and force-push? If I'm not mistaken the latter could mess up the reviews in Github. |
Feel free to either keep pushing commits or squash and force push however you prefer. GH preserves comments across revisions so no concerns :) |
Add Rust abstractions for basic network entities, socket and its wrappers (
TcpStream
andUdpSocket
).Specifically, it was added:
in_addr
,in6_addr
,sockaddr_in
,sockaddr_in6
,sockaddr_storage
).Currently, there is no explicit user for these abstractions.
The original patch series was sent as a RFC to the mailing list.
The mailing list patches contained some questions about some portions of the code; if it's relevant, I will promptly rewrite them in this PR as well.
I'm not sure how the
rust-net
workflow will be, but I'm super available and happy to discuss changes to the code and improve/correct it.By the way, I am very glad this branch become a thing and hopefully Rust will be able to become more and more used in the net subsystem!