Generate kernel response to user request

This commit is contained in:
jiangjianfeng
2025-04-11 07:33:40 +00:00
committed by Tate, Hongliang Tian
parent 3e66732889
commit 5d84ac7775
4 changed files with 330 additions and 0 deletions

View 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))
}

View 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)
}

View 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
}

View 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();
}
}