From cf772b804ea7e96fd1f0d7baa90cdfeac6e2e109 Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Mon, 1 Dec 2025 09:44:45 +0800 Subject: [PATCH] Fix option behavior in UNIX/netlink sockets --- kernel/src/net/socket/netlink/common/mod.rs | 81 +++++++++++++++---- kernel/src/net/socket/netlink/mod.rs | 1 + kernel/src/net/socket/netlink/receiver.rs | 2 +- kernel/src/net/socket/unix/datagram/socket.rs | 15 +++- kernel/src/net/socket/unix/stream/socket.rs | 14 +++- kernel/src/net/socket/util/options.rs | 15 +++- test/src/apps/network/uevent_err.c | 36 +++++++++ 7 files changed, 139 insertions(+), 25 deletions(-) diff --git a/kernel/src/net/socket/netlink/common/mod.rs b/kernel/src/net/socket/netlink/common/mod.rs index c92a90402..25b292ce2 100644 --- a/kernel/src/net/socket/netlink/common/mod.rs +++ b/kernel/src/net/socket/netlink/common/mod.rs @@ -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 { inner: RwMutex, BoundNetlink>>, + options: RwLock, is_nonblocking: AtomicBool, pollee: Pollee, pseudo_inode: Arc, } +#[derive(Debug, Clone)] +struct OptionSet { + socket: SocketOptionSet, +} + +impl OptionSet { + pub(self) fn new() -> Self { + Self { + socket: SocketOptionSet::new_netlink(), + } + } +} + impl NetlinkSocket

where BoundNetlink: Bound, @@ -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 { @@ -210,6 +248,19 @@ where } } +impl GetSocketLevelOption + for Inner, BoundNetlink> +{ + fn is_listening(&self) -> bool { + false + } +} + +impl SetSocketLevelOption + for Inner, BoundNetlink> +{ +} + impl Inner, BoundNetlink> { fn add_groups(&mut self, groups: GroupIdSet) { match self { @@ -226,18 +277,18 @@ impl Inner, BoundNetlink( - inner: &RwMutex, BoundNetlink>>, +fn do_netlink_setsockopt( option: &dyn SocketOption, + inner: &mut Inner, BoundNetlink>, ) -> 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") }); diff --git a/kernel/src/net/socket/netlink/mod.rs b/kernel/src/net/socket/netlink/mod.rs index 34a667a42..1ac3e388a 100644 --- a/kernel/src/net/socket/netlink/mod.rs +++ b/kernel/src/net/socket/netlink/mod.rs @@ -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}; diff --git a/kernel/src/net/socket/netlink/receiver.rs b/kernel/src/net/socket/netlink/receiver.rs index 1c63b95cd..156d3cac1 100644 --- a/kernel/src/net/socket/netlink/receiver.rs +++ b/kernel/src/net/socket/netlink/receiver.rs @@ -112,4 +112,4 @@ impl MessageReceiver { } } -const NETLINK_DEFAULT_BUF_SIZE: usize = 65536; +pub(in crate::net) const NETLINK_DEFAULT_BUF_SIZE: usize = 65536; diff --git a/kernel/src/net/socket/unix/datagram/socket.rs b/kernel/src/net/socket/unix/datagram/socket.rs index 8e08740a0..4b14a5b88 100644 --- a/kernel/src/net/socket/unix/datagram/socket.rs +++ b/kernel/src/net/socket/unix/datagram/socket.rs @@ -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| ()), } } diff --git a/kernel/src/net/socket/unix/stream/socket.rs b/kernel/src/net/socket/unix/stream/socket.rs index cb77284e2..b7f74c499 100644 --- a/kernel/src/net/socket/unix/stream/socket.rs +++ b/kernel/src/net/socket/unix/stream/socket.rs @@ -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| ()), } } diff --git a/kernel/src/net/socket/util/options.rs b/kernel/src/net/socket/util/options.rs index 84e7953e8..1e1d7f02a 100644 --- a/kernel/src/net/socket/util/options.rs +++ b/kernel/src/net/socket/util/options.rs @@ -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, diff --git a/test/src/apps/network/uevent_err.c b/test/src/apps/network/uevent_err.c index 26c0ba2a1..641c123ba 100644 --- a/test/src/apps/network/uevent_err.c +++ b/test/src/apps/network/uevent_err.c @@ -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()