Add ip level socket option

This commit is contained in:
jiangjianfeng 2025-01-16 08:55:28 +00:00 committed by Tate, Hongliang Tian
parent ffc7e3612d
commit 388eec449a
7 changed files with 340 additions and 5 deletions

View File

@ -3,6 +3,7 @@
mod addr;
mod common;
pub mod datagram;
pub mod options;
pub mod stream;
use addr::UNSPECIFIED_LOCAL_ENDPOINT;

View File

@ -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<NeedIfacePoll> {
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<NonZeroU8>);
impl IpTtl {
pub const fn new(val: Option<NonZeroU8>) -> 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<()>;
}

View File

@ -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();

View File

@ -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<Box<dyn RawSocketOption>> {
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);

View File

@ -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<Box<dyn RawSocketOption>> {
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"),
}

View File

@ -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<Self> {
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<usize> {
let val = self.get() as i32;
val.write_to_user(addr, max_len)
}
}
impl WriteToUser for Option<Error> {
fn write_to_user(&self, addr: Vaddr, max_len: u32) -> Result<usize> {
let write_len = core::mem::size_of::<i32>();

View File

@ -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()