Support SO_TYPE socket option

Report the concrete socket type through getsockopt(SOL_SOCKET, SO_TYPE) for IP, UNIX, and netlink sockets. This lets user space inspect sockets without relying on family-specific behavior.
This commit is contained in:
volca 2026-06-30 23:18:03 +08:00 committed by Jianfeng Jiang
parent 003568118b
commit e68b9218d4
17 changed files with 172 additions and 39 deletions

View File

@ -26,7 +26,7 @@ use crate::{
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
util::{MultiRead, MultiWrite},
util::{MultiRead, MultiWrite, net::SockType},
};
mod bound;
@ -298,6 +298,10 @@ impl Socket for DatagramSocket {
}
impl GetSocketLevelOption for Inner<UnboundDatagram, BoundDatagram> {
fn socket_type(&self) -> SockType {
SockType::SOCK_DGRAM
}
fn is_listening(&self) -> bool {
false
}

View File

@ -44,7 +44,7 @@ use crate::{
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
util::{MultiRead, MultiWrite},
util::{MultiRead, MultiWrite, net::SockType},
};
mod connected;
@ -913,6 +913,10 @@ impl State {
}
impl GetSocketLevelOption for State {
fn socket_type(&self) -> SockType {
SockType::SOCK_STREAM
}
fn is_listening(&self) -> bool {
matches!(self, Self::Listen(_))
}

View File

@ -25,7 +25,7 @@ use crate::{
},
prelude::*,
process::signal::{PollHandle, Pollable, Pollee},
util::{MultiRead, MultiWrite},
util::{MultiRead, MultiWrite, net::SockType},
};
mod bound;
@ -34,6 +34,7 @@ mod unbound;
pub struct NetlinkSocket<P: SupportedNetlinkProtocol> {
inner: RwMutex<Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>>,
options: RwLock<OptionSet>,
socket_type: SockType,
is_nonblocking: AtomicBool,
pollee: Pollee,
@ -57,11 +58,14 @@ impl<P: SupportedNetlinkProtocol> NetlinkSocket<P>
where
BoundNetlink<P::Message>: Bound<Endpoint = NetlinkSocketAddr>,
{
pub fn new(is_nonblocking: bool) -> Arc<Self> {
pub fn new(is_nonblocking: bool, socket_type: SockType) -> Arc<Self> {
debug_assert!(socket_type == SockType::SOCK_RAW || socket_type == SockType::SOCK_DGRAM);
let unbound = UnboundNetlink::new();
Arc::new(Self {
inner: RwMutex::new(Inner::Unbound(unbound)),
options: RwLock::new(OptionSet::new()),
socket_type,
is_nonblocking: AtomicBool::new(is_nonblocking),
pollee: Pollee::new(),
pseudo_path: SockFs::new_path(),
@ -200,7 +204,9 @@ where
let options = self.options.read();
// Deal with socket-level options
options.socket.get_option(option, &*inner)
options
.socket
.get_option(option, &(&*inner, self.socket_type))
// TODO: Deal with netlink-level options
}
@ -250,8 +256,15 @@ where
}
impl<P: SupportedNetlinkProtocol> GetSocketLevelOption
for Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>
for (
&Inner<UnboundNetlink<P>, BoundNetlink<P::Message>>,
SockType,
)
{
fn socket_type(&self) -> SockType {
self.1
}
fn is_listening(&self) -> bool {
false
}

View File

@ -22,6 +22,7 @@ use crate::{
util::{SendRecvFlags, SocketAddr},
},
prelude::*,
util::net::SockType,
};
#[ktest]
@ -53,7 +54,7 @@ fn multicast_synthetic_uevent() {
crate::net::socket::netlink::init();
// Creates a new netlink uevent socket and joins the group for kobject uevents.
let socket = NetlinkUeventSocket::new(true);
let socket = NetlinkUeventSocket::new(true, SockType::SOCK_DGRAM);
let socket_addr = SocketAddr::Netlink(NetlinkSocketAddr::new(100, GroupIdSet::new(0x1)));
socket.bind(socket_addr).unwrap();

View File

@ -3,7 +3,7 @@
use macros::impl_socket_options;
use super::util::LingerOption;
use crate::{net::socket::unix::CUserCred, prelude::*, process::Gid};
use crate::{net::socket::unix::CUserCred, prelude::*, process::Gid, util::net::SockType};
pub(in crate::net) mod macros;
@ -16,6 +16,7 @@ pub trait SocketOption: Any + Send + Sync + Debug {
impl_socket_options!(
pub struct ReuseAddr(bool);
pub struct SocketType(SockType);
pub struct Error(Option<crate::error::Error>);
pub struct Broadcast(bool);
pub struct SendBuf(u32);

View File

@ -20,7 +20,7 @@ use crate::{
},
prelude::*,
process::signal::{PollHandle, Pollable},
util::{MultiRead, MultiWrite},
util::{MultiRead, MultiWrite, net::SockType},
};
pub struct UnixDatagramSocket {
@ -323,6 +323,10 @@ fn do_unix_getsockopt(option: &mut dyn SocketOption, socket: &UnixDatagramSocket
}
impl GetSocketLevelOption for MessageReceiver {
fn socket_type(&self) -> SockType {
SockType::SOCK_DGRAM
}
fn is_listening(&self) -> bool {
false
}

View File

@ -25,6 +25,7 @@ use crate::{
},
prelude::*,
process::signal::Pollee,
util::net::SockType,
};
pub(super) struct Listener {
@ -55,13 +56,20 @@ impl Listener {
self.backlog.addr()
}
pub(super) fn try_accept(&self, is_seqpacket: bool) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
pub(super) fn try_accept(
&self,
socket_type: SockType,
) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
debug_assert!(
socket_type == SockType::SOCK_STREAM || socket_type == SockType::SOCK_SEQPACKET
);
let connected = self.backlog.pop_incoming()?;
let peer_addr = connected.peer_addr().into();
let options = OptionSet::new_accepted(connected.is_pass_cred());
let socket = UnixStreamSocket::new_connected(connected, options, false, is_seqpacket);
let socket = UnixStreamSocket::new_connected(connected, options, false, socket_type);
Ok((socket, peer_addr))
}

View File

@ -30,7 +30,7 @@ use crate::{
Gid,
signal::{PollHandle, Pollable, Pollee},
},
util::{MultiRead, MultiWrite},
util::{MultiRead, MultiWrite, net::SockType},
};
pub struct UnixStreamSocket {
@ -41,7 +41,7 @@ pub struct UnixStreamSocket {
pollee: Pollee,
is_nonblocking: AtomicBool,
is_seqpacket: bool,
socket_type: SockType,
pseudo_path: Path,
}
@ -163,22 +163,30 @@ impl OptionSet {
}
impl UnixStreamSocket {
pub fn new(is_nonblocking: bool, is_seqpacket: bool) -> Arc<Self> {
Self::new_init(Init::new(), is_nonblocking, is_seqpacket)
pub fn new(is_nonblocking: bool, socket_type: SockType) -> Arc<Self> {
debug_assert!(
socket_type == SockType::SOCK_STREAM || socket_type == SockType::SOCK_SEQPACKET
);
Self::new_init(Init::new(), is_nonblocking, socket_type)
}
fn new_init(init: Init, is_nonblocking: bool, is_seqpacket: bool) -> Arc<Self> {
fn new_init(init: Init, is_nonblocking: bool, socket_type: SockType) -> Arc<Self> {
Arc::new(Self {
state: RwMutex::new(Takeable::new(State::Init(init))),
options: RwLock::new(OptionSet::new()),
pollee: Pollee::new(),
is_nonblocking: AtomicBool::new(is_nonblocking),
is_seqpacket,
socket_type,
pseudo_path: SockFs::new_path(),
})
}
pub fn new_pair(is_nonblocking: bool, is_seqpacket: bool) -> (Arc<Self>, Arc<Self>) {
pub fn new_pair(is_nonblocking: bool, socket_type: SockType) -> (Arc<Self>, Arc<Self>) {
debug_assert!(
socket_type == SockType::SOCK_STREAM || socket_type == SockType::SOCK_SEQPACKET
);
let cred = SocketCred::<ReadDupOp>::new_current();
let (conn_a, conn_b) = Connected::new_pair(
@ -190,8 +198,8 @@ impl UnixStreamSocket {
cred.restrict(),
);
(
Self::new_connected(conn_a, OptionSet::new(), is_nonblocking, is_seqpacket),
Self::new_connected(conn_b, OptionSet::new(), is_nonblocking, is_seqpacket),
Self::new_connected(conn_a, OptionSet::new(), is_nonblocking, socket_type),
Self::new_connected(conn_b, OptionSet::new(), is_nonblocking, socket_type),
)
}
@ -199,7 +207,7 @@ impl UnixStreamSocket {
connected: Connected,
options: OptionSet,
is_nonblocking: bool,
is_seqpacket: bool,
socket_type: SockType,
) -> Arc<Self> {
let cloned_pollee = connected.cloned_pollee();
Arc::new(Self {
@ -207,7 +215,7 @@ impl UnixStreamSocket {
options: RwLock::new(options),
pollee: cloned_pollee,
is_nonblocking: AtomicBool::new(is_nonblocking),
is_seqpacket,
socket_type,
pseudo_path: SockFs::new_path(),
})
}
@ -219,7 +227,7 @@ impl UnixStreamSocket {
_flags: SendRecvFlags,
) -> Result<usize> {
match self.state.read().as_ref() {
State::Connected(connected) => connected.try_write(buf, aux_data, self.is_seqpacket),
State::Connected(connected) => connected.try_write(buf, aux_data, self.is_seqpacket()),
State::Init(_) | State::Listen(_) => {
return_errno_with_message!(Errno::ENOTCONN, "the socket is not connected")
}
@ -232,7 +240,7 @@ impl UnixStreamSocket {
flags: SendRecvFlags,
) -> Result<(usize, Vec<ControlMessage>)> {
match self.state.read().as_ref() {
State::Connected(connected) => connected.try_read(buf, self.is_seqpacket, flags),
State::Connected(connected) => connected.try_read(buf, self.is_seqpacket(), flags),
State::Init(_) | State::Listen(_) => {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected")
}
@ -269,7 +277,7 @@ impl UnixStreamSocket {
init,
self.pollee.clone(),
&self.options.read(),
self.is_seqpacket,
self.is_seqpacket(),
) {
Ok(connected) => connected,
Err((err, init)) => return (State::Init(init), Err(err)),
@ -281,12 +289,16 @@ impl UnixStreamSocket {
fn try_accept(&self) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
match self.state.read().as_ref() {
State::Listen(listen) => listen.try_accept(self.is_seqpacket) as _,
State::Listen(listen) => listen.try_accept(self.socket_type) as _,
State::Init(_) | State::Connected(_) => {
return_errno_with_message!(Errno::EINVAL, "the socket is not listening")
}
}
}
fn is_seqpacket(&self) -> bool {
self.socket_type == SockType::SOCK_SEQPACKET
}
}
pub(super) const SHUT_READ_EVENTS: IoEvents =
@ -363,7 +375,7 @@ impl Socket for UnixStreamSocket {
}
};
let listener = match init.listen(backlog, self.pollee.clone(), self.is_seqpacket) {
let listener = match init.listen(backlog, self.pollee.clone(), self.is_seqpacket()) {
Ok(listener) => listener,
Err((err, init)) => {
return (State::Init(init), Err(err));
@ -421,7 +433,6 @@ impl Socket for UnixStreamSocket {
});
let state = self.state.read();
let options = self.options.read();
// Deal with UNIX-socket-specific socket-level options
match do_unix_getsockopt(option, state.as_ref()) {
@ -430,7 +441,11 @@ impl Socket for UnixStreamSocket {
}
// Deal with socket-level options
match options.socket.get_option(option, state.as_ref()) {
let options = self.options.read();
match options
.socket
.get_option(option, &(state.as_ref(), self.socket_type))
{
Err(err) if err.error() == Errno::ENOPROTOOPT => (),
res => return res,
}
@ -477,7 +492,7 @@ impl Socket for UnixStreamSocket {
// According to the Linux man pages, `EISCONN` _may_ be returned when the destination
// address is specified for a connection-mode socket. In practice, `sendmsg` on UNIX stream
// sockets will fail due to that. We follow the same behavior as the Linux implementation.
if !self.is_seqpacket && addr.is_some() {
if !self.is_seqpacket() && addr.is_some() {
match self.state.read().as_ref() {
State::Init(_) | State::Listen(_) => return_errno_with_message!(
Errno::EOPNOTSUPP,
@ -538,9 +553,13 @@ fn do_unix_getsockopt(option: &mut dyn SocketOption, state: &State) -> Result<()
Ok(())
}
impl GetSocketLevelOption for State {
impl GetSocketLevelOption for (&State, SockType) {
fn socket_type(&self) -> SockType {
self.1
}
fn is_listening(&self) -> bool {
matches!(self, Self::Listen(_))
matches!(self.0, State::Listen(_))
}
}

View File

@ -13,6 +13,7 @@ use crate::{
options::{
AcceptConn, Broadcast, KeepAlive, Linger, PassCred, PeerCred, PeerGroups, Priority,
RecvBuf, RecvBufForce, ReuseAddr, ReusePort, SendBuf, SendBufForce, SocketOption,
SocketType,
macros::{sock_option_mut, sock_option_ref},
},
unix::{CUserCred, UNIX_DATAGRAM_DEFAULT_BUF_SIZE, UNIX_STREAM_DEFAULT_BUF_SIZE},
@ -20,6 +21,7 @@ use crate::{
prelude::*,
process::{UserNamespace, credentials::capabilities::CapSet, posix_thread::AsPosixThread},
security::lsm::hooks as lsm_hooks,
util::net::SockType,
};
#[derive(Clone, CopyGetters, Debug, Setters)]
@ -113,6 +115,9 @@ impl SocketOptionSet {
let reuse_addr = self.reuse_addr();
socket_reuse_addr.set(reuse_addr);
}
socket_type @ SocketType => {
socket_type.set(socket.socket_type());
}
socket_broadcast @ Broadcast => {
let broadcast = self.broadcast();
socket_broadcast.set(broadcast);
@ -286,6 +291,9 @@ pub const MIN_RECVBUF: u32 = 2304;
/// A trait used for getting socket level options on actual sockets.
pub(in crate::net) trait GetSocketLevelOption {
/// Returns the socket type.
fn socket_type(&self) -> SockType;
/// Returns whether the socket is in listening state.
fn is_listening(&self) -> bool;
}

View File

@ -27,10 +27,10 @@ pub fn sys_socket(domain: i32, type_: i32, protocol: i32, ctx: &Context) -> Resu
let is_nonblocking = sock_flags.contains(SockFlags::SOCK_NONBLOCK);
let file_like = match (domain, sock_type) {
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_STREAM) => {
UnixStreamSocket::new(is_nonblocking, false) as Arc<dyn FileLike>
UnixStreamSocket::new(is_nonblocking, sock_type) as Arc<dyn FileLike>
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_SEQPACKET) => {
UnixStreamSocket::new(is_nonblocking, true) as Arc<dyn FileLike>
UnixStreamSocket::new(is_nonblocking, sock_type) as Arc<dyn FileLike>
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_RAW | SockType::SOCK_DGRAM) => {
UnixDatagramSocket::new(is_nonblocking) as Arc<dyn FileLike>
@ -65,10 +65,10 @@ pub fn sys_socket(domain: i32, type_: i32, protocol: i32, ctx: &Context) -> Resu
debug!("netlink family = {:?}", netlink_family);
match netlink_family {
Ok(StandardNetlinkProtocol::ROUTE) => {
NetlinkRouteSocket::new(is_nonblocking) as Arc<dyn FileLike>
NetlinkRouteSocket::new(is_nonblocking, sock_type) as Arc<dyn FileLike>
}
Ok(StandardNetlinkProtocol::KOBJECT_UEVENT) => {
NetlinkUeventSocket::new(is_nonblocking) as Arc<dyn FileLike>
NetlinkUeventSocket::new(is_nonblocking, sock_type) as Arc<dyn FileLike>
}
Ok(_) => {
return_errno_with_message!(

View File

@ -39,10 +39,10 @@ pub fn sys_socketpair(
let nonblocking = sock_flags.contains(SockFlags::SOCK_NONBLOCK);
let (socket_a, socket_b) = match (domain, sock_type) {
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_STREAM) => {
file_pair!(UnixStreamSocket::new_pair(nonblocking, false))
file_pair!(UnixStreamSocket::new_pair(nonblocking, sock_type))
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_SEQPACKET) => {
file_pair!(UnixStreamSocket::new_pair(nonblocking, true))
file_pair!(UnixStreamSocket::new_pair(nonblocking, sock_type))
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_RAW | SockType::SOCK_DGRAM) => {
file_pair!(UnixDatagramSocket::new_pair(nonblocking))

View File

@ -8,6 +8,7 @@ use crate::{
net::socket::options::{
AcceptConn, Broadcast, Error, KeepAlive, Linger, PassCred, PeerCred, PeerGroups, Priority,
RecvBuf, RecvBufForce, ReuseAddr, ReusePort, SendBuf, SendBufForce, SocketOption,
SocketType,
},
prelude::*,
process::Gid,
@ -53,6 +54,7 @@ pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
let name = CSocketOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?;
match name {
CSocketOptionName::REUSEADDR => Ok(Box::new(ReuseAddr::new())),
CSocketOptionName::TYPE => Ok(Box::new(SocketType::new())),
CSocketOptionName::ERROR => Ok(Box::new(Error::new())),
CSocketOptionName::BROADCAST => Ok(Box::new(Broadcast::new())),
CSocketOptionName::SNDBUF => Ok(Box::new(SendBuf::new())),
@ -72,6 +74,7 @@ pub fn new_socket_option(name: i32) -> Result<Box<dyn RawSocketOption>> {
}
impl_raw_socket_option!(ReuseAddr);
impl_raw_sock_option_get_only!(SocketType);
impl_raw_sock_option_get_only!(Error);
impl_raw_socket_option!(Broadcast);
impl_raw_socket_option!(SendBuf);

View File

@ -12,6 +12,7 @@ use crate::{
util::LingerOption,
},
prelude::*,
util::net::SockType,
};
/// Create an object by reading its C counterpart from the user space.
@ -200,6 +201,12 @@ impl WriteToUser for CongestionControl {
}
}
impl WriteToUser for SockType {
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize> {
(*self as i32).write_to_user(addr, max_len)
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod)]
struct CLinger {

View File

@ -48,7 +48,7 @@ pub enum Protocol {
/// From <https://elixir.bootlin.com/linux/v6.0.9/source/include/linux/net.h>.
#[expect(non_camel_case_types)]
#[repr(i32)]
#[derive(Clone, Copy, Debug, TryFromInt)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromInt)]
pub enum SockType {
/// Stream socket
SOCK_STREAM = 1,

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
#include <netlink/netlink.h>
#include <sys/socket.h>
#include <unistd.h>
#include "../common/test.h"
@ -79,6 +80,22 @@ FN_TEST(getpeername)
}
END_TEST()
FN_TEST(socket_type)
{
int type = -1;
socklen_t type_len = sizeof(type);
TEST_RES(getsockopt(sk_unbound, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_DGRAM && type_len == sizeof(type));
int sk_raw = TEST_SUCC(
socket(PF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE));
TEST_RES(getsockopt(sk_raw, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_RAW && type_len == sizeof(type));
TEST_SUCC(close(sk_raw));
}
END_TEST()
FN_TEST(send)
{
char buf[1] = { 'z' };

View File

@ -122,6 +122,22 @@ FN_TEST(socket_error)
}
END_TEST()
FN_TEST(socket_type)
{
int type = -1;
socklen_t type_len = sizeof(type);
TEST_ERRNO(setsockopt(sk_unbound, SOL_SOCKET, SO_TYPE, &type, type_len),
ENOPROTOOPT);
TEST_RES(getsockopt(sk_unbound, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_STREAM && type_len == sizeof(type));
TEST_RES(getsockopt(sk_udp, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_DGRAM && type_len == sizeof(type));
}
END_TEST()
FN_TEST(nagle)
{
int option = 1;

View File

@ -82,6 +82,34 @@ FN_TEST(acceptconn)
}
END_TEST()
FN_TEST(socket_type)
{
int type = -1;
socklen_t type_len = sizeof(type);
TEST_ERRNO(setsockopt(sk_unbound, SOL_SOCKET, SO_TYPE, &type, type_len),
ENOPROTOOPT);
TEST_RES(getsockopt(sk_unbound, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_STREAM && type_len == sizeof(type));
int sk_dgram =
TEST_SUCC(socket(PF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0));
TEST_RES(getsockopt(sk_dgram, SOL_SOCKET, SO_TYPE, &type, &type_len),
type == SOCK_DGRAM && type_len == sizeof(type));
TEST_SUCC(close(sk_dgram));
int seqpacket_fds[2];
TEST_SUCC(socketpair(PF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0,
seqpacket_fds));
TEST_RES(getsockopt(seqpacket_fds[0], SOL_SOCKET, SO_TYPE, &type,
&type_len),
type == SOCK_SEQPACKET && type_len == sizeof(type));
TEST_SUCC(close(seqpacket_fds[0]));
TEST_SUCC(close(seqpacket_fds[1]));
}
END_TEST()
FN_TEST(pass_cred)
{
int val = 0;