diff --git a/apps/starry/qemu/apk-curl/qemu-aarch64.toml b/apps/starry/qemu/apk-curl/qemu-aarch64.toml index de027ec1b1..7b42580f55 100644 --- a/apps/starry/qemu/apk-curl/qemu-aarch64.toml +++ b/apps/starry/qemu/apk-curl/qemu-aarch64.toml @@ -19,4 +19,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/apk-curl-tests.sh" success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/apps/starry/qemu/apk-curl/qemu-loongarch64.toml b/apps/starry/qemu/apk-curl/qemu-loongarch64.toml index fdfffd11ae..135b1f22b7 100644 --- a/apps/starry/qemu/apk-curl/qemu-loongarch64.toml +++ b/apps/starry/qemu/apk-curl/qemu-loongarch64.toml @@ -21,4 +21,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/apk-curl-tests.sh" success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/apps/starry/qemu/apk-curl/qemu-riscv64.toml b/apps/starry/qemu/apk-curl/qemu-riscv64.toml index b87ef107ef..2e83d3fac3 100644 --- a/apps/starry/qemu/apk-curl/qemu-riscv64.toml +++ b/apps/starry/qemu/apk-curl/qemu-riscv64.toml @@ -19,4 +19,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/apk-curl-tests.sh" success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/apps/starry/qemu/apk-curl/qemu-x86_64.toml b/apps/starry/qemu/apk-curl/qemu-x86_64.toml index d3eaf3dda7..c7b27d2e11 100644 --- a/apps/starry/qemu/apk-curl/qemu-x86_64.toml +++ b/apps/starry/qemu/apk-curl/qemu-x86_64.toml @@ -19,4 +19,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/apk-curl-tests.sh" success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 420 +timeout = 720 diff --git a/apps/starry/qemu/apk-curl/sh/apk-curl-tests.sh b/apps/starry/qemu/apk-curl/sh/apk-curl-tests.sh index eb21e632a9..ab80ee737b 100644 --- a/apps/starry/qemu/apk-curl/sh/apk-curl-tests.sh +++ b/apps/starry/qemu/apk-curl/sh/apk-curl-tests.sh @@ -1,12 +1,13 @@ #!/bin/sh -fetch_timeout=60 +default_fetch_timeout=180 repo_file=/etc/apk/repositories original_repos="$(cat "$repo_file")" try_apk_curl() { mirror="$1" label="$2" + fetch_timeout="$3" probe_url="$mirror/MIRRORS.txt" printf '%s\n' "$original_repos" | sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" @@ -26,14 +27,16 @@ try_apk_curl() { i=0 for repo in \ - "https://mirrors.cernet.edu.cn/alpine cernet" \ - "https://dl-cdn.alpinelinux.org/alpine upstream" + "https://mirrors.cernet.edu.cn/alpine cernet 60" \ + "https://dl-cdn.alpinelinux.org/alpine upstream $default_fetch_timeout" do i=$((i + 1)) - mirror=${repo% *} - label=${repo#* } + set -- $repo + mirror=$1 + label=$2 + fetch_timeout=$3 echo "APK_CURL_ATTEMPT_$i" - if try_apk_curl "$mirror" "$label"; then + if try_apk_curl "$mirror" "$label" "$fetch_timeout"; then echo 'APK_CURL_TEST_PASSED' exit 0 fi diff --git a/os/StarryOS/kernel/src/syscall/net/opt.rs b/os/StarryOS/kernel/src/syscall/net/opt.rs index 9a5ed2e39b..ccc6c8e46f 100644 --- a/os/StarryOS/kernel/src/syscall/net/opt.rs +++ b/os/StarryOS/kernel/src/syscall/net/opt.rs @@ -1,6 +1,12 @@ use ax_errno::{AxError, AxResult, LinuxError}; -use axnet::options::{Configurable, GetSocketOption, SetSocketOption}; -use linux_raw_sys::net::{IPPROTO_IPV6, IPV6_V6ONLY, socklen_t}; +use axnet::options::{ + Configurable, GetSocketOption, SetSocketOption, TcpInfo, TcpInfoOptions, TcpState, +}; +use linux_raw_sys::net::{ + IPPROTO_IPV6, IPV6_V6ONLY, TCP_INFO, TCPI_OPT_ECN, TCPI_OPT_ECN_SEEN, TCPI_OPT_SACK, + TCPI_OPT_SYN_DATA, TCPI_OPT_TIMESTAMPS, TCPI_OPT_WSCALE, socklen_t, tcp_info, +}; +use starry_vm::vm_write_slice; use crate::{ file::{FileLike, Socket, netlink::NetlinkSocket}, @@ -18,6 +24,93 @@ fn read_int_sockopt(optval: UserConstPtr, optlen: socklen_t) -> AxResult().get_as_ref()?) } +fn tcp_state_to_linux(state: TcpState) -> u8 { + match state { + TcpState::Established => 1, + TcpState::SynSent => 2, + TcpState::SynReceived => 3, + TcpState::FinWait1 => 4, + TcpState::FinWait2 => 5, + TcpState::TimeWait => 6, + TcpState::Closed => 7, + TcpState::CloseWait => 8, + TcpState::LastAck => 9, + TcpState::Listen => 10, + TcpState::Closing => 11, + } +} + +fn tcp_options_to_linux(options: TcpInfoOptions) -> u8 { + let mut raw = 0u8; + if options.contains(TcpInfoOptions::TIMESTAMPS) { + raw |= TCPI_OPT_TIMESTAMPS as u8; + } + if options.contains(TcpInfoOptions::SACK) { + raw |= TCPI_OPT_SACK as u8; + } + if options.contains(TcpInfoOptions::WSCALE) { + raw |= TCPI_OPT_WSCALE as u8; + } + if options.contains(TcpInfoOptions::ECN) { + raw |= TCPI_OPT_ECN as u8; + } + if options.contains(TcpInfoOptions::ECN_SEEN) { + raw |= TCPI_OPT_ECN_SEEN as u8; + } + if options.contains(TcpInfoOptions::SYN_DATA) { + raw |= TCPI_OPT_SYN_DATA as u8; + } + raw +} + +fn to_linux_tcp_info(info: TcpInfo) -> tcp_info { + // SAFETY: linux-raw-sys tcp_info is a plain C data record. An all-zero + // value is a valid baseline before the supported fields are filled below. + let mut raw = unsafe { core::mem::zeroed::() }; + raw.tcpi_state = tcp_state_to_linux(info.state); + raw.tcpi_ca_state = info.ca_state; + raw.tcpi_retransmits = info.retransmits; + raw.tcpi_probes = info.probes; + raw.tcpi_backoff = info.backoff; + raw.tcpi_options = tcp_options_to_linux(info.options); + raw.set_tcpi_snd_wscale(info.snd_wscale); + raw.set_tcpi_rcv_wscale(info.rcv_wscale); + raw.tcpi_rto = info.rto_micros; + raw.tcpi_ato = info.ato_micros; + raw.tcpi_snd_mss = info.snd_mss; + raw.tcpi_rcv_mss = info.rcv_mss; + raw.tcpi_notsent_bytes = info.notsent_bytes; + raw.tcpi_pmtu = info.pmtu; + raw.tcpi_advmss = info.advmss; + raw.tcpi_snd_cwnd = info.snd_cwnd; + raw.tcpi_reordering = info.reordering; + raw.tcpi_rcv_space = info.rcv_space; + raw.tcpi_snd_wnd = info.snd_wnd; + raw.tcpi_rcv_wnd = info.rcv_wnd; + raw +} + +fn write_tcp_info(socket: &Socket, optval: UserPtr, optlen: &mut socklen_t) -> AxResult<()> { + let mut info = TcpInfo::default(); + socket.get_option(GetSocketOption::TcpInfo(&mut info))?; + + let write_len = (*optlen as usize).min(size_of::()); + *optlen = write_len as socklen_t; + if write_len == 0 { + return Ok(()); + } + + let raw = to_linux_tcp_info(info); + // SAFETY: raw lives for the whole copy and is viewed as its C byte layout. + let raw_bytes = unsafe { + core::slice::from_raw_parts( + (&raw as *const tcp_info).cast::(), + size_of::(), + ) + }; + Ok(vm_write_slice(optval.as_ptr(), &raw_bytes[..write_len])?) +} + mod conv { use ax_errno::{AxError, AxResult}; use axnet::options::UnixCredentials; @@ -110,7 +203,6 @@ macro_rules! call_dispatch { (PROTO_TCP, TCP_KEEPINTVL) => TcpKeepInterval as Int, (PROTO_TCP, TCP_KEEPCNT) => TcpKeepCount as Int, (PROTO_TCP, TCP_USER_TIMEOUT) => TcpUserTimeout as Int, - (PROTO_TCP, TCP_INFO) => TcpInfo, // TODO: stub, returns empty struct (PROTO_IP, IP_TTL) => Ttl as Int, (PROTO_IP, IP_RECVERR) => RecvErr as IntBool, // TODO: hardcoded false, no errqueue support @@ -198,6 +290,11 @@ pub fn sys_getsockopt( return Ok(0); } + if level == PROTO_TCP && optname == TCP_INFO { + write_tcp_info(&socket, optval, optlen)?; + return Ok(0); + } + macro_rules! dispatch { ($which:ident) => { socket.get_option(GetSocketOption::$which(get(optval, optlen)?))?; @@ -269,6 +366,10 @@ pub fn sys_setsockopt( } let socket = Socket::from_fd(fd)?; + if level == PROTO_TCP && optname == TCP_INFO { + return Err(AxError::from(LinuxError::ENOPROTOOPT)); + } + if level == IPPROTO_IPV6 as u32 && optname == IPV6_V6ONLY { // TODO: Store and enforce IPV6_V6ONLY once native IPv6 sockets exist. let _ = *get::(optval, optlen)?; diff --git a/os/arceos/modules/axnet-ng/src/options.rs b/os/arceos/modules/axnet-ng/src/options.rs index 608aa79a67..9b3e798dac 100644 --- a/os/arceos/modules/axnet-ng/src/options.rs +++ b/os/arceos/modules/axnet-ng/src/options.rs @@ -4,6 +4,98 @@ use core::time::Duration; use ax_errno::{AxError, AxResult, LinuxError}; use enum_dispatch::enum_dispatch; +/// Linux-like TCP connection state reported by TCP_INFO. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum TcpState { + /// No active TCP connection. + #[default] + Closed, + /// Passive listener. + Listen, + /// SYN sent, waiting for a matching SYN+ACK. + SynSent, + /// SYN received, waiting for the final ACK. + SynReceived, + /// Fully established connection. + Established, + /// Local endpoint has closed and sent FIN. + FinWait1, + /// Local FIN has been acknowledged. + FinWait2, + /// Remote endpoint has closed first. + CloseWait, + /// Both endpoints have closed simultaneously. + Closing, + /// Waiting for ACK of the local FIN after remote close. + LastAck, + /// Waiting for delayed packets to expire. + TimeWait, +} + +bitflags::bitflags! { + /// Negotiated TCP options reported by TCP_INFO. + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] + pub struct TcpInfoOptions: u8 { + /// TCP timestamps are enabled. + const TIMESTAMPS = 1 << 0; + /// Selective ACK is enabled. + const SACK = 1 << 1; + /// Window scaling is enabled. + const WSCALE = 1 << 2; + /// Explicit congestion notification is enabled. + const ECN = 1 << 3; + /// ECN has been seen on the connection. + const ECN_SEEN = 1 << 4; + /// SYN data was used by the connection. + const SYN_DATA = 1 << 5; + } +} + +/// Transport-independent TCP_INFO snapshot. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct TcpInfo { + /// Current TCP state. + pub state: TcpState, + /// Congestion-avoidance state. Zero means open. + pub ca_state: u8, + /// Number of unacknowledged retransmits. + pub retransmits: u8, + /// Number of pending keepalive or zero-window probes. + pub probes: u8, + /// Exponential backoff counter. + pub backoff: u8, + /// Negotiated TCP options. + pub options: TcpInfoOptions, + /// Send window scale. + pub snd_wscale: u8, + /// Receive window scale. + pub rcv_wscale: u8, + /// Retransmission timeout in microseconds. + pub rto_micros: u32, + /// Delayed ACK timeout in microseconds. + pub ato_micros: u32, + /// Send maximum segment size. + pub snd_mss: u32, + /// Receive maximum segment size. + pub rcv_mss: u32, + /// Bytes currently queued for transmit. + pub notsent_bytes: u32, + /// Advertised path MTU. + pub pmtu: u32, + /// Advertised maximum segment size. + pub advmss: u32, + /// Current send congestion window, in segments. + pub snd_cwnd: u32, + /// Packet reordering tolerance. + pub reordering: u32, + /// Available receive buffer space. + pub rcv_space: u32, + /// Current send window estimate. + pub snd_wnd: u32, + /// Current receive window estimate. + pub rcv_wnd: u32, +} + macro_rules! define_options { ($($name:ident($value:ty),)*) => { /// Operation to get a socket option. @@ -75,7 +167,7 @@ define_options! { TcpKeepInterval(u32), TcpKeepCount(u32), TcpUserTimeout(u32), - TcpInfo(()), + TcpInfo(TcpInfo), // ---- IP level options (IP_*) ---- Ttl(u8), diff --git a/os/arceos/modules/axnet-ng/src/tcp.rs b/os/arceos/modules/axnet-ng/src/tcp.rs index 6412247e62..f4a2c7e94c 100644 --- a/os/arceos/modules/axnet-ng/src/tcp.rs +++ b/os/arceos/modules/axnet-ng/src/tcp.rs @@ -24,7 +24,7 @@ use crate::{ consts::{TCP_RX_BUF_LEN, TCP_TX_BUF_LEN}, general::GeneralOptions, get_service, - options::{Configurable, GetSocketOption, SetSocketOption}, + options::{Configurable, GetSocketOption, SetSocketOption, TcpInfo, TcpInfoOptions, TcpState}, poll_interfaces, state::*, }; @@ -43,6 +43,10 @@ const TCP_USER_TIMEOUT_DEFAULT_MS: u32 = 0; const TCP_KEEPIDLE_MAX_SECS: u32 = 32767; const TCP_KEEPINTVL_MAX_SECS: u32 = 32767; const TCP_KEEPCNT_MAX: u32 = 127; +const TCP_INFO_DEFAULT_MSS: u32 = 1460; +const TCP_INFO_DEFAULT_PMTU: u32 = 1500; +const TCP_INFO_INITIAL_RTO_MICROS: u32 = 1_000_000; +const TCP_INFO_DEFAULT_REORDERING: u32 = 3; /// A TCP socket that provides POSIX-like APIs. pub struct TcpSocket { @@ -141,6 +145,36 @@ impl TcpSocket { Duration::from_secs(self.keep_idle_secs.load(Ordering::Relaxed) as u64) } + fn tcp_info_snapshot(&self) -> TcpInfo { + self.with_smol_socket(|socket| { + let send_queue = saturating_u32(socket.send_queue()); + let snd_mss = TCP_INFO_DEFAULT_MSS; + + let mut options = TcpInfoOptions::empty(); + if socket.timestamp_enabled() { + options |= TcpInfoOptions::TIMESTAMPS; + } + + TcpInfo { + state: tcp_state_info(socket.state()), + options, + rto_micros: socket + .timeout() + .map(duration_micros_u32) + .unwrap_or(TCP_INFO_INITIAL_RTO_MICROS), + ato_micros: socket.ack_delay().map(duration_micros_u32).unwrap_or(0), + snd_mss, + rcv_mss: snd_mss, + notsent_bytes: send_queue, + pmtu: TCP_INFO_DEFAULT_PMTU, + advmss: snd_mss, + reordering: TCP_INFO_DEFAULT_REORDERING, + snd_wnd: 0, + ..Default::default() + } + }) + } + fn bound_endpoint(&self) -> AxResult { let endpoint = *self.bound_endpoint.lock(); if endpoint.port == 0 { @@ -266,8 +300,8 @@ impl Configurable for TcpSocket { O::ReceiveBuffer(size) => { **size = TCP_RX_BUF_LEN; } - O::TcpInfo(_) => { - // TODO(mivik): implement TCP_INFO + O::TcpInfo(info) => { + **info = self.tcp_info_snapshot(); } _ => return Ok(false), } @@ -629,6 +663,30 @@ impl Drop for TcpSocket { } } +fn saturating_u32(value: usize) -> u32 { + value.min(u32::MAX as usize) as u32 +} + +fn duration_micros_u32(value: Duration) -> u32 { + value.total_micros().min(u32::MAX as u64) as u32 +} + +fn tcp_state_info(state: smol::State) -> TcpState { + match state { + smol::State::Closed => TcpState::Closed, + smol::State::Listen => TcpState::Listen, + smol::State::SynSent => TcpState::SynSent, + smol::State::SynReceived => TcpState::SynReceived, + smol::State::Established => TcpState::Established, + smol::State::FinWait1 => TcpState::FinWait1, + smol::State::FinWait2 => TcpState::FinWait2, + smol::State::CloseWait => TcpState::CloseWait, + smol::State::Closing => TcpState::Closing, + smol::State::LastAck => TcpState::LastAck, + smol::State::TimeWait => TcpState::TimeWait, + } +} + const fn empty_endpoint() -> IpListenEndpoint { IpListenEndpoint { addr: None, @@ -800,13 +858,36 @@ mod tests { use super::*; use crate::{ - options::{Configurable, SetSocketOption}, + options::{Configurable, GetSocketOption, SetSocketOption, TcpState}, test_support::{ LOCAL_ADDR, LOCAL_MASK, PEER_ADDR, PEER_MASK, init_split_route_network, network_test_guard, }, }; + #[test] + fn tcp_info_reports_default_socket_metrics() { + let _guard = network_test_guard(); + init_split_route_network(); + + let socket = TcpSocket::new(); + let mut info = TcpInfo::default(); + + socket + .get_option(GetSocketOption::TcpInfo(&mut info)) + .unwrap(); + + assert_eq!(info.state, TcpState::Closed); + assert_eq!(info.snd_mss, TCP_INFO_DEFAULT_MSS); + assert_eq!(info.rcv_mss, TCP_INFO_DEFAULT_MSS); + assert_eq!(info.pmtu, TCP_INFO_DEFAULT_PMTU); + assert_eq!(info.notsent_bytes, 0); + assert_eq!(info.snd_wnd, 0); + assert_eq!(info.snd_cwnd, 0); + assert_eq!(info.rcv_space, 0); + assert_eq!(info.rcv_wnd, 0); + } + #[test] fn connect_uses_peer_route_for_device_mask() { let _guard = network_test_guard(); diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/CMakeLists.txt new file mode 100644 index 0000000000..c2fd1be495 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-tcp-info C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-tcp-info src/main.c) +target_include_directories(test-tcp-info PRIVATE src ${CMAKE_CURRENT_SOURCE_DIR}/../../common) +target_compile_options(test-tcp-info PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-tcp-info RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/src/main.c new file mode 100644 index 0000000000..75f534f959 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-tcp-info/c/src/main.c @@ -0,0 +1,145 @@ +#include "test_framework.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef TCP_CLOSE +#define TCP_CLOSE 7 +#endif + +#ifndef TCP_INFO +#define TCP_INFO 11 +#endif + +struct linux_tcp_info_tail { + uint8_t tcpi_state; + uint8_t tcpi_ca_state; + uint8_t tcpi_retransmits; + uint8_t tcpi_probes; + uint8_t tcpi_backoff; + uint8_t tcpi_options; + uint8_t tcpi_snd_rcv_wscale; + uint8_t tcpi_delivery_fastopen; + uint32_t tcpi_rto; + uint32_t tcpi_ato; + uint32_t tcpi_snd_mss; + uint32_t tcpi_rcv_mss; + uint32_t tcpi_unacked; + uint32_t tcpi_sacked; + uint32_t tcpi_lost; + uint32_t tcpi_retrans; + uint32_t tcpi_fackets; + uint32_t tcpi_last_data_sent; + uint32_t tcpi_last_ack_sent; + uint32_t tcpi_last_data_recv; + uint32_t tcpi_last_ack_recv; + uint32_t tcpi_pmtu; + uint32_t tcpi_rcv_ssthresh; + uint32_t tcpi_rtt; + uint32_t tcpi_rttvar; + uint32_t tcpi_snd_ssthresh; + uint32_t tcpi_snd_cwnd; + uint32_t tcpi_advmss; + uint32_t tcpi_reordering; + uint32_t tcpi_rcv_rtt; + uint32_t tcpi_rcv_space; + uint32_t tcpi_total_retrans; + uint64_t tcpi_pacing_rate; + uint64_t tcpi_max_pacing_rate; + uint64_t tcpi_bytes_acked; + uint64_t tcpi_bytes_received; + uint32_t tcpi_segs_out; + uint32_t tcpi_segs_in; + uint32_t tcpi_notsent_bytes; + uint32_t tcpi_min_rtt; + uint32_t tcpi_data_segs_in; + uint32_t tcpi_data_segs_out; + uint64_t tcpi_delivery_rate; + uint64_t tcpi_busy_time; + uint64_t tcpi_rwnd_limited; + uint64_t tcpi_sndbuf_limited; + uint32_t tcpi_delivered; + uint32_t tcpi_delivered_ce; + uint64_t tcpi_bytes_sent; + uint64_t tcpi_bytes_retrans; + uint32_t tcpi_dsack_dups; + uint32_t tcpi_reord_seen; + uint32_t tcpi_rcv_ooopack; + uint32_t tcpi_snd_wnd; +}; + +static void test_tcp_info_full_copy(void) +{ + int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + CHECK(fd >= 0, "create TCP socket"); + if (fd < 0) { + return; + } + + struct linux_tcp_info_tail info; + memset(&info, 0xa5, sizeof(info)); + socklen_t len = sizeof(info); + + CHECK_RET(getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len), 0, + "TCP_INFO succeeds on TCP socket"); + CHECK(len <= sizeof(info), "TCP_INFO returns bounded optlen"); + socklen_t snd_wnd_end = + offsetof(struct linux_tcp_info_tail, tcpi_snd_wnd) + sizeof(info.tcpi_snd_wnd); + CHECK(len >= snd_wnd_end, + "TCP_INFO includes tcpi_snd_wnd"); + CHECK(info.tcpi_state == TCP_CLOSE, "new TCP socket reports TCP_CLOSE"); + CHECK(info.tcpi_snd_wnd == 0, "tcpi_snd_wnd is zero when peer window is unknown"); + + close(fd); +} + +static void test_tcp_info_short_copy(void) +{ + int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + CHECK(fd >= 0, "create TCP socket for short TCP_INFO copy"); + if (fd < 0) { + return; + } + + uint8_t value = 0xa5; + socklen_t len = sizeof(value); + + CHECK_RET(getsockopt(fd, IPPROTO_TCP, TCP_INFO, &value, &len), 0, + "TCP_INFO accepts a short optlen"); + CHECK(len == sizeof(value), "TCP_INFO reports the short copied length"); + CHECK(value == TCP_CLOSE, "short TCP_INFO copy returns tcpi_state byte"); + + close(fd); +} + +static void test_tcp_info_udp_rejected(void) +{ + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + CHECK(fd >= 0, "create UDP socket"); + if (fd < 0) { + return; + } + + struct linux_tcp_info_tail info; + socklen_t len = sizeof(info); + CHECK_ERR(getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len), ENOPROTOOPT, + "TCP_INFO is rejected on UDP sockets"); + + close(fd); +} + +int main(void) +{ + TEST_START("getsockopt TCP_INFO semantics"); + + test_tcp_info_full_copy(); + test_tcp_info_short_copy(); + test_tcp_info_udp_rejected(); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt index 2c5f328ae9..4a2dcf6edb 100644 --- a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt +++ b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/CMakeLists.txt @@ -7,3 +7,4 @@ add_executable(test-dup2 src/main.c) target_include_directories(test-dup2 PRIVATE src) target_compile_options(test-dup2 PRIVATE -Wall -Wextra -Werror) install(TARGETS test-dup2 RUNTIME DESTINATION usr/bin) +install(PROGRAMS profile.d/99-test-dup2-autorun.sh DESTINATION etc/profile.d) diff --git a/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh new file mode 100644 index 0000000000..120145c02f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-dup2/c/profile.d/99-test-dup2-autorun.sh @@ -0,0 +1,9 @@ +if [ "${AXBUILD_TEST_DUP2_AUTORUN_DONE:-0}" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + +export AXBUILD_TEST_DUP2_AUTORUN_DONE=1 + +if [ -x /usr/bin/test-dup2 ]; then + /usr/bin/test-dup2 +fi