diff --git a/kernel/src/net/socket/ip/mod.rs b/kernel/src/net/socket/ip/mod.rs index 7e4c8c98..21bb2efc 100644 --- a/kernel/src/net/socket/ip/mod.rs +++ b/kernel/src/net/socket/ip/mod.rs @@ -3,6 +3,7 @@ mod addr; mod common; pub mod datagram; +pub mod options; pub mod stream; use addr::UNSPECIFIED_LOCAL_ENDPOINT; diff --git a/kernel/src/net/socket/ip/options.rs b/kernel/src/net/socket/ip/options.rs new file mode 100644 index 00000000..a14004ca --- /dev/null +++ b/kernel/src/net/socket/ip/options.rs @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::num::NonZeroU8; + +use aster_bigtcp::socket::NeedIfacePoll; + +use crate::{ + impl_socket_options, match_sock_option_mut, match_sock_option_ref, + net::socket::options::SocketOption, prelude::*, +}; + +/// IP-level socket options. +#[derive(Debug, Clone, Copy, CopyGetters, Setters)] +#[get_copy = "pub"] +#[set = "pub"] +pub(super) struct IpOptionSet { + tos: u8, + ttl: IpTtl, + hdrincl: bool, +} + +const DEFAULT_TTL: u8 = 64; +pub(super) const INET_ECN_MASK: u8 = 3; + +impl IpOptionSet { + pub(super) const fn new_tcp() -> Self { + Self { + tos: 0, + ttl: IpTtl(None), + hdrincl: false, + } + } + + pub(super) fn get_option(&self, option: &mut dyn SocketOption) -> Result<()> { + match_sock_option_mut!(option, { + ip_tos: Tos => { + let tos = self.tos(); + ip_tos.set(tos as _); + }, + ip_ttl: Ttl => { + let ttl = self.ttl(); + ip_ttl.set(ttl); + }, + ip_hdrincl: Hdrincl => { + let hdrincl = self.hdrincl(); + ip_hdrincl.set(hdrincl); + }, + _ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option is unknown") + }); + + Ok(()) + } + + pub(super) fn set_option( + &mut self, + option: &dyn SocketOption, + socket: &mut dyn SetIpLevelOption, + ) -> Result { + match_sock_option_ref!(option, { + ip_tos: Tos => { + let old_value = self.tos(); + let mut val = *ip_tos.get().unwrap() as u8; + val &= !INET_ECN_MASK; + val |= old_value & INET_ECN_MASK; + self.set_tos(val); + }, + ip_ttl: Ttl => { + let ttl = ip_ttl.get().unwrap(); + self.set_ttl(*ttl); + }, + ip_hdrincl: Hdrincl => { + let hdrincl = ip_hdrincl.get().unwrap(); + socket.set_hdrincl(*hdrincl)?; + self.set_hdrincl(*hdrincl); + }, + _ => return_errno_with_message!(Errno::ENOPROTOOPT, "the socket option to be set is unknown") + }); + + Ok(NeedIfacePoll::FALSE) + } +} + +impl_socket_options!( + pub struct Tos(i32); + pub struct Ttl(IpTtl); + pub struct Hdrincl(bool); +); + +#[derive(Debug, Clone, Copy)] +pub struct IpTtl(Option); + +impl IpTtl { + pub const fn new(val: Option) -> Self { + Self(val) + } + + pub const fn get(&self) -> u8 { + if let Some(val) = self.0 { + val.get() + } else { + DEFAULT_TTL + } + } +} + +pub trait SetIpLevelOption { + fn set_hdrincl(&self, _hdrincl: bool) -> Result<()>; +} diff --git a/kernel/src/net/socket/ip/stream/mod.rs b/kernel/src/net/socket/ip/stream/mod.rs index 25ebfd27..64180273 100644 --- a/kernel/src/net/socket/ip/stream/mod.rs +++ b/kernel/src/net/socket/ip/stream/mod.rs @@ -18,7 +18,10 @@ use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard}; use takeable::Takeable; use util::{Retrans, TcpOptionSet}; -use super::UNSPECIFIED_LOCAL_ENDPOINT; +use super::{ + options::{IpOptionSet, SetIpLevelOption}, + UNSPECIFIED_LOCAL_ENDPOINT, +}; use crate::{ events::IoEvents, fs::file_handle::FileLike, @@ -75,14 +78,16 @@ enum State { #[derive(Debug, Clone)] struct OptionSet { socket: SocketOptionSet, + ip: IpOptionSet, tcp: TcpOptionSet, } impl OptionSet { fn new() -> Self { let socket = SocketOptionSet::new_tcp(); + let ip = IpOptionSet::new_tcp(); let tcp = TcpOptionSet::new(); - OptionSet { socket, tcp } + OptionSet { socket, ip, tcp } } fn raw(&self) -> RawTcpOption { @@ -582,11 +587,19 @@ impl Socket for StreamSocket { let options = self.options.read(); + // Deal with socket-level options match options.socket.get_option(option) { Err(err) if err.error() == Errno::ENOPROTOOPT => (), res => return res, } + // Deal with IP-level options + match options.ip.get_option(option) { + Err(err) if err.error() == Errno::ENOPROTOOPT => (), + res => return res, + } + + // Deal with TCP-level options // FIXME: Here we only return the previously set values, without actually // asking the underlying sockets for the real, effective values. match_sock_option_mut!(option, { @@ -648,9 +661,18 @@ impl Socket for StreamSocket { let mut options = self.options.write(); let mut state = self.write_updated_state(); + // Deal with socket-level options let need_iface_poll = match options.socket.set_option(option, state.as_mut()) { Err(err) if err.error() == Errno::ENOPROTOOPT => { - do_tcp_setsockopt(option, &mut options, state.as_mut())? + // Deal with IP-level options + match options.ip.set_option(option, state.as_mut()) { + Err(err) if err.error() == Errno::ENOPROTOOPT => { + // Deal with TCP-level options + do_tcp_setsockopt(option, &mut options, state.as_mut())? + } + Err(err) => return Err(err), + Ok(need_iface_poll) => need_iface_poll, + } } Err(err) => return Err(err), Ok(need_iface_poll) => need_iface_poll, @@ -790,6 +812,15 @@ impl SetSocketLevelOption for State { } } +impl SetIpLevelOption for State { + fn set_hdrincl(&self, _hdrincl: bool) -> Result<()> { + return_errno_with_message!( + Errno::ENOPROTOOPT, + "IP_HDRINCL cannot be set on TCP sockets" + ); + } +} + impl Drop for StreamSocket { fn drop(&mut self) { let state = self.state.get_mut().take(); diff --git a/kernel/src/util/net/options/ip.rs b/kernel/src/util/net/options/ip.rs new file mode 100644 index 00000000..c1ac9a43 --- /dev/null +++ b/kernel/src/util/net/options/ip.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MPL-2.0 + +use int_to_c_enum::TryFromInt; + +use super::RawSocketOption; +use crate::{ + impl_raw_socket_option, + net::socket::ip::options::{Hdrincl, Tos, Ttl}, + prelude::*, + util::net::options::SocketOption, +}; + +/// Socket options for IP socket. +/// +/// The raw definitions can be found at: +/// https://elixir.bootlin.com/linux/v6.0.19/source/include/uapi/linux/in.h#L94 +#[repr(i32)] +#[derive(Debug, Clone, Copy, TryFromInt)] +#[expect(non_camel_case_types)] +#[expect(clippy::upper_case_acronyms)] +pub enum CIpOptionName { + TOS = 1, + TTL = 2, + HDRINCL = 3, + OPTIONS = 4, + ROUTER_ALERT = 5, + RECVOPTS = 6, + RETOPTS = 7, + PKTINFO = 8, + PKTOPTIONS = 9, + MTU_DISCOVER = 10, + RECVERR = 11, + RECVTTL = 12, + RECVTOS = 13, + MTU = 14, + FREEBIND = 15, + IPSEC_POLICY = 16, + XFRM_POLICY = 17, + PASSSEC = 18, + TRANSPARENT = 19, + ORIGDSTADDR = 20, + MINTTL = 21, + NODEFRAG = 22, + CHECKSUM = 23, + BIND_ADDRESS_NO_PORT = 24, + RECVFRAGSIZE = 25, + RECVERR_RFC4884 = 26, + MULTICAST_IF = 32, + MULTICAST_TTL = 33, + MULTICAST_LOOP = 34, + ADD_MEMBERSHIP = 35, + DROP_MEMBERSHIP = 36, + UNBLOCK_SOURCE = 37, + BLOCK_SOURCE = 38, + ADD_SOURCE_MEMBERSHIP = 39, + DROP_SOURCE_MEMBERSHIP = 40, + MSFILTER = 41, + MCAST_JOIN_GROUP = 42, + MCAST_BLOCK_SOURCE = 43, + MCAST_UNBLOCK_SOURCE = 44, + MCAST_LEAVE_GROUP = 45, + MCAST_JOIN_SOURCE_GROUP = 46, + MCAST_LEAVE_SOURCE_GROUP = 47, + MCAST_MSFILTER = 48, + MULTICAST_ALL = 49, + UNICAST_IF = 50, +} + +pub fn new_ip_option(name: i32) -> Result> { + let name = CIpOptionName::try_from(name).map_err(|_| Errno::ENOPROTOOPT)?; + match name { + CIpOptionName::TOS => Ok(Box::new(Tos::new())), + CIpOptionName::TTL => Ok(Box::new(Ttl::new())), + CIpOptionName::HDRINCL => Ok(Box::new(Hdrincl::new())), + _ => return_errno_with_message!(Errno::ENOPROTOOPT, "unsupported ip level option"), + } +} + +impl_raw_socket_option!(Ttl); +impl_raw_socket_option!(Tos); +impl_raw_socket_option!(Hdrincl); diff --git a/kernel/src/util/net/options/mod.rs b/kernel/src/util/net/options/mod.rs index 796e9829..aedc66e1 100644 --- a/kernel/src/util/net/options/mod.rs +++ b/kernel/src/util/net/options/mod.rs @@ -51,8 +51,11 @@ //! At the syscall level, the interface is unified for all options and does not need to be modified. //! +use ip::new_ip_option; + use crate::{net::socket::options::SocketOption, prelude::*}; +mod ip; mod socket; mod tcp; mod utils; @@ -133,6 +136,7 @@ pub fn new_raw_socket_option( ) -> Result> { match level { CSocketOptionLevel::SOL_SOCKET => new_socket_option(name), + CSocketOptionLevel::SOL_IP => new_ip_option(name), CSocketOptionLevel::SOL_TCP => new_tcp_option(name), _ => return_errno_with_message!(Errno::EOPNOTSUPP, "unsupported option level"), } diff --git a/kernel/src/util/net/options/utils.rs b/kernel/src/util/net/options/utils.rs index ab623141..24cb19f2 100644 --- a/kernel/src/util/net/options/utils.rs +++ b/kernel/src/util/net/options/utils.rs @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 -use core::time::Duration; +use core::{num::NonZeroU8, time::Duration}; use crate::{ current_userspace, - net::socket::{ip::stream::CongestionControl, LingerOption}, + net::socket::{ + ip::{options::IpTtl, stream::CongestionControl}, + LingerOption, + }, prelude::*, }; @@ -94,6 +97,27 @@ impl WriteToUser for u8 { } } +impl ReadFromUser for IpTtl { + fn read_from_user(addr: Vaddr, max_len: u32) -> Result { + let val = i32::read_from_user(addr, max_len)?; + + let ttl_value = match val { + -1 => None, + 1..255 => Some(NonZeroU8::new(val as u8).unwrap()), + _ => return_errno_with_message!(Errno::EINVAL, "invalid ttl value"), + }; + + Ok(IpTtl::new(ttl_value)) + } +} + +impl WriteToUser for IpTtl { + fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result { + let val = self.get() as i32; + val.write_to_user(addr, max_len) + } +} + impl WriteToUser for Option { fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result { let write_len = core::mem::size_of::(); diff --git a/test/apps/network/sockoption.c b/test/apps/network/sockoption.c index 1c355120..52f696c4 100644 --- a/test/apps/network/sockoption.c +++ b/test/apps/network/sockoption.c @@ -55,6 +55,10 @@ FN_TEST(invalid_socket_option) TEST_ERRNO(getsockopt(sk_connected, IPPROTO_TCP, INVALID_TCP_OPTION, &res, &res_len), ENOPROTOOPT); +#define INVALID_IP_OPTION 99999 + TEST_ERRNO(getsockopt(sk_connected, IPPROTO_IP, INVALID_IP_OPTION, &res, + &res_len), + ENOPROTOOPT); } END_TEST() @@ -274,3 +278,85 @@ FN_TEST(keepidle) keepidle == 200); } END_TEST() + +FN_TEST(ip_tos) +{ + int tos; + socklen_t tos_len = sizeof(tos); + + // 1. Check default value + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, &tos_len), + tos == 0 && tos_len == 4); + + // 2. Set and get value + tos = 0x10; + CHECK(setsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, tos_len)); + tos = 0; + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, &tos_len), + tos == 0x10 && tos_len == 4); + + tos = 0x123; + CHECK(setsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, tos_len)); + tos = 0; + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, &tos_len), + tos == 32 && tos_len == 4); + + tos = 0x1111; + CHECK(setsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, tos_len)); + tos = 0; + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TOS, &tos, &tos_len), + tos == 16 && tos_len == 4); +} +END_TEST() + +FN_TEST(ip_ttl) +{ + int ttl; + socklen_t ttl_len = sizeof(ttl); + + // 1. Check default value + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, &ttl_len), + ttl == 64 && ttl_len == 4); + + // 2. Set and get value + ttl = 0x0; + TEST_ERRNO(setsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, ttl_len), + EINVAL); + + ttl = 0x100; + TEST_ERRNO(setsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, ttl_len), + EINVAL); + + ttl = 0x10; + CHECK(setsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, ttl_len)); + + ttl = 0; + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, &ttl_len), + ttl == 0x10 && ttl_len == 4); + + ttl = -1; + CHECK(setsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, ttl_len)); + + ttl = 0; + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_TTL, &ttl, &ttl_len), + ttl == 64 && ttl_len == 4); +} +END_TEST() + +FN_TEST(ip_hdrincl) +{ + int hdrincl; + socklen_t hdrincl_len = sizeof(hdrincl); + + // 1. Check default value + TEST_RES(getsockopt(sk_unbound, IPPROTO_IP, IP_HDRINCL, &hdrincl, + &hdrincl_len), + hdrincl == 0 && hdrincl_len == 4); + + // 2. Set value + hdrincl = 0x10; + TEST_ERRNO(setsockopt(sk_unbound, IPPROTO_IP, IP_HDRINCL, &hdrincl, + hdrincl_len), + ENOPROTOOPT); +} +END_TEST()