mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2026-09-08 23:57:59 +08:00
feat(net): implement SO_BINDTODEVICE for UDP sockets (#2237)
* feat(net): implement SO_BINDTODEVICE for UDP sockets Add a reusable ifindex-based socket device binding abstraction with Linux-compatible set/get, capability, rename, and device removal semantics. Reuse the same authoritative state for raw sockets without expanding raw data-path scope. Move UDP port ownership and demultiplexing into a network-namespace binding table. Account for bound-device overlap, dynamic SO_REUSEADDR, bind-time SO_REUSEPORT groups, and efficient per-port lookup and removal. Honor the selected device across bind, connect, send, multicast selection, socket migration, local delivery, and receive demultiplexing. Preserve queued datagrams while moving sockets between interface socket sets and release resources on failure paths. Add focused dunitest coverage for getsockopt behavior, invalid devices, loopback traffic, device-scoped port conflicts, dynamic reuse settings, and CAP_NET_RAW authorization. Refs: #2233 Signed-off-by: longjin <longjin@dragonos.org> * fix(net): enforce UDP bound-device isolation Prevent the UDP local-delivery fast path from crossing the interface selected by SO_BINDTODEVICE. Snapshot the effective delivery interface and source endpoint while the socket state is stable so disconnects and concurrent state changes cannot corrupt loopback metadata. Reserve ifindex 1 for loopback in the root namespace, matching Linux and keeping SIOCGIFCONF discovery deterministic when many interfaces are present. Add focused dunitest coverage for cross-interface isolation, wildcard source selection, and the loopback ifindex contract. Remove the PR-local interface transition gate and retain the pre-existing IP_MULTICAST_IF behavior. SO_BINDTODEVICE permanently places the UDP socket on its selected interface, so the gate was unnecessary for this feature and did not solve per-datagram egress for asynchronous NAPI devices. Signed-off-by: longjin <longjin@dragonos.org> * fix(page-cache): synchronize writeback budget selftest Wait for production asynchronous writeback to quiesce before the debug selftest claims the global batch budget. Reserve every slot while holding the retry queue lock so normal acquisition and retry registration cannot race the selftest setup.\n\nThis removes the test-ordering failure seen when page-cache accounting runs immediately after overlayfs activity, while leaving production scheduling unchanged. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): serialize UDP interface placement updates Model the smoltcp UDP socket's interface placement as a per-socket read/write transaction. Ordinary unicast sends retain shared concurrency, while multicast interface moves and control or lifecycle updates use exclusive ownership.\n\nThis prevents a concurrent SO_BINDTODEVICE update, multicast-interface publish, close, disconnect, or socket recreation from being overwritten by a stale post-send interface restore. Connected destination changes also participate in the transaction so send classification remains coherent. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
This commit is contained in:
@@ -13,7 +13,7 @@ use crate::filesystem::kernfs::KernFSInode;
|
|||||||
use crate::init::initcall::INITCALL_DEVICE;
|
use crate::init::initcall::INITCALL_DEVICE;
|
||||||
use crate::libs::rwsem::{RwSemReadGuard, RwSemWriteGuard};
|
use crate::libs::rwsem::{RwSemReadGuard, RwSemWriteGuard};
|
||||||
use crate::libs::spinlock::{SpinLock, SpinLockGuard};
|
use crate::libs::spinlock::{SpinLock, SpinLockGuard};
|
||||||
use crate::net::generate_iface_id;
|
use crate::net::LOOPBACK_IFINDEX;
|
||||||
use crate::process::namespace::net_namespace::INIT_NET_NAMESPACE;
|
use crate::process::namespace::net_namespace::INIT_NET_NAMESPACE;
|
||||||
use crate::time::Instant;
|
use crate::time::Instant;
|
||||||
use alloc::collections::VecDeque;
|
use alloc::collections::VecDeque;
|
||||||
@@ -319,7 +319,7 @@ impl LoopbackInterface {
|
|||||||
pub const DEVICE_NAME: &str = "lo";
|
pub const DEVICE_NAME: &str = "lo";
|
||||||
|
|
||||||
/// ## `new` 是一个公共函数,用于创建一个新的 `LoopbackInterface` 实例。
|
/// ## `new` 是一个公共函数,用于创建一个新的 `LoopbackInterface` 实例。
|
||||||
/// 生成一个新的接口 ID。创建一个新的接口配置,设置其硬件地址和随机种子,使用接口配置和驱动器创建一个新的 `smoltcp::iface::Interface` 实例。
|
/// 使用 Linux 约定的 loopback 接口 ID。创建一个新的接口配置,设置其硬件地址和随机种子,使用接口配置和驱动器创建一个新的 `smoltcp::iface::Interface` 实例。
|
||||||
/// 设置接口的 IP 地址为 127.0.0.1。
|
/// 设置接口的 IP 地址为 127.0.0.1。
|
||||||
/// Saves a cloneable driver handle; the underlying queue and the interface
|
/// Saves a cloneable driver handle; the underlying queue and the interface
|
||||||
/// back-reference are protected by a shared lock.
|
/// back-reference are protected by a shared lock.
|
||||||
@@ -330,7 +330,7 @@ impl LoopbackInterface {
|
|||||||
/// ## 返回值
|
/// ## 返回值
|
||||||
/// 返回一个 `Arc<Self>`,即一个指向新创建的 `LoopbackInterface` 实例的智能指针。
|
/// 返回一个 `Arc<Self>`,即一个指向新创建的 `LoopbackInterface` 实例的智能指针。
|
||||||
pub fn new(driver: LoopbackDriver) -> Arc<Self> {
|
pub fn new(driver: LoopbackDriver) -> Arc<Self> {
|
||||||
Self::new_with_ifindex(driver, generate_iface_id())
|
Self::new_with_ifindex(driver, LOOPBACK_IFINDEX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 在指定网络命名空间中创建 loopback,使用给定的 per-netns ifindex(Linux 新 netns 中 lo 通常为 1)。
|
/// 在指定网络命名空间中创建 loopback,使用给定的 per-netns ifindex(Linux 新 netns 中 lo 通常为 1)。
|
||||||
|
|||||||
@@ -72,6 +72,29 @@ impl AsyncWritebackPermit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait for production writeback to drain, then reserve the complete
|
||||||
|
/// budget atomically with respect to normal acquisition and retry
|
||||||
|
/// registration. The debug selftest needs exclusive ownership of every
|
||||||
|
/// slot, but it may run immediately after another test has queued
|
||||||
|
/// asynchronous writeback.
|
||||||
|
fn acquire_all_for_selftest() -> Vec<Self> {
|
||||||
|
ASYNC_WRITEBACK_WAIT.wait_until(|| {
|
||||||
|
let retries = ASYNC_WRITEBACK_RETRIES.lock();
|
||||||
|
if !retries.is_empty() || ASYNC_WRITEBACK_BATCHES.load(Ordering::Acquire) != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut permits = Vec::with_capacity(MAX_ASYNC_WRITEBACK_BATCHES);
|
||||||
|
for _ in 0..MAX_ASYNC_WRITEBACK_BATCHES {
|
||||||
|
let Some(permit) = Self::try_acquire_locked() else {
|
||||||
|
unreachable!("idle writeback budget must have every slot available");
|
||||||
|
};
|
||||||
|
permits.push(permit);
|
||||||
|
}
|
||||||
|
Some(permits)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Arrange a one-shot, non-blocking retry when a global batch slot is
|
/// Arrange a one-shot, non-blocking retry when a global batch slot is
|
||||||
/// available. A release transfers its permit directly to the FIFO head,
|
/// available. A release transfers its permit directly to the FIFO head,
|
||||||
/// so `sync_file_range(WRITE)` neither sleeps nor races queued waiters for
|
/// so `sync_file_range(WRITE)` neither sleeps nor races queued waiters for
|
||||||
@@ -134,20 +157,7 @@ impl Drop for AsyncWritebackPermit {
|
|||||||
/// cancellation, FIFO permit handoff, and that cancelled tickets never
|
/// cancellation, FIFO permit handoff, and that cancelled tickets never
|
||||||
/// receive a grant.
|
/// receive a grant.
|
||||||
pub(super) fn run_async_writeback_budget_retry_selftest() -> bool {
|
pub(super) fn run_async_writeback_budget_retry_selftest() -> bool {
|
||||||
if !ASYNC_WRITEBACK_RETRIES.lock().is_empty()
|
let mut permits = AsyncWritebackPermit::acquire_all_for_selftest();
|
||||||
|| ASYNC_WRITEBACK_BATCHES.load(Ordering::Acquire) != 0
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut permits = Vec::with_capacity(MAX_ASYNC_WRITEBACK_BATCHES);
|
|
||||||
for _ in 0..MAX_ASYNC_WRITEBACK_BATCHES {
|
|
||||||
let Some(permit) = AsyncWritebackPermit::try_acquire() else {
|
|
||||||
drop(permits);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
permits.push(permit);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cache = PageCache::new_unowned(None, None);
|
let cache = PageCache::new_unowned(None, None);
|
||||||
let predecessor_page = match cache.get_or_create_page_zero(0) {
|
let predecessor_page = match cache.get_or_create_page_zero(0) {
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ pub mod syscall;
|
|||||||
pub mod tcp_close_defer;
|
pub mod tcp_close_defer;
|
||||||
pub mod tcp_listener_backlog;
|
pub mod tcp_listener_backlog;
|
||||||
|
|
||||||
|
/// Linux reserves interface index 1 for the loopback device in every netns.
|
||||||
|
pub const LOOPBACK_IFINDEX: usize = 1;
|
||||||
|
|
||||||
/// 生成网络接口的id (全局自增)
|
/// 生成网络接口的id (全局自增)
|
||||||
pub fn generate_iface_id() -> usize {
|
pub fn generate_iface_id() -> usize {
|
||||||
static IFACE_ID: AtomicUsize = AtomicUsize::new(1);
|
static IFACE_ID: AtomicUsize = AtomicUsize::new(LOOPBACK_IFINDEX + 1);
|
||||||
return IFACE_ID.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
|
return IFACE_ID.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use alloc::sync::Arc;
|
||||||
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use system_error::SystemError;
|
||||||
|
|
||||||
|
use crate::driver::net::Iface;
|
||||||
|
use crate::libs::mutex::{Mutex, MutexGuard};
|
||||||
|
use crate::net::socket::IFNAMSIZ;
|
||||||
|
use crate::process::cred::{ns_capable, CAPFlags};
|
||||||
|
use crate::process::namespace::net_namespace::NetNamespace;
|
||||||
|
|
||||||
|
/// Linux `sk_bound_dev_if` semantics shared by inet sockets.
|
||||||
|
///
|
||||||
|
/// The interface index is the sole authoritative state. Names are resolved in
|
||||||
|
/// the socket-owned network namespace when setting or getting the option, so
|
||||||
|
/// rename and device removal remain observable without cached device objects.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SocketDeviceBinding {
|
||||||
|
ifindex: AtomicUsize,
|
||||||
|
update_lock: Mutex<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SocketDeviceBinding {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ifindex: AtomicUsize::new(0),
|
||||||
|
update_lock: Mutex::new(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SocketDeviceBinding {
|
||||||
|
#[inline]
|
||||||
|
pub fn ifindex(&self) -> usize {
|
||||||
|
self.ifindex.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn allows(&self, ingress_ifindex: usize) -> bool {
|
||||||
|
let bound = self.ifindex();
|
||||||
|
bound == 0 || bound == ingress_ifindex
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_iface(
|
||||||
|
&self,
|
||||||
|
netns: &Arc<NetNamespace>,
|
||||||
|
) -> Result<Option<Arc<dyn Iface>>, SystemError> {
|
||||||
|
let ifindex = self.ifindex();
|
||||||
|
if ifindex == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
netns
|
||||||
|
.device_list()
|
||||||
|
.get(&ifindex)
|
||||||
|
.cloned()
|
||||||
|
.map(Some)
|
||||||
|
.ok_or(SystemError::ENODEV)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse and authorize a `SO_BINDTODEVICE` update while serializing writers.
|
||||||
|
/// Dropping the returned update without committing leaves the binding intact.
|
||||||
|
pub fn prepare_update<'a>(
|
||||||
|
&'a self,
|
||||||
|
netns: &Arc<NetNamespace>,
|
||||||
|
value: &[u8],
|
||||||
|
) -> Result<DeviceBindingUpdate<'a>, SystemError> {
|
||||||
|
let guard = self.update_lock.lock();
|
||||||
|
let end = value
|
||||||
|
.iter()
|
||||||
|
.position(|byte| *byte == 0)
|
||||||
|
.unwrap_or(value.len());
|
||||||
|
let name = &value[..end];
|
||||||
|
|
||||||
|
let (target_ifindex, target_iface) = if name.is_empty() {
|
||||||
|
(0, None)
|
||||||
|
} else {
|
||||||
|
let iface = netns
|
||||||
|
.device_list()
|
||||||
|
.values()
|
||||||
|
.find(|iface| iface.iface_name().as_bytes() == name)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(SystemError::ENODEV)?;
|
||||||
|
(iface.nic_id(), Some(iface))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Linux resolves the requested name before checking the capability and
|
||||||
|
// only requires CAP_NET_RAW when replacing an existing non-zero binding.
|
||||||
|
if self.ifindex.load(Ordering::Relaxed) != 0
|
||||||
|
&& !ns_capable(netns.user_ns(), CAPFlags::CAP_NET_RAW)
|
||||||
|
{
|
||||||
|
return Err(SystemError::EPERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DeviceBindingUpdate {
|
||||||
|
binding: self,
|
||||||
|
_guard: guard,
|
||||||
|
target_ifindex,
|
||||||
|
target_iface,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, netns: &Arc<NetNamespace>, value: &mut [u8]) -> Result<usize, SystemError> {
|
||||||
|
let ifindex = self.ifindex();
|
||||||
|
if ifindex == 0 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
if value.len() < IFNAMSIZ {
|
||||||
|
return Err(SystemError::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iface = netns
|
||||||
|
.device_list()
|
||||||
|
.get(&ifindex)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(SystemError::ENODEV)?;
|
||||||
|
let name = iface.iface_name();
|
||||||
|
let bytes = name.as_bytes();
|
||||||
|
let copy_len = bytes.len().min(IFNAMSIZ - 1);
|
||||||
|
value[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||||
|
value[copy_len] = 0;
|
||||||
|
Ok(copy_len + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceBindingUpdate<'a> {
|
||||||
|
binding: &'a SocketDeviceBinding,
|
||||||
|
_guard: MutexGuard<'a, ()>,
|
||||||
|
target_ifindex: usize,
|
||||||
|
target_iface: Option<Arc<dyn Iface>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceBindingUpdate<'_> {
|
||||||
|
#[inline]
|
||||||
|
pub fn target_iface(&self) -> Option<Arc<dyn Iface>> {
|
||||||
|
self.target_iface.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn commit(&mut self) {
|
||||||
|
self.binding
|
||||||
|
.ifindex
|
||||||
|
.store(self.target_ifindex, Ordering::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ use alloc::sync::Arc;
|
|||||||
|
|
||||||
pub mod port;
|
pub mod port;
|
||||||
pub use port::PortManager;
|
pub use port::PortManager;
|
||||||
|
mod device_binding;
|
||||||
|
pub use device_binding::{DeviceBindingUpdate, SocketDeviceBinding};
|
||||||
pub mod multicast;
|
pub mod multicast;
|
||||||
pub use multicast::{apply_ipv4_membership, apply_ipv4_multicast_if, Ipv4MulticastMembership};
|
pub use multicast::{apply_ipv4_membership, apply_ipv4_multicast_if, Ipv4MulticastMembership};
|
||||||
use system_error::SystemError;
|
use system_error::SystemError;
|
||||||
@@ -164,13 +166,29 @@ impl BoundInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_udp_to_iface(&mut self, iface: Arc<dyn Iface>) -> Result<(), SystemError> {
|
pub fn move_udp_to_iface(&mut self, iface: Arc<dyn Iface>) -> Result<(), SystemError> {
|
||||||
|
self.move_udp_to_iface_with(iface, || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move a UDP socket between smoltcp interface socket sets. `detached` is
|
||||||
|
/// invoked after removal from the old set and before publication in the new
|
||||||
|
/// set, which gives callers one linearization point for related state.
|
||||||
|
pub fn move_udp_to_iface_with<F>(
|
||||||
|
&mut self,
|
||||||
|
iface: Arc<dyn Iface>,
|
||||||
|
detached: F,
|
||||||
|
) -> Result<(), SystemError>
|
||||||
|
where
|
||||||
|
F: FnOnce(),
|
||||||
|
{
|
||||||
if Arc::ptr_eq(&self.iface, &iface) {
|
if Arc::ptr_eq(&self.iface, &iface) {
|
||||||
|
detached();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let socket = self.iface.sockets().lock().remove(self.handle);
|
let socket = self.iface.sockets().lock().remove(self.handle);
|
||||||
let smoltcp::socket::Socket::Udp(socket) = socket else {
|
let smoltcp::socket::Socket::Udp(socket) = socket else {
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
};
|
};
|
||||||
|
detached();
|
||||||
let handle = iface.sockets().lock().add(socket);
|
let handle = iface.sockets().lock().add(socket);
|
||||||
self.iface = iface;
|
self.iface = iface;
|
||||||
self.handle = handle;
|
self.handle = handle;
|
||||||
@@ -245,7 +263,7 @@ pub fn normalize_unspecified_endpoint_to_loopback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_iface_for_local_bind(
|
pub(crate) fn get_iface_for_local_bind(
|
||||||
ip_addr: &smoltcp::wire::IpAddress,
|
ip_addr: &smoltcp::wire::IpAddress,
|
||||||
netns: &Arc<NetNamespace>,
|
netns: &Arc<NetNamespace>,
|
||||||
) -> Option<Arc<dyn Iface>> {
|
) -> Option<Arc<dyn Iface>> {
|
||||||
@@ -438,7 +456,7 @@ fn no_source_addr_error(remote_ip_addr: &smoltcp::wire::IpAddress) -> SystemErro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pick_configured_source_addr(
|
pub(crate) fn pick_configured_source_addr(
|
||||||
iface: &Arc<dyn Iface>,
|
iface: &Arc<dyn Iface>,
|
||||||
remote_ip_addr: &smoltcp::wire::IpAddress,
|
remote_ip_addr: &smoltcp::wire::IpAddress,
|
||||||
) -> Option<smoltcp::wire::IpAddress> {
|
) -> Option<smoltcp::wire::IpAddress> {
|
||||||
@@ -494,7 +512,7 @@ fn bind_addr_not_found_error(
|
|||||||
/// 1. Use the explicitly set default interface
|
/// 1. Use the explicitly set default interface
|
||||||
/// 2. Find an interface with a matching address family (IPv6 socket -> interface with IPv6 address)
|
/// 2. Find an interface with a matching address family (IPv6 socket -> interface with IPv6 address)
|
||||||
/// 3. Fall back to the first available interface
|
/// 3. Fall back to the first available interface
|
||||||
fn select_iface_for_unspecified(
|
pub(crate) fn select_iface_for_unspecified(
|
||||||
address: &smoltcp::wire::IpAddress,
|
address: &smoltcp::wire::IpAddress,
|
||||||
netns: &Arc<NetNamespace>,
|
netns: &Arc<NetNamespace>,
|
||||||
) -> Result<Arc<dyn Iface>, SystemError> {
|
) -> Result<Arc<dyn Iface>, SystemError> {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use alloc::vec::Vec;
|
|
||||||
use core::sync::atomic::{AtomicU16, Ordering};
|
use core::sync::atomic::{AtomicU16, Ordering};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use smoltcp::wire::IpAddress;
|
|
||||||
use system_error::SystemError;
|
use system_error::SystemError;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -12,21 +10,20 @@ use crate::{
|
|||||||
|
|
||||||
use super::Types::{self, *};
|
use super::Types::{self, *};
|
||||||
|
|
||||||
/// # TCP 和 UDP 的端口管理器。
|
/// Per-interface TCP port manager.
|
||||||
/// 如果 TCP/UDP 的 socket 绑定了某个端口,它会在对应的表中记录,以检测端口冲突。
|
///
|
||||||
|
/// UDP reservations are network-namespace-wide and live in `UdpBindingTable`,
|
||||||
|
/// because Linux device-bound sockets can legally share a port across ifaces.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PortManager {
|
pub struct PortManager {
|
||||||
// TCP 端口记录表
|
// TCP 端口记录表
|
||||||
tcp_port_table: Mutex<HashMap<u16, RawPid>>,
|
tcp_port_table: Mutex<HashMap<u16, RawPid>>,
|
||||||
// UDP 端口记录表
|
|
||||||
udp_port_table: Mutex<HashMap<u16, Vec<UdpPortBinding>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PortManager {
|
impl Default for PortManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tcp_port_table: Mutex::new(HashMap::new()),
|
tcp_port_table: Mutex::new(HashMap::new()),
|
||||||
udp_port_table: Mutex::new(HashMap::new()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,13 +70,6 @@ impl PortManager {
|
|||||||
|
|
||||||
// 使用 ListenTable 检查端口是否被占用
|
// 使用 ListenTable 检查端口是否被占用
|
||||||
match socket_type {
|
match socket_type {
|
||||||
Udp => {
|
|
||||||
let guard = self.udp_port_table.lock();
|
|
||||||
if guard.get(&port).is_none() {
|
|
||||||
drop(guard);
|
|
||||||
return Ok(port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Tcp => {
|
Tcp => {
|
||||||
let guard = self.tcp_port_table.lock();
|
let guard = self.tcp_port_table.lock();
|
||||||
if guard.get(&port).is_none() {
|
if guard.get(&port).is_none() {
|
||||||
@@ -117,57 +107,15 @@ impl PortManager {
|
|||||||
Err(SystemError::EADDRINUSE)
|
Err(SystemError::EADDRINUSE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UDP: 绑定随机端口(支持 reuseaddr/reuseport 规则)
|
|
||||||
pub fn bind_udp_ephemeral_port(
|
|
||||||
&self,
|
|
||||||
addr: IpAddress,
|
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
) -> Result<u16, SystemError> {
|
|
||||||
let (min, max) = Self::local_port_range();
|
|
||||||
let range = (max - min) as u32 + 1;
|
|
||||||
if range == 0 {
|
|
||||||
return Err(SystemError::EINVAL);
|
|
||||||
}
|
|
||||||
let mut remaining = range;
|
|
||||||
while remaining > 0 {
|
|
||||||
let port = self.get_ephemeral_port(Types::Udp)?;
|
|
||||||
match self.bind_udp_port(port, addr, reuseaddr, reuseport, bind_id) {
|
|
||||||
Ok(()) => return Ok(port),
|
|
||||||
Err(SystemError::EADDRINUSE) => {
|
|
||||||
// Race: another thread grabbed the port after we checked.
|
|
||||||
remaining -= 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(SystemError::EADDRINUSE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief 检测给定端口是否已被占用,如果未被占用则在 TCP 对应的表中记录
|
/// @brief 检测给定端口是否已被占用,如果未被占用则在 TCP 对应的表中记录
|
||||||
///
|
///
|
||||||
/// UDP 复用逻辑请使用 `bind_udp_port`
|
|
||||||
pub fn bind_port(&self, socket_type: Types, port: u16) -> Result<(), SystemError> {
|
pub fn bind_port(&self, socket_type: Types, port: u16) -> Result<(), SystemError> {
|
||||||
if port > 0 {
|
if port > 0 && socket_type == Tcp {
|
||||||
match socket_type {
|
let mut guard = self.tcp_port_table.lock();
|
||||||
Udp => {
|
if guard.get(&port).is_some() {
|
||||||
let mut guard = self.udp_port_table.lock();
|
return Err(SystemError::EADDRINUSE);
|
||||||
if guard.get(&port).is_some() {
|
}
|
||||||
return Err(SystemError::EADDRINUSE);
|
guard.insert(port, ProcessManager::current_pid());
|
||||||
}
|
|
||||||
guard.insert(port, Vec::new());
|
|
||||||
}
|
|
||||||
Tcp => {
|
|
||||||
let mut guard = self.tcp_port_table.lock();
|
|
||||||
if guard.get(&port).is_some() {
|
|
||||||
return Err(SystemError::EADDRINUSE);
|
|
||||||
}
|
|
||||||
guard.insert(port, ProcessManager::current_pid());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -175,76 +123,8 @@ impl PortManager {
|
|||||||
/// @brief 在对应的端口记录表中将端口和 socket 解绑
|
/// @brief 在对应的端口记录表中将端口和 socket 解绑
|
||||||
/// should call this function when socket is closed or aborted
|
/// should call this function when socket is closed or aborted
|
||||||
pub fn unbind_port(&self, socket_type: Types, port: u16) {
|
pub fn unbind_port(&self, socket_type: Types, port: u16) {
|
||||||
match socket_type {
|
if socket_type == Tcp {
|
||||||
Udp => {
|
self.tcp_port_table.lock().remove(&port);
|
||||||
self.udp_port_table.lock().remove(&port);
|
|
||||||
}
|
|
||||||
Tcp => {
|
|
||||||
self.tcp_port_table.lock().remove(&port);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UDP: 绑定端口,支持 SO_REUSEADDR/SO_REUSEPORT
|
|
||||||
pub fn bind_udp_port(
|
|
||||||
&self,
|
|
||||||
port: u16,
|
|
||||||
addr: IpAddress,
|
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
) -> Result<(), SystemError> {
|
|
||||||
if port == 0 {
|
|
||||||
return Err(SystemError::EINVAL);
|
|
||||||
}
|
|
||||||
let mut guard = self.udp_port_table.lock();
|
|
||||||
let bindings = guard.entry(port).or_default();
|
|
||||||
for binding in bindings.iter() {
|
|
||||||
if !udp_addrs_conflict(addr, binding.addr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let share_ok = (reuseport && binding.reuseport) || (reuseaddr && binding.reuseaddr);
|
|
||||||
if !share_ok {
|
|
||||||
return Err(SystemError::EADDRINUSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bindings.push(UdpPortBinding {
|
|
||||||
addr,
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
bind_id,
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UDP: 解绑端口(按 bind_id)
|
|
||||||
pub fn unbind_udp_port(&self, port: u16, bind_id: usize) {
|
|
||||||
let mut guard = self.udp_port_table.lock();
|
|
||||||
if let Some(list) = guard.get_mut(&port) {
|
|
||||||
list.retain(|b| b.bind_id != bind_id);
|
|
||||||
if list.is_empty() {
|
|
||||||
guard.remove(&port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct UdpPortBinding {
|
|
||||||
addr: IpAddress,
|
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn udp_addrs_conflict(a: IpAddress, b: IpAddress) -> bool {
|
|
||||||
if a.version() != b.version() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if a.is_unspecified() || b.is_unspecified() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
a == b
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use alloc::sync::Arc;
|
use alloc::sync::{Arc, Weak};
|
||||||
|
|
||||||
use smoltcp;
|
use smoltcp;
|
||||||
use system_error::SystemError;
|
use system_error::SystemError;
|
||||||
@@ -8,6 +8,8 @@ use crate::{
|
|||||||
process::namespace::net_namespace::NetNamespace,
|
process::namespace::net_namespace::NetNamespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::UdpSocket;
|
||||||
|
|
||||||
pub type SmolUdpSocket = smoltcp::socket::udp::Socket<'static>;
|
pub type SmolUdpSocket = smoltcp::socket::udp::Socket<'static>;
|
||||||
|
|
||||||
pub const DEFAULT_METADATA_BUF_SIZE: usize = 1024;
|
pub const DEFAULT_METADATA_BUF_SIZE: usize = 1024;
|
||||||
@@ -17,6 +19,15 @@ pub const DEFAULT_RX_BUF_SIZE: usize = 128 * 1024; // 128 KB
|
|||||||
pub const DEFAULT_TX_BUF_SIZE: usize = 128 * 1024; // 128 KB
|
pub const DEFAULT_TX_BUF_SIZE: usize = 128 * 1024; // 128 KB
|
||||||
// Minimum buffer size (Linux uses 256 bytes minimum)
|
// Minimum buffer size (Linux uses 256 bytes minimum)
|
||||||
|
|
||||||
|
pub struct UdpBindContext {
|
||||||
|
pub netns: Arc<NetNamespace>,
|
||||||
|
pub socket: Weak<UdpSocket>,
|
||||||
|
pub reuseaddr: bool,
|
||||||
|
pub reuseport: bool,
|
||||||
|
pub bind_id: usize,
|
||||||
|
pub bound_ifindex: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UnboundUdp {
|
pub struct UnboundUdp {
|
||||||
socket: SmolUdpSocket,
|
socket: SmolUdpSocket,
|
||||||
@@ -80,32 +91,48 @@ impl UnboundUdp {
|
|||||||
pub fn bind(
|
pub fn bind(
|
||||||
self,
|
self,
|
||||||
local_endpoint: smoltcp::wire::IpEndpoint,
|
local_endpoint: smoltcp::wire::IpEndpoint,
|
||||||
netns: Arc<NetNamespace>,
|
context: UdpBindContext,
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
) -> Result<BoundUdp, SystemError> {
|
) -> Result<BoundUdp, SystemError> {
|
||||||
let inner = BoundInner::bind(self.socket, &local_endpoint.addr, netns)?;
|
let UdpBindContext {
|
||||||
|
netns,
|
||||||
|
socket,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
} = context;
|
||||||
|
let inner = BoundInner::bind(self.socket, &local_endpoint.addr, netns.clone())?;
|
||||||
let bind_addr = local_endpoint.addr;
|
let bind_addr = local_endpoint.addr;
|
||||||
let bind_port = if local_endpoint.port == 0 {
|
let bind_port_result = if local_endpoint.port == 0 {
|
||||||
let port = inner
|
netns.udp_bindings().bind_ephemeral(
|
||||||
.port_manager()
|
socket,
|
||||||
.bind_udp_ephemeral_port(bind_addr, reuseaddr, reuseport, bind_id)?;
|
|
||||||
// log::debug!("UnboundUdp::bind: allocated ephemeral port {}", port);
|
|
||||||
port
|
|
||||||
} else {
|
|
||||||
inner.port_manager().bind_udp_port(
|
|
||||||
local_endpoint.port,
|
|
||||||
bind_addr,
|
bind_addr,
|
||||||
reuseaddr,
|
reuseaddr,
|
||||||
reuseport,
|
reuseport,
|
||||||
bind_id,
|
bind_id,
|
||||||
)?;
|
bound_ifindex,
|
||||||
// log::debug!(
|
netns.local_port_range(),
|
||||||
// "UnboundUdp::bind: explicit bind to port {}",
|
)
|
||||||
// local_endpoint.port
|
} else {
|
||||||
// );
|
netns
|
||||||
local_endpoint.port
|
.udp_bindings()
|
||||||
|
.bind(
|
||||||
|
socket,
|
||||||
|
bind_addr,
|
||||||
|
local_endpoint.port,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
)
|
||||||
|
.map(|()| local_endpoint.port)
|
||||||
|
};
|
||||||
|
let bind_port = match bind_port_result {
|
||||||
|
Ok(port) => port,
|
||||||
|
Err(err) => {
|
||||||
|
inner.release();
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if bind_addr.is_unspecified() {
|
if bind_addr.is_unspecified() {
|
||||||
@@ -113,7 +140,8 @@ impl UnboundUdp {
|
|||||||
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bind_port))
|
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bind_port))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
inner.port_manager().unbind_udp_port(bind_port, bind_id);
|
netns.udp_bindings().unbind(bind_port, bind_id);
|
||||||
|
inner.release();
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
}
|
}
|
||||||
} else if inner
|
} else if inner
|
||||||
@@ -122,33 +150,113 @@ impl UnboundUdp {
|
|||||||
})
|
})
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
inner.port_manager().unbind_udp_port(bind_port, bind_id);
|
netns.udp_bindings().unbind(bind_port, bind_id);
|
||||||
|
inner.release();
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
}
|
}
|
||||||
let port_mgr_ifindex = inner.iface().nic_id();
|
|
||||||
Ok(BoundUdp {
|
Ok(BoundUdp {
|
||||||
inner,
|
inner,
|
||||||
remote: Mutex::new(None),
|
remote: Mutex::new(None),
|
||||||
explicitly_bound: true,
|
explicitly_bound: true,
|
||||||
has_preconnect_data: Mutex::new(false),
|
has_preconnect_data: Mutex::new(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind_on_iface(
|
||||||
|
self,
|
||||||
|
iface: Arc<dyn Iface>,
|
||||||
|
local_endpoint: smoltcp::wire::IpEndpoint,
|
||||||
|
context: UdpBindContext,
|
||||||
|
) -> Result<BoundUdp, SystemError> {
|
||||||
|
let UdpBindContext {
|
||||||
|
netns,
|
||||||
|
socket,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
bind_id,
|
bind_id,
|
||||||
port_mgr_ifindex,
|
bound_ifindex,
|
||||||
|
} = context;
|
||||||
|
let inner = BoundInner::bind_on_iface(self.socket, iface, netns.clone())?;
|
||||||
|
let bind_addr = local_endpoint.addr;
|
||||||
|
let bind_port_result = if local_endpoint.port == 0 {
|
||||||
|
netns.udp_bindings().bind_ephemeral(
|
||||||
|
socket,
|
||||||
|
bind_addr,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
netns.local_port_range(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
netns
|
||||||
|
.udp_bindings()
|
||||||
|
.bind(
|
||||||
|
socket,
|
||||||
|
bind_addr,
|
||||||
|
local_endpoint.port,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
)
|
||||||
|
.map(|()| local_endpoint.port)
|
||||||
|
};
|
||||||
|
let bind_port = match bind_port_result {
|
||||||
|
Ok(port) => port,
|
||||||
|
Err(err) => {
|
||||||
|
inner.release();
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint = if bind_addr.is_unspecified() {
|
||||||
|
smoltcp::wire::IpListenEndpoint::from(bind_port)
|
||||||
|
} else {
|
||||||
|
smoltcp::wire::IpListenEndpoint::from(smoltcp::wire::IpEndpoint::new(
|
||||||
|
bind_addr, bind_port,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
if inner
|
||||||
|
.with_mut::<SmolUdpSocket, _, _>(|socket| socket.bind(endpoint))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
netns.udp_bindings().unbind(bind_port, bind_id);
|
||||||
|
inner.release();
|
||||||
|
return Err(SystemError::EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BoundUdp {
|
||||||
|
inner,
|
||||||
|
remote: Mutex::new(None),
|
||||||
|
explicitly_bound: true,
|
||||||
|
has_preconnect_data: Mutex::new(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_ephemeral(
|
pub fn bind_ephemeral(
|
||||||
self,
|
self,
|
||||||
remote: smoltcp::wire::IpAddress,
|
remote: smoltcp::wire::IpAddress,
|
||||||
netns: Arc<NetNamespace>,
|
context: UdpBindContext,
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
) -> Result<BoundUdp, SystemError> {
|
) -> Result<BoundUdp, SystemError> {
|
||||||
let (inner, local_addr) = BoundInner::bind_ephemeral(self.socket, remote, netns)?;
|
let UdpBindContext {
|
||||||
let bound_port = match inner
|
netns,
|
||||||
.port_manager()
|
socket,
|
||||||
.bind_udp_ephemeral_port(local_addr, reuseaddr, reuseport, bind_id)
|
reuseaddr,
|
||||||
{
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
} = context;
|
||||||
|
let (inner, local_addr) = BoundInner::bind_ephemeral(self.socket, remote, netns.clone())?;
|
||||||
|
let bound_port = match netns.udp_bindings().bind_ephemeral(
|
||||||
|
socket,
|
||||||
|
local_addr,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
netns.local_port_range(),
|
||||||
|
) {
|
||||||
Ok(port) => port,
|
Ok(port) => port,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
inner.release();
|
inner.release();
|
||||||
@@ -167,7 +275,7 @@ impl UnboundUdp {
|
|||||||
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bound_port))
|
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bound_port))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||||
inner.release();
|
inner.release();
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
}
|
}
|
||||||
@@ -177,19 +285,16 @@ impl UnboundUdp {
|
|||||||
})
|
})
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||||
inner.release();
|
inner.release();
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
let port_mgr_ifindex = inner.iface().nic_id();
|
|
||||||
Ok(BoundUdp {
|
Ok(BoundUdp {
|
||||||
inner,
|
inner,
|
||||||
remote: Mutex::new(None),
|
remote: Mutex::new(None),
|
||||||
explicitly_bound: false,
|
explicitly_bound: false,
|
||||||
has_preconnect_data: Mutex::new(false),
|
has_preconnect_data: Mutex::new(false),
|
||||||
bind_id,
|
|
||||||
port_mgr_ifindex,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +302,26 @@ impl UnboundUdp {
|
|||||||
self,
|
self,
|
||||||
iface: Arc<dyn Iface>,
|
iface: Arc<dyn Iface>,
|
||||||
local_addr: smoltcp::wire::IpAddress,
|
local_addr: smoltcp::wire::IpAddress,
|
||||||
netns: Arc<NetNamespace>,
|
context: UdpBindContext,
|
||||||
reuseaddr: bool,
|
|
||||||
reuseport: bool,
|
|
||||||
bind_id: usize,
|
|
||||||
) -> Result<BoundUdp, SystemError> {
|
) -> Result<BoundUdp, SystemError> {
|
||||||
let inner = BoundInner::bind_on_iface(self.socket, iface, netns)?;
|
let UdpBindContext {
|
||||||
let bound_port = match inner
|
netns,
|
||||||
.port_manager()
|
socket,
|
||||||
.bind_udp_ephemeral_port(local_addr, reuseaddr, reuseport, bind_id)
|
reuseaddr,
|
||||||
{
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
} = context;
|
||||||
|
let inner = BoundInner::bind_on_iface(self.socket, iface, netns.clone())?;
|
||||||
|
let bound_port = match netns.udp_bindings().bind_ephemeral(
|
||||||
|
socket,
|
||||||
|
local_addr,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_ifindex,
|
||||||
|
netns.local_port_range(),
|
||||||
|
) {
|
||||||
Ok(port) => port,
|
Ok(port) => port,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
inner.release();
|
inner.release();
|
||||||
@@ -220,19 +335,16 @@ impl UnboundUdp {
|
|||||||
})
|
})
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||||
inner.release();
|
inner.release();
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
let port_mgr_ifindex = inner.iface().nic_id();
|
|
||||||
Ok(BoundUdp {
|
Ok(BoundUdp {
|
||||||
inner,
|
inner,
|
||||||
remote: Mutex::new(None),
|
remote: Mutex::new(None),
|
||||||
explicitly_bound: false,
|
explicitly_bound: false,
|
||||||
has_preconnect_data: Mutex::new(false),
|
has_preconnect_data: Mutex::new(false),
|
||||||
bind_id,
|
|
||||||
port_mgr_ifindex,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,11 +360,12 @@ pub struct BoundUdp {
|
|||||||
/// udp socket queue 中,而不是先针对connect进行filter操作。这里做workaround, 当connect是检查是否有包
|
/// udp socket queue 中,而不是先针对connect进行filter操作。这里做workaround, 当connect是检查是否有包
|
||||||
/// 在缓冲区,如果有,第一个包我们走非connect而不是connect的recv方法(即接受第一个非connect对端对应的包)
|
/// 在缓冲区,如果有,第一个包我们走非connect而不是connect的recv方法(即接受第一个非connect对端对应的包)
|
||||||
has_preconnect_data: Mutex<bool>,
|
has_preconnect_data: Mutex<bool>,
|
||||||
bind_id: usize,
|
|
||||||
port_mgr_ifindex: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoundUdp {
|
impl BoundUdp {
|
||||||
|
pub fn set_explicitly_bound(&mut self, explicitly_bound: bool) {
|
||||||
|
self.explicitly_bound = explicitly_bound;
|
||||||
|
}
|
||||||
pub fn with_mut_socket<F, T>(&self, f: F) -> T
|
pub fn with_mut_socket<F, T>(&self, f: F) -> T
|
||||||
where
|
where
|
||||||
F: FnMut(&mut SmolUdpSocket) -> T,
|
F: FnMut(&mut SmolUdpSocket) -> T,
|
||||||
@@ -729,17 +842,10 @@ impl BoundUdp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
let netns = self.inner.netns();
|
|
||||||
crate::net::socket::inet::common::multicast::find_iface_by_ifindex(
|
|
||||||
&netns,
|
|
||||||
self.port_mgr_ifindex as i32,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|| self.inner.iface().clone())
|
|
||||||
.port_manager()
|
|
||||||
.unbind_udp_port(self.endpoint().port, self.bind_id);
|
|
||||||
self.with_mut_socket(|socket| {
|
self.with_mut_socket(|socket| {
|
||||||
socket.close();
|
socket.close();
|
||||||
});
|
});
|
||||||
|
self.inner.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use inner::{UdpInner, UnboundUdp};
|
use inner::{UdpBindContext, UdpInner, UnboundUdp};
|
||||||
use smoltcp;
|
use smoltcp;
|
||||||
use system_error::SystemError;
|
use system_error::SystemError;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ use core::sync::atomic::{
|
|||||||
use smoltcp::wire::{IpAddress::*, IpEndpoint, IpListenEndpoint, IpVersion, Ipv4Address};
|
use smoltcp::wire::{IpAddress::*, IpEndpoint, IpListenEndpoint, IpVersion, Ipv4Address};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::{ensure_bound_dual_stack_remote_compatible, loopback_iface_contains_v4},
|
common::{ensure_bound_dual_stack_remote_compatible, DeviceBindingUpdate, SocketDeviceBinding},
|
||||||
InetSocket, UNSPECIFIED_LOCAL_ENDPOINT_V4, UNSPECIFIED_LOCAL_ENDPOINT_V6,
|
InetSocket, UNSPECIFIED_LOCAL_ENDPOINT_V4, UNSPECIFIED_LOCAL_ENDPOINT_V6,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,10 +36,11 @@ mod option;
|
|||||||
|
|
||||||
pub mod inner;
|
pub mod inner;
|
||||||
pub mod multicast_loopback;
|
pub mod multicast_loopback;
|
||||||
mod udp_bindings;
|
pub(crate) mod udp_bindings;
|
||||||
|
|
||||||
type EP = crate::filesystem::epoll::EPollEventType;
|
type EP = crate::filesystem::epoll::EPollEventType;
|
||||||
const IFACE_POLL_BATCH_ROUNDS: usize = 128;
|
const IFACE_POLL_BATCH_ROUNDS: usize = 128;
|
||||||
|
type EphemeralBindTarget = (Arc<dyn Iface>, smoltcp::wire::IpAddress);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
@@ -79,6 +80,10 @@ struct UdpErrQueueEntry {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UdpSocket {
|
pub struct UdpSocket {
|
||||||
inner: RwSem<Option<UdpInner>>,
|
inner: RwSem<Option<UdpInner>>,
|
||||||
|
/// Stabilizes the smoltcp socket's interface placement across send-side
|
||||||
|
/// polling. Ordinary unicast sends share the read side; temporary interface
|
||||||
|
/// moves and control/lifecycle updates take the write side.
|
||||||
|
iface_placement: RwSem<()>,
|
||||||
nonblock: AtomicBool,
|
nonblock: AtomicBool,
|
||||||
shutdown: AtomicU8,
|
shutdown: AtomicU8,
|
||||||
wait_queue: WaitQueue,
|
wait_queue: WaitQueue,
|
||||||
@@ -87,6 +92,8 @@ pub struct UdpSocket {
|
|||||||
fsnotify_watches: AtomicUsize,
|
fsnotify_watches: AtomicUsize,
|
||||||
self_ref: Weak<UdpSocket>,
|
self_ref: Weak<UdpSocket>,
|
||||||
netns: Arc<NetNamespace>,
|
netns: Arc<NetNamespace>,
|
||||||
|
/// SO_BINDTODEVICE authoritative interface index.
|
||||||
|
device_binding: SocketDeviceBinding,
|
||||||
epoll_items: EPollItems,
|
epoll_items: EPollItems,
|
||||||
fasync_items: FAsyncItems,
|
fasync_items: FAsyncItems,
|
||||||
/// Custom send buffer size (SO_SNDBUF), 0 means use default
|
/// Custom send buffer size (SO_SNDBUF), 0 means use default
|
||||||
@@ -176,6 +183,7 @@ impl UdpSocket {
|
|||||||
let netns = ProcessManager::current_netns();
|
let netns = ProcessManager::current_netns();
|
||||||
Arc::new_cyclic(|me| Self {
|
Arc::new_cyclic(|me| Self {
|
||||||
inner: RwSem::new(Some(UdpInner::Unbound(UnboundUdp::new()))),
|
inner: RwSem::new(Some(UdpInner::Unbound(UnboundUdp::new()))),
|
||||||
|
iface_placement: RwSem::new(()),
|
||||||
nonblock: AtomicBool::new(nonblock),
|
nonblock: AtomicBool::new(nonblock),
|
||||||
shutdown: AtomicU8::new(0),
|
shutdown: AtomicU8::new(0),
|
||||||
wait_queue: WaitQueue::default(),
|
wait_queue: WaitQueue::default(),
|
||||||
@@ -184,6 +192,7 @@ impl UdpSocket {
|
|||||||
fsnotify_watches: AtomicUsize::new(0),
|
fsnotify_watches: AtomicUsize::new(0),
|
||||||
self_ref: me.clone(),
|
self_ref: me.clone(),
|
||||||
netns,
|
netns,
|
||||||
|
device_binding: SocketDeviceBinding::default(),
|
||||||
epoll_items: EPollItems::default(),
|
epoll_items: EPollItems::default(),
|
||||||
fasync_items: FAsyncItems::default(),
|
fasync_items: FAsyncItems::default(),
|
||||||
send_buf_size: AtomicUsize::new(0), // 0 means use default
|
send_buf_size: AtomicUsize::new(0), // 0 means use default
|
||||||
@@ -223,6 +232,99 @@ impl UdpSocket {
|
|||||||
self as *const UdpSocket as usize
|
self as *const UdpSocket as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bind_context(&self) -> UdpBindContext {
|
||||||
|
UdpBindContext {
|
||||||
|
netns: self.netns(),
|
||||||
|
socket: self.self_ref.clone(),
|
||||||
|
reuseaddr: self.so_reuseaddr.load(Ordering::Relaxed),
|
||||||
|
reuseport: self.so_reuseport.load(Ordering::Relaxed),
|
||||||
|
bind_id: self.bind_id(),
|
||||||
|
bound_ifindex: self.bound_device_ifindex(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn bound_device_ifindex(&self) -> usize {
|
||||||
|
self.device_binding.ifindex()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn device_binding_allows(&self, ifindex: usize) -> bool {
|
||||||
|
self.device_binding.allows(ifindex)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn reuse_options(&self) -> (bool, bool) {
|
||||||
|
(
|
||||||
|
self.so_reuseaddr.load(Ordering::Relaxed),
|
||||||
|
self.so_reuseport.load(Ordering::Relaxed),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_device_binding(
|
||||||
|
&self,
|
||||||
|
update: &mut DeviceBindingUpdate<'_>,
|
||||||
|
) -> Result<(), SystemError> {
|
||||||
|
// `prepare_update()` already holds the device-binding writer lock.
|
||||||
|
// Keep this ordering consistent with send: binding writer -> placement
|
||||||
|
// writer -> inner. This prevents a multicast send from restoring an
|
||||||
|
// interface after a newer SO_BINDTODEVICE update has committed.
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
|
let mut inner = self.inner.write();
|
||||||
|
match inner.as_mut().ok_or(SystemError::EBADF)? {
|
||||||
|
UdpInner::Unbound(_) => update.commit(),
|
||||||
|
UdpInner::Bound(bound) => {
|
||||||
|
let Some(target_iface) = update.target_iface() else {
|
||||||
|
// Clearing sk_bound_dev_if must not depend on route selection.
|
||||||
|
update.commit();
|
||||||
|
let endpoint = bound.endpoint();
|
||||||
|
let auto_iface = match endpoint.addr {
|
||||||
|
Some(addr) if !addr.is_unspecified() => {
|
||||||
|
crate::net::socket::inet::common::get_iface_for_local_bind(
|
||||||
|
&addr,
|
||||||
|
&self.netns,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => crate::net::socket::inet::common::select_iface_for_unspecified(
|
||||||
|
&self.unspecified_addr(),
|
||||||
|
&self.netns,
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
};
|
||||||
|
if let Some(auto_iface) = auto_iface {
|
||||||
|
let old_iface = bound.inner().iface().clone();
|
||||||
|
if old_iface.nic_id() != auto_iface.nic_id()
|
||||||
|
&& bound
|
||||||
|
.inner_mut()
|
||||||
|
.move_udp_to_iface(auto_iface.clone())
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
if let Some(socket) = self.self_ref.upgrade() {
|
||||||
|
old_iface.common().unbind_socket(socket.clone());
|
||||||
|
auto_iface.common().bind_socket(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let old_iface = bound.inner().iface().clone();
|
||||||
|
if old_iface.nic_id() == target_iface.nic_id() {
|
||||||
|
update.commit();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
bound
|
||||||
|
.inner_mut()
|
||||||
|
.move_udp_to_iface_with(target_iface.clone(), || update.commit())?;
|
||||||
|
if let Some(socket) = self.self_ref.upgrade() {
|
||||||
|
old_iface.common().unbind_socket(socket.clone());
|
||||||
|
target_iface.common().bind_socket(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn unspecified_addr(&self) -> smoltcp::wire::IpAddress {
|
fn unspecified_addr(&self) -> smoltcp::wire::IpAddress {
|
||||||
match self.ip_version {
|
match self.ip_version {
|
||||||
@@ -388,6 +490,7 @@ impl UdpSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_bind(&self, local_endpoint: smoltcp::wire::IpEndpoint) -> Result<(), SystemError> {
|
pub fn do_bind(&self, local_endpoint: smoltcp::wire::IpEndpoint) -> Result<(), SystemError> {
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
let mut inner = self.inner.write();
|
let mut inner = self.inner.write();
|
||||||
|
|
||||||
// Check socket state first without taking
|
// Check socket state first without taking
|
||||||
@@ -396,6 +499,17 @@ impl UdpSocket {
|
|||||||
Some(UdpInner::Bound(_)) => return Err(SystemError::EINVAL), // Already bound
|
Some(UdpInner::Bound(_)) => return Err(SystemError::EINVAL), // Already bound
|
||||||
Some(UdpInner::Unbound(_)) => {}
|
Some(UdpInner::Unbound(_)) => {}
|
||||||
}
|
}
|
||||||
|
let bound_iface = self.device_binding.resolve_iface(&self.netns)?;
|
||||||
|
if bound_iface.is_some()
|
||||||
|
&& !local_endpoint.addr.is_unspecified()
|
||||||
|
&& crate::net::socket::inet::common::get_iface_for_local_bind(
|
||||||
|
&local_endpoint.addr,
|
||||||
|
&self.netns,
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Err(SystemError::EADDRNOTAVAIL);
|
||||||
|
}
|
||||||
|
|
||||||
// Now safe to take - we know it's Unbound
|
// Now safe to take - we know it's Unbound
|
||||||
let _old_unbound = match inner.take() {
|
let _old_unbound = match inner.take() {
|
||||||
@@ -427,31 +541,18 @@ impl UdpSocket {
|
|||||||
UnboundUdp::new()
|
UnboundUdp::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
let result = if let Some(iface) = bound_iface {
|
||||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
unbound.bind_on_iface(iface, local_endpoint, self.bind_context())
|
||||||
match unbound.bind(
|
} else {
|
||||||
local_endpoint,
|
unbound.bind(local_endpoint, self.bind_context())
|
||||||
self.netns(),
|
};
|
||||||
reuseaddr,
|
match result {
|
||||||
reuseport,
|
|
||||||
self.bind_id(),
|
|
||||||
) {
|
|
||||||
Ok(bound) => {
|
Ok(bound) => {
|
||||||
bound
|
bound
|
||||||
.inner()
|
.inner()
|
||||||
.iface()
|
.iface()
|
||||||
.common()
|
.common()
|
||||||
.bind_socket(self.self_ref.upgrade().unwrap());
|
.bind_socket(self.self_ref.upgrade().unwrap());
|
||||||
let local = bound.endpoint();
|
|
||||||
let addr = local.addr.unwrap_or_else(|| self.unspecified_addr());
|
|
||||||
udp_bindings::register_udp_binding(
|
|
||||||
&self.netns,
|
|
||||||
self.self_ref.clone(),
|
|
||||||
addr,
|
|
||||||
local.port,
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
);
|
|
||||||
*inner = Some(UdpInner::Bound(bound));
|
*inner = Some(UdpInner::Bound(bound));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -465,6 +566,7 @@ impl UdpSocket {
|
|||||||
|
|
||||||
pub fn bind_ephemeral(&self, remote: smoltcp::wire::IpAddress) -> Result<(), SystemError> {
|
pub fn bind_ephemeral(&self, remote: smoltcp::wire::IpAddress) -> Result<(), SystemError> {
|
||||||
let mut inner_guard = self.inner.write();
|
let mut inner_guard = self.inner.write();
|
||||||
|
let device_target = self.device_ephemeral_bind_target(remote)?;
|
||||||
let inner = inner_guard.take().ok_or(SystemError::EBADF)?;
|
let inner = inner_guard.take().ok_or(SystemError::EBADF)?;
|
||||||
let mut newly_bound_iface = None;
|
let mut newly_bound_iface = None;
|
||||||
let bound = match inner {
|
let bound = match inner {
|
||||||
@@ -481,35 +583,18 @@ impl UdpSocket {
|
|||||||
UnboundUdp::new()
|
UnboundUdp::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
let bound_result = if let Some((iface, local_addr)) = device_target {
|
||||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
inner.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||||
let bound_result = if let Some((iface, local_addr)) =
|
} else if let Some((iface, local_addr)) =
|
||||||
self.ipv4_multicast_ephemeral_bind_target(remote)
|
self.ipv4_multicast_ephemeral_bind_target(remote)
|
||||||
{
|
{
|
||||||
inner.bind_ephemeral_on_iface(
|
inner.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||||
iface,
|
|
||||||
local_addr,
|
|
||||||
self.netns(),
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
self.bind_id(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
inner.bind_ephemeral(remote, self.netns(), reuseaddr, reuseport, self.bind_id())
|
inner.bind_ephemeral(remote, self.bind_context())
|
||||||
};
|
};
|
||||||
match bound_result {
|
match bound_result {
|
||||||
Ok(bound) => {
|
Ok(bound) => {
|
||||||
newly_bound_iface = Some(bound.inner().iface().clone());
|
newly_bound_iface = Some(bound.inner().iface().clone());
|
||||||
let local = bound.endpoint();
|
|
||||||
let addr = local.addr.unwrap_or_else(|| self.unspecified_addr());
|
|
||||||
udp_bindings::register_udp_binding(
|
|
||||||
&self.netns,
|
|
||||||
self.self_ref.clone(),
|
|
||||||
addr,
|
|
||||||
local.port,
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
);
|
|
||||||
bound
|
bound
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -540,6 +625,7 @@ impl UdpSocket {
|
|||||||
/// Recreates the socket with new buffer sizes if it's already bound.
|
/// Recreates the socket with new buffer sizes if it's already bound.
|
||||||
/// This is needed because smoltcp doesn't support resizing socket buffers dynamically.
|
/// This is needed because smoltcp doesn't support resizing socket buffers dynamically.
|
||||||
fn recreate_socket_if_bound(&self) -> Result<(), SystemError> {
|
fn recreate_socket_if_bound(&self) -> Result<(), SystemError> {
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
let mut inner_guard = self.inner.write();
|
let mut inner_guard = self.inner.write();
|
||||||
|
|
||||||
// Check if socket is bound
|
// Check if socket is bound
|
||||||
@@ -551,7 +637,14 @@ impl UdpSocket {
|
|||||||
// Save current state before recreating
|
// Save current state before recreating
|
||||||
let local_ep = bound.endpoint();
|
let local_ep = bound.endpoint();
|
||||||
let remote_ep = bound.remote_endpoint().ok(); // May be None if not connected
|
let remote_ep = bound.remote_endpoint().ok(); // May be None if not connected
|
||||||
let _explicitly_bound = !bound.should_unbind_on_disconnect();
|
let explicitly_bound = !bound.should_unbind_on_disconnect();
|
||||||
|
let old_iface = bound.inner().iface().clone();
|
||||||
|
let target_iface = self
|
||||||
|
.device_binding
|
||||||
|
.resolve_iface(&self.netns)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_else(|| old_iface.clone());
|
||||||
|
|
||||||
// log::debug!(
|
// log::debug!(
|
||||||
// "Recreating UDP socket: local={:?}, remote={:?}, explicit={}",
|
// "Recreating UDP socket: local={:?}, remote={:?}, explicit={}",
|
||||||
@@ -566,7 +659,10 @@ impl UdpSocket {
|
|||||||
|
|
||||||
// Unbind the old socket and drop it
|
// Unbind the old socket and drop it
|
||||||
if let Some(UdpInner::Bound(b)) = inner_guard.take() {
|
if let Some(UdpInner::Bound(b)) = inner_guard.take() {
|
||||||
udp_bindings::unregister_udp_binding(&self.netns, &self.self_ref);
|
old_iface
|
||||||
|
.common()
|
||||||
|
.unbind_socket(self.self_ref.upgrade().unwrap());
|
||||||
|
self.netns.udp_bindings().unbind(port, self.bind_id());
|
||||||
b.close();
|
b.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,15 +677,7 @@ impl UdpSocket {
|
|||||||
|
|
||||||
// Rebind to the same endpoint
|
// Rebind to the same endpoint
|
||||||
let new_endpoint = smoltcp::wire::IpEndpoint::new(local_addr, port);
|
let new_endpoint = smoltcp::wire::IpEndpoint::new(local_addr, port);
|
||||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
let bound = match unbound.bind_on_iface(target_iface, new_endpoint, self.bind_context()) {
|
||||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
|
||||||
let bound = match unbound.bind(
|
|
||||||
new_endpoint,
|
|
||||||
self.netns(),
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
self.bind_id(),
|
|
||||||
) {
|
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Restore unbound state on error
|
// Restore unbound state on error
|
||||||
@@ -599,6 +687,8 @@ impl UdpSocket {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Restore connection if it existed
|
// Restore connection if it existed
|
||||||
|
let mut bound = bound;
|
||||||
|
bound.set_explicitly_bound(explicitly_bound);
|
||||||
if let Some(remote) = remote_ep {
|
if let Some(remote) = remote_ep {
|
||||||
bound.connect(remote);
|
bound.connect(remote);
|
||||||
}
|
}
|
||||||
@@ -609,24 +699,18 @@ impl UdpSocket {
|
|||||||
.iface()
|
.iface()
|
||||||
.common()
|
.common()
|
||||||
.bind_socket(self.self_ref.upgrade().unwrap());
|
.bind_socket(self.self_ref.upgrade().unwrap());
|
||||||
udp_bindings::register_udp_binding(
|
|
||||||
&self.netns,
|
|
||||||
self.self_ref.clone(),
|
|
||||||
local_addr,
|
|
||||||
port,
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
);
|
|
||||||
|
|
||||||
*inner_guard = Some(UdpInner::Bound(bound));
|
*inner_guard = Some(UdpInner::Bound(bound));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
let mut inner = self.inner.write();
|
let mut inner = self.inner.write();
|
||||||
if let Some(UdpInner::Bound(bound)) = &mut *inner {
|
if let Some(UdpInner::Bound(bound)) = &mut *inner {
|
||||||
udp_bindings::unregister_udp_binding(&self.netns, &self.self_ref);
|
self.netns
|
||||||
|
.udp_bindings()
|
||||||
|
.unbind(bound.endpoint().port, self.bind_id());
|
||||||
multicast_loopback::multicast_registry().unregister_all(&self.self_ref);
|
multicast_loopback::multicast_registry().unregister_all(&self.self_ref);
|
||||||
crate::net::socket::inet::common::multicast::drop_ipv4_memberships(
|
crate::net::socket::inet::common::multicast::drop_ipv4_memberships(
|
||||||
&self.netns,
|
&self.netns,
|
||||||
@@ -643,6 +727,37 @@ impl UdpSocket {
|
|||||||
// unbound socket just drop (only need to free memory)
|
// unbound socket just drop (only need to free memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn disconnect_udp(&self) -> Result<(), SystemError> {
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
|
let mut inner_guard = self.inner.write();
|
||||||
|
let should_unbind = match inner_guard.as_ref() {
|
||||||
|
Some(UdpInner::Bound(bound)) => {
|
||||||
|
bound.disconnect();
|
||||||
|
bound.should_unbind_on_disconnect()
|
||||||
|
}
|
||||||
|
Some(UdpInner::Unbound(_)) => return Ok(()),
|
||||||
|
None => return Err(SystemError::EBADF),
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_unbind {
|
||||||
|
let Some(UdpInner::Bound(bound)) = inner_guard.take() else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
self.netns
|
||||||
|
.udp_bindings()
|
||||||
|
.unbind(bound.endpoint().port, self.bind_id());
|
||||||
|
multicast_loopback::multicast_registry().unregister_all(&self.self_ref);
|
||||||
|
bound
|
||||||
|
.inner()
|
||||||
|
.iface()
|
||||||
|
.common()
|
||||||
|
.unbind_socket(self.self_ref.upgrade().unwrap());
|
||||||
|
bound.close();
|
||||||
|
inner_guard.replace(UdpInner::Unbound(UnboundUdp::new()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_recv(
|
pub fn try_recv(
|
||||||
&self,
|
&self,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
@@ -844,6 +959,18 @@ impl UdpSocket {
|
|||||||
Some((iface, local_addr))
|
Some((iface, local_addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn device_ephemeral_bind_target(
|
||||||
|
&self,
|
||||||
|
remote: smoltcp::wire::IpAddress,
|
||||||
|
) -> Result<Option<EphemeralBindTarget>, SystemError> {
|
||||||
|
let Some(iface) = self.device_binding.resolve_iface(&self.netns)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let local = crate::net::socket::inet::common::pick_configured_source_addr(&iface, &remote)
|
||||||
|
.ok_or(SystemError::EADDRNOTAVAIL)?;
|
||||||
|
Ok(Some((iface, local)))
|
||||||
|
}
|
||||||
|
|
||||||
fn enqueue_errqueue(
|
fn enqueue_errqueue(
|
||||||
&self,
|
&self,
|
||||||
err: SockExtendedErr,
|
err: SockExtendedErr,
|
||||||
@@ -910,6 +1037,24 @@ impl UdpSocket {
|
|||||||
.map(Self::normalize_unspecified_dest)
|
.map(Self::normalize_unspecified_dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn local_send_source_endpoint(
|
||||||
|
&self,
|
||||||
|
local: IpListenEndpoint,
|
||||||
|
dest_addr: smoltcp::wire::IpAddress,
|
||||||
|
ifindex: Option<i32>,
|
||||||
|
) -> IpEndpoint {
|
||||||
|
let local_addr = local
|
||||||
|
.addr
|
||||||
|
.filter(|addr| !addr.is_unspecified())
|
||||||
|
.or_else(|| {
|
||||||
|
let ifindex = usize::try_from(ifindex?).ok()?;
|
||||||
|
let iface = self.netns.device_list().get(&ifindex).cloned()?;
|
||||||
|
crate::net::socket::inet::common::pick_configured_source_addr(&iface, &dest_addr)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| self.unspecified_addr());
|
||||||
|
IpEndpoint::new(local_addr, local.port)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_send(
|
pub fn try_send(
|
||||||
&self,
|
&self,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
@@ -922,7 +1067,30 @@ impl UdpSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data and get iface reference, then release lock before polling
|
let placement = self.iface_placement.read();
|
||||||
|
let explicit = to.map(Endpoint::Ip);
|
||||||
|
let is_multicast = self
|
||||||
|
.connected_or_explicit_send_dest(explicit.as_ref())
|
||||||
|
.is_some_and(|dest| dest.addr.is_multicast());
|
||||||
|
if is_multicast {
|
||||||
|
drop(placement);
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
|
return self.try_send_with_stable_iface(buf, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.try_send_with_stable_iface(buf, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_send_with_stable_iface(
|
||||||
|
&self,
|
||||||
|
buf: &[u8],
|
||||||
|
to: Option<smoltcp::wire::IpEndpoint>,
|
||||||
|
) -> Result<usize, SystemError> {
|
||||||
|
// The caller's placement guard intentionally spans polling below.
|
||||||
|
// `inner` cannot remain locked because notifications may re-enter
|
||||||
|
// socket event checks.
|
||||||
|
|
||||||
|
// Send data and snapshot the delivery metadata before releasing inner.
|
||||||
let (
|
let (
|
||||||
result,
|
result,
|
||||||
send_iface,
|
send_iface,
|
||||||
@@ -930,7 +1098,8 @@ impl UdpSocket {
|
|||||||
dest_is_broadcast,
|
dest_is_broadcast,
|
||||||
loopback_send,
|
loopback_send,
|
||||||
send_iface_is_loopback,
|
send_iface_is_loopback,
|
||||||
mcast_ifindex,
|
local_delivery_ifindex,
|
||||||
|
src_endpoint,
|
||||||
restore_iface,
|
restore_iface,
|
||||||
) = {
|
) = {
|
||||||
let mut inner_guard = self.inner.write();
|
let mut inner_guard = self.inner.write();
|
||||||
@@ -942,31 +1111,19 @@ impl UdpSocket {
|
|||||||
if let UdpInner::Unbound(_) = inner {
|
if let UdpInner::Unbound(_) = inner {
|
||||||
let to_addr =
|
let to_addr =
|
||||||
Self::normalize_unspecified_dest(to.ok_or(SystemError::EDESTADDRREQ)?).addr;
|
Self::normalize_unspecified_dest(to.ok_or(SystemError::EDESTADDRREQ)?).addr;
|
||||||
|
let device_target = self.device_ephemeral_bind_target(to_addr)?;
|
||||||
let unbound = match inner_guard.take().unwrap() {
|
let unbound = match inner_guard.take().unwrap() {
|
||||||
UdpInner::Unbound(unbound) => unbound,
|
UdpInner::Unbound(unbound) => unbound,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
let bound_result = if let Some((iface, local_addr)) = device_target {
|
||||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
unbound.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||||
let bound_result = if let Some((iface, local_addr)) =
|
} else if let Some((iface, local_addr)) =
|
||||||
self.ipv4_multicast_ephemeral_bind_target(to_addr)
|
self.ipv4_multicast_ephemeral_bind_target(to_addr)
|
||||||
{
|
{
|
||||||
unbound.bind_ephemeral_on_iface(
|
unbound.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||||
iface,
|
|
||||||
local_addr,
|
|
||||||
self.netns(),
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
self.bind_id(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
unbound.bind_ephemeral(
|
unbound.bind_ephemeral(to_addr, self.bind_context())
|
||||||
to_addr,
|
|
||||||
self.netns(),
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
self.bind_id(),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
match bound_result {
|
match bound_result {
|
||||||
Ok(bound) => {
|
Ok(bound) => {
|
||||||
@@ -976,16 +1133,6 @@ impl UdpSocket {
|
|||||||
.iface()
|
.iface()
|
||||||
.common()
|
.common()
|
||||||
.bind_socket(self.self_ref.upgrade().unwrap());
|
.bind_socket(self.self_ref.upgrade().unwrap());
|
||||||
let local = bound.endpoint();
|
|
||||||
let addr = local.addr.unwrap_or_else(|| self.unspecified_addr());
|
|
||||||
udp_bindings::register_udp_binding(
|
|
||||||
&self.netns,
|
|
||||||
self.self_ref.clone(),
|
|
||||||
addr,
|
|
||||||
local.port,
|
|
||||||
reuseaddr,
|
|
||||||
reuseport,
|
|
||||||
);
|
|
||||||
inner_guard.replace(UdpInner::Bound(bound));
|
inner_guard.replace(UdpInner::Bound(bound));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -999,6 +1146,7 @@ impl UdpSocket {
|
|||||||
// Send data and get iface Arc before releasing lock
|
// Send data and get iface Arc before releasing lock
|
||||||
match inner_guard.as_mut().ok_or(SystemError::EBADF)? {
|
match inner_guard.as_mut().ok_or(SystemError::EBADF)? {
|
||||||
UdpInner::Bound(bound) => {
|
UdpInner::Bound(bound) => {
|
||||||
|
self.device_binding.resolve_iface(&self.netns)?;
|
||||||
let dest = to
|
let dest = to
|
||||||
.or_else(|| bound.remote_endpoint().ok())
|
.or_else(|| bound.remote_endpoint().ok())
|
||||||
.ok_or(SystemError::EDESTADDRREQ)?;
|
.ok_or(SystemError::EDESTADDRREQ)?;
|
||||||
@@ -1006,7 +1154,10 @@ impl UdpSocket {
|
|||||||
self.validate_bound_send_dest(bound, dest)?;
|
self.validate_bound_send_dest(bound, dest)?;
|
||||||
let bound_iface = bound.inner().iface().clone();
|
let bound_iface = bound.inner().iface().clone();
|
||||||
let is_multicast = dest.addr.is_multicast();
|
let is_multicast = dest.addr.is_multicast();
|
||||||
let mcast_ifindex = if is_multicast {
|
let device_ifindex = self.bound_device_ifindex() as i32;
|
||||||
|
let mcast_ifindex = if is_multicast && device_ifindex != 0 {
|
||||||
|
device_ifindex
|
||||||
|
} else if is_multicast {
|
||||||
let ifindex = self.ip_multicast_ifindex.load(Ordering::Relaxed);
|
let ifindex = self.ip_multicast_ifindex.load(Ordering::Relaxed);
|
||||||
if ifindex != 0 {
|
if ifindex != 0 {
|
||||||
ifindex
|
ifindex
|
||||||
@@ -1033,30 +1184,39 @@ impl UdpSocket {
|
|||||||
.map(|lo| lo.nic_id() == bound_iface.nic_id())
|
.map(|lo| lo.nic_id() == bound_iface.nic_id())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
};
|
};
|
||||||
let is_loopback = match dest.addr {
|
|
||||||
Ipv4(v4) => v4.is_loopback(),
|
|
||||||
Ipv6(v6) => v6.is_loopback(),
|
|
||||||
};
|
|
||||||
let is_loopback_local_subnet = match dest.addr {
|
|
||||||
Ipv4(v4) => loopback_iface_contains_v4(&bound_iface, v4),
|
|
||||||
Ipv6(_) => false,
|
|
||||||
};
|
|
||||||
let loopback_broadcast = self
|
let loopback_broadcast = self
|
||||||
.netns
|
.netns
|
||||||
.loopback_iface()
|
.loopback_iface()
|
||||||
.map(|lo| lo.smol_iface().lock().inner.is_broadcast(&dest.addr))
|
.map(|lo| lo.smol_iface().lock().inner.is_broadcast(&dest.addr))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let is_broadcast = loopback_broadcast
|
let bound_iface_is_broadcast = bound_iface
|
||||||
|| (send_iface_is_loopback
|
.smol_iface()
|
||||||
&& bound_iface
|
.lock()
|
||||||
.smol_iface()
|
.inner
|
||||||
.lock()
|
.is_broadcast(&dest.addr);
|
||||||
.inner
|
let is_broadcast = loopback_broadcast || bound_iface_is_broadcast;
|
||||||
.is_broadcast(&dest.addr));
|
let local_delivery_ifindex =
|
||||||
let should_loopback_send = is_loopback
|
if device_ifindex != 0 && (is_multicast || bound_iface_is_broadcast) {
|
||||||
|| is_loopback_local_subnet
|
Some(device_ifindex)
|
||||||
|| ((is_multicast || is_broadcast)
|
} else if is_multicast && mcast_ifindex != 0 {
|
||||||
&& (send_iface_is_loopback || loopback_broadcast));
|
Some(mcast_ifindex)
|
||||||
|
} else {
|
||||||
|
crate::net::socket::inet::common::get_iface_to_bind(
|
||||||
|
&dest.addr,
|
||||||
|
self.netns(),
|
||||||
|
)
|
||||||
|
.map(|iface| iface.nic_id() as i32)
|
||||||
|
};
|
||||||
|
let binding_allows_local_delivery = local_delivery_ifindex
|
||||||
|
.is_some_and(|ifindex| device_ifindex == 0 || device_ifindex == ifindex);
|
||||||
|
let should_loopback_send = binding_allows_local_delivery
|
||||||
|
&& ((!is_multicast && local_delivery_ifindex.is_some())
|
||||||
|
|| (is_multicast && (send_iface_is_loopback || loopback_broadcast)));
|
||||||
|
let src_endpoint = self.local_send_source_endpoint(
|
||||||
|
bound.endpoint(),
|
||||||
|
dest.addr,
|
||||||
|
local_delivery_ifindex,
|
||||||
|
);
|
||||||
if should_loopback_send {
|
if should_loopback_send {
|
||||||
let max_payload =
|
let max_payload =
|
||||||
bound.with_socket(|socket| socket.payload_send_capacity());
|
bound.with_socket(|socket| socket.payload_send_capacity());
|
||||||
@@ -1067,7 +1227,8 @@ impl UdpSocket {
|
|||||||
is_broadcast,
|
is_broadcast,
|
||||||
true,
|
true,
|
||||||
send_iface_is_loopback,
|
send_iface_is_loopback,
|
||||||
mcast_ifindex,
|
local_delivery_ifindex,
|
||||||
|
src_endpoint,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -1096,42 +1257,29 @@ impl UdpSocket {
|
|||||||
is_broadcast,
|
is_broadcast,
|
||||||
false,
|
false,
|
||||||
send_iface_is_loopback,
|
send_iface_is_loopback,
|
||||||
mcast_ifindex,
|
local_delivery_ifindex,
|
||||||
|
src_endpoint,
|
||||||
restore_iface,
|
restore_iface,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(SystemError::ENOTCONN),
|
_ => return Err(SystemError::ENOTCONN),
|
||||||
}
|
}
|
||||||
}; // Lock released here
|
}; // `inner` is released here.
|
||||||
|
|
||||||
if loopback_send {
|
if loopback_send {
|
||||||
if let Some(dest) = dest {
|
if let Some(dest) = dest {
|
||||||
let src_endpoint = match self.inner.read().as_ref() {
|
let ifindex = local_delivery_ifindex.unwrap_or_else(|| send_iface.nic_id() as i32);
|
||||||
Some(UdpInner::Bound(bound)) => {
|
|
||||||
let local = bound.endpoint();
|
|
||||||
let local_addr = local.addr.unwrap_or_else(|| self.unspecified_addr());
|
|
||||||
IpEndpoint::new(local_addr, local.port)
|
|
||||||
}
|
|
||||||
_ => IpEndpoint::new(self.unspecified_addr(), 0),
|
|
||||||
};
|
|
||||||
let ifindex = self
|
|
||||||
.netns
|
|
||||||
.loopback_iface()
|
|
||||||
.map(|lo| lo.nic_id() as i32)
|
|
||||||
.unwrap_or_else(|| send_iface.nic_id() as i32);
|
|
||||||
if dest.addr.is_multicast() {
|
if dest.addr.is_multicast() {
|
||||||
if let Ipv4(addr) = dest.addr {
|
if let Ipv4(addr) = dest.addr {
|
||||||
let octets = addr.octets();
|
let octets = addr.octets();
|
||||||
let multiaddr = u32::from_ne_bytes(octets);
|
let multiaddr = u32::from_ne_bytes(octets);
|
||||||
let ifindex = mcast_ifindex.max(ifindex);
|
|
||||||
if multicast_loopback::multicast_registry().has_membership(
|
if multicast_loopback::multicast_registry().has_membership(
|
||||||
self.netns.ns_common().nsid.data(),
|
self.netns.ns_common().nsid.data(),
|
||||||
multiaddr,
|
multiaddr,
|
||||||
ifindex,
|
ifindex,
|
||||||
) {
|
) {
|
||||||
udp_bindings::deliver_multicast_all(
|
self.netns.udp_bindings().deliver_multicast(
|
||||||
&self.netns,
|
|
||||||
dest,
|
dest,
|
||||||
src_endpoint,
|
src_endpoint,
|
||||||
ifindex,
|
ifindex,
|
||||||
@@ -1140,21 +1288,13 @@ impl UdpSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if dest_is_broadcast {
|
} else if dest_is_broadcast {
|
||||||
udp_bindings::deliver_broadcast_all(
|
self.netns
|
||||||
&self.netns,
|
.udp_bindings()
|
||||||
dest,
|
.deliver_broadcast(dest, src_endpoint, ifindex, buf);
|
||||||
src_endpoint,
|
|
||||||
ifindex,
|
|
||||||
buf,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
udp_bindings::deliver_unicast_loopback(
|
self.netns
|
||||||
&self.netns,
|
.udp_bindings()
|
||||||
dest,
|
.deliver_unicast(dest, src_endpoint, ifindex, buf);
|
||||||
src_endpoint,
|
|
||||||
ifindex,
|
|
||||||
buf,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为 raw socket 构建完整 IP 包并投递(用于 RAW 接收场景)。
|
// 为 raw socket 构建完整 IP 包并投递(用于 RAW 接收场景)。
|
||||||
@@ -1174,8 +1314,8 @@ impl UdpSocket {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll AFTER releasing the lock to avoid deadlock
|
// Poll after releasing inner. This is required because polling notifies all
|
||||||
// when socket sends to itself on loopback
|
// sockets on the interface and may re-enter this socket's event checks.
|
||||||
Self::poll_iface_until_quiescent(send_iface.as_ref());
|
Self::poll_iface_until_quiescent(send_iface.as_ref());
|
||||||
|
|
||||||
if let Some(orig_iface) = restore_iface {
|
if let Some(orig_iface) = restore_iface {
|
||||||
@@ -1188,36 +1328,23 @@ impl UdpSocket {
|
|||||||
// Multicast loopback: if sending to a multicast address and loopback is enabled,
|
// Multicast loopback: if sending to a multicast address and loopback is enabled,
|
||||||
// deliver the packet to all local sockets that have joined the group
|
// deliver the packet to all local sockets that have joined the group
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
if let Some(dest) = dest.or_else(|| match self.inner.read().as_ref() {
|
if let Some(dest) = dest {
|
||||||
Some(UdpInner::Bound(bound)) => bound.remote_endpoint().ok(),
|
|
||||||
_ => None,
|
|
||||||
}) {
|
|
||||||
let allow_mcast_loop =
|
let allow_mcast_loop =
|
||||||
self.is_multicast_loopback_enabled() || send_iface_is_loopback;
|
self.is_multicast_loopback_enabled() || send_iface_is_loopback;
|
||||||
if dest.addr.is_multicast() && allow_mcast_loop {
|
if dest.addr.is_multicast() && allow_mcast_loop {
|
||||||
// Get the source endpoint (this socket's local address)
|
|
||||||
let src_endpoint = match self.inner.read().as_ref() {
|
|
||||||
Some(UdpInner::Bound(bound)) => {
|
|
||||||
let local = bound.endpoint();
|
|
||||||
let local_addr = local.addr.unwrap_or_else(|| self.unspecified_addr());
|
|
||||||
IpEndpoint::new(local_addr, local.port)
|
|
||||||
}
|
|
||||||
_ => IpEndpoint::new(self.unspecified_addr(), 0),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get multicast address and interface index
|
// Get multicast address and interface index
|
||||||
if let Ipv4(addr) = dest.addr {
|
if let Ipv4(addr) = dest.addr {
|
||||||
let octets = addr.octets();
|
let octets = addr.octets();
|
||||||
let multiaddr = u32::from_ne_bytes(octets);
|
let multiaddr = u32::from_ne_bytes(octets);
|
||||||
let ifindex = self.get_multicast_ifindex();
|
let ifindex =
|
||||||
|
local_delivery_ifindex.unwrap_or_else(|| self.get_multicast_ifindex());
|
||||||
|
|
||||||
if multicast_loopback::multicast_registry().has_membership(
|
if multicast_loopback::multicast_registry().has_membership(
|
||||||
self.netns.ns_common().nsid.data(),
|
self.netns.ns_common().nsid.data(),
|
||||||
multiaddr,
|
multiaddr,
|
||||||
ifindex,
|
ifindex,
|
||||||
) {
|
) {
|
||||||
udp_bindings::deliver_multicast_all(
|
self.netns.udp_bindings().deliver_multicast(
|
||||||
&self.netns,
|
|
||||||
dest,
|
dest,
|
||||||
src_endpoint,
|
src_endpoint,
|
||||||
ifindex,
|
ifindex,
|
||||||
@@ -1251,6 +1378,9 @@ impl UdpSocket {
|
|||||||
ifindex: i32,
|
ifindex: i32,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
if ifindex <= 0 || !self.device_binding.allows(ifindex as usize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Check if socket is bound
|
// Check if socket is bound
|
||||||
{
|
{
|
||||||
let inner = self.inner.read();
|
let inner = self.inner.read();
|
||||||
@@ -1417,37 +1547,12 @@ impl Socket for UdpSocket {
|
|||||||
// Port 0 is treated as disconnect (like AF_UNSPEC)
|
// Port 0 is treated as disconnect (like AF_UNSPEC)
|
||||||
// This matches Linux behavior where connect() to port 0 succeeds but disconnects the socket
|
// This matches Linux behavior where connect() to port 0 succeeds but disconnects the socket
|
||||||
if remote.port == 0 {
|
if remote.port == 0 {
|
||||||
// log::debug!("UDP connect: port 0 treated as disconnect");
|
return self.disconnect_udp();
|
||||||
// Disconnect logic - same as AF_UNSPEC case
|
|
||||||
let should_unbind = {
|
|
||||||
match self.inner.read().as_ref() {
|
|
||||||
Some(UdpInner::Bound(inner)) => {
|
|
||||||
inner.disconnect();
|
|
||||||
inner.should_unbind_on_disconnect()
|
|
||||||
}
|
|
||||||
Some(UdpInner::Unbound(_)) => return Ok(()), // Already disconnected
|
|
||||||
None => return Err(SystemError::EBADF),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_unbind {
|
|
||||||
// Socket was implicitly bound by connect, unbind it
|
|
||||||
let mut inner_guard = self.inner.write();
|
|
||||||
if let Some(UdpInner::Bound(bound)) = inner_guard.take() {
|
|
||||||
udp_bindings::unregister_udp_binding(&self.netns, &self.self_ref);
|
|
||||||
multicast_loopback::multicast_registry().unregister_all(&self.self_ref);
|
|
||||||
bound
|
|
||||||
.inner()
|
|
||||||
.iface()
|
|
||||||
.common()
|
|
||||||
.unbind_socket(self.self_ref.upgrade().unwrap());
|
|
||||||
bound.close();
|
|
||||||
inner_guard.replace(UdpInner::Unbound(UnboundUdp::new()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The connected peer participates in send-side interface
|
||||||
|
// selection, so keep it stable while readers classify a send.
|
||||||
|
let _placement = self.iface_placement.write();
|
||||||
let remote = Self::normalize_unspecified_dest(remote);
|
let remote = Self::normalize_unspecified_dest(remote);
|
||||||
if !self.is_bound() {
|
if !self.is_bound() {
|
||||||
self.bind_ephemeral(remote.addr)?;
|
self.bind_ephemeral(remote.addr)?;
|
||||||
@@ -1465,35 +1570,8 @@ impl Socket for UdpSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Endpoint::Unspecified => {
|
Endpoint::Unspecified => {
|
||||||
// AF_UNSPEC: disconnect the UDP socket (clear remote endpoint)
|
// AF_UNSPEC disconnects and drops an implicit bind.
|
||||||
// If socket was implicitly bound (by connect), unbind it
|
self.disconnect_udp()
|
||||||
let should_unbind = {
|
|
||||||
match self.inner.read().as_ref() {
|
|
||||||
Some(UdpInner::Bound(inner)) => {
|
|
||||||
inner.disconnect();
|
|
||||||
inner.should_unbind_on_disconnect()
|
|
||||||
}
|
|
||||||
Some(UdpInner::Unbound(_)) => return Ok(()), // Already disconnected
|
|
||||||
None => return Err(SystemError::EBADF),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_unbind {
|
|
||||||
// Socket was implicitly bound by connect, unbind it
|
|
||||||
let mut inner_guard = self.inner.write();
|
|
||||||
if let Some(UdpInner::Bound(bound)) = inner_guard.take() {
|
|
||||||
udp_bindings::unregister_udp_binding(&self.netns, &self.self_ref);
|
|
||||||
multicast_loopback::multicast_registry().unregister_all(&self.self_ref);
|
|
||||||
bound
|
|
||||||
.inner()
|
|
||||||
.iface()
|
|
||||||
.common()
|
|
||||||
.unbind_socket(self.self_ref.upgrade().unwrap());
|
|
||||||
bound.close();
|
|
||||||
inner_guard.replace(UdpInner::Unbound(UnboundUdp::new()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
_ => Err(SystemError::EAFNOSUPPORT),
|
_ => Err(SystemError::EAFNOSUPPORT),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ impl UdpSocket {
|
|||||||
self.so_reuseaddr.store(v != 0, Ordering::Relaxed);
|
self.so_reuseaddr.store(v != 0, Ordering::Relaxed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
PSO::BINDTODEVICE => {
|
||||||
|
let mut update = self.device_binding.prepare_update(&self.netns, val)?;
|
||||||
|
self.apply_device_binding(&mut update)
|
||||||
|
}
|
||||||
PSO::BROADCAST => {
|
PSO::BROADCAST => {
|
||||||
if val.len() < core::mem::size_of::<i32>() {
|
if val.len() < core::mem::size_of::<i32>() {
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
@@ -229,6 +233,7 @@ impl UdpSocket {
|
|||||||
value,
|
value,
|
||||||
self.so_reuseaddr.load(Ordering::Relaxed) as i32,
|
self.so_reuseaddr.load(Ordering::Relaxed) as i32,
|
||||||
)),
|
)),
|
||||||
|
PSO::BINDTODEVICE => self.device_binding.get(&self.netns, value),
|
||||||
PSO::BROADCAST => Ok(write_i32_getsockopt(
|
PSO::BROADCAST => Ok(write_i32_getsockopt(
|
||||||
value,
|
value,
|
||||||
self.so_broadcast.load(Ordering::Relaxed) as i32,
|
self.so_broadcast.load(Ordering::Relaxed) as i32,
|
||||||
@@ -325,12 +330,32 @@ impl UdpSocket {
|
|||||||
self.ip_multicast_all.store(on, Ordering::Relaxed);
|
self.ip_multicast_all.store(on, Ordering::Relaxed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
IpOption::MULTICAST_IF => apply_ipv4_multicast_if(
|
IpOption::MULTICAST_IF => {
|
||||||
&self.netns,
|
// Keep validation and the two-field configuration publish
|
||||||
val,
|
// atomic with respect to send-side interface selection.
|
||||||
&self.ip_multicast_ifindex,
|
let _placement = self.iface_placement.write();
|
||||||
&self.ip_multicast_addr,
|
use crate::net::socket::inet::common::multicast::{
|
||||||
),
|
find_iface_by_ifindex, find_iface_by_ipv4, parse_mreqn_for_multicast_if,
|
||||||
|
};
|
||||||
|
let (addr, index) = parse_mreqn_for_multicast_if(val)?;
|
||||||
|
let selected = if index != 0 {
|
||||||
|
find_iface_by_ifindex(&self.netns, index)
|
||||||
|
} else if addr != 0 {
|
||||||
|
find_iface_by_ipv4(&self.netns, addr)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let bound = self.bound_device_ifindex();
|
||||||
|
if bound != 0 && selected.is_some_and(|iface| iface.nic_id() != bound) {
|
||||||
|
return Err(SystemError::EINVAL);
|
||||||
|
}
|
||||||
|
apply_ipv4_multicast_if(
|
||||||
|
&self.netns,
|
||||||
|
val,
|
||||||
|
&self.ip_multicast_ifindex,
|
||||||
|
&self.ip_multicast_addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
IpOption::PKTINFO => {
|
IpOption::PKTINFO => {
|
||||||
if val.len() < core::mem::size_of::<i32>() {
|
if val.len() < core::mem::size_of::<i32>() {
|
||||||
return Err(SystemError::EINVAL);
|
return Err(SystemError::EINVAL);
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
use alloc::sync::{Arc, Weak};
|
use alloc::sync::{Arc, Weak};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::sync::atomic::{AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicU16, AtomicU64, Ordering};
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
use jhash::jhash2;
|
use jhash::jhash2;
|
||||||
use smoltcp::wire::{IpAddress, IpEndpoint};
|
use smoltcp::wire::{IpAddress, IpEndpoint};
|
||||||
|
use system_error::SystemError;
|
||||||
|
|
||||||
use crate::libs::rwsem::RwSem;
|
use crate::arch::rand::rand;
|
||||||
use crate::process::namespace::net_namespace::NetNamespace;
|
use crate::libs::mutex::Mutex;
|
||||||
use crate::process::namespace::NamespaceOps;
|
|
||||||
|
|
||||||
use super::UdpSocket;
|
use super::UdpSocket;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct UdpBinding {
|
struct UdpBinding {
|
||||||
netns_id: usize,
|
|
||||||
socket: Weak<UdpSocket>,
|
socket: Weak<UdpSocket>,
|
||||||
addr: IpAddress,
|
addr: IpAddress,
|
||||||
port: u16,
|
/// Bind-time reuseport group membership. Unlike SO_REUSEADDR conflict
|
||||||
|
/// checks, Linux reuseport delivery membership is not the live option bit.
|
||||||
reuseport: bool,
|
reuseport: bool,
|
||||||
|
bind_id: usize,
|
||||||
bound_seq: u64,
|
bound_seq: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,149 +30,269 @@ struct UdpBindingMatch {
|
|||||||
bound_seq: u64,
|
bound_seq: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
static BIND_SEQ: AtomicU64 = AtomicU64::new(1);
|
/// Per-network-namespace UDP port reservation and local-delivery table.
|
||||||
|
///
|
||||||
lazy_static! {
|
/// Device binding is intentionally not cached in an entry. Conflict checks and
|
||||||
static ref UDP_BINDINGS: RwSem<Vec<UdpBinding>> = RwSem::new(Vec::new());
|
/// delivery read each socket's authoritative `SocketDeviceBinding` so changing
|
||||||
|
/// SO_BINDTODEVICE cannot leave a stale port-table projection.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UdpBindingTable {
|
||||||
|
bindings: Mutex<HashMap<u16, Vec<UdpBinding>>>,
|
||||||
|
next_ephemeral: AtomicU16,
|
||||||
|
bind_seq: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_udp_binding(
|
impl Default for UdpBindingTable {
|
||||||
netns: &Arc<NetNamespace>,
|
fn default() -> Self {
|
||||||
socket: Weak<UdpSocket>,
|
Self {
|
||||||
addr: IpAddress,
|
bindings: Mutex::new(HashMap::new()),
|
||||||
port: u16,
|
next_ephemeral: AtomicU16::new(0),
|
||||||
_reuseaddr: bool,
|
bind_seq: AtomicU64::new(1),
|
||||||
reuseport: bool,
|
|
||||||
) {
|
|
||||||
let netns_id = netns.ns_common().nsid.data();
|
|
||||||
let bound_seq = BIND_SEQ.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let mut guard = UDP_BINDINGS.write();
|
|
||||||
guard.push(UdpBinding {
|
|
||||||
netns_id,
|
|
||||||
socket,
|
|
||||||
addr,
|
|
||||||
port,
|
|
||||||
reuseport,
|
|
||||||
bound_seq,
|
|
||||||
});
|
|
||||||
guard.retain(|b| b.socket.strong_count() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unregister_udp_binding(netns: &Arc<NetNamespace>, socket: &Weak<UdpSocket>) {
|
|
||||||
let netns_id = netns.ns_common().nsid.data();
|
|
||||||
let mut guard = UDP_BINDINGS.write();
|
|
||||||
guard.retain(|b| b.netns_id != netns_id || b.socket.as_ptr() != socket.as_ptr());
|
|
||||||
guard.retain(|b| b.socket.strong_count() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deliver_unicast_loopback(
|
|
||||||
netns: &Arc<NetNamespace>,
|
|
||||||
dest: IpEndpoint,
|
|
||||||
src: IpEndpoint,
|
|
||||||
ifindex: i32,
|
|
||||||
payload: &[u8],
|
|
||||||
) -> usize {
|
|
||||||
let candidates = match_udp_bindings(netns, dest.addr, dest.port);
|
|
||||||
if candidates.is_empty() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let chosen = if candidates.iter().any(|c| c.reuseport) {
|
|
||||||
choose_reuseport_socket(&candidates, dest, src)
|
|
||||||
} else {
|
|
||||||
choose_recent_socket(&candidates)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(sock) = chosen {
|
|
||||||
if sock.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload) {
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deliver_multicast_all(
|
impl UdpBindingTable {
|
||||||
netns: &Arc<NetNamespace>,
|
#[allow(clippy::too_many_arguments)]
|
||||||
dest: IpEndpoint,
|
pub fn bind(
|
||||||
src: IpEndpoint,
|
&self,
|
||||||
ifindex: i32,
|
socket: Weak<UdpSocket>,
|
||||||
payload: &[u8],
|
addr: IpAddress,
|
||||||
) -> usize {
|
port: u16,
|
||||||
let candidates = match_udp_bindings(netns, dest.addr, dest.port);
|
reuseaddr: bool,
|
||||||
if candidates.is_empty() {
|
reuseport: bool,
|
||||||
return 0;
|
bind_id: usize,
|
||||||
|
prospective_ifindex: usize,
|
||||||
|
) -> Result<(), SystemError> {
|
||||||
|
if port == 0 {
|
||||||
|
return Err(SystemError::EINVAL);
|
||||||
|
}
|
||||||
|
let mut bindings = self.bindings.lock();
|
||||||
|
let bucket = bindings.entry(port).or_default();
|
||||||
|
Self::cleanup_bucket(bucket);
|
||||||
|
if Self::conflicts(
|
||||||
|
bucket,
|
||||||
|
addr,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
prospective_ifindex,
|
||||||
|
bind_id,
|
||||||
|
) {
|
||||||
|
return Err(SystemError::EADDRINUSE);
|
||||||
|
}
|
||||||
|
bucket.push(UdpBinding {
|
||||||
|
socket,
|
||||||
|
addr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_seq: self.bind_seq.fetch_add(1, Ordering::Relaxed),
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
let multiaddr = match dest.addr {
|
|
||||||
IpAddress::Ipv4(addr) => {
|
|
||||||
let octets = addr.octets();
|
|
||||||
u32::from_ne_bytes(octets)
|
|
||||||
}
|
|
||||||
_ => return 0,
|
|
||||||
};
|
|
||||||
let mut delivered = 0;
|
|
||||||
for cand in candidates {
|
|
||||||
let multicast_all = cand.socket.ip_multicast_all.load(Ordering::Relaxed);
|
|
||||||
if !multicast_all
|
|
||||||
&& !cand
|
|
||||||
.socket
|
|
||||||
.has_ipv4_multicast_membership(multiaddr, ifindex)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if cand
|
|
||||||
.socket
|
|
||||||
.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
|
||||||
{
|
|
||||||
delivered += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delivered
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deliver_broadcast_all(
|
#[allow(clippy::too_many_arguments)]
|
||||||
netns: &Arc<NetNamespace>,
|
pub fn bind_ephemeral(
|
||||||
dest: IpEndpoint,
|
&self,
|
||||||
src: IpEndpoint,
|
socket: Weak<UdpSocket>,
|
||||||
ifindex: i32,
|
addr: IpAddress,
|
||||||
payload: &[u8],
|
reuseaddr: bool,
|
||||||
) -> usize {
|
reuseport: bool,
|
||||||
let candidates = match_udp_bindings(netns, dest.addr, dest.port);
|
bind_id: usize,
|
||||||
if candidates.is_empty() {
|
prospective_ifindex: usize,
|
||||||
return 0;
|
range: (u16, u16),
|
||||||
|
) -> Result<u16, SystemError> {
|
||||||
|
let (min, max) = range;
|
||||||
|
if min == 0 || max == 0 || min > max {
|
||||||
|
return Err(SystemError::EINVAL);
|
||||||
|
}
|
||||||
|
let count = (max - min) as u32 + 1;
|
||||||
|
let current = self.next_ephemeral.load(Ordering::Relaxed);
|
||||||
|
if current < min || current > max {
|
||||||
|
self.next_ephemeral
|
||||||
|
.store(min + (rand() % count as usize) as u16, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bindings = self.bindings.lock();
|
||||||
|
for _ in 0..count {
|
||||||
|
let old = self
|
||||||
|
.next_ephemeral
|
||||||
|
.fetch_update(Ordering::AcqRel, Ordering::Relaxed, |cur| {
|
||||||
|
let cur = if cur < min || cur > max { min } else { cur };
|
||||||
|
Some(if cur >= max { min } else { cur + 1 })
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|cur| cur);
|
||||||
|
let port = if old < min || old >= max {
|
||||||
|
min
|
||||||
|
} else {
|
||||||
|
old + 1
|
||||||
|
};
|
||||||
|
let bucket = bindings.entry(port).or_default();
|
||||||
|
Self::cleanup_bucket(bucket);
|
||||||
|
if Self::conflicts(
|
||||||
|
bucket,
|
||||||
|
addr,
|
||||||
|
reuseaddr,
|
||||||
|
reuseport,
|
||||||
|
prospective_ifindex,
|
||||||
|
bind_id,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bucket.push(UdpBinding {
|
||||||
|
socket,
|
||||||
|
addr,
|
||||||
|
reuseport,
|
||||||
|
bind_id,
|
||||||
|
bound_seq: self.bind_seq.fetch_add(1, Ordering::Relaxed),
|
||||||
|
});
|
||||||
|
return Ok(port);
|
||||||
|
}
|
||||||
|
Err(SystemError::EADDRINUSE)
|
||||||
}
|
}
|
||||||
let mut delivered = 0;
|
|
||||||
for cand in candidates {
|
pub fn unbind(&self, port: u16, bind_id: usize) {
|
||||||
if cand
|
let mut bindings = self.bindings.lock();
|
||||||
.socket
|
let remove_bucket = if let Some(bucket) = bindings.get_mut(&port) {
|
||||||
.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
bucket
|
||||||
{
|
.retain(|binding| binding.bind_id != bind_id && binding.socket.strong_count() > 0);
|
||||||
delivered += 1;
|
bucket.is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if remove_bucket {
|
||||||
|
bindings.remove(&port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delivered
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_udp_bindings(
|
pub fn deliver_unicast(
|
||||||
netns: &Arc<NetNamespace>,
|
&self,
|
||||||
dest_addr: IpAddress,
|
dest: IpEndpoint,
|
||||||
dest_port: u16,
|
src: IpEndpoint,
|
||||||
) -> Vec<UdpBindingMatch> {
|
ifindex: i32,
|
||||||
let netns_id = netns.ns_common().nsid.data();
|
payload: &[u8],
|
||||||
let mut guard = UDP_BINDINGS.write();
|
) -> usize {
|
||||||
guard.retain(|b| b.socket.strong_count() > 0);
|
let candidates = self.match_bindings(dest.addr, dest.port, ifindex);
|
||||||
guard
|
let chosen = if candidates.iter().any(|candidate| candidate.reuseport) {
|
||||||
.iter()
|
choose_reuseport_socket(&candidates, dest, src)
|
||||||
.filter(|b| b.netns_id == netns_id)
|
} else {
|
||||||
.filter(|b| b.port == dest_port)
|
choose_recent_socket(&candidates)
|
||||||
.filter(|b| udp_addr_match(b.addr, dest_addr))
|
};
|
||||||
.filter_map(|b| {
|
chosen
|
||||||
b.socket.upgrade().map(|sock| UdpBindingMatch {
|
.filter(|socket| {
|
||||||
socket: sock,
|
socket.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
||||||
reuseport: b.reuseport,
|
|
||||||
bound_seq: b.bound_seq,
|
|
||||||
})
|
})
|
||||||
|
.map_or(0, |_| 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deliver_multicast(
|
||||||
|
&self,
|
||||||
|
dest: IpEndpoint,
|
||||||
|
src: IpEndpoint,
|
||||||
|
ifindex: i32,
|
||||||
|
payload: &[u8],
|
||||||
|
) -> usize {
|
||||||
|
let candidates = self.match_bindings(dest.addr, dest.port, ifindex);
|
||||||
|
let multiaddr = match dest.addr {
|
||||||
|
IpAddress::Ipv4(addr) => u32::from_ne_bytes(addr.octets()),
|
||||||
|
_ => return 0,
|
||||||
|
};
|
||||||
|
candidates
|
||||||
|
.into_iter()
|
||||||
|
.filter(|candidate| {
|
||||||
|
candidate.socket.ip_multicast_all.load(Ordering::Relaxed)
|
||||||
|
|| candidate
|
||||||
|
.socket
|
||||||
|
.has_ipv4_multicast_membership(multiaddr, ifindex)
|
||||||
|
})
|
||||||
|
.filter(|candidate| {
|
||||||
|
candidate
|
||||||
|
.socket
|
||||||
|
.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deliver_broadcast(
|
||||||
|
&self,
|
||||||
|
dest: IpEndpoint,
|
||||||
|
src: IpEndpoint,
|
||||||
|
ifindex: i32,
|
||||||
|
payload: &[u8],
|
||||||
|
) -> usize {
|
||||||
|
self.match_bindings(dest.addr, dest.port, ifindex)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|candidate| {
|
||||||
|
candidate
|
||||||
|
.socket
|
||||||
|
.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn conflicts(
|
||||||
|
bindings: &[UdpBinding],
|
||||||
|
addr: IpAddress,
|
||||||
|
reuseaddr: bool,
|
||||||
|
reuseport: bool,
|
||||||
|
prospective_ifindex: usize,
|
||||||
|
bind_id: usize,
|
||||||
|
) -> bool {
|
||||||
|
bindings.iter().any(|binding| {
|
||||||
|
if binding.bind_id == bind_id || !udp_addrs_conflict(binding.addr, addr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Some(socket) = binding.socket.upgrade() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let existing_ifindex = socket.bound_device_ifindex();
|
||||||
|
if existing_ifindex != 0
|
||||||
|
&& prospective_ifindex != 0
|
||||||
|
&& existing_ifindex != prospective_ifindex
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let (existing_reuseaddr, _) = socket.reuse_options();
|
||||||
|
!((reuseport && binding.reuseport) || (reuseaddr && existing_reuseaddr))
|
||||||
})
|
})
|
||||||
.collect()
|
}
|
||||||
|
|
||||||
|
fn match_bindings(
|
||||||
|
&self,
|
||||||
|
dest_addr: IpAddress,
|
||||||
|
dest_port: u16,
|
||||||
|
ingress_ifindex: i32,
|
||||||
|
) -> Vec<UdpBindingMatch> {
|
||||||
|
let mut bindings = self.bindings.lock();
|
||||||
|
let Some(bucket) = bindings.get_mut(&dest_port) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
Self::cleanup_bucket(bucket);
|
||||||
|
bucket
|
||||||
|
.iter()
|
||||||
|
.filter(|binding| udp_addr_match(binding.addr, dest_addr))
|
||||||
|
.filter_map(|binding| {
|
||||||
|
let socket = binding.socket.upgrade()?;
|
||||||
|
if ingress_ifindex <= 0 || !socket.device_binding_allows(ingress_ifindex as usize) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(UdpBindingMatch {
|
||||||
|
socket,
|
||||||
|
reuseport: binding.reuseport,
|
||||||
|
bound_seq: binding.bound_seq,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_bucket(bindings: &mut Vec<UdpBinding>) {
|
||||||
|
bindings.retain(|binding| binding.socket.strong_count() > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn udp_addrs_conflict(a: IpAddress, b: IpAddress) -> bool {
|
||||||
|
a.version() == b.version() && (a.is_unspecified() || b.is_unspecified() || a == b)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -178,20 +300,17 @@ fn udp_addr_match(bound_addr: IpAddress, dest_addr: IpAddress) -> bool {
|
|||||||
if bound_addr.version() != dest_addr.version() {
|
if bound_addr.version() != dest_addr.version() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if bound_addr.is_unspecified() {
|
bound_addr.is_unspecified()
|
||||||
return true;
|
|| dest_addr.is_multicast()
|
||||||
}
|
|| dest_addr.is_broadcast()
|
||||||
if dest_addr.is_multicast() || dest_addr.is_broadcast() {
|
|| bound_addr == dest_addr
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bound_addr == dest_addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_recent_socket(candidates: &[UdpBindingMatch]) -> Option<Arc<UdpSocket>> {
|
fn choose_recent_socket(candidates: &[UdpBindingMatch]) -> Option<Arc<UdpSocket>> {
|
||||||
candidates
|
candidates
|
||||||
.iter()
|
.iter()
|
||||||
.max_by_key(|c| c.bound_seq)
|
.max_by_key(|candidate| candidate.bound_seq)
|
||||||
.map(|c| c.socket.clone())
|
.map(|candidate| candidate.socket.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_reuseport_socket(
|
fn choose_reuseport_socket(
|
||||||
@@ -199,14 +318,17 @@ fn choose_reuseport_socket(
|
|||||||
dest: IpEndpoint,
|
dest: IpEndpoint,
|
||||||
src: IpEndpoint,
|
src: IpEndpoint,
|
||||||
) -> Option<Arc<UdpSocket>> {
|
) -> Option<Arc<UdpSocket>> {
|
||||||
let reuseport: Vec<&UdpBindingMatch> = candidates.iter().filter(|c| c.reuseport).collect();
|
let reuseport: Vec<&UdpBindingMatch> = candidates
|
||||||
|
.iter()
|
||||||
|
.filter(|candidate| candidate.reuseport)
|
||||||
|
.collect();
|
||||||
if reuseport.is_empty() {
|
if reuseport.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let index = (udp_4tuple_hash(dest, src) as usize) % reuseport.len();
|
||||||
let hash = udp_4tuple_hash(dest, src);
|
reuseport
|
||||||
let idx = (hash as usize) % reuseport.len();
|
.get(index)
|
||||||
reuseport.get(idx).map(|c| c.socket.clone())
|
.map(|candidate| candidate.socket.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn udp_4tuple_hash(dest: IpEndpoint, src: IpEndpoint) -> u32 {
|
fn udp_4tuple_hash(dest: IpEndpoint, src: IpEndpoint) -> u32 {
|
||||||
@@ -214,25 +336,23 @@ fn udp_4tuple_hash(dest: IpEndpoint, src: IpEndpoint) -> u32 {
|
|||||||
let dst_port = dest.port as u32;
|
let dst_port = dest.port as u32;
|
||||||
match (dest.addr, src.addr) {
|
match (dest.addr, src.addr) {
|
||||||
(IpAddress::Ipv4(dst), IpAddress::Ipv4(src)) => {
|
(IpAddress::Ipv4(dst), IpAddress::Ipv4(src)) => {
|
||||||
let data = [src.to_bits(), dst.to_bits(), src_port, dst_port];
|
jhash2(&[src.to_bits(), dst.to_bits(), src_port, dst_port], 0)
|
||||||
jhash2(&data, 0)
|
|
||||||
}
|
}
|
||||||
(IpAddress::Ipv6(dst), IpAddress::Ipv6(src)) => {
|
(IpAddress::Ipv6(dst), IpAddress::Ipv6(src)) => {
|
||||||
let src_oct = src.octets();
|
let src_octets = src.octets();
|
||||||
let dst_oct = dst.octets();
|
let dst_octets = dst.octets();
|
||||||
let data = [
|
jhash2(
|
||||||
u32::from_be_bytes([src_oct[0], src_oct[1], src_oct[2], src_oct[3]]),
|
&[
|
||||||
u32::from_be_bytes([src_oct[4], src_oct[5], src_oct[6], src_oct[7]]),
|
u32::from_be_bytes(src_octets[0..4].try_into().unwrap()),
|
||||||
u32::from_be_bytes([dst_oct[0], dst_oct[1], dst_oct[2], dst_oct[3]]),
|
u32::from_be_bytes(src_octets[4..8].try_into().unwrap()),
|
||||||
u32::from_be_bytes([dst_oct[4], dst_oct[5], dst_oct[6], dst_oct[7]]),
|
u32::from_be_bytes(dst_octets[0..4].try_into().unwrap()),
|
||||||
src_port,
|
u32::from_be_bytes(dst_octets[4..8].try_into().unwrap()),
|
||||||
dst_port,
|
src_port,
|
||||||
];
|
dst_port,
|
||||||
jhash2(&data, 0)
|
],
|
||||||
}
|
0,
|
||||||
_ => {
|
)
|
||||||
let data = [src_port, dst_port, 0, 0];
|
|
||||||
jhash2(&data, 0)
|
|
||||||
}
|
}
|
||||||
|
_ => jhash2(&[src_port, dst_port, 0, 0], 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use alloc::vec::Vec;
|
|||||||
|
|
||||||
use smoltcp::wire::{IpAddress, IpProtocol, IpVersion, UdpPacket};
|
use smoltcp::wire::{IpAddress, IpProtocol, IpVersion, UdpPacket};
|
||||||
|
|
||||||
|
use crate::driver::net::Iface;
|
||||||
use crate::libs::rwsem::RwSem;
|
use crate::libs::rwsem::RwSem;
|
||||||
use crate::process::namespace::net_namespace::NetNamespace;
|
use crate::process::namespace::net_namespace::NetNamespace;
|
||||||
use crate::process::namespace::NamespaceOps;
|
use crate::process::namespace::NamespaceOps;
|
||||||
@@ -142,11 +143,13 @@ fn should_deliver_to_socket(s: &RawSocket, ctx: &LoopbackDeliverContext) -> bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. SO_BINDTODEVICE:loopback 快速路径视为来自 lo
|
// 4. SO_BINDTODEVICE:loopback 快速路径视为来自当前 netns 的 lo
|
||||||
if let Some(dev) = &s.options.read().bind_to_device {
|
if let Some(lo) = s.netns.loopback_iface() {
|
||||||
if dev.as_str() != "lo" {
|
if !s.device_binding.allows(lo.nic_id()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if s.device_binding.ifindex() != 0 {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. bind(2) 目的地址过滤
|
// 5. bind(2) 目的地址过滤
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::libs::mutex::Mutex;
|
|||||||
use crate::libs::rwsem::RwSem;
|
use crate::libs::rwsem::RwSem;
|
||||||
use crate::libs::wait_queue::WaitQueue;
|
use crate::libs::wait_queue::WaitQueue;
|
||||||
use crate::net::socket::common::EPollItems;
|
use crate::net::socket::common::EPollItems;
|
||||||
|
use crate::net::socket::inet::common::SocketDeviceBinding;
|
||||||
use crate::process::namespace::net_namespace::NetNamespace;
|
use crate::process::namespace::net_namespace::NetNamespace;
|
||||||
|
|
||||||
use inner::RawInner;
|
use inner::RawInner;
|
||||||
@@ -69,6 +70,8 @@ pub struct RawSocket {
|
|||||||
self_ref: Weak<Self>,
|
self_ref: Weak<Self>,
|
||||||
/// 网络命名空间
|
/// 网络命名空间
|
||||||
netns: Arc<NetNamespace>,
|
netns: Arc<NetNamespace>,
|
||||||
|
/// SO_BINDTODEVICE authoritative interface index.
|
||||||
|
device_binding: SocketDeviceBinding,
|
||||||
/// epoll 项
|
/// epoll 项
|
||||||
epoll_items: EPollItems,
|
epoll_items: EPollItems,
|
||||||
/// fasync 项
|
/// fasync 项
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use alloc::string::String;
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
use super::constants::{SYSCTL_RMEM_MAX, SYSCTL_WMEM_MAX};
|
use super::constants::{SYSCTL_RMEM_MAX, SYSCTL_WMEM_MAX};
|
||||||
@@ -100,9 +99,6 @@ pub struct RawSocketOptions {
|
|||||||
/// SO_RCVBUF: 返回给 getsockopt 的 sk_rcvbuf(Linux 会将 setsockopt 的值 *2 后存储)
|
/// SO_RCVBUF: 返回给 getsockopt 的 sk_rcvbuf(Linux 会将 setsockopt 的值 *2 后存储)
|
||||||
pub sock_rcvbuf: u32,
|
pub sock_rcvbuf: u32,
|
||||||
|
|
||||||
/// SO_BINDTODEVICE: 绑定的设备名(不含 '\0')
|
|
||||||
pub bind_to_device: Option<String>,
|
|
||||||
|
|
||||||
/// SO_LINGER
|
/// SO_LINGER
|
||||||
pub linger_onoff: i32,
|
pub linger_onoff: i32,
|
||||||
pub linger_linger: i32,
|
pub linger_linger: i32,
|
||||||
@@ -137,7 +133,6 @@ impl Default for RawSocketOptions {
|
|||||||
// 初始值设为 sysctl_*mem_max * 2,与 Linux 默认行为一致。
|
// 初始值设为 sysctl_*mem_max * 2,与 Linux 默认行为一致。
|
||||||
sock_sndbuf: SYSCTL_WMEM_MAX.saturating_mul(2),
|
sock_sndbuf: SYSCTL_WMEM_MAX.saturating_mul(2),
|
||||||
sock_rcvbuf: SYSCTL_RMEM_MAX.saturating_mul(2),
|
sock_rcvbuf: SYSCTL_RMEM_MAX.saturating_mul(2),
|
||||||
bind_to_device: None,
|
|
||||||
linger_onoff: 0,
|
linger_onoff: 0,
|
||||||
linger_linger: 0,
|
linger_linger: 0,
|
||||||
filter_attached: false,
|
filter_attached: false,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ impl RawSocket {
|
|||||||
fsnotify_watches: core::sync::atomic::AtomicUsize::new(0),
|
fsnotify_watches: core::sync::atomic::AtomicUsize::new(0),
|
||||||
self_ref: me.clone(),
|
self_ref: me.clone(),
|
||||||
netns,
|
netns,
|
||||||
|
device_binding: crate::net::socket::inet::common::SocketDeviceBinding::default(),
|
||||||
epoll_items: crate::net::socket::common::EPollItems::default(),
|
epoll_items: crate::net::socket::common::EPollItems::default(),
|
||||||
fasync_items: crate::filesystem::vfs::fasync::FAsyncItems::default(),
|
fasync_items: crate::filesystem::vfs::fasync::FAsyncItems::default(),
|
||||||
ip_version,
|
ip_version,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use alloc::string::String;
|
|
||||||
|
|
||||||
use smoltcp::wire::IpProtocol;
|
use smoltcp::wire::IpProtocol;
|
||||||
use system_error::SystemError;
|
use system_error::SystemError;
|
||||||
|
|
||||||
@@ -13,7 +11,7 @@ use crate::net::socket::common::{
|
|||||||
write_timeval_opt, write_u32_getsockopt,
|
write_timeval_opt, write_u32_getsockopt,
|
||||||
};
|
};
|
||||||
use crate::net::socket::inet::common::{apply_ipv4_membership, apply_ipv4_multicast_if};
|
use crate::net::socket::inet::common::{apply_ipv4_membership, apply_ipv4_multicast_if};
|
||||||
use crate::net::socket::{IpOption, IFNAMSIZ, PIPV6, PRAW, PSO};
|
use crate::net::socket::{IpOption, PIPV6, PRAW, PSO};
|
||||||
|
|
||||||
fn sock_buf_u32_from_opt(val: &[u8]) -> Result<u32, SystemError> {
|
fn sock_buf_u32_from_opt(val: &[u8]) -> Result<u32, SystemError> {
|
||||||
if val.len() < 4 {
|
if val.len() < 4 {
|
||||||
@@ -52,27 +50,7 @@ impl RawSocket {
|
|||||||
match PSO::try_from(name as u32) {
|
match PSO::try_from(name as u32) {
|
||||||
Ok(PSO::SNDBUF) => Ok(write_u32_getsockopt(value, self.options.read().sock_sndbuf)),
|
Ok(PSO::SNDBUF) => Ok(write_u32_getsockopt(value, self.options.read().sock_sndbuf)),
|
||||||
Ok(PSO::RCVBUF) => Ok(write_u32_getsockopt(value, self.options.read().sock_rcvbuf)),
|
Ok(PSO::RCVBUF) => Ok(write_u32_getsockopt(value, self.options.read().sock_rcvbuf)),
|
||||||
Ok(PSO::BINDTODEVICE) => {
|
Ok(PSO::BINDTODEVICE) => self.device_binding.get(&self.netns, value),
|
||||||
let name = self
|
|
||||||
.options
|
|
||||||
.read()
|
|
||||||
.bind_to_device
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default();
|
|
||||||
if name.is_empty() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
if value.len() < IFNAMSIZ {
|
|
||||||
return Err(SystemError::EINVAL);
|
|
||||||
}
|
|
||||||
let need = core::cmp::min(name.len() + 1, IFNAMSIZ);
|
|
||||||
let bytes = name.as_bytes();
|
|
||||||
let name_len = core::cmp::min(bytes.len(), need.saturating_sub(1));
|
|
||||||
let copy_len = core::cmp::min(name_len, need.saturating_sub(1));
|
|
||||||
value[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
|
||||||
value[copy_len] = 0;
|
|
||||||
Ok(need)
|
|
||||||
}
|
|
||||||
Ok(PSO::LINGER) => {
|
Ok(PSO::LINGER) => {
|
||||||
let opts = self.options.read();
|
let opts = self.options.read();
|
||||||
Ok(write_linger_getsockopt(
|
Ok(write_linger_getsockopt(
|
||||||
@@ -252,22 +230,8 @@ impl RawSocket {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(PSO::BINDTODEVICE) => {
|
Ok(PSO::BINDTODEVICE) => {
|
||||||
let end = val.iter().position(|&b| b == 0).unwrap_or(val.len());
|
let mut update = self.device_binding.prepare_update(&self.netns, val)?;
|
||||||
let name_bytes = &val[..end];
|
update.commit();
|
||||||
if name_bytes.is_empty() {
|
|
||||||
self.options.write().bind_to_device = None;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let name = core::str::from_utf8(name_bytes).map_err(|_| SystemError::EINVAL)?;
|
|
||||||
let found = self
|
|
||||||
.netns
|
|
||||||
.device_list()
|
|
||||||
.values()
|
|
||||||
.any(|iface| iface.iface_name() == name);
|
|
||||||
if !found {
|
|
||||||
return Err(SystemError::ENODEV);
|
|
||||||
}
|
|
||||||
self.options.write().bind_to_device = Some(String::from(name));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(PSO::DETACH_FILTER) => {
|
Ok(PSO::DETACH_FILTER) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::libs::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|||||||
use crate::libs::rwsem::{RwSem, RwSemReadGuard, RwSemWriteGuard};
|
use crate::libs::rwsem::{RwSem, RwSemReadGuard, RwSemWriteGuard};
|
||||||
use crate::libs::wait_queue::WaitQueue;
|
use crate::libs::wait_queue::WaitQueue;
|
||||||
use crate::net::routing::Router;
|
use crate::net::routing::Router;
|
||||||
|
use crate::net::socket::inet::datagram::udp_bindings::UdpBindingTable;
|
||||||
use crate::net::socket::netlink::table::{
|
use crate::net::socket::netlink::table::{
|
||||||
generate_supported_netlink_kernel_sockets, NetlinkKernelSocket, NetlinkSocketTable,
|
generate_supported_netlink_kernel_sockets, NetlinkKernelSocket, NetlinkSocketTable,
|
||||||
};
|
};
|
||||||
@@ -87,6 +88,8 @@ pub struct NetNamespace {
|
|||||||
/// 注意:该结构会在 bind/connect 等路径被访问,且这些路径可能会获取可睡眠的 Mutex,
|
/// 注意:该结构会在 bind/connect 等路径被访问,且这些路径可能会获取可睡眠的 Mutex,
|
||||||
/// 因此这里使用可睡眠的 `RwSem`,避免自旋锁 + schedule 的组合导致崩溃。
|
/// 因此这里使用可睡眠的 `RwSem`,避免自旋锁 + schedule 的组合导致崩溃。
|
||||||
device_list: RwSem<BTreeMap<usize, Arc<dyn Iface>>>,
|
device_list: RwSem<BTreeMap<usize, Arc<dyn Iface>>>,
|
||||||
|
/// Per-netns UDP port reservation and local-delivery table.
|
||||||
|
udp_bindings: UdpBindingTable,
|
||||||
/// Lock-free read-side snapshot for AF_PACKET delivery from NAPI context.
|
/// Lock-free read-side snapshot for AF_PACKET delivery from NAPI context.
|
||||||
packet_sockets: RcuArcSlot<PacketSocketRegistrySnapshot>,
|
packet_sockets: RcuArcSlot<PacketSocketRegistrySnapshot>,
|
||||||
/// Serializes all plain/fanout topology updates and owns group IDs.
|
/// Serializes all plain/fanout topology updates and owns group IDs.
|
||||||
@@ -468,6 +471,7 @@ impl NetNamespace {
|
|||||||
inner: RwLock::new(inner),
|
inner: RwLock::new(inner),
|
||||||
poller: NetnsPoller::new(self_ref.clone()),
|
poller: NetnsPoller::new(self_ref.clone()),
|
||||||
device_list: RwSem::new(BTreeMap::new()),
|
device_list: RwSem::new(BTreeMap::new()),
|
||||||
|
udp_bindings: UdpBindingTable::default(),
|
||||||
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
||||||
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
||||||
packet_sockets_need_cleanup: AtomicBool::new(false),
|
packet_sockets_need_cleanup: AtomicBool::new(false),
|
||||||
@@ -490,7 +494,7 @@ impl NetNamespace {
|
|||||||
let counter = get_next_netns_counter();
|
let counter = get_next_netns_counter();
|
||||||
let loopback = crate::driver::net::loopback::LoopbackInterface::new_with_ifindex(
|
let loopback = crate::driver::net::loopback::LoopbackInterface::new_with_ifindex(
|
||||||
crate::driver::net::loopback::LoopbackDriver::default(),
|
crate::driver::net::loopback::LoopbackDriver::default(),
|
||||||
1,
|
crate::net::LOOPBACK_IFINDEX,
|
||||||
);
|
);
|
||||||
|
|
||||||
let inner = InnerNetNamespace {
|
let inner = InnerNetNamespace {
|
||||||
@@ -507,6 +511,7 @@ impl NetNamespace {
|
|||||||
inner: RwLock::new(inner),
|
inner: RwLock::new(inner),
|
||||||
poller: NetnsPoller::new(self_ref.clone()),
|
poller: NetnsPoller::new(self_ref.clone()),
|
||||||
device_list: RwSem::new(BTreeMap::new()),
|
device_list: RwSem::new(BTreeMap::new()),
|
||||||
|
udp_bindings: UdpBindingTable::default(),
|
||||||
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
||||||
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
||||||
packet_sockets_need_cleanup: AtomicBool::new(false),
|
packet_sockets_need_cleanup: AtomicBool::new(false),
|
||||||
@@ -554,6 +559,10 @@ impl NetNamespace {
|
|||||||
self.device_list.read()
|
self.device_list.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn udp_bindings(&self) -> &UdpBindingTable {
|
||||||
|
&self.udp_bindings
|
||||||
|
}
|
||||||
|
|
||||||
pub fn register_packet_socket(&self, socket: Weak<PacketSocket>) -> Result<(), SystemError> {
|
pub fn register_packet_socket(&self, socket: Weak<PacketSocket>) -> Result<(), SystemError> {
|
||||||
let writer = self.packet_sockets_writer.lock();
|
let writer = self.packet_sockets_writer.lock();
|
||||||
let current = self.packet_sockets.load();
|
let current = self.packet_sockets.load();
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ TEST(SocketIoctlNetdevQuery, LoopbackFieldsAndUnionWidths) {
|
|||||||
struct ifreq ifr {};
|
struct ifreq ifr {};
|
||||||
InitIfreq(&ifr, "lo");
|
InitIfreq(&ifr, "lo");
|
||||||
ASSERT_EQ(ioctl(fd.get(), SIOCGIFINDEX, &ifr), 0) << strerror(errno);
|
ASSERT_EQ(ioctl(fd.get(), SIOCGIFINDEX, &ifr), 0) << strerror(errno);
|
||||||
EXPECT_GT(ifr.ifr_ifindex, 0);
|
EXPECT_EQ(ifr.ifr_ifindex, 1);
|
||||||
ExpectSentinelFrom(ifr, sizeof(int));
|
ExpectSentinelFrom(ifr, sizeof(int));
|
||||||
|
|
||||||
InitIfreq(&ifr, "lo");
|
InitIfreq(&ifr, "lo");
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class FdGuard {
|
||||||
|
public:
|
||||||
|
explicit FdGuard(int fd = -1) : fd_(fd) {}
|
||||||
|
~FdGuard() {
|
||||||
|
if (fd_ >= 0) close(fd_);
|
||||||
|
}
|
||||||
|
int Get() const { return fd_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void BindToLoopback(int fd) {
|
||||||
|
const char name[] = "lo";
|
||||||
|
ASSERT_EQ(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, sizeof(name)), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FindNonLoopbackInterface() {
|
||||||
|
std::string name;
|
||||||
|
struct if_nameindex* interfaces = if_nameindex();
|
||||||
|
if (interfaces == nullptr) return name;
|
||||||
|
for (struct if_nameindex* current = interfaces; current->if_index != 0; ++current) {
|
||||||
|
if (strcmp(current->if_name, "lo") != 0) {
|
||||||
|
name = current->if_name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if_freenameindex(interfaces);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, UnboundGetsockoptHasZeroLengthAndDoesNotWrite) {
|
||||||
|
FdGuard fd(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(fd.Get(), 0);
|
||||||
|
|
||||||
|
char value[IFNAMSIZ];
|
||||||
|
memset(value, 0x5a, sizeof(value));
|
||||||
|
socklen_t len = sizeof(value);
|
||||||
|
ASSERT_EQ(getsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, value, &len), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
EXPECT_EQ(len, 0u);
|
||||||
|
for (char byte : value) EXPECT_EQ(static_cast<unsigned char>(byte), 0x5a);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, SetGetInvalidAndClearFollowLinuxAbi) {
|
||||||
|
FdGuard fd(socket(AF_INET6, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(fd.Get(), 0);
|
||||||
|
|
||||||
|
const char missing[] = "dunit-no-such-iface";
|
||||||
|
errno = 0;
|
||||||
|
EXPECT_EQ(setsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, missing, sizeof(missing)), -1);
|
||||||
|
EXPECT_EQ(errno, ENODEV);
|
||||||
|
|
||||||
|
BindToLoopback(fd.Get());
|
||||||
|
|
||||||
|
char small[IFNAMSIZ - 1] = {};
|
||||||
|
socklen_t small_len = sizeof(small);
|
||||||
|
errno = 0;
|
||||||
|
EXPECT_EQ(getsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, small, &small_len), -1);
|
||||||
|
EXPECT_EQ(errno, EINVAL);
|
||||||
|
|
||||||
|
char value[IFNAMSIZ] = {};
|
||||||
|
socklen_t len = sizeof(value);
|
||||||
|
ASSERT_EQ(getsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, value, &len), 0);
|
||||||
|
EXPECT_STREQ(value, "lo");
|
||||||
|
EXPECT_EQ(len, 3u);
|
||||||
|
|
||||||
|
const char clear[] = "";
|
||||||
|
ASSERT_EQ(setsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, clear, sizeof(clear)), 0);
|
||||||
|
len = sizeof(value);
|
||||||
|
ASSERT_EQ(getsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, value, &len), 0);
|
||||||
|
EXPECT_EQ(len, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, LoopbackDatagramUsesBoundInterface) {
|
||||||
|
FdGuard receiver(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard sender(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(receiver.Get(), 0);
|
||||||
|
ASSERT_GE(sender.Get(), 0);
|
||||||
|
BindToLoopback(receiver.Get());
|
||||||
|
BindToLoopback(sender.Get());
|
||||||
|
|
||||||
|
timeval timeout = {.tv_sec = 1, .tv_usec = 0};
|
||||||
|
ASSERT_EQ(setsockopt(receiver.Get(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)), 0);
|
||||||
|
|
||||||
|
sockaddr_in address = {};
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
ASSERT_EQ(bind(receiver.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
socklen_t address_len = sizeof(address);
|
||||||
|
ASSERT_EQ(getsockname(receiver.Get(), reinterpret_cast<sockaddr*>(&address), &address_len), 0);
|
||||||
|
|
||||||
|
const char payload[] = "bound-lo";
|
||||||
|
ASSERT_EQ(sendto(sender.Get(), payload, sizeof(payload), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&address), sizeof(address)),
|
||||||
|
static_cast<ssize_t>(sizeof(payload)))
|
||||||
|
<< strerror(errno);
|
||||||
|
char received[sizeof(payload)] = {};
|
||||||
|
ASSERT_EQ(recv(receiver.Get(), received, sizeof(received), 0),
|
||||||
|
static_cast<ssize_t>(sizeof(payload)))
|
||||||
|
<< strerror(errno);
|
||||||
|
EXPECT_EQ(memcmp(received, payload, sizeof(payload)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, WildcardLocalSendSelectsInterfaceSourceAddress) {
|
||||||
|
FdGuard receiver(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard sender(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(receiver.Get(), 0);
|
||||||
|
ASSERT_GE(sender.Get(), 0);
|
||||||
|
|
||||||
|
sockaddr_in receiver_address = {};
|
||||||
|
receiver_address.sin_family = AF_INET;
|
||||||
|
receiver_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
ASSERT_EQ(bind(receiver.Get(), reinterpret_cast<sockaddr*>(&receiver_address),
|
||||||
|
sizeof(receiver_address)),
|
||||||
|
0)
|
||||||
|
<< strerror(errno);
|
||||||
|
socklen_t receiver_address_len = sizeof(receiver_address);
|
||||||
|
ASSERT_EQ(getsockname(receiver.Get(), reinterpret_cast<sockaddr*>(&receiver_address),
|
||||||
|
&receiver_address_len),
|
||||||
|
0);
|
||||||
|
|
||||||
|
sockaddr_in wildcard = {};
|
||||||
|
wildcard.sin_family = AF_INET;
|
||||||
|
wildcard.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
ASSERT_EQ(bind(sender.Get(), reinterpret_cast<sockaddr*>(&wildcard), sizeof(wildcard)), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
|
||||||
|
const char payload[] = "wildcard-source";
|
||||||
|
ASSERT_EQ(sendto(sender.Get(), payload, sizeof(payload), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&receiver_address), sizeof(receiver_address)),
|
||||||
|
static_cast<ssize_t>(sizeof(payload)))
|
||||||
|
<< strerror(errno);
|
||||||
|
|
||||||
|
char received[sizeof(payload)] = {};
|
||||||
|
sockaddr_in peer = {};
|
||||||
|
socklen_t peer_len = sizeof(peer);
|
||||||
|
ASSERT_EQ(recvfrom(receiver.Get(), received, sizeof(received), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&peer), &peer_len),
|
||||||
|
static_cast<ssize_t>(sizeof(payload)))
|
||||||
|
<< strerror(errno);
|
||||||
|
EXPECT_EQ(peer.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
|
||||||
|
EXPECT_EQ(memcmp(received, payload, sizeof(payload)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, PortConflictsIncludeTheBoundDeviceDimension) {
|
||||||
|
const std::string non_loopback = FindNonLoopbackInterface();
|
||||||
|
if (non_loopback.empty()) GTEST_SKIP() << "no non-loopback interface";
|
||||||
|
|
||||||
|
FdGuard loopback(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard physical(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard wildcard(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(loopback.Get(), 0);
|
||||||
|
ASSERT_GE(physical.Get(), 0);
|
||||||
|
ASSERT_GE(wildcard.Get(), 0);
|
||||||
|
BindToLoopback(loopback.Get());
|
||||||
|
ASSERT_EQ(setsockopt(physical.Get(), SOL_SOCKET, SO_BINDTODEVICE, non_loopback.c_str(),
|
||||||
|
non_loopback.size() + 1),
|
||||||
|
0)
|
||||||
|
<< strerror(errno);
|
||||||
|
|
||||||
|
sockaddr_in address = {};
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
ASSERT_EQ(bind(loopback.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0);
|
||||||
|
socklen_t address_len = sizeof(address);
|
||||||
|
ASSERT_EQ(getsockname(loopback.Get(), reinterpret_cast<sockaddr*>(&address), &address_len), 0);
|
||||||
|
|
||||||
|
ASSERT_EQ(bind(physical.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0)
|
||||||
|
<< "different nonzero bound devices may share a UDP port: " << strerror(errno);
|
||||||
|
errno = 0;
|
||||||
|
EXPECT_EQ(bind(wildcard.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), -1);
|
||||||
|
EXPECT_EQ(errno, EADDRINUSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, LocalFastPathDoesNotCrossBoundInterface) {
|
||||||
|
const std::string non_loopback = FindNonLoopbackInterface();
|
||||||
|
if (non_loopback.empty()) GTEST_SKIP() << "no non-loopback interface";
|
||||||
|
|
||||||
|
FdGuard receiver(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard sender(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(receiver.Get(), 0);
|
||||||
|
ASSERT_GE(sender.Get(), 0);
|
||||||
|
|
||||||
|
ASSERT_EQ(setsockopt(sender.Get(), SOL_SOCKET, SO_BINDTODEVICE, non_loopback.c_str(),
|
||||||
|
non_loopback.size() + 1),
|
||||||
|
0)
|
||||||
|
<< strerror(errno);
|
||||||
|
|
||||||
|
sockaddr_in address = {};
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
ASSERT_EQ(bind(receiver.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
socklen_t address_len = sizeof(address);
|
||||||
|
ASSERT_EQ(getsockname(receiver.Get(), reinterpret_cast<sockaddr*>(&address), &address_len), 0);
|
||||||
|
|
||||||
|
const char payload[] = "wrong-interface";
|
||||||
|
ASSERT_EQ(sendto(sender.Get(), payload, sizeof(payload), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&address), sizeof(address)),
|
||||||
|
static_cast<ssize_t>(sizeof(payload)))
|
||||||
|
<< strerror(errno);
|
||||||
|
|
||||||
|
char received[sizeof(payload)] = {};
|
||||||
|
errno = 0;
|
||||||
|
EXPECT_EQ(recv(receiver.Get(), received, sizeof(received), MSG_DONTWAIT), -1);
|
||||||
|
EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK) << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, ReuseOptionsAreReadAtBindConflictTime) {
|
||||||
|
FdGuard first(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
FdGuard second(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
ASSERT_GE(first.Get(), 0);
|
||||||
|
ASSERT_GE(second.Get(), 0);
|
||||||
|
|
||||||
|
sockaddr_in address = {};
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
ASSERT_EQ(bind(first.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0);
|
||||||
|
socklen_t address_len = sizeof(address);
|
||||||
|
ASSERT_EQ(getsockname(first.Get(), reinterpret_cast<sockaddr*>(&address), &address_len), 0);
|
||||||
|
|
||||||
|
int one = 1;
|
||||||
|
ASSERT_EQ(setsockopt(first.Get(), SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
|
||||||
|
ASSERT_EQ(setsockopt(second.Get(), SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
|
||||||
|
EXPECT_EQ(bind(second.Get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0)
|
||||||
|
<< strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UdpBindToDevice, RebindingAnExistingBindingRequiresCapability) {
|
||||||
|
pid_t child = fork();
|
||||||
|
ASSERT_GE(child, 0);
|
||||||
|
if (child == 0) {
|
||||||
|
FdGuard fd(socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
if (fd.Get() < 0 || setuid(65534) != 0) _exit(10);
|
||||||
|
const char name[] = "lo";
|
||||||
|
if (setsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, name, sizeof(name)) != 0) _exit(11);
|
||||||
|
errno = 0;
|
||||||
|
if (setsockopt(fd.Get(), SOL_SOCKET, SO_BINDTODEVICE, name, sizeof(name)) != -1 ||
|
||||||
|
errno != EPERM) {
|
||||||
|
_exit(12);
|
||||||
|
}
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
int status = 0;
|
||||||
|
ASSERT_EQ(waitpid(child, &status, 0), child);
|
||||||
|
ASSERT_TRUE(WIFEXITED(status));
|
||||||
|
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
@@ -110,6 +110,7 @@ normal/af_packet_mcast
|
|||||||
normal/rtnetlink_link_semantics
|
normal/rtnetlink_link_semantics
|
||||||
normal/rtnetlink_permission_semantics
|
normal/rtnetlink_permission_semantics
|
||||||
normal/rtnetlink_serialization_semantics
|
normal/rtnetlink_serialization_semantics
|
||||||
|
normal/udp_bind_to_device_semantics
|
||||||
normal/tty_termios
|
normal/tty_termios
|
||||||
normal/inotify_dir_watch
|
normal/inotify_dir_watch
|
||||||
normal/inotify_events
|
normal/inotify_events
|
||||||
|
|||||||
Reference in New Issue
Block a user