From 5d84ac77757f467b62a414eb773c2392107eb428 Mon Sep 17 00:00:00 2001 From: jiangjianfeng Date: Fri, 11 Apr 2025 07:33:40 +0000 Subject: [PATCH] Generate kernel response to user request --- .../net/socket/netlink/route/kernel/addr.rs | 70 +++++++++ .../net/socket/netlink/route/kernel/link.rs | 147 ++++++++++++++++++ .../net/socket/netlink/route/kernel/mod.rs | 74 +++++++++ .../net/socket/netlink/route/kernel/util.rs | 39 +++++ 4 files changed, 330 insertions(+) create mode 100644 kernel/src/net/socket/netlink/route/kernel/addr.rs create mode 100644 kernel/src/net/socket/netlink/route/kernel/link.rs create mode 100644 kernel/src/net/socket/netlink/route/kernel/mod.rs create mode 100644 kernel/src/net/socket/netlink/route/kernel/util.rs diff --git a/kernel/src/net/socket/netlink/route/kernel/addr.rs b/kernel/src/net/socket/netlink/route/kernel/addr.rs new file mode 100644 index 000000000..e3c4cd21a --- /dev/null +++ b/kernel/src/net/socket/netlink/route/kernel/addr.rs @@ -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> { + 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 = 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) -> Option { + 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)) +} diff --git a/kernel/src/net/socket/netlink/route/kernel/link.rs b/kernel/src/net/socket/netlink/route/kernel/link.rs new file mode 100644 index 000000000..786ac1789 --- /dev/null +++ b/kernel/src/net/socket/netlink/route/kernel/link.rs @@ -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> { + let filter_by = FilterBy::from_request(request_segment)?; + + let ifaces = IFACES.get().unwrap(); + let mut response_segments: Vec = 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 { + 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: . + +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: . + 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: . + 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 . + 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) -> 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) +} diff --git a/kernel/src/net/socket/netlink/route/kernel/mod.rs b/kernel/src/net/socket/netlink/route/kernel/mod.rs new file mode 100644 index 000000000..4635ab0b4 --- /dev/null +++ b/kernel/src/net/socket/netlink/route/kernel/mod.rs @@ -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( + &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: . + 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 +} diff --git a/kernel/src/net/socket/netlink/route/kernel/util.rs b/kernel/src/net/socket/netlink/route/kernel/util.rs new file mode 100644 index 000000000..596aa6d93 --- /dev/null +++ b/kernel/src/net/socket/netlink/route/kernel/util.rs @@ -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, +) { + 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) { + 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) { + 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(); + } +}