mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-24 18:03:25 +00:00
Generate kernel response to user request
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
3e66732889
commit
5d84ac7775
70
kernel/src/net/socket/netlink/route/kernel/addr.rs
Normal file
70
kernel/src/net/socket/netlink/route/kernel/addr.rs
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Handle address-related requests.
|
||||
|
||||
use core::num::NonZeroU32;
|
||||
|
||||
use super::util::finish_response;
|
||||
use crate::{
|
||||
net::{
|
||||
iface::{Iface, IFACES},
|
||||
socket::netlink::{
|
||||
message::{CMsgSegHdr, CSegmentType, GetRequestFlags, SegHdrCommonFlags},
|
||||
route::message::{
|
||||
AddrAttr, AddrMessageFlags, AddrSegment, AddrSegmentBody, RtScope, RtnlSegment,
|
||||
},
|
||||
},
|
||||
},
|
||||
prelude::*,
|
||||
util::net::CSocketAddrFamily,
|
||||
};
|
||||
|
||||
pub(super) fn do_get_addr(request_segment: &AddrSegment) -> Result<Vec<RtnlSegment>> {
|
||||
let dump_all = {
|
||||
let flags = GetRequestFlags::from_bits_truncate(request_segment.header().flags);
|
||||
flags.contains(GetRequestFlags::DUMP)
|
||||
};
|
||||
if !dump_all {
|
||||
return_errno_with_message!(Errno::EOPNOTSUPP, "GETADDR only supports dump requests");
|
||||
}
|
||||
|
||||
let ifaces = IFACES.get().unwrap();
|
||||
let mut response_segments: Vec<RtnlSegment> = ifaces
|
||||
.iter()
|
||||
// GETADDR only supports dump mode, so we're going to report all addresses.
|
||||
.filter_map(|iface| iface_to_new_addr(request_segment.header(), iface))
|
||||
.map(RtnlSegment::NewAddr)
|
||||
.collect();
|
||||
|
||||
finish_response(request_segment.header(), dump_all, &mut response_segments);
|
||||
|
||||
Ok(response_segments)
|
||||
}
|
||||
|
||||
fn iface_to_new_addr(request_header: &CMsgSegHdr, iface: &Arc<Iface>) -> Option<AddrSegment> {
|
||||
let ipv4_addr = iface.ipv4_addr()?;
|
||||
|
||||
let header = CMsgSegHdr {
|
||||
len: 0,
|
||||
type_: CSegmentType::NEWADDR as _,
|
||||
flags: SegHdrCommonFlags::empty().bits(),
|
||||
seq: request_header.seq,
|
||||
pid: request_header.pid,
|
||||
};
|
||||
|
||||
let addr_message = AddrSegmentBody {
|
||||
family: CSocketAddrFamily::AF_INET as _,
|
||||
prefix_len: iface.prefix_len().unwrap(),
|
||||
flags: AddrMessageFlags::PERMANENT,
|
||||
scope: RtScope::HOST,
|
||||
index: NonZeroU32::new(iface.index()),
|
||||
};
|
||||
|
||||
let attrs = vec![
|
||||
AddrAttr::Address(ipv4_addr.octets()),
|
||||
AddrAttr::Label(CString::new(iface.name()).unwrap()),
|
||||
AddrAttr::Local(ipv4_addr.octets()),
|
||||
];
|
||||
|
||||
Some(AddrSegment::new(header, addr_message, attrs))
|
||||
}
|
147
kernel/src/net/socket/netlink/route/kernel/link.rs
Normal file
147
kernel/src/net/socket/netlink/route/kernel/link.rs
Normal file
@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Handle link-related requests.
|
||||
|
||||
use core::num::NonZero;
|
||||
|
||||
use aster_bigtcp::iface::InterfaceType;
|
||||
|
||||
use super::util::finish_response;
|
||||
use crate::{
|
||||
net::{
|
||||
iface::{Iface, IFACES},
|
||||
socket::netlink::{
|
||||
message::{CMsgSegHdr, CSegmentType, GetRequestFlags, SegHdrCommonFlags},
|
||||
route::message::{LinkAttr, LinkSegment, LinkSegmentBody, RtnlSegment},
|
||||
},
|
||||
},
|
||||
prelude::*,
|
||||
util::net::CSocketAddrFamily,
|
||||
};
|
||||
|
||||
pub(super) fn do_get_link(request_segment: &LinkSegment) -> Result<Vec<RtnlSegment>> {
|
||||
let filter_by = FilterBy::from_request(request_segment)?;
|
||||
|
||||
let ifaces = IFACES.get().unwrap();
|
||||
let mut response_segments: Vec<RtnlSegment> = ifaces
|
||||
.iter()
|
||||
// Filter to include only requested links.
|
||||
.filter(|iface| match &filter_by {
|
||||
FilterBy::Index(index) => *index == iface.index(),
|
||||
FilterBy::Name(name) => *name == iface.name(),
|
||||
FilterBy::Dump => true,
|
||||
})
|
||||
.map(|iface| iface_to_new_link(request_segment.header(), iface))
|
||||
.map(RtnlSegment::NewLink)
|
||||
.collect();
|
||||
|
||||
let dump_all = matches!(filter_by, FilterBy::Dump);
|
||||
|
||||
if !dump_all && response_segments.is_empty() {
|
||||
return_errno_with_message!(Errno::ENODEV, "no link found");
|
||||
}
|
||||
|
||||
finish_response(request_segment.header(), dump_all, &mut response_segments);
|
||||
|
||||
Ok(response_segments)
|
||||
}
|
||||
|
||||
enum FilterBy<'a> {
|
||||
Index(u32),
|
||||
Name(&'a str),
|
||||
Dump,
|
||||
}
|
||||
|
||||
impl<'a> FilterBy<'a> {
|
||||
fn from_request(request_segment: &'a LinkSegment) -> Result<Self> {
|
||||
let dump_all = {
|
||||
let flags = GetRequestFlags::from_bits_truncate(request_segment.header().flags);
|
||||
flags.contains(GetRequestFlags::DUMP)
|
||||
};
|
||||
if dump_all {
|
||||
validate_dumplink_request(request_segment.body())?;
|
||||
return Ok(Self::Dump);
|
||||
}
|
||||
|
||||
validate_getlink_request(request_segment.body())?;
|
||||
|
||||
// `index` takes precedence over `required_name`.
|
||||
|
||||
if let Some(required_index) = request_segment.body().index {
|
||||
return Ok(Self::Index(required_index.get()));
|
||||
}
|
||||
|
||||
let required_name = request_segment.attrs().iter().find_map(|attr| {
|
||||
if let LinkAttr::Name(name) = attr {
|
||||
Some(name.to_str().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(required_name) = required_name {
|
||||
return Ok(Self::Name(required_name));
|
||||
}
|
||||
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"either interface name or index should be specified for non-dump mode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The below functions starting with `validate_` should only be enabled in strict mode.
|
||||
// Reference: <https://docs.kernel.org/userspace-api/netlink/intro.html#strict-checking>.
|
||||
|
||||
fn validate_getlink_request(body: &LinkSegmentBody) -> Result<()> {
|
||||
// FIXME: The Linux implementation also checks the `padding` and `change` fields,
|
||||
// but these fields are lost during the conversion of a `CIfInfoMsg` to `LinkSegmentBody`.
|
||||
// We should consider including the `change` field in `LinkSegmentBody`.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.13/source/net/core/rtnetlink.c#L4043>.
|
||||
if !body.flags.is_empty() || body.type_ != InterfaceType::NETROM {
|
||||
return_errno_with_message!(Errno::EINVAL, "the flags or the type is not valid");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_dumplink_request(body: &LinkSegmentBody) -> Result<()> {
|
||||
// FIXME: The Linux implementation also checks the `padding` and `change` fields.
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.13/source/net/core/rtnetlink.c#L2378>.
|
||||
if !body.flags.is_empty() || body.type_ != InterfaceType::NETROM {
|
||||
return_errno_with_message!(Errno::EINVAL, "the flags or the type is not valid");
|
||||
}
|
||||
|
||||
// The check is from <https://elixir.bootlin.com/linux/v6.13/source/net/core/rtnetlink.c#L2383>.
|
||||
if body.index.is_some() {
|
||||
return_errno_with_message!(
|
||||
Errno::EINVAL,
|
||||
"filtering by interface index is not valid for link dumps"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn iface_to_new_link(request_header: &CMsgSegHdr, iface: &Arc<Iface>) -> LinkSegment {
|
||||
let header = CMsgSegHdr {
|
||||
len: 0,
|
||||
type_: CSegmentType::NEWLINK as _,
|
||||
flags: SegHdrCommonFlags::empty().bits(),
|
||||
seq: request_header.seq,
|
||||
pid: request_header.pid,
|
||||
};
|
||||
|
||||
let link_message = LinkSegmentBody {
|
||||
family: CSocketAddrFamily::AF_UNSPEC,
|
||||
type_: iface.type_(),
|
||||
index: NonZero::new(iface.index()),
|
||||
flags: iface.flags(),
|
||||
};
|
||||
|
||||
let attrs = vec![
|
||||
LinkAttr::Name(CString::new(iface.name()).unwrap()),
|
||||
LinkAttr::Mtu(iface.mtu() as u32),
|
||||
];
|
||||
|
||||
LinkSegment::new(header, link_message, attrs)
|
||||
}
|
74
kernel/src/net/socket/netlink/route/kernel/mod.rs
Normal file
74
kernel/src/net/socket/netlink/route/kernel/mod.rs
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! This module defines the kernel socket,
|
||||
//! which is responsible for handling requests from user space.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::message::{RtnlMessage, RtnlSegment};
|
||||
use crate::{
|
||||
net::socket::netlink::message::{CSegmentType, ErrorSegment, ProtocolSegment},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
mod addr;
|
||||
mod link;
|
||||
mod util;
|
||||
|
||||
pub(super) struct NetlinkRouteKernelSocket {
|
||||
_private: PhantomData<()>,
|
||||
}
|
||||
|
||||
impl NetlinkRouteKernelSocket {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
_private: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request<F: FnMut(RtnlMessage)>(
|
||||
&self,
|
||||
request: &RtnlMessage,
|
||||
mut consume_response: F,
|
||||
) {
|
||||
debug!("netlink route request: {:?}", request);
|
||||
|
||||
for segment in request.segments() {
|
||||
let request_header = segment.header();
|
||||
|
||||
let segment_type = CSegmentType::try_from(request_header.type_).unwrap();
|
||||
|
||||
let response_segments = match segment {
|
||||
RtnlSegment::GetLink(request_segment) => link::do_get_link(request_segment),
|
||||
RtnlSegment::GetAddr(request_segment) => addr::do_get_addr(request_segment),
|
||||
_ => {
|
||||
// FIXME: The error is currently silently ignored.
|
||||
warn!("unsupported request type: {:?}", segment_type);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let response = match response_segments {
|
||||
Ok(segments) => RtnlMessage::new(segments),
|
||||
Err(error) => {
|
||||
// TODO: Deal with the `NetlinkMessageCommonFlags::ACK` flag.
|
||||
// Should we return `ErrorSegment` if ACK flag does not exist?
|
||||
// Reference: <https://docs.kernel.org/userspace-api/netlink/intro.html#netlink-message-types>.
|
||||
let err_segment = ErrorSegment::new_from_request(request_header, Some(error));
|
||||
RtnlMessage::new(vec![RtnlSegment::Error(err_segment)])
|
||||
}
|
||||
};
|
||||
|
||||
debug!("netlink route response: {:?}", response);
|
||||
|
||||
consume_response(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME: NETLINK_ROUTE_KERNEL should be a per-network namespace socket
|
||||
static NETLINK_ROUTE_KERNEL: NetlinkRouteKernelSocket = NetlinkRouteKernelSocket::new();
|
||||
|
||||
pub(super) fn get_netlink_route_kernel() -> &'static NetlinkRouteKernelSocket {
|
||||
&NETLINK_ROUTE_KERNEL
|
||||
}
|
39
kernel/src/net/socket/netlink/route/kernel/util.rs
Normal file
39
kernel/src/net/socket/netlink/route/kernel/util.rs
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::{
|
||||
net::socket::netlink::{
|
||||
message::{CMsgSegHdr, DoneSegment, ProtocolSegment, SegHdrCommonFlags},
|
||||
route::message::RtnlSegment,
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
/// Finishes a response message.
|
||||
pub fn finish_response(
|
||||
request_header: &CMsgSegHdr,
|
||||
dump_all: bool,
|
||||
response_segments: &mut Vec<RtnlSegment>,
|
||||
) {
|
||||
if !dump_all {
|
||||
assert_eq!(response_segments.len(), 1);
|
||||
return;
|
||||
}
|
||||
append_done_segment(request_header, response_segments);
|
||||
add_multi_flag(response_segments);
|
||||
}
|
||||
|
||||
/// Appends a done segment as the last segment of the provided segments.
|
||||
fn append_done_segment(request_header: &CMsgSegHdr, response_segments: &mut Vec<RtnlSegment>) {
|
||||
let done_segment = DoneSegment::new_from_request(request_header, None);
|
||||
response_segments.push(RtnlSegment::Done(done_segment));
|
||||
}
|
||||
|
||||
/// Adds the `MULTI` flag to all segments in `segments`.
|
||||
fn add_multi_flag(response_segments: &mut Vec<RtnlSegment>) {
|
||||
for segment in response_segments.iter_mut() {
|
||||
let header = segment.header_mut();
|
||||
let mut flags = SegHdrCommonFlags::from_bits_truncate(header.flags);
|
||||
flags |= SegHdrCommonFlags::MULTI;
|
||||
header.flags = flags.bits();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user