mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 12:56:48 +00:00
Add ip level socket option
This commit is contained in:
parent
ffc7e3612d
commit
388eec449a
@ -3,6 +3,7 @@
|
||||
mod addr;
|
||||
mod common;
|
||||
pub mod datagram;
|
||||
pub mod options;
|
||||
pub mod stream;
|
||||
|
||||
use addr::UNSPECIFIED_LOCAL_ENDPOINT;
|
||||
|
108
kernel/src/net/socket/ip/options.rs
Normal file
108
kernel/src/net/socket/ip/options.rs
Normal 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<()>;
|
||||
}
|
@ -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();
|
||||
|
81
kernel/src/util/net/options/ip.rs
Normal file
81
kernel/src/util/net/options/ip.rs
Normal 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);
|
@ -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"),
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user