Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kernel/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ linkme = "=0.3.27"
num = { version = "=0.4.0", default-features = false }
num-derive = "=0.3"
num-traits = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/num-traits.git", rev = "1597c1c", default-features = false }
smoltcp = { version = "=0.12.0", git = "https://github.com/DragonOS-Community/smoltcp", rev = "225c271ef40fa483c60a8f69c8d000d2af58a483", default-features = false, features = [
smoltcp = { version = "=0.12.0", git = "https://github.com/DragonOS-Community/smoltcp", rev = "9805dae47858c7247303d9d6d19ca504632c1e4a", default-features = false, features = [
"alloc",
"medium-ethernet",
"socket-raw",
Expand Down
3 changes: 2 additions & 1 deletion kernel/src/driver/net/iface_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ impl IfaceCommon {
id: u64,
domain: crate::net::socket::inet::common::port::TcpBindDomain,
port: u16,
device: u32,
) {
self.tcp_listeners.register(id, domain, port);
self.tcp_listeners.register(id, domain, port, device);
}

/// Unregister an active TCP listener port on this iface.
Expand Down
25 changes: 23 additions & 2 deletions kernel/src/driver/net/local_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ pub(super) struct RoutedTxDevice<'a, D: SmolDevice + ?Sized> {
pub(super) backend_policy: OutputBackendPolicy<'a>,
}

/// Metadata describes the physical ingress, not the TCP SocketSet owner.
pub(super) struct RoutedRxToken<T> {
inner: T,
ifindex: u32,
}

impl<T: RxToken> RxToken for RoutedRxToken<T> {
fn consume<R, F: FnOnce(&[u8]) -> R>(self, f: F) -> R {
self.inner.consume(f)
}

fn meta(&self) -> PacketMeta {
let mut meta = self.inner.meta();
meta.id = self.ifindex;
meta
}
}

/// A physical transmit token with a lazily admitted namespace-routed fallback.
///
/// The physical token remains the common path. `LocalInputTxToken` is used
Expand Down Expand Up @@ -524,7 +542,7 @@ impl<D: SmolDevice + ?Sized> SmolDevice for LocalInputDevice<'_, D> {

impl<D: SmolDevice + ?Sized> SmolDevice for RoutedTxDevice<'_, D> {
type RxToken<'a>
= D::RxToken<'a>
= RoutedRxToken<D::RxToken<'a>>
where
Self: 'a;
type TxToken<'a>
Expand All @@ -539,7 +557,10 @@ impl<D: SmolDevice + ?Sized> SmolDevice for RoutedTxDevice<'_, D> {
let capabilities = self.device.capabilities();
let (rx_token, physical) = self.device.receive(timestamp)?;
Some((
rx_token,
RoutedRxToken {
inner: rx_token,
ifindex: self.backend_policy.owner_ifindex,
},
RoutedTxToken {
physical: Some(physical),
routed: None,
Expand Down
11 changes: 11 additions & 0 deletions kernel/src/net/route/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ pub(crate) fn resolve_ipv4_route(
let devices = netns.device_list();
let router = netns.router();
let fib = router.fib.read();
// Linux's default route_localnet=0 rejects loopback sources on an
// explicitly selected non-loopback device, including TCP self-connect.
if let (Some(IpAddress::Ipv4(source)), Some(oif)) = (fixed_source, required_oif) {
if source.is_loopback()
&& devices
.get(&(oif as usize))
.is_some_and(|iface| !iface.flags().contains(InterfaceFlags::LOOPBACK))
{
return Err(SystemError::EINVAL);
}
}
// Linux derives an output device from a fixed local source only for
// multicast and limited broadcast when no caller supplied an OIF. This is
// the ip_route_output_key_hash_rcu() compatibility path that lets an
Expand Down
9 changes: 9 additions & 0 deletions kernel/src/net/socket/inet/common/device_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ impl Default for SocketDeviceBinding {
}

impl SocketDeviceBinding {
/// Construct independent state for a connection inheriting a listener's
/// SYN-time device constraint. Subsequent parent/child updates are separate.
pub(crate) fn from_ifindex(ifindex: usize) -> Self {
Self {
ifindex: AtomicUsize::new(ifindex),
update_lock: Mutex::new(()),
}
}

#[inline]
pub fn ifindex(&self) -> usize {
self.ifindex.load(Ordering::Acquire)
Expand Down
53 changes: 52 additions & 1 deletion kernel/src/net/socket/inet/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,19 @@ impl BoundInner {
where
T: smoltcp::socket::AnySocket<'static>,
{
let target = match get_ephemeral_bind_target(&remote, netns.clone()) {
Self::bind_ephemeral_recoverable_on_device(socket, remote, netns, None)
}

pub(crate) fn bind_ephemeral_recoverable_on_device<T>(
socket: T,
remote: smoltcp::wire::IpAddress,
netns: Arc<NetNamespace>,
device: Option<Arc<dyn Iface>>,
) -> Result<(Self, smoltcp::wire::IpAddress), (T, SystemError)>
where
T: smoltcp::socket::AnySocket<'static>,
{
let target = match tcp_connect_target(&remote, &netns, device) {
Ok(result) => result,
Err(err) => return Err((socket, err)),
};
Expand Down Expand Up @@ -193,6 +205,34 @@ impl BoundInner {
&self.iface
}

/// Place an inactive TCP endpoint before its first SYN is published.
/// Notification ownership is updated by the caller after releasing inner.
pub(crate) fn move_closed_tcp_to_iface(
&mut self,
iface: Arc<dyn Iface>,
) -> Result<(), SystemError> {
if Arc::ptr_eq(&self.iface, &iface) {
return Ok(());
}
let socket = {
let mut sockets = self.iface.sockets().lock();
if sockets
.get::<smoltcp::socket::tcp::Socket>(self.handle)
.state()
!= smoltcp::socket::tcp::State::Closed
{
return Err(SystemError::EINVAL);
}
sockets.remove(self.handle)
};
let smoltcp::socket::Socket::Tcp(socket) = socket else {
unreachable!("validated TCP socket");
};
self.handle = iface.sockets().lock().add(socket);
self.iface = iface;
Ok(())
}

pub fn move_udp_to_iface(&mut self, iface: Arc<dyn Iface>) -> Result<(), SystemError> {
self.move_udp_to_iface_with(iface, || {})
}
Expand Down Expand Up @@ -385,6 +425,17 @@ fn loopback_iface_contains_v6(iface: &Arc<dyn Iface>, v6_addr: smoltcp::wire::Ip
/// Get a suitable iface to deal with sendto/connect request if the socket is not bound to an iface.
/// Linux-like behavior: for implicit bind on connect/sendto, the stack must be able to select a
/// valid local source address for the given remote destination.
pub(crate) fn tcp_connect_target(
remote: &smoltcp::wire::IpAddress,
netns: &Arc<NetNamespace>,
device: Option<Arc<dyn Iface>>,
) -> Result<EphemeralBindTarget, SystemError> {
match device {
Some(iface) => ephemeral_bind_target_on_iface(netns, iface, remote),
None => get_ephemeral_bind_target(remote, netns.clone()),
}
}

fn get_ephemeral_bind_target(
remote_ip_addr: &smoltcp::wire::IpAddress,
netns: Arc<NetNamespace>,
Expand Down
26 changes: 24 additions & 2 deletions kernel/src/net/socket/inet/common/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use hashbrown::HashMap;
use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint, IpVersion};
use system_error::SystemError;

use super::device_binding::SocketDeviceBinding;
use crate::process::namespace::net_namespace::NetNamespace;
use crate::{arch::rand::rand, libs::mutex::Mutex, process::ProcessManager};

Expand Down Expand Up @@ -53,6 +54,7 @@ impl TcpBindDomain {
struct Binding {
id: u64,
domain: TcpBindDomain,
device: Arc<SocketDeviceBinding>,
}

/// Network-namespace TCP reservations, independently of the interface hosting
Expand Down Expand Up @@ -92,6 +94,21 @@ impl Drop for TcpPortReservation {
}
}

impl TcpPortReservation {
/// A successful implicit source selection narrows a wildcard reservation
/// without allocating a new port or repeating bind-time conflict checks.
pub(crate) fn update_domain(&mut self, domain: TcpBindDomain) {
let mut bindings = self.netns.tcp_ports().bindings.lock();
if let Some(binding) = bindings
.get_mut(&self.port)
.and_then(|bucket| bucket.iter_mut().find(|binding| binding.id == self.id))
{
binding.domain = domain;
}
self.domain = domain;
}
}

impl PortManager {
pub fn local_port_range() -> (u16, u16) {
ProcessManager::current_netns().local_port_range()
Expand All @@ -105,6 +122,7 @@ impl PortManager {
netns: Arc<NetNamespace>,
domain: TcpBindDomain,
port: u16,
device: Arc<SocketDeviceBinding>,
) -> Result<TcpPortReservation, SystemError> {
let manager = netns.tcp_ports();
let (min, max) = netns.local_port_range();
Expand All @@ -120,10 +138,14 @@ impl PortManager {
};
for _ in 0..if port == 0 { count } else { 1 } {
let bucket = bindings.entry(candidate).or_default();
if !bucket.iter().any(|binding| domain.overlaps(binding.domain)) {
if !bucket.iter().any(|binding| {
let a = device.ifindex();
let b = binding.device.ifindex();
(a == 0 || b == 0 || a == b) && domain.overlaps(binding.domain)
}) {
bucket.try_reserve(1).map_err(|_| SystemError::ENOMEM)?;
let id = manager.next_id.fetch_add(1, Ordering::Relaxed);
bucket.push(Binding { id, domain });
bucket.push(Binding { id, domain, device });
if port == 0 {
manager.next_ephemeral.store(
if candidate == max { min } else { candidate + 1 },
Expand Down
50 changes: 9 additions & 41 deletions kernel/src/net/socket/inet/stream/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,58 +59,26 @@ impl TcpSocket {
Some(inner::Inner::Established(established)) => {
established.update_io_events(&self.pollee);

// If SHUT_WR was requested while there were pending TX bytes, send FIN once
// the TX queue drains to preserve Linux-like semantics.
if self.is_send_shutdown()
&& self
.send_fin_deferred
.load(core::sync::atomic::Ordering::Relaxed)
{
let pending = established.with(|socket| socket.send_queue());
if pending == 0 {
established.with_mut(|socket| socket.close());
self.send_fin_deferred
.store(false, core::sync::atomic::Ordering::Relaxed);
}
}

// If SHUT_WR, set EPOLLOUT so send() wakes up and returns EPIPE.
if self.is_send_shutdown() {
self.pollee.fetch_or(
(EP::EPOLLOUT | EP::EPOLLWRNORM).bits() as usize,
core::sync::atomic::Ordering::Relaxed,
);
}
// If SHUT_RD, set EPOLLIN so recv() wakes up and returns 0 (EOF).
// Linux tcp_poll combines local shutdown with transport state:
// receive shutdown is a half-close even before a peer FIN.
if self.is_recv_shutdown() {
self.pollee.fetch_or(
(EP::EPOLLIN | EP::EPOLLRDNORM).bits() as usize,
core::sync::atomic::Ordering::Relaxed,
);
}

// Note: EPOLLHUP/EPOLLRDHUP/EPOLLERR are now handled in
// Established::update_io_events() based on socket state.
false
}
Some(inner::Inner::SelfConnected(sc)) => {
// Self-connect is modeled by an internal receive queue. Readable becomes true
// when the queue has data OR after SHUT_WR (EOF). Writable depends on queue
// free space unless SHUT_WR (then send() returns EPIPE).
sc.update_io_events(&self.pollee, self.is_recv_shutdown());

// Match established behavior for shutdown bits.
if self.is_send_shutdown() {
self.pollee.fetch_or(
(EP::EPOLLOUT | EP::EPOLLWRNORM).bits() as usize,
core::sync::atomic::Ordering::Relaxed,
);
}
if self.is_recv_shutdown() {
self.pollee.fetch_or(
(EP::EPOLLIN | EP::EPOLLRDNORM).bits() as usize,
(EP::EPOLLIN | EP::EPOLLRDNORM | EP::EPOLLRDHUP).bits() as usize,
core::sync::atomic::Ordering::Relaxed,
);
if self.is_send_shutdown() {
self.pollee.fetch_or(
EP::EPOLLHUP.bits() as usize,
core::sync::atomic::Ordering::Relaxed,
);
}
}
false
}
Expand Down
Loading
Loading