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::libs::rwsem::{RwSemReadGuard, RwSemWriteGuard};
|
||||
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::time::Instant;
|
||||
use alloc::collections::VecDeque;
|
||||
@@ -319,7 +319,7 @@ impl LoopbackInterface {
|
||||
pub const DEVICE_NAME: &str = "lo";
|
||||
|
||||
/// ## `new` 是一个公共函数,用于创建一个新的 `LoopbackInterface` 实例。
|
||||
/// 生成一个新的接口 ID。创建一个新的接口配置,设置其硬件地址和随机种子,使用接口配置和驱动器创建一个新的 `smoltcp::iface::Interface` 实例。
|
||||
/// 使用 Linux 约定的 loopback 接口 ID。创建一个新的接口配置,设置其硬件地址和随机种子,使用接口配置和驱动器创建一个新的 `smoltcp::iface::Interface` 实例。
|
||||
/// 设置接口的 IP 地址为 127.0.0.1。
|
||||
/// Saves a cloneable driver handle; the underlying queue and the interface
|
||||
/// back-reference are protected by a shared lock.
|
||||
@@ -330,7 +330,7 @@ impl LoopbackInterface {
|
||||
/// ## 返回值
|
||||
/// 返回一个 `Arc<Self>`,即一个指向新创建的 `LoopbackInterface` 实例的智能指针。
|
||||
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)。
|
||||
|
||||
@@ -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
|
||||
/// available. A release transfers its permit directly to the FIFO head,
|
||||
/// 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
|
||||
/// receive a grant.
|
||||
pub(super) fn run_async_writeback_budget_retry_selftest() -> bool {
|
||||
if !ASYNC_WRITEBACK_RETRIES.lock().is_empty()
|
||||
|| 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 mut permits = AsyncWritebackPermit::acquire_all_for_selftest();
|
||||
|
||||
let cache = PageCache::new_unowned(None, None);
|
||||
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_listener_backlog;
|
||||
|
||||
/// Linux reserves interface index 1 for the loopback device in every netns.
|
||||
pub const LOOPBACK_IFINDEX: usize = 1;
|
||||
|
||||
/// 生成网络接口的id (全局自增)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 use port::PortManager;
|
||||
mod device_binding;
|
||||
pub use device_binding::{DeviceBindingUpdate, SocketDeviceBinding};
|
||||
pub mod multicast;
|
||||
pub use multicast::{apply_ipv4_membership, apply_ipv4_multicast_if, Ipv4MulticastMembership};
|
||||
use system_error::SystemError;
|
||||
@@ -164,13 +166,29 @@ impl BoundInner {
|
||||
}
|
||||
|
||||
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) {
|
||||
detached();
|
||||
return Ok(());
|
||||
}
|
||||
let socket = self.iface.sockets().lock().remove(self.handle);
|
||||
let smoltcp::socket::Socket::Udp(socket) = socket else {
|
||||
return Err(SystemError::EINVAL);
|
||||
};
|
||||
detached();
|
||||
let handle = iface.sockets().lock().add(socket);
|
||||
self.iface = iface;
|
||||
self.handle = handle;
|
||||
@@ -245,7 +263,7 @@ pub fn normalize_unspecified_endpoint_to_loopback(
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_iface_for_local_bind(
|
||||
pub(crate) fn get_iface_for_local_bind(
|
||||
ip_addr: &smoltcp::wire::IpAddress,
|
||||
netns: &Arc<NetNamespace>,
|
||||
) -> 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>,
|
||||
remote_ip_addr: &smoltcp::wire::IpAddress,
|
||||
) -> Option<smoltcp::wire::IpAddress> {
|
||||
@@ -494,7 +512,7 @@ fn bind_addr_not_found_error(
|
||||
/// 1. Use the explicitly set default interface
|
||||
/// 2. Find an interface with a matching address family (IPv6 socket -> interface with IPv6 address)
|
||||
/// 3. Fall back to the first available interface
|
||||
fn select_iface_for_unspecified(
|
||||
pub(crate) fn select_iface_for_unspecified(
|
||||
address: &smoltcp::wire::IpAddress,
|
||||
netns: &Arc<NetNamespace>,
|
||||
) -> Result<Arc<dyn Iface>, SystemError> {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::sync::atomic::{AtomicU16, Ordering};
|
||||
use hashbrown::HashMap;
|
||||
use smoltcp::wire::IpAddress;
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::{
|
||||
@@ -12,21 +10,20 @@ use crate::{
|
||||
|
||||
use super::Types::{self, *};
|
||||
|
||||
/// # TCP 和 UDP 的端口管理器。
|
||||
/// 如果 TCP/UDP 的 socket 绑定了某个端口,它会在对应的表中记录,以检测端口冲突。
|
||||
/// Per-interface TCP port manager.
|
||||
///
|
||||
/// UDP reservations are network-namespace-wide and live in `UdpBindingTable`,
|
||||
/// because Linux device-bound sockets can legally share a port across ifaces.
|
||||
#[derive(Debug)]
|
||||
pub struct PortManager {
|
||||
// TCP 端口记录表
|
||||
tcp_port_table: Mutex<HashMap<u16, RawPid>>,
|
||||
// UDP 端口记录表
|
||||
udp_port_table: Mutex<HashMap<u16, Vec<UdpPortBinding>>>,
|
||||
}
|
||||
|
||||
impl Default for PortManager {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tcp_port_table: Mutex::new(HashMap::new()),
|
||||
udp_port_table: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,13 +70,6 @@ impl PortManager {
|
||||
|
||||
// 使用 ListenTable 检查端口是否被占用
|
||||
match socket_type {
|
||||
Udp => {
|
||||
let guard = self.udp_port_table.lock();
|
||||
if guard.get(&port).is_none() {
|
||||
drop(guard);
|
||||
return Ok(port);
|
||||
}
|
||||
}
|
||||
Tcp => {
|
||||
let guard = self.tcp_port_table.lock();
|
||||
if guard.get(&port).is_none() {
|
||||
@@ -117,57 +107,15 @@ impl PortManager {
|
||||
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 对应的表中记录
|
||||
///
|
||||
/// UDP 复用逻辑请使用 `bind_udp_port`
|
||||
pub fn bind_port(&self, socket_type: Types, port: u16) -> Result<(), SystemError> {
|
||||
if port > 0 {
|
||||
match socket_type {
|
||||
Udp => {
|
||||
let mut guard = self.udp_port_table.lock();
|
||||
if guard.get(&port).is_some() {
|
||||
return Err(SystemError::EADDRINUSE);
|
||||
}
|
||||
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());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if port > 0 && socket_type == 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(());
|
||||
}
|
||||
@@ -175,76 +123,8 @@ impl PortManager {
|
||||
/// @brief 在对应的端口记录表中将端口和 socket 解绑
|
||||
/// should call this function when socket is closed or aborted
|
||||
pub fn unbind_port(&self, socket_type: Types, port: u16) {
|
||||
match socket_type {
|
||||
Udp => {
|
||||
self.udp_port_table.lock().remove(&port);
|
||||
}
|
||||
Tcp => {
|
||||
self.tcp_port_table.lock().remove(&port);
|
||||
}
|
||||
_ => {}
|
||||
if socket_type == 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 system_error::SystemError;
|
||||
@@ -8,6 +8,8 @@ use crate::{
|
||||
process::namespace::net_namespace::NetNamespace,
|
||||
};
|
||||
|
||||
use super::UdpSocket;
|
||||
|
||||
pub type SmolUdpSocket = smoltcp::socket::udp::Socket<'static>;
|
||||
|
||||
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
|
||||
// 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)]
|
||||
pub struct UnboundUdp {
|
||||
socket: SmolUdpSocket,
|
||||
@@ -80,32 +91,48 @@ impl UnboundUdp {
|
||||
pub fn bind(
|
||||
self,
|
||||
local_endpoint: smoltcp::wire::IpEndpoint,
|
||||
netns: Arc<NetNamespace>,
|
||||
reuseaddr: bool,
|
||||
reuseport: bool,
|
||||
bind_id: usize,
|
||||
context: UdpBindContext,
|
||||
) -> 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_port = if local_endpoint.port == 0 {
|
||||
let port = inner
|
||||
.port_manager()
|
||||
.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,
|
||||
let bind_port_result = if local_endpoint.port == 0 {
|
||||
netns.udp_bindings().bind_ephemeral(
|
||||
socket,
|
||||
bind_addr,
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
bind_id,
|
||||
)?;
|
||||
// log::debug!(
|
||||
// "UnboundUdp::bind: explicit bind to port {}",
|
||||
// local_endpoint.port
|
||||
// );
|
||||
local_endpoint.port
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
if bind_addr.is_unspecified() {
|
||||
@@ -113,7 +140,8 @@ impl UnboundUdp {
|
||||
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bind_port))
|
||||
.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);
|
||||
}
|
||||
} else if inner
|
||||
@@ -122,33 +150,113 @@ impl UnboundUdp {
|
||||
})
|
||||
.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);
|
||||
}
|
||||
let port_mgr_ifindex = inner.iface().nic_id();
|
||||
Ok(BoundUdp {
|
||||
inner,
|
||||
remote: Mutex::new(None),
|
||||
explicitly_bound: true,
|
||||
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,
|
||||
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(
|
||||
self,
|
||||
remote: smoltcp::wire::IpAddress,
|
||||
netns: Arc<NetNamespace>,
|
||||
reuseaddr: bool,
|
||||
reuseport: bool,
|
||||
bind_id: usize,
|
||||
context: UdpBindContext,
|
||||
) -> Result<BoundUdp, SystemError> {
|
||||
let (inner, local_addr) = BoundInner::bind_ephemeral(self.socket, remote, netns)?;
|
||||
let bound_port = match inner
|
||||
.port_manager()
|
||||
.bind_udp_ephemeral_port(local_addr, reuseaddr, reuseport, bind_id)
|
||||
{
|
||||
let UdpBindContext {
|
||||
netns,
|
||||
socket,
|
||||
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,
|
||||
Err(e) => {
|
||||
inner.release();
|
||||
@@ -167,7 +275,7 @@ impl UnboundUdp {
|
||||
.with_mut::<smoltcp::socket::udp::Socket, _, _>(|socket| socket.bind(bound_port))
|
||||
.is_err()
|
||||
{
|
||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
||||
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||
inner.release();
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
@@ -177,19 +285,16 @@ impl UnboundUdp {
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
||||
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||
inner.release();
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
let port_mgr_ifindex = inner.iface().nic_id();
|
||||
Ok(BoundUdp {
|
||||
inner,
|
||||
remote: Mutex::new(None),
|
||||
explicitly_bound: false,
|
||||
has_preconnect_data: Mutex::new(false),
|
||||
bind_id,
|
||||
port_mgr_ifindex,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -197,16 +302,26 @@ impl UnboundUdp {
|
||||
self,
|
||||
iface: Arc<dyn Iface>,
|
||||
local_addr: smoltcp::wire::IpAddress,
|
||||
netns: Arc<NetNamespace>,
|
||||
reuseaddr: bool,
|
||||
reuseport: bool,
|
||||
bind_id: usize,
|
||||
context: UdpBindContext,
|
||||
) -> Result<BoundUdp, SystemError> {
|
||||
let inner = BoundInner::bind_on_iface(self.socket, iface, netns)?;
|
||||
let bound_port = match inner
|
||||
.port_manager()
|
||||
.bind_udp_ephemeral_port(local_addr, reuseaddr, reuseport, bind_id)
|
||||
{
|
||||
let UdpBindContext {
|
||||
netns,
|
||||
socket,
|
||||
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,
|
||||
Err(e) => {
|
||||
inner.release();
|
||||
@@ -220,19 +335,16 @@ impl UnboundUdp {
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
inner.port_manager().unbind_udp_port(bound_port, bind_id);
|
||||
netns.udp_bindings().unbind(bound_port, bind_id);
|
||||
inner.release();
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
let port_mgr_ifindex = inner.iface().nic_id();
|
||||
Ok(BoundUdp {
|
||||
inner,
|
||||
remote: Mutex::new(None),
|
||||
explicitly_bound: 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是检查是否有包
|
||||
/// 在缓冲区,如果有,第一个包我们走非connect而不是connect的recv方法(即接受第一个非connect对端对应的包)
|
||||
has_preconnect_data: Mutex<bool>,
|
||||
bind_id: usize,
|
||||
port_mgr_ifindex: usize,
|
||||
}
|
||||
|
||||
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
|
||||
where
|
||||
F: FnMut(&mut SmolUdpSocket) -> T,
|
||||
@@ -729,17 +842,10 @@ impl BoundUdp {
|
||||
}
|
||||
|
||||
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| {
|
||||
socket.close();
|
||||
});
|
||||
self.inner.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use inner::{UdpInner, UnboundUdp};
|
||||
use inner::{UdpBindContext, UdpInner, UnboundUdp};
|
||||
use smoltcp;
|
||||
use system_error::SystemError;
|
||||
|
||||
@@ -28,7 +28,7 @@ use core::sync::atomic::{
|
||||
use smoltcp::wire::{IpAddress::*, IpEndpoint, IpListenEndpoint, IpVersion, Ipv4Address};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -36,10 +36,11 @@ mod option;
|
||||
|
||||
pub mod inner;
|
||||
pub mod multicast_loopback;
|
||||
mod udp_bindings;
|
||||
pub(crate) mod udp_bindings;
|
||||
|
||||
type EP = crate::filesystem::epoll::EPollEventType;
|
||||
const IFACE_POLL_BATCH_ROUNDS: usize = 128;
|
||||
type EphemeralBindTarget = (Arc<dyn Iface>, smoltcp::wire::IpAddress);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
@@ -79,6 +80,10 @@ struct UdpErrQueueEntry {
|
||||
#[derive(Debug)]
|
||||
pub struct UdpSocket {
|
||||
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,
|
||||
shutdown: AtomicU8,
|
||||
wait_queue: WaitQueue,
|
||||
@@ -87,6 +92,8 @@ pub struct UdpSocket {
|
||||
fsnotify_watches: AtomicUsize,
|
||||
self_ref: Weak<UdpSocket>,
|
||||
netns: Arc<NetNamespace>,
|
||||
/// SO_BINDTODEVICE authoritative interface index.
|
||||
device_binding: SocketDeviceBinding,
|
||||
epoll_items: EPollItems,
|
||||
fasync_items: FAsyncItems,
|
||||
/// Custom send buffer size (SO_SNDBUF), 0 means use default
|
||||
@@ -176,6 +183,7 @@ impl UdpSocket {
|
||||
let netns = ProcessManager::current_netns();
|
||||
Arc::new_cyclic(|me| Self {
|
||||
inner: RwSem::new(Some(UdpInner::Unbound(UnboundUdp::new()))),
|
||||
iface_placement: RwSem::new(()),
|
||||
nonblock: AtomicBool::new(nonblock),
|
||||
shutdown: AtomicU8::new(0),
|
||||
wait_queue: WaitQueue::default(),
|
||||
@@ -184,6 +192,7 @@ impl UdpSocket {
|
||||
fsnotify_watches: AtomicUsize::new(0),
|
||||
self_ref: me.clone(),
|
||||
netns,
|
||||
device_binding: SocketDeviceBinding::default(),
|
||||
epoll_items: EPollItems::default(),
|
||||
fasync_items: FAsyncItems::default(),
|
||||
send_buf_size: AtomicUsize::new(0), // 0 means use default
|
||||
@@ -223,6 +232,99 @@ impl UdpSocket {
|
||||
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]
|
||||
fn unspecified_addr(&self) -> smoltcp::wire::IpAddress {
|
||||
match self.ip_version {
|
||||
@@ -388,6 +490,7 @@ impl UdpSocket {
|
||||
}
|
||||
|
||||
pub fn do_bind(&self, local_endpoint: smoltcp::wire::IpEndpoint) -> Result<(), SystemError> {
|
||||
let _placement = self.iface_placement.write();
|
||||
let mut inner = self.inner.write();
|
||||
|
||||
// Check socket state first without taking
|
||||
@@ -396,6 +499,17 @@ impl UdpSocket {
|
||||
Some(UdpInner::Bound(_)) => return Err(SystemError::EINVAL), // Already bound
|
||||
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
|
||||
let _old_unbound = match inner.take() {
|
||||
@@ -427,31 +541,18 @@ impl UdpSocket {
|
||||
UnboundUdp::new()
|
||||
};
|
||||
|
||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
||||
match unbound.bind(
|
||||
local_endpoint,
|
||||
self.netns(),
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
self.bind_id(),
|
||||
) {
|
||||
let result = if let Some(iface) = bound_iface {
|
||||
unbound.bind_on_iface(iface, local_endpoint, self.bind_context())
|
||||
} else {
|
||||
unbound.bind(local_endpoint, self.bind_context())
|
||||
};
|
||||
match result {
|
||||
Ok(bound) => {
|
||||
bound
|
||||
.inner()
|
||||
.iface()
|
||||
.common()
|
||||
.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));
|
||||
Ok(())
|
||||
}
|
||||
@@ -465,6 +566,7 @@ impl UdpSocket {
|
||||
|
||||
pub fn bind_ephemeral(&self, remote: smoltcp::wire::IpAddress) -> Result<(), SystemError> {
|
||||
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 mut newly_bound_iface = None;
|
||||
let bound = match inner {
|
||||
@@ -481,35 +583,18 @@ impl UdpSocket {
|
||||
UnboundUdp::new()
|
||||
};
|
||||
|
||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
||||
let bound_result = if let Some((iface, local_addr)) =
|
||||
let bound_result = if let Some((iface, local_addr)) = device_target {
|
||||
inner.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||
} else if let Some((iface, local_addr)) =
|
||||
self.ipv4_multicast_ephemeral_bind_target(remote)
|
||||
{
|
||||
inner.bind_ephemeral_on_iface(
|
||||
iface,
|
||||
local_addr,
|
||||
self.netns(),
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
self.bind_id(),
|
||||
)
|
||||
inner.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||
} else {
|
||||
inner.bind_ephemeral(remote, self.netns(), reuseaddr, reuseport, self.bind_id())
|
||||
inner.bind_ephemeral(remote, self.bind_context())
|
||||
};
|
||||
match bound_result {
|
||||
Ok(bound) => {
|
||||
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
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -540,6 +625,7 @@ impl UdpSocket {
|
||||
/// Recreates the socket with new buffer sizes if it's already bound.
|
||||
/// This is needed because smoltcp doesn't support resizing socket buffers dynamically.
|
||||
fn recreate_socket_if_bound(&self) -> Result<(), SystemError> {
|
||||
let _placement = self.iface_placement.write();
|
||||
let mut inner_guard = self.inner.write();
|
||||
|
||||
// Check if socket is bound
|
||||
@@ -551,7 +637,14 @@ impl UdpSocket {
|
||||
// Save current state before recreating
|
||||
let local_ep = bound.endpoint();
|
||||
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!(
|
||||
// "Recreating UDP socket: local={:?}, remote={:?}, explicit={}",
|
||||
@@ -566,7 +659,10 @@ impl UdpSocket {
|
||||
|
||||
// Unbind the old socket and drop it
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -581,15 +677,7 @@ impl UdpSocket {
|
||||
|
||||
// Rebind to the same endpoint
|
||||
let new_endpoint = smoltcp::wire::IpEndpoint::new(local_addr, port);
|
||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
||||
let bound = match unbound.bind(
|
||||
new_endpoint,
|
||||
self.netns(),
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
self.bind_id(),
|
||||
) {
|
||||
let bound = match unbound.bind_on_iface(target_iface, new_endpoint, self.bind_context()) {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
// Restore unbound state on error
|
||||
@@ -599,6 +687,8 @@ impl UdpSocket {
|
||||
};
|
||||
|
||||
// Restore connection if it existed
|
||||
let mut bound = bound;
|
||||
bound.set_explicitly_bound(explicitly_bound);
|
||||
if let Some(remote) = remote_ep {
|
||||
bound.connect(remote);
|
||||
}
|
||||
@@ -609,24 +699,18 @@ impl UdpSocket {
|
||||
.iface()
|
||||
.common()
|
||||
.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));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
let _placement = self.iface_placement.write();
|
||||
let mut inner = self.inner.write();
|
||||
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);
|
||||
crate::net::socket::inet::common::multicast::drop_ipv4_memberships(
|
||||
&self.netns,
|
||||
@@ -643,6 +727,37 @@ impl UdpSocket {
|
||||
// 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(
|
||||
&self,
|
||||
buf: &mut [u8],
|
||||
@@ -844,6 +959,18 @@ impl UdpSocket {
|
||||
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(
|
||||
&self,
|
||||
err: SockExtendedErr,
|
||||
@@ -910,6 +1037,24 @@ impl UdpSocket {
|
||||
.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(
|
||||
&self,
|
||||
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 (
|
||||
result,
|
||||
send_iface,
|
||||
@@ -930,7 +1098,8 @@ impl UdpSocket {
|
||||
dest_is_broadcast,
|
||||
loopback_send,
|
||||
send_iface_is_loopback,
|
||||
mcast_ifindex,
|
||||
local_delivery_ifindex,
|
||||
src_endpoint,
|
||||
restore_iface,
|
||||
) = {
|
||||
let mut inner_guard = self.inner.write();
|
||||
@@ -942,31 +1111,19 @@ impl UdpSocket {
|
||||
if let UdpInner::Unbound(_) = inner {
|
||||
let to_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() {
|
||||
UdpInner::Unbound(unbound) => unbound,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let reuseaddr = self.so_reuseaddr.load(Ordering::Relaxed);
|
||||
let reuseport = self.so_reuseport.load(Ordering::Relaxed);
|
||||
let bound_result = if let Some((iface, local_addr)) =
|
||||
let bound_result = if let Some((iface, local_addr)) = device_target {
|
||||
unbound.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||
} else if let Some((iface, local_addr)) =
|
||||
self.ipv4_multicast_ephemeral_bind_target(to_addr)
|
||||
{
|
||||
unbound.bind_ephemeral_on_iface(
|
||||
iface,
|
||||
local_addr,
|
||||
self.netns(),
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
self.bind_id(),
|
||||
)
|
||||
unbound.bind_ephemeral_on_iface(iface, local_addr, self.bind_context())
|
||||
} else {
|
||||
unbound.bind_ephemeral(
|
||||
to_addr,
|
||||
self.netns(),
|
||||
reuseaddr,
|
||||
reuseport,
|
||||
self.bind_id(),
|
||||
)
|
||||
unbound.bind_ephemeral(to_addr, self.bind_context())
|
||||
};
|
||||
match bound_result {
|
||||
Ok(bound) => {
|
||||
@@ -976,16 +1133,6 @@ impl UdpSocket {
|
||||
.iface()
|
||||
.common()
|
||||
.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));
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -999,6 +1146,7 @@ impl UdpSocket {
|
||||
// Send data and get iface Arc before releasing lock
|
||||
match inner_guard.as_mut().ok_or(SystemError::EBADF)? {
|
||||
UdpInner::Bound(bound) => {
|
||||
self.device_binding.resolve_iface(&self.netns)?;
|
||||
let dest = to
|
||||
.or_else(|| bound.remote_endpoint().ok())
|
||||
.ok_or(SystemError::EDESTADDRREQ)?;
|
||||
@@ -1006,7 +1154,10 @@ impl UdpSocket {
|
||||
self.validate_bound_send_dest(bound, dest)?;
|
||||
let bound_iface = bound.inner().iface().clone();
|
||||
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);
|
||||
if ifindex != 0 {
|
||||
ifindex
|
||||
@@ -1033,30 +1184,39 @@ impl UdpSocket {
|
||||
.map(|lo| lo.nic_id() == bound_iface.nic_id())
|
||||
.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
|
||||
.netns
|
||||
.loopback_iface()
|
||||
.map(|lo| lo.smol_iface().lock().inner.is_broadcast(&dest.addr))
|
||||
.unwrap_or(false);
|
||||
let is_broadcast = loopback_broadcast
|
||||
|| (send_iface_is_loopback
|
||||
&& bound_iface
|
||||
.smol_iface()
|
||||
.lock()
|
||||
.inner
|
||||
.is_broadcast(&dest.addr));
|
||||
let should_loopback_send = is_loopback
|
||||
|| is_loopback_local_subnet
|
||||
|| ((is_multicast || is_broadcast)
|
||||
&& (send_iface_is_loopback || loopback_broadcast));
|
||||
let bound_iface_is_broadcast = bound_iface
|
||||
.smol_iface()
|
||||
.lock()
|
||||
.inner
|
||||
.is_broadcast(&dest.addr);
|
||||
let is_broadcast = loopback_broadcast || bound_iface_is_broadcast;
|
||||
let local_delivery_ifindex =
|
||||
if device_ifindex != 0 && (is_multicast || bound_iface_is_broadcast) {
|
||||
Some(device_ifindex)
|
||||
} else if is_multicast && mcast_ifindex != 0 {
|
||||
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 {
|
||||
let max_payload =
|
||||
bound.with_socket(|socket| socket.payload_send_capacity());
|
||||
@@ -1067,7 +1227,8 @@ impl UdpSocket {
|
||||
is_broadcast,
|
||||
true,
|
||||
send_iface_is_loopback,
|
||||
mcast_ifindex,
|
||||
local_delivery_ifindex,
|
||||
src_endpoint,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
@@ -1096,42 +1257,29 @@ impl UdpSocket {
|
||||
is_broadcast,
|
||||
false,
|
||||
send_iface_is_loopback,
|
||||
mcast_ifindex,
|
||||
local_delivery_ifindex,
|
||||
src_endpoint,
|
||||
restore_iface,
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => return Err(SystemError::ENOTCONN),
|
||||
}
|
||||
}; // Lock released here
|
||||
}; // `inner` is released here.
|
||||
|
||||
if loopback_send {
|
||||
if let Some(dest) = dest {
|
||||
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),
|
||||
};
|
||||
let ifindex = self
|
||||
.netns
|
||||
.loopback_iface()
|
||||
.map(|lo| lo.nic_id() as i32)
|
||||
.unwrap_or_else(|| send_iface.nic_id() as i32);
|
||||
let ifindex = local_delivery_ifindex.unwrap_or_else(|| send_iface.nic_id() as i32);
|
||||
if dest.addr.is_multicast() {
|
||||
if let Ipv4(addr) = dest.addr {
|
||||
let octets = addr.octets();
|
||||
let multiaddr = u32::from_ne_bytes(octets);
|
||||
let ifindex = mcast_ifindex.max(ifindex);
|
||||
if multicast_loopback::multicast_registry().has_membership(
|
||||
self.netns.ns_common().nsid.data(),
|
||||
multiaddr,
|
||||
ifindex,
|
||||
) {
|
||||
udp_bindings::deliver_multicast_all(
|
||||
&self.netns,
|
||||
self.netns.udp_bindings().deliver_multicast(
|
||||
dest,
|
||||
src_endpoint,
|
||||
ifindex,
|
||||
@@ -1140,21 +1288,13 @@ impl UdpSocket {
|
||||
}
|
||||
}
|
||||
} else if dest_is_broadcast {
|
||||
udp_bindings::deliver_broadcast_all(
|
||||
&self.netns,
|
||||
dest,
|
||||
src_endpoint,
|
||||
ifindex,
|
||||
buf,
|
||||
);
|
||||
self.netns
|
||||
.udp_bindings()
|
||||
.deliver_broadcast(dest, src_endpoint, ifindex, buf);
|
||||
} else {
|
||||
udp_bindings::deliver_unicast_loopback(
|
||||
&self.netns,
|
||||
dest,
|
||||
src_endpoint,
|
||||
ifindex,
|
||||
buf,
|
||||
);
|
||||
self.netns
|
||||
.udp_bindings()
|
||||
.deliver_unicast(dest, src_endpoint, ifindex, buf);
|
||||
}
|
||||
|
||||
// 为 raw socket 构建完整 IP 包并投递(用于 RAW 接收场景)。
|
||||
@@ -1174,8 +1314,8 @@ impl UdpSocket {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Poll AFTER releasing the lock to avoid deadlock
|
||||
// when socket sends to itself on loopback
|
||||
// Poll after releasing inner. This is required because polling notifies all
|
||||
// sockets on the interface and may re-enter this socket's event checks.
|
||||
Self::poll_iface_until_quiescent(send_iface.as_ref());
|
||||
|
||||
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,
|
||||
// deliver the packet to all local sockets that have joined the group
|
||||
if result.is_ok() {
|
||||
if let Some(dest) = dest.or_else(|| match self.inner.read().as_ref() {
|
||||
Some(UdpInner::Bound(bound)) => bound.remote_endpoint().ok(),
|
||||
_ => None,
|
||||
}) {
|
||||
if let Some(dest) = dest {
|
||||
let allow_mcast_loop =
|
||||
self.is_multicast_loopback_enabled() || send_iface_is_loopback;
|
||||
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
|
||||
if let Ipv4(addr) = dest.addr {
|
||||
let octets = addr.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(
|
||||
self.netns.ns_common().nsid.data(),
|
||||
multiaddr,
|
||||
ifindex,
|
||||
) {
|
||||
udp_bindings::deliver_multicast_all(
|
||||
&self.netns,
|
||||
self.netns.udp_bindings().deliver_multicast(
|
||||
dest,
|
||||
src_endpoint,
|
||||
ifindex,
|
||||
@@ -1251,6 +1378,9 @@ impl UdpSocket {
|
||||
ifindex: i32,
|
||||
payload: &[u8],
|
||||
) -> bool {
|
||||
if ifindex <= 0 || !self.device_binding.allows(ifindex as usize) {
|
||||
return false;
|
||||
}
|
||||
// Check if socket is bound
|
||||
{
|
||||
let inner = self.inner.read();
|
||||
@@ -1417,37 +1547,12 @@ impl Socket for UdpSocket {
|
||||
// Port 0 is treated as disconnect (like AF_UNSPEC)
|
||||
// This matches Linux behavior where connect() to port 0 succeeds but disconnects the socket
|
||||
if remote.port == 0 {
|
||||
// log::debug!("UDP connect: port 0 treated as disconnect");
|
||||
// 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(());
|
||||
return self.disconnect_udp();
|
||||
}
|
||||
|
||||
// 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);
|
||||
if !self.is_bound() {
|
||||
self.bind_ephemeral(remote.addr)?;
|
||||
@@ -1465,35 +1570,8 @@ impl Socket for UdpSocket {
|
||||
}
|
||||
}
|
||||
Endpoint::Unspecified => {
|
||||
// AF_UNSPEC: disconnect the UDP socket (clear remote endpoint)
|
||||
// If socket was implicitly bound (by connect), unbind it
|
||||
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(())
|
||||
// AF_UNSPEC disconnects and drops an implicit bind.
|
||||
self.disconnect_udp()
|
||||
}
|
||||
_ => Err(SystemError::EAFNOSUPPORT),
|
||||
}
|
||||
|
||||
@@ -112,6 +112,10 @@ impl UdpSocket {
|
||||
self.so_reuseaddr.store(v != 0, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
PSO::BINDTODEVICE => {
|
||||
let mut update = self.device_binding.prepare_update(&self.netns, val)?;
|
||||
self.apply_device_binding(&mut update)
|
||||
}
|
||||
PSO::BROADCAST => {
|
||||
if val.len() < core::mem::size_of::<i32>() {
|
||||
return Err(SystemError::EINVAL);
|
||||
@@ -229,6 +233,7 @@ impl UdpSocket {
|
||||
value,
|
||||
self.so_reuseaddr.load(Ordering::Relaxed) as i32,
|
||||
)),
|
||||
PSO::BINDTODEVICE => self.device_binding.get(&self.netns, value),
|
||||
PSO::BROADCAST => Ok(write_i32_getsockopt(
|
||||
value,
|
||||
self.so_broadcast.load(Ordering::Relaxed) as i32,
|
||||
@@ -325,12 +330,32 @@ impl UdpSocket {
|
||||
self.ip_multicast_all.store(on, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
IpOption::MULTICAST_IF => apply_ipv4_multicast_if(
|
||||
&self.netns,
|
||||
val,
|
||||
&self.ip_multicast_ifindex,
|
||||
&self.ip_multicast_addr,
|
||||
),
|
||||
IpOption::MULTICAST_IF => {
|
||||
// Keep validation and the two-field configuration publish
|
||||
// atomic with respect to send-side interface selection.
|
||||
let _placement = self.iface_placement.write();
|
||||
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 => {
|
||||
if val.len() < core::mem::size_of::<i32>() {
|
||||
return Err(SystemError::EINVAL);
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
use alloc::sync::{Arc, Weak};
|
||||
use alloc::vec::Vec;
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
use core::sync::atomic::{AtomicU16, AtomicU64, Ordering};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use jhash::jhash2;
|
||||
use smoltcp::wire::{IpAddress, IpEndpoint};
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::libs::rwsem::RwSem;
|
||||
use crate::process::namespace::net_namespace::NetNamespace;
|
||||
use crate::process::namespace::NamespaceOps;
|
||||
use crate::arch::rand::rand;
|
||||
use crate::libs::mutex::Mutex;
|
||||
|
||||
use super::UdpSocket;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct UdpBinding {
|
||||
netns_id: usize,
|
||||
socket: Weak<UdpSocket>,
|
||||
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,
|
||||
bind_id: usize,
|
||||
bound_seq: u64,
|
||||
}
|
||||
|
||||
@@ -28,149 +30,269 @@ struct UdpBindingMatch {
|
||||
bound_seq: u64,
|
||||
}
|
||||
|
||||
static BIND_SEQ: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
lazy_static! {
|
||||
static ref UDP_BINDINGS: RwSem<Vec<UdpBinding>> = RwSem::new(Vec::new());
|
||||
/// Per-network-namespace UDP port reservation and local-delivery table.
|
||||
///
|
||||
/// Device binding is intentionally not cached in an entry. Conflict checks and
|
||||
/// 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(
|
||||
netns: &Arc<NetNamespace>,
|
||||
socket: Weak<UdpSocket>,
|
||||
addr: IpAddress,
|
||||
port: u16,
|
||||
_reuseaddr: bool,
|
||||
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;
|
||||
impl Default for UdpBindingTable {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bindings: Mutex::new(HashMap::new()),
|
||||
next_ephemeral: AtomicU16::new(0),
|
||||
bind_seq: AtomicU64::new(1),
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn deliver_multicast_all(
|
||||
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;
|
||||
impl UdpBindingTable {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn bind(
|
||||
&self,
|
||||
socket: Weak<UdpSocket>,
|
||||
addr: IpAddress,
|
||||
port: u16,
|
||||
reuseaddr: bool,
|
||||
reuseport: bool,
|
||||
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(
|
||||
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;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn bind_ephemeral(
|
||||
&self,
|
||||
socket: Weak<UdpSocket>,
|
||||
addr: IpAddress,
|
||||
reuseaddr: bool,
|
||||
reuseport: bool,
|
||||
bind_id: usize,
|
||||
prospective_ifindex: usize,
|
||||
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 {
|
||||
if cand
|
||||
.socket
|
||||
.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
||||
{
|
||||
delivered += 1;
|
||||
|
||||
pub fn unbind(&self, port: u16, bind_id: usize) {
|
||||
let mut bindings = self.bindings.lock();
|
||||
let remove_bucket = if let Some(bucket) = bindings.get_mut(&port) {
|
||||
bucket
|
||||
.retain(|binding| binding.bind_id != bind_id && binding.socket.strong_count() > 0);
|
||||
bucket.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if remove_bucket {
|
||||
bindings.remove(&port);
|
||||
}
|
||||
}
|
||||
delivered
|
||||
}
|
||||
|
||||
fn match_udp_bindings(
|
||||
netns: &Arc<NetNamespace>,
|
||||
dest_addr: IpAddress,
|
||||
dest_port: u16,
|
||||
) -> Vec<UdpBindingMatch> {
|
||||
let netns_id = netns.ns_common().nsid.data();
|
||||
let mut guard = UDP_BINDINGS.write();
|
||||
guard.retain(|b| b.socket.strong_count() > 0);
|
||||
guard
|
||||
.iter()
|
||||
.filter(|b| b.netns_id == netns_id)
|
||||
.filter(|b| b.port == dest_port)
|
||||
.filter(|b| udp_addr_match(b.addr, dest_addr))
|
||||
.filter_map(|b| {
|
||||
b.socket.upgrade().map(|sock| UdpBindingMatch {
|
||||
socket: sock,
|
||||
reuseport: b.reuseport,
|
||||
bound_seq: b.bound_seq,
|
||||
pub fn deliver_unicast(
|
||||
&self,
|
||||
dest: IpEndpoint,
|
||||
src: IpEndpoint,
|
||||
ifindex: i32,
|
||||
payload: &[u8],
|
||||
) -> usize {
|
||||
let candidates = self.match_bindings(dest.addr, dest.port, ifindex);
|
||||
let chosen = if candidates.iter().any(|candidate| candidate.reuseport) {
|
||||
choose_reuseport_socket(&candidates, dest, src)
|
||||
} else {
|
||||
choose_recent_socket(&candidates)
|
||||
};
|
||||
chosen
|
||||
.filter(|socket| {
|
||||
socket.inject_loopback_packet(src, dest.addr, dest.port, ifindex, payload)
|
||||
})
|
||||
.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]
|
||||
@@ -178,20 +300,17 @@ fn udp_addr_match(bound_addr: IpAddress, dest_addr: IpAddress) -> bool {
|
||||
if bound_addr.version() != dest_addr.version() {
|
||||
return false;
|
||||
}
|
||||
if bound_addr.is_unspecified() {
|
||||
return true;
|
||||
}
|
||||
if dest_addr.is_multicast() || dest_addr.is_broadcast() {
|
||||
return true;
|
||||
}
|
||||
bound_addr == dest_addr
|
||||
bound_addr.is_unspecified()
|
||||
|| dest_addr.is_multicast()
|
||||
|| dest_addr.is_broadcast()
|
||||
|| bound_addr == dest_addr
|
||||
}
|
||||
|
||||
fn choose_recent_socket(candidates: &[UdpBindingMatch]) -> Option<Arc<UdpSocket>> {
|
||||
candidates
|
||||
.iter()
|
||||
.max_by_key(|c| c.bound_seq)
|
||||
.map(|c| c.socket.clone())
|
||||
.max_by_key(|candidate| candidate.bound_seq)
|
||||
.map(|candidate| candidate.socket.clone())
|
||||
}
|
||||
|
||||
fn choose_reuseport_socket(
|
||||
@@ -199,14 +318,17 @@ fn choose_reuseport_socket(
|
||||
dest: IpEndpoint,
|
||||
src: IpEndpoint,
|
||||
) -> 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() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hash = udp_4tuple_hash(dest, src);
|
||||
let idx = (hash as usize) % reuseport.len();
|
||||
reuseport.get(idx).map(|c| c.socket.clone())
|
||||
let index = (udp_4tuple_hash(dest, src) as usize) % reuseport.len();
|
||||
reuseport
|
||||
.get(index)
|
||||
.map(|candidate| candidate.socket.clone())
|
||||
}
|
||||
|
||||
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;
|
||||
match (dest.addr, src.addr) {
|
||||
(IpAddress::Ipv4(dst), IpAddress::Ipv4(src)) => {
|
||||
let data = [src.to_bits(), dst.to_bits(), src_port, dst_port];
|
||||
jhash2(&data, 0)
|
||||
jhash2(&[src.to_bits(), dst.to_bits(), src_port, dst_port], 0)
|
||||
}
|
||||
(IpAddress::Ipv6(dst), IpAddress::Ipv6(src)) => {
|
||||
let src_oct = src.octets();
|
||||
let dst_oct = dst.octets();
|
||||
let data = [
|
||||
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([dst_oct[0], dst_oct[1], dst_oct[2], dst_oct[3]]),
|
||||
u32::from_be_bytes([dst_oct[4], dst_oct[5], dst_oct[6], dst_oct[7]]),
|
||||
src_port,
|
||||
dst_port,
|
||||
];
|
||||
jhash2(&data, 0)
|
||||
}
|
||||
_ => {
|
||||
let data = [src_port, dst_port, 0, 0];
|
||||
jhash2(&data, 0)
|
||||
let src_octets = src.octets();
|
||||
let dst_octets = dst.octets();
|
||||
jhash2(
|
||||
&[
|
||||
u32::from_be_bytes(src_octets[0..4].try_into().unwrap()),
|
||||
u32::from_be_bytes(src_octets[4..8].try_into().unwrap()),
|
||||
u32::from_be_bytes(dst_octets[0..4].try_into().unwrap()),
|
||||
u32::from_be_bytes(dst_octets[4..8].try_into().unwrap()),
|
||||
src_port,
|
||||
dst_port,
|
||||
],
|
||||
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 crate::driver::net::Iface;
|
||||
use crate::libs::rwsem::RwSem;
|
||||
use crate::process::namespace::net_namespace::NetNamespace;
|
||||
use crate::process::namespace::NamespaceOps;
|
||||
@@ -142,11 +143,13 @@ fn should_deliver_to_socket(s: &RawSocket, ctx: &LoopbackDeliverContext) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
// 4. SO_BINDTODEVICE:loopback 快速路径视为来自 lo
|
||||
if let Some(dev) = &s.options.read().bind_to_device {
|
||||
if dev.as_str() != "lo" {
|
||||
// 4. SO_BINDTODEVICE:loopback 快速路径视为来自当前 netns 的 lo
|
||||
if let Some(lo) = s.netns.loopback_iface() {
|
||||
if !s.device_binding.allows(lo.nic_id()) {
|
||||
return false;
|
||||
}
|
||||
} else if s.device_binding.ifindex() != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. bind(2) 目的地址过滤
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::libs::mutex::Mutex;
|
||||
use crate::libs::rwsem::RwSem;
|
||||
use crate::libs::wait_queue::WaitQueue;
|
||||
use crate::net::socket::common::EPollItems;
|
||||
use crate::net::socket::inet::common::SocketDeviceBinding;
|
||||
use crate::process::namespace::net_namespace::NetNamespace;
|
||||
|
||||
use inner::RawInner;
|
||||
@@ -69,6 +70,8 @@ pub struct RawSocket {
|
||||
self_ref: Weak<Self>,
|
||||
/// 网络命名空间
|
||||
netns: Arc<NetNamespace>,
|
||||
/// SO_BINDTODEVICE authoritative interface index.
|
||||
device_binding: SocketDeviceBinding,
|
||||
/// epoll 项
|
||||
epoll_items: EPollItems,
|
||||
/// fasync 项
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use alloc::string::String;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use super::constants::{SYSCTL_RMEM_MAX, SYSCTL_WMEM_MAX};
|
||||
@@ -100,9 +99,6 @@ pub struct RawSocketOptions {
|
||||
/// SO_RCVBUF: 返回给 getsockopt 的 sk_rcvbuf(Linux 会将 setsockopt 的值 *2 后存储)
|
||||
pub sock_rcvbuf: u32,
|
||||
|
||||
/// SO_BINDTODEVICE: 绑定的设备名(不含 '\0')
|
||||
pub bind_to_device: Option<String>,
|
||||
|
||||
/// SO_LINGER
|
||||
pub linger_onoff: i32,
|
||||
pub linger_linger: i32,
|
||||
@@ -137,7 +133,6 @@ impl Default for RawSocketOptions {
|
||||
// 初始值设为 sysctl_*mem_max * 2,与 Linux 默认行为一致。
|
||||
sock_sndbuf: SYSCTL_WMEM_MAX.saturating_mul(2),
|
||||
sock_rcvbuf: SYSCTL_RMEM_MAX.saturating_mul(2),
|
||||
bind_to_device: None,
|
||||
linger_onoff: 0,
|
||||
linger_linger: 0,
|
||||
filter_attached: false,
|
||||
|
||||
@@ -79,6 +79,7 @@ impl RawSocket {
|
||||
fsnotify_watches: core::sync::atomic::AtomicUsize::new(0),
|
||||
self_ref: me.clone(),
|
||||
netns,
|
||||
device_binding: crate::net::socket::inet::common::SocketDeviceBinding::default(),
|
||||
epoll_items: crate::net::socket::common::EPollItems::default(),
|
||||
fasync_items: crate::filesystem::vfs::fasync::FAsyncItems::default(),
|
||||
ip_version,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use alloc::string::String;
|
||||
|
||||
use smoltcp::wire::IpProtocol;
|
||||
use system_error::SystemError;
|
||||
|
||||
@@ -13,7 +11,7 @@ use crate::net::socket::common::{
|
||||
write_timeval_opt, write_u32_getsockopt,
|
||||
};
|
||||
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> {
|
||||
if val.len() < 4 {
|
||||
@@ -52,27 +50,7 @@ impl RawSocket {
|
||||
match PSO::try_from(name as u32) {
|
||||
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::BINDTODEVICE) => {
|
||||
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::BINDTODEVICE) => self.device_binding.get(&self.netns, value),
|
||||
Ok(PSO::LINGER) => {
|
||||
let opts = self.options.read();
|
||||
Ok(write_linger_getsockopt(
|
||||
@@ -252,22 +230,8 @@ impl RawSocket {
|
||||
Ok(())
|
||||
}
|
||||
Ok(PSO::BINDTODEVICE) => {
|
||||
let end = val.iter().position(|&b| b == 0).unwrap_or(val.len());
|
||||
let name_bytes = &val[..end];
|
||||
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));
|
||||
let mut update = self.device_binding.prepare_update(&self.netns, val)?;
|
||||
update.commit();
|
||||
Ok(())
|
||||
}
|
||||
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::wait_queue::WaitQueue;
|
||||
use crate::net::routing::Router;
|
||||
use crate::net::socket::inet::datagram::udp_bindings::UdpBindingTable;
|
||||
use crate::net::socket::netlink::table::{
|
||||
generate_supported_netlink_kernel_sockets, NetlinkKernelSocket, NetlinkSocketTable,
|
||||
};
|
||||
@@ -87,6 +88,8 @@ pub struct NetNamespace {
|
||||
/// 注意:该结构会在 bind/connect 等路径被访问,且这些路径可能会获取可睡眠的 Mutex,
|
||||
/// 因此这里使用可睡眠的 `RwSem`,避免自旋锁 + schedule 的组合导致崩溃。
|
||||
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.
|
||||
packet_sockets: RcuArcSlot<PacketSocketRegistrySnapshot>,
|
||||
/// Serializes all plain/fanout topology updates and owns group IDs.
|
||||
@@ -468,6 +471,7 @@ impl NetNamespace {
|
||||
inner: RwLock::new(inner),
|
||||
poller: NetnsPoller::new(self_ref.clone()),
|
||||
device_list: RwSem::new(BTreeMap::new()),
|
||||
udp_bindings: UdpBindingTable::default(),
|
||||
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
||||
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
||||
packet_sockets_need_cleanup: AtomicBool::new(false),
|
||||
@@ -490,7 +494,7 @@ impl NetNamespace {
|
||||
let counter = get_next_netns_counter();
|
||||
let loopback = crate::driver::net::loopback::LoopbackInterface::new_with_ifindex(
|
||||
crate::driver::net::loopback::LoopbackDriver::default(),
|
||||
1,
|
||||
crate::net::LOOPBACK_IFINDEX,
|
||||
);
|
||||
|
||||
let inner = InnerNetNamespace {
|
||||
@@ -507,6 +511,7 @@ impl NetNamespace {
|
||||
inner: RwLock::new(inner),
|
||||
poller: NetnsPoller::new(self_ref.clone()),
|
||||
device_list: RwSem::new(BTreeMap::new()),
|
||||
udp_bindings: UdpBindingTable::default(),
|
||||
packet_sockets: RcuArcSlot::new(Arc::new(PacketSocketRegistrySnapshot::default())),
|
||||
packet_sockets_writer: Mutex::new(PacketSocketRegistryWriter::new()),
|
||||
packet_sockets_need_cleanup: AtomicBool::new(false),
|
||||
@@ -554,6 +559,10 @@ impl NetNamespace {
|
||||
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> {
|
||||
let writer = self.packet_sockets_writer.lock();
|
||||
let current = self.packet_sockets.load();
|
||||
|
||||
@@ -99,7 +99,7 @@ TEST(SocketIoctlNetdevQuery, LoopbackFieldsAndUnionWidths) {
|
||||
struct ifreq ifr {};
|
||||
InitIfreq(&ifr, "lo");
|
||||
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));
|
||||
|
||||
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_permission_semantics
|
||||
normal/rtnetlink_serialization_semantics
|
||||
normal/udp_bind_to_device_semantics
|
||||
normal/tty_termios
|
||||
normal/inotify_dir_watch
|
||||
normal/inotify_events
|
||||
|
||||
Reference in New Issue
Block a user