Fix option behavior in UNIX/netlink sockets

This commit is contained in:
Ruihan Li 2025-12-01 09:44:45 +08:00 committed by Jianfeng Jiang
parent f522bd72b2
commit cf772b804e
7 changed files with 139 additions and 25 deletions

View File

@ -9,14 +9,15 @@ use super::{GroupIdSet, NetlinkSocketAddr};
use crate::{
events::IoEvents,
fs::utils::Inode,
match_sock_option_ref,
match_sock_option_mut, match_sock_option_ref,
net::socket::{
netlink::{table::SupportedNetlinkProtocol, AddMembership, DropMembership},
new_pseudo_inode,
options::SocketOption,
options::{Error as SocketError, SocketOption},
private::SocketPrivate,
util::{
datagram_common::{select_remote_and_bind, Bound, Inner},
options::{GetSocketLevelOption, SetSocketLevelOption, SocketOptionSet},
MessageHeader, SendRecvFlags, SocketAddr,
},
Socket,
@ -31,12 +32,26 @@ mod unbound;
pub struct NetlinkSocket<P: SupportedNetlinkProtocol> {
inner: RwMutex<Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>>,
options: RwLock<OptionSet>,
is_nonblocking: AtomicBool,
pollee: Pollee,
pseudo_inode: Arc<dyn Inode>,
}
#[derive(Debug, Clone)]
struct OptionSet {
socket: SocketOptionSet,
}
impl OptionSet {
pub(self) fn new() -> Self {
Self {
socket: SocketOptionSet::new_netlink(),
}
}
}
impl<P: SupportedNetlinkProtocol> NetlinkSocket<P>
where
BoundNetlink<P::Message>: Bound<Endpoint = NetlinkSocketAddr>,
@ -45,6 +60,7 @@ where
let unbound = UnboundNetlink::new();
Arc::new(Self {
inner: RwMutex::new(Inner::Unbound(unbound)),
options: RwLock::new(OptionSet::new()),
is_nonblocking: AtomicBool::new(is_nonblocking),
pollee: Pollee::new(),
pseudo_inode: new_pseudo_inode(),
@ -169,17 +185,39 @@ where
Ok((received_len, message_header))
}
fn get_option(&self, option: &mut dyn SocketOption) -> Result<()> {
match_sock_option_mut!(option, {
socket_errors: SocketError => {
// TODO: Support socket errors for netlink sockets
socket_errors.set(None);
return Ok(());
},
_ => ()
});
let inner = self.inner.read();
let options = self.options.read();
// Deal with socket-level options
options.socket.get_option(option, &*inner)
// TODO: Deal with netlink-level options
}
fn set_option(&self, option: &dyn SocketOption) -> Result<()> {
match do_set_netlink_option(&self.inner, option) {
Ok(()) => Ok(()),
Err(e) => {
warn!(
"We currently ignore set option errors to pass libnl test: {:?}",
e
);
Ok(())
}
let mut inner = self.inner.write();
// Deal with socket-level options
let mut options = self.options.write();
match options.socket.set_option(option, &*inner) {
Err(err) if err.error() == Errno::ENOPROTOOPT => (),
res => return res.map(|_need_iface_poll| ()),
}
// `options` must be dropped here because `do_netlink_setsockopt` may lock other mutexes.
drop(options);
// Deal with netlink-level options
do_netlink_setsockopt(option, &mut inner)
}
fn pseudo_inode(&self) -> &Arc<dyn Inode> {
@ -210,6 +248,19 @@ where
}
}
impl<P: SupportedNetlinkProtocol> GetSocketLevelOption
for Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>
{
fn is_listening(&self) -> bool {
false
}
}
impl<P: SupportedNetlinkProtocol> SetSocketLevelOption
for Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>
{
}
impl<P: SupportedNetlinkProtocol> Inner<UnboundNetlink<P>, BoundNetlink<P::Message>> {
fn add_groups(&mut self, groups: GroupIdSet) {
match self {
@ -226,18 +277,18 @@ impl<P: SupportedNetlinkProtocol> Inner<UnboundNetlink<P>, BoundNetlink<P::Messa
}
}
fn do_set_netlink_option<P: SupportedNetlinkProtocol>(
inner: &RwMutex<Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>>,
fn do_netlink_setsockopt<P: SupportedNetlinkProtocol>(
option: &dyn SocketOption,
inner: &mut Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>,
) -> Result<()> {
match_sock_option_ref!(option, {
add_membership: AddMembership => {
let groups = add_membership.get().unwrap();
inner.write().add_groups(GroupIdSet::new(*groups));
inner.add_groups(GroupIdSet::new(*groups));
},
drop_membership: DropMembership => {
let groups = drop_membership.get().unwrap();
inner.write().drop_groups(GroupIdSet::new(*groups));
inner.drop_groups(GroupIdSet::new(*groups));
},
_ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to be set is unknown")
});

View File

@ -49,6 +49,7 @@ mod table;
pub use addr::{GroupIdSet, NetlinkSocketAddr};
pub use kobject_uevent::NetlinkUeventSocket;
pub use options::{AddMembership, DropMembership};
pub(super) use receiver::NETLINK_DEFAULT_BUF_SIZE;
pub use route::NetlinkRouteSocket;
pub use table::{is_valid_protocol, StandardNetlinkProtocol};

View File

@ -112,4 +112,4 @@ impl<Message: QueueableMessage> MessageReceiver<Message> {
}
}
const NETLINK_DEFAULT_BUF_SIZE: usize = 65536;
pub(in crate::net) const NETLINK_DEFAULT_BUF_SIZE: usize = 65536;

View File

@ -6,9 +6,10 @@ use super::message::{MessageQueue, MessageReceiver};
use crate::{
events::IoEvents,
fs::utils::Inode,
match_sock_option_mut,
net::socket::{
new_pseudo_inode,
options::SocketOption,
options::{Error as SocketError, SocketOption},
private::SocketPrivate,
unix::{ctrl_msg::AuxiliaryData, UnixSocketAddr},
util::{
@ -199,6 +200,15 @@ impl Socket for UnixDatagramSocket {
}
fn get_option(&self, option: &mut dyn SocketOption) -> Result<()> {
match_sock_option_mut!(option, {
socket_errors: SocketError => {
// TODO: Support socket errors for UNIX sockets
socket_errors.set(None);
return Ok(());
},
_ => ()
});
let options = self.options.read();
// Deal with socket-level options
@ -217,7 +227,6 @@ impl Socket for UnixDatagramSocket {
let mut options = self.options.write();
match options.socket.set_option(option, &self.local_receiver) {
Ok(_) => Ok(()),
Err(err) if err.error() == Errno::ENOPROTOOPT => {
// TODO: Deal with socket options from other levels
warn!("only socket-level options are supported");
@ -226,7 +235,7 @@ impl Socket for UnixDatagramSocket {
"the socket option to get is unknown"
)
}
Err(e) => Err(e),
res => res.map(|_need_iface_poll| ()),
}
}

View File

@ -19,7 +19,7 @@ use crate::{
match_sock_option_mut,
net::socket::{
new_pseudo_inode,
options::{PeerCred, PeerGroups, SocketOption},
options::{Error as SocketError, PeerCred, PeerGroups, SocketOption},
private::SocketPrivate,
unix::{cred::SocketCred, ctrl_msg::AuxiliaryData, CUserCred, UnixSocketAddr},
util::{
@ -387,6 +387,15 @@ impl Socket for UnixStreamSocket {
}
fn get_option(&self, option: &mut dyn SocketOption) -> Result<()> {
match_sock_option_mut!(option, {
socket_errors: SocketError => {
// TODO: Support socket errors for UNIX sockets
socket_errors.set(None);
return Ok(());
},
_ => ()
});
let state = self.state.read();
let options = self.options.read();
@ -413,7 +422,6 @@ impl Socket for UnixStreamSocket {
let mut options = self.options.write();
match options.socket.set_option(option, state.as_ref()) {
Ok(_) => Ok(()),
Err(err) if err.error() == Errno::ENOPROTOOPT => {
// TODO: Deal with socket options from other levels
warn!("only socket-level options are supported");
@ -422,7 +430,7 @@ impl Socket for UnixStreamSocket {
"the socket option to get is unknown"
)
}
Err(e) => Err(e),
res => res.map(|_need_iface_poll| ()),
}
}

View File

@ -10,6 +10,7 @@ use super::LingerOption;
use crate::{
match_sock_option_mut, match_sock_option_ref,
net::socket::{
netlink::NETLINK_DEFAULT_BUF_SIZE,
options::{
AcceptConn, Broadcast, KeepAlive, Linger, PassCred, PeerCred, PeerGroups, Priority,
RecvBuf, RecvBufForce, ReuseAddr, ReusePort, SendBuf, SendBufForce, SocketOption,
@ -88,11 +89,19 @@ impl SocketOptionSet {
}
}
/// Returns the default socket level options for netlink socket.
pub(in crate::net) fn new_netlink() -> Self {
Self {
send_buf: NETLINK_DEFAULT_BUF_SIZE as u32,
recv_buf: NETLINK_DEFAULT_BUF_SIZE as u32,
..Default::default()
}
}
/// Gets socket-level options.
///
/// Note that the socket error has to be handled separately, because it is automatically
/// cleared after reading. This method does not handle it. Instead,
/// [`Self::get_and_clear_socket_errors`] should be used.
/// Note that the socket error has to be handled separately. This method does not handle it
/// because it is automatically cleared after reading.
pub fn get_option(
&self,
option: &mut dyn SocketOption,

View File

@ -257,3 +257,39 @@ FN_TEST(add_and_drop_membership)
TEST_SUCC(close(sk_new));
}
END_TEST()
FN_TEST(getsockopt_membership)
{
int group;
socklen_t group_size = sizeof(group);
TEST_ERRNO(getsockopt(sk_unbound, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&group, &group_size),
ENOPROTOOPT);
TEST_ERRNO(getsockopt(sk_unbound, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
&group, &group_size),
ENOPROTOOPT);
TEST_ERRNO(getsockopt(sk_bound, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&group, &group_size),
ENOPROTOOPT);
TEST_ERRNO(getsockopt(sk_bound, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
&group, &group_size),
ENOPROTOOPT);
TEST_ERRNO(getsockopt(sk_connected, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&group, &group_size),
ENOPROTOOPT);
TEST_ERRNO(getsockopt(sk_connected, SOL_NETLINK,
NETLINK_DROP_MEMBERSHIP, &group, &group_size),
ENOPROTOOPT);
}
END_TEST()
FN_SETUP(cleanup)
{
CHECK(close(sk_unbound));
CHECK(close(sk_bound));
CHECK(close(sk_connected));
}
END_SETUP()