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