mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-15 00:56:15 -08:00
3809 lines
140 KiB
Rust
3809 lines
140 KiB
Rust
use std::{
|
|
collections::{BTreeMap, BTreeSet, HashMap},
|
|
ops::Bound::{Excluded, Included, Unbounded},
|
|
pin::Pin,
|
|
sync::{
|
|
Arc, Mutex as StdMutex,
|
|
atomic::{AtomicBool, Ordering},
|
|
},
|
|
task::{Context, Poll},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use cidr::{Inet, Ipv4Inet, Ipv6Inet};
|
|
use futures::{Sink, SinkExt, Stream, StreamExt, future::poll_fn, task::noop_waker_ref};
|
|
use smoltcp::wire::{
|
|
Icmpv4Message, Icmpv4Packet, Icmpv6Message, Icmpv6Packet, IpAddress, IpProtocol, Ipv4Packet,
|
|
Ipv6Packet, TcpPacket, UdpPacket,
|
|
};
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
use tokio::sync::Mutex;
|
|
use tokio::sync::{Notify, mpsc, oneshot};
|
|
use tokio_util::task::AbortOnDropHandle;
|
|
|
|
use crate::common::error::Error;
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
use crate::instance::virtual_nic::VirtualNic;
|
|
use easytier_core::{
|
|
packet::ZCPacket,
|
|
tunnel::{
|
|
Tunnel, ZCPacketSink, ZCPacketStream,
|
|
ring::{RingSink, RingStream, RingTunnel, create_ring_socket_pair, split_ring_socket},
|
|
},
|
|
};
|
|
|
|
use super::{
|
|
SHARED_VIRTUAL_NIC_RUNTIME, SharedIfConfigClaims, SharedIpv4Route, SharedIpv6Route,
|
|
SharedVirtualNicMemberId, SharedVirtualNicMemberRegistrationId,
|
|
};
|
|
|
|
const MEMBER_TUNNEL_BUFFER_SIZE: usize = 1024;
|
|
const FLOW_OWNER_LIMIT: usize = 4096;
|
|
// Match the core UDP proxy's idle lifetime; reclaim lazily under table pressure.
|
|
const FLOW_IDLE_TIMEOUT: Duration = Duration::from_secs(180);
|
|
const DISPATCHER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1);
|
|
const DISPATCHER_TUN_WRITE_TIMEOUT: Duration = Duration::from_secs(1);
|
|
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
struct SharedVirtualNicMobileTunUpdate {
|
|
tun_fd: std::os::fd::RawFd,
|
|
ack: oneshot::Sender<Result<(), Error>>,
|
|
}
|
|
|
|
enum SharedVirtualNicControl {
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
UpdateMobileTun(SharedVirtualNicMobileTunUpdate),
|
|
Register {
|
|
member_id: SharedVirtualNicMemberId,
|
|
entry: SharedVirtualNicMemberTunnelEntry,
|
|
},
|
|
Unregister {
|
|
member_id: SharedVirtualNicMemberId,
|
|
registration_id: SharedVirtualNicMemberRegistrationId,
|
|
},
|
|
UpdateSources {
|
|
member_id: SharedVirtualNicMemberId,
|
|
sources: SharedVirtualNicMemberSources,
|
|
ack: oneshot::Sender<()>,
|
|
},
|
|
Shutdown {
|
|
invalidate: bool,
|
|
ack: oneshot::Sender<()>,
|
|
},
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(super) struct SharedVirtualNicMemberTunnelTable {
|
|
control_sender: Arc<StdMutex<Option<mpsc::UnboundedSender<SharedVirtualNicControl>>>>,
|
|
}
|
|
|
|
struct SharedVirtualNicMemberTunnelEntry {
|
|
registration_id: SharedVirtualNicMemberRegistrationId,
|
|
stream: RingStream,
|
|
sink: RingSink,
|
|
close_notifier: Arc<Notify>,
|
|
}
|
|
|
|
impl SharedVirtualNicMemberTunnelTable {
|
|
fn attach_dispatcher(&self, control_sender: mpsc::UnboundedSender<SharedVirtualNicControl>) {
|
|
*self.control_sender.lock().unwrap() = Some(control_sender);
|
|
}
|
|
|
|
pub(super) fn detach_dispatcher(&self) {
|
|
self.control_sender.lock().unwrap().take();
|
|
}
|
|
|
|
pub(super) fn register(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
registration_id: SharedVirtualNicMemberRegistrationId,
|
|
close_notifier: Arc<Notify>,
|
|
) -> Result<Box<dyn Tunnel>, Error> {
|
|
let control_sender = self
|
|
.control_sender()
|
|
.ok_or_else(|| anyhow::anyhow!("shared virtual nic dispatcher is not running"))?;
|
|
let (member_socket, dispatcher_socket) = create_ring_socket_pair(MEMBER_TUNNEL_BUFFER_SIZE);
|
|
let (stream, sink) = split_ring_socket(dispatcher_socket);
|
|
let entry = SharedVirtualNicMemberTunnelEntry {
|
|
registration_id,
|
|
stream,
|
|
sink,
|
|
close_notifier,
|
|
};
|
|
control_sender
|
|
.send(SharedVirtualNicControl::Register { member_id, entry })
|
|
.map_err(|_| anyhow::anyhow!("shared virtual nic dispatcher is not running"))?;
|
|
Ok(Box::new(RingTunnel::new(member_socket, None)))
|
|
}
|
|
|
|
pub(super) fn unregister(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
registration_id: SharedVirtualNicMemberRegistrationId,
|
|
) {
|
|
if let Some(control_sender) = self.control_sender() {
|
|
let _ = control_sender.send(SharedVirtualNicControl::Unregister {
|
|
member_id,
|
|
registration_id,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn control_sender(&self) -> Option<mpsc::UnboundedSender<SharedVirtualNicControl>> {
|
|
self.control_sender.lock().unwrap().clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
enum SharedVirtualNicFlowAddr {
|
|
V4(u32),
|
|
V6([u8; 16]),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
struct SharedVirtualNicTransportPorts {
|
|
src: u16,
|
|
dst: u16,
|
|
}
|
|
|
|
impl SharedVirtualNicTransportPorts {
|
|
fn reversed(self) -> Self {
|
|
Self {
|
|
src: self.dst,
|
|
dst: self.src,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
struct SharedVirtualNicFlowKey {
|
|
src: SharedVirtualNicFlowAddr,
|
|
dst: SharedVirtualNicFlowAddr,
|
|
protocol: u8,
|
|
ports: Option<SharedVirtualNicTransportPorts>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
struct SharedVirtualNicMemberSources {
|
|
ipv4_addresses: BTreeSet<Ipv4Inet>,
|
|
ipv6_addresses: BTreeSet<Ipv6Inet>,
|
|
ipv4_routes: BTreeSet<Ipv4Inet>,
|
|
ipv6_routes: BTreeSet<Ipv6Inet>,
|
|
}
|
|
|
|
impl SharedVirtualNicMemberSources {
|
|
fn from_claims(claims: &SharedIfConfigClaims) -> Self {
|
|
let ipv4_addresses = claims
|
|
.ipv4_addresses
|
|
.iter()
|
|
.filter(|addr| !addr.address().is_unspecified())
|
|
.copied()
|
|
.collect::<BTreeSet<_>>();
|
|
let ipv6_addresses = claims
|
|
.ipv6_addresses
|
|
.iter()
|
|
.filter(|addr| !addr.address().is_unspecified())
|
|
.copied()
|
|
.collect::<BTreeSet<_>>();
|
|
let ipv4_routes = claims
|
|
.ipv4_routes
|
|
.iter()
|
|
.filter_map(ipv4_route_to_inet)
|
|
.collect::<BTreeSet<_>>();
|
|
let ipv6_routes = claims
|
|
.ipv6_routes
|
|
.iter()
|
|
.filter_map(ipv6_route_to_inet)
|
|
.collect::<BTreeSet<_>>();
|
|
|
|
Self {
|
|
ipv4_addresses,
|
|
ipv6_addresses,
|
|
ipv4_routes,
|
|
ipv6_routes,
|
|
}
|
|
}
|
|
|
|
fn is_empty(&self) -> bool {
|
|
self.ipv4_addresses.is_empty()
|
|
&& self.ipv6_addresses.is_empty()
|
|
&& self.ipv4_routes.is_empty()
|
|
&& self.ipv6_routes.is_empty()
|
|
}
|
|
|
|
fn owns_source(&self, source: SharedVirtualNicFlowAddr) -> bool {
|
|
match source {
|
|
SharedVirtualNicFlowAddr::V4(source) => self
|
|
.ipv4_addresses
|
|
.iter()
|
|
.any(|address| u32::from(address.address()) == source),
|
|
SharedVirtualNicFlowAddr::V6(source) => self
|
|
.ipv6_addresses
|
|
.iter()
|
|
.any(|address| address.address().octets() == source),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ipv4_route_to_inet(route: &SharedIpv4Route) -> Option<Ipv4Inet> {
|
|
Ipv4Inet::new(route.address, route.prefix).ok()
|
|
}
|
|
|
|
fn ipv6_route_to_inet(route: &SharedIpv6Route) -> Option<Ipv6Inet> {
|
|
Ipv6Inet::new(route.address, route.prefix).ok()
|
|
}
|
|
|
|
impl From<std::net::Ipv4Addr> for SharedVirtualNicFlowAddr {
|
|
fn from(addr: std::net::Ipv4Addr) -> Self {
|
|
Self::V4(u32::from_be_bytes(addr.octets()))
|
|
}
|
|
}
|
|
|
|
impl From<std::net::Ipv6Addr> for SharedVirtualNicFlowAddr {
|
|
fn from(addr: std::net::Ipv6Addr) -> Self {
|
|
Self::V6(addr.octets())
|
|
}
|
|
}
|
|
|
|
impl SharedVirtualNicFlowAddr {
|
|
fn as_ipv4(self) -> Option<std::net::Ipv4Addr> {
|
|
match self {
|
|
Self::V4(addr) => Some(std::net::Ipv4Addr::from(addr)),
|
|
Self::V6(_) => None,
|
|
}
|
|
}
|
|
|
|
fn as_ipv6(self) -> Option<std::net::Ipv6Addr> {
|
|
match self {
|
|
Self::V4(_) => None,
|
|
Self::V6(addr) => Some(std::net::Ipv6Addr::from(addr)),
|
|
}
|
|
}
|
|
fn packet_addrs(packet: &ZCPacket) -> Option<(Self, Self)> {
|
|
let payload = packet.payload();
|
|
match payload.first()? >> 4 {
|
|
4 => {
|
|
let ipv4 = Ipv4Packet::new_checked(payload).ok()?;
|
|
Some((ipv4.src_addr().into(), ipv4.dst_addr().into()))
|
|
}
|
|
6 => {
|
|
let ipv6 = Ipv6Packet::new_checked(payload).ok()?;
|
|
Some((ipv6.src_addr().into(), ipv6.dst_addr().into()))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SharedVirtualNicFlowKey {
|
|
fn from_packet(packet: &ZCPacket) -> Option<Self> {
|
|
let payload = packet.payload();
|
|
let version = payload.first()? >> 4;
|
|
match version {
|
|
4 => Self::from_ipv4_payload(payload),
|
|
6 => Self::from_ipv6_payload(payload),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn from_ipv4_payload(payload: &[u8]) -> Option<Self> {
|
|
let ipv4 = Ipv4Packet::new_checked(payload).ok()?;
|
|
if ipv4.version() != 4 {
|
|
return None;
|
|
}
|
|
|
|
let protocol = ipv4.next_header().into();
|
|
Some(Self {
|
|
src: ipv4.src_addr().into(),
|
|
dst: ipv4.dst_addr().into(),
|
|
protocol,
|
|
ports: if ipv4.frag_offset() == 0 {
|
|
transport_ports(protocol, ipv4.payload())
|
|
} else {
|
|
None
|
|
},
|
|
})
|
|
}
|
|
|
|
fn from_ipv6_payload(payload: &[u8]) -> Option<Self> {
|
|
let ipv6 = Ipv6Packet::new_checked(payload).ok()?;
|
|
if ipv6.version() != 6 {
|
|
return None;
|
|
}
|
|
|
|
let protocol = ipv6.next_header().into();
|
|
Some(Self {
|
|
src: ipv6.src_addr().into(),
|
|
dst: ipv6.dst_addr().into(),
|
|
protocol,
|
|
ports: transport_ports(protocol, ipv6.payload()),
|
|
})
|
|
}
|
|
|
|
fn reversed(&self) -> Self {
|
|
Self {
|
|
src: self.dst,
|
|
dst: self.src,
|
|
protocol: self.protocol,
|
|
ports: self.ports.map(|ports| {
|
|
if matches!(
|
|
IpProtocol::from(self.protocol),
|
|
IpProtocol::Icmp | IpProtocol::Icmpv6
|
|
) {
|
|
ports
|
|
} else {
|
|
ports.reversed()
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
struct SharedVirtualNicNatEntry {
|
|
original: SharedVirtualNicFlowKey,
|
|
wire: SharedVirtualNicFlowKey,
|
|
source_owner: Option<SharedVirtualNicMemberId>,
|
|
incoming: bool,
|
|
last_active: Instant,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SharedVirtualNicNatTable {
|
|
entries: HashMap<(SharedVirtualNicMemberId, SharedVirtualNicFlowKey), SharedVirtualNicNatEntry>,
|
|
replies: HashMap<(SharedVirtualNicMemberId, SharedVirtualNicFlowKey), SharedVirtualNicFlowKey>,
|
|
}
|
|
|
|
impl SharedVirtualNicNatTable {
|
|
fn prepare_packet(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
source_owner: Option<SharedVirtualNicMemberId>,
|
|
original: SharedVirtualNicFlowKey,
|
|
packet: &mut ZCPacket,
|
|
translated_src: SharedVirtualNicFlowAddr,
|
|
) -> bool {
|
|
if original.ports.is_none() {
|
|
return original.src == translated_src;
|
|
}
|
|
let now = Instant::now();
|
|
if let Some(entry) = self.entries.get_mut(&(member_id, original)) {
|
|
entry.last_active = now;
|
|
return rewrite_packet_flow(packet, original, entry.wire, RewriteIpAddr::Source);
|
|
}
|
|
let mut wire = original;
|
|
wire.src = translated_src;
|
|
// Wildcard UDP sockets may reply with another member's source address.
|
|
// Prefer an incoming reservation after source correction, preserving its
|
|
// service port and incoming destination. Exact outgoing mappings win above;
|
|
// packet tuples cannot distinguish a new client with the same endpoints.
|
|
if IpProtocol::from(original.protocol) == IpProtocol::Udp
|
|
&& let Some(entry) = self.entries.get_mut(&(member_id, wire))
|
|
&& entry.incoming
|
|
{
|
|
entry.last_active = now;
|
|
return rewrite_packet_flow(packet, original, entry.wire, RewriteIpAddr::Source);
|
|
}
|
|
if !self.ensure_capacity(now) {
|
|
return false;
|
|
}
|
|
|
|
while self.replies.contains_key(&(member_id, wire.reversed())) {
|
|
let Some(ports) = wire.ports.as_mut() else {
|
|
return false;
|
|
};
|
|
ports.src = ports.src.wrapping_add(1).max(1);
|
|
if Some(*ports) == original.ports {
|
|
return false;
|
|
}
|
|
}
|
|
if !rewrite_packet_flow(packet, original, wire, RewriteIpAddr::Source) {
|
|
return false;
|
|
}
|
|
self.entries.insert(
|
|
(member_id, original),
|
|
SharedVirtualNicNatEntry {
|
|
original,
|
|
wire,
|
|
source_owner,
|
|
incoming: false,
|
|
last_active: now,
|
|
},
|
|
);
|
|
self.replies.insert((member_id, wire.reversed()), original);
|
|
true
|
|
}
|
|
|
|
fn reserve_reply(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
reply: SharedVirtualNicFlowKey,
|
|
) -> bool {
|
|
if reply.ports.is_none() || self.replies.contains_key(&(member_id, reply)) {
|
|
return true;
|
|
}
|
|
let original = reply.reversed();
|
|
// An outgoing native flow may already have been assigned a different port.
|
|
if self.entries.contains_key(&(member_id, original)) {
|
|
return false;
|
|
}
|
|
let now = Instant::now();
|
|
if !self.ensure_capacity(now) {
|
|
return false;
|
|
}
|
|
self.entries.insert(
|
|
(member_id, original),
|
|
SharedVirtualNicNatEntry {
|
|
original,
|
|
wire: original,
|
|
source_owner: Some(member_id),
|
|
incoming: true,
|
|
last_active: now,
|
|
},
|
|
);
|
|
self.replies.insert((member_id, reply), original);
|
|
true
|
|
}
|
|
|
|
fn ensure_capacity(&mut self, now: Instant) -> bool {
|
|
// Reuse only idle reservations, never an active flow's wire tuple.
|
|
if self.entries.len() >= FLOW_OWNER_LIMIT {
|
|
self.retain(|_, entry| now.duration_since(entry.last_active) < FLOW_IDLE_TIMEOUT);
|
|
}
|
|
self.entries.len() < FLOW_OWNER_LIMIT
|
|
}
|
|
|
|
fn translate_reply(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
packet: &mut ZCPacket,
|
|
) -> bool {
|
|
let Some(key) = SharedVirtualNicFlowKey::from_packet(packet) else {
|
|
return false;
|
|
};
|
|
let Some(original) = self.replies.get(&(member_id, key)) else {
|
|
return false;
|
|
};
|
|
self.entries
|
|
.get_mut(&(member_id, *original))
|
|
.unwrap()
|
|
.last_active = Instant::now();
|
|
rewrite_packet_flow(packet, key, original.reversed(), RewriteIpAddr::Destination)
|
|
}
|
|
|
|
fn remove_member(&mut self, member_id: SharedVirtualNicMemberId) {
|
|
self.retain(|owner, entry| owner != member_id && entry.source_owner != Some(member_id));
|
|
}
|
|
|
|
fn remove_changed_addresses(
|
|
&mut self,
|
|
previous: &SharedVirtualNicMemberSources,
|
|
current: &SharedVirtualNicMemberSources,
|
|
) {
|
|
self.retain(|_, entry| {
|
|
[entry.original.src, entry.wire.src]
|
|
.into_iter()
|
|
.all(|addr| !previous.owns_source(addr) || current.owns_source(addr))
|
|
});
|
|
}
|
|
|
|
fn retain(
|
|
&mut self,
|
|
keep: impl Fn(SharedVirtualNicMemberId, &SharedVirtualNicNatEntry) -> bool,
|
|
) {
|
|
self.entries.retain(|(member_id, _), entry| {
|
|
if keep(*member_id, entry) {
|
|
true
|
|
} else {
|
|
self.replies.remove(&(*member_id, entry.wire.reversed()));
|
|
false
|
|
}
|
|
});
|
|
}
|
|
|
|
fn clear(&mut self) {
|
|
self.entries.clear();
|
|
self.replies.clear();
|
|
}
|
|
}
|
|
|
|
pub(super) struct SharedVirtualNicDispatcher {
|
|
_task: AbortOnDropHandle<()>,
|
|
control_sender: mpsc::UnboundedSender<SharedVirtualNicControl>,
|
|
}
|
|
|
|
impl SharedVirtualNicDispatcher {
|
|
pub(super) fn start(
|
|
tunnel: Box<dyn Tunnel>,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable,
|
|
valid: Arc<AtomicBool>,
|
|
) -> Self {
|
|
let (tun_stream, tun_sink) = tunnel.split();
|
|
let (control_sender, control_receiver) = mpsc::unbounded_channel();
|
|
member_tunnel_table.attach_dispatcher(control_sender.clone());
|
|
|
|
let task = SharedVirtualNicDispatcherTask {
|
|
tun_stream: Some(tun_stream),
|
|
tun_sink: Some(tun_sink),
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
mobile: None,
|
|
control_receiver,
|
|
member_tunnel_table,
|
|
valid,
|
|
state: SharedVirtualNicDispatcherState::default(),
|
|
};
|
|
|
|
Self {
|
|
_task: AbortOnDropHandle::new(SHARED_VIRTUAL_NIC_RUNTIME.spawn(task.run())),
|
|
control_sender,
|
|
}
|
|
}
|
|
|
|
#[cfg(mobile)]
|
|
pub(super) async fn start_for_mobile(
|
|
nic: Arc<Mutex<VirtualNic>>,
|
|
tun_fd: std::os::fd::RawFd,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable,
|
|
valid: Arc<AtomicBool>,
|
|
) -> Result<Self, Error> {
|
|
let (control_sender, control_receiver) = mpsc::unbounded_channel();
|
|
let (first_open_sender, first_open_receiver) = oneshot::channel();
|
|
let task_member_tunnel_table = member_tunnel_table.clone();
|
|
member_tunnel_table.attach_dispatcher(control_sender.clone());
|
|
|
|
let task = SharedVirtualNicDispatcherTask {
|
|
tun_stream: None,
|
|
tun_sink: None,
|
|
mobile: Some(SharedVirtualNicMobileTun { nic, tun_fd }),
|
|
control_receiver,
|
|
member_tunnel_table: task_member_tunnel_table,
|
|
valid,
|
|
state: SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources: true,
|
|
..Default::default()
|
|
},
|
|
};
|
|
let task = SHARED_VIRTUAL_NIC_RUNTIME.spawn(task.run_for_mobile(first_open_sender));
|
|
|
|
let dispatcher = Self {
|
|
_task: AbortOnDropHandle::new(task),
|
|
control_sender,
|
|
};
|
|
await_mobile_open(first_open_receiver)
|
|
.await
|
|
.map(|()| dispatcher)
|
|
}
|
|
|
|
#[cfg(mobile)]
|
|
pub(super) async fn update_mobile_tun_fd(
|
|
&self,
|
|
tun_fd: std::os::fd::RawFd,
|
|
) -> Result<(), Error> {
|
|
let (ack, receiver) = oneshot::channel();
|
|
self.control_sender
|
|
.send(SharedVirtualNicControl::UpdateMobileTun(
|
|
SharedVirtualNicMobileTunUpdate { tun_fd, ack },
|
|
))
|
|
.map_err(|_| {
|
|
Error::from(anyhow::anyhow!(
|
|
"shared virtual nic mobile dispatcher is not running"
|
|
))
|
|
})?;
|
|
await_mobile_open(receiver).await
|
|
}
|
|
|
|
pub(super) async fn update_sources(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
claims: &SharedIfConfigClaims,
|
|
) -> Result<(), Error> {
|
|
self.send_source_update(
|
|
member_id,
|
|
SharedVirtualNicMemberSources::from_claims(claims),
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub(super) async fn remove_sources(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
) -> Result<(), Error> {
|
|
self.send_source_update(member_id, SharedVirtualNicMemberSources::default())
|
|
.await
|
|
}
|
|
|
|
async fn send_source_update(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
sources: SharedVirtualNicMemberSources,
|
|
) -> Result<(), Error> {
|
|
let (ack, rx) = oneshot::channel();
|
|
self.control_sender
|
|
.send(SharedVirtualNicControl::UpdateSources {
|
|
member_id,
|
|
sources,
|
|
ack,
|
|
})
|
|
.map_err(|_| anyhow::anyhow!("shared virtual nic dispatcher is not running"))?;
|
|
rx.await
|
|
.map_err(|_| anyhow::anyhow!("shared virtual nic dispatcher is not running").into())
|
|
}
|
|
|
|
pub(super) async fn shutdown_without_invalidation(self) {
|
|
let (ack, rx) = oneshot::channel();
|
|
if self
|
|
.control_sender
|
|
.send(SharedVirtualNicControl::Shutdown {
|
|
invalidate: false,
|
|
ack,
|
|
})
|
|
.is_err()
|
|
{
|
|
return;
|
|
}
|
|
|
|
if tokio::time::timeout(DISPATCHER_SHUTDOWN_TIMEOUT, rx)
|
|
.await
|
|
.is_err()
|
|
{
|
|
tracing::warn!("timed out shutting down shared virtual nic dispatcher");
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SharedVirtualNicDispatcherTask {
|
|
tun_stream: Option<Pin<Box<dyn ZCPacketStream>>>,
|
|
tun_sink: Option<Pin<Box<dyn ZCPacketSink>>>,
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
mobile: Option<SharedVirtualNicMobileTun>,
|
|
control_receiver: mpsc::UnboundedReceiver<SharedVirtualNicControl>,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable,
|
|
valid: Arc<AtomicBool>,
|
|
state: SharedVirtualNicDispatcherState,
|
|
}
|
|
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
struct SharedVirtualNicMobileTun {
|
|
nic: Arc<Mutex<VirtualNic>>,
|
|
tun_fd: std::os::fd::RawFd,
|
|
}
|
|
|
|
impl SharedVirtualNicDispatcherTask {
|
|
async fn run(mut self) {
|
|
loop {
|
|
// Ring polling is synchronous, including packets dropped before TUN I/O.
|
|
tokio::task::consume_budget().await;
|
|
tokio::select! {
|
|
control = self.control_receiver.recv() => {
|
|
match control {
|
|
Some(SharedVirtualNicControl::Shutdown { invalidate, ack }) => {
|
|
self.cleanup(invalidate);
|
|
let _ = ack.send(());
|
|
return;
|
|
}
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
Some(SharedVirtualNicControl::UpdateMobileTun(update)) => {
|
|
if !self.apply_tun_update(update).await {
|
|
return;
|
|
}
|
|
}
|
|
Some(control) => self.state.handle_control(control),
|
|
None => break,
|
|
}
|
|
}
|
|
(member_id, packet) = self.state.next_member_packet() => {
|
|
let Some(packet) = packet else {
|
|
self.state.close_member(member_id);
|
|
continue;
|
|
};
|
|
let Some(packet) = self.state.prepare_member_packet_to_tun(member_id, packet) else {
|
|
continue;
|
|
};
|
|
let Some(sink) = self.tun_sink.as_mut() else {
|
|
break;
|
|
};
|
|
if let Err(err) = async {
|
|
tokio::time::timeout(DISPATCHER_TUN_WRITE_TIMEOUT, sink.send(packet)).await??;
|
|
Ok::<(), Error>(())
|
|
}.await {
|
|
tracing::error!(?err, "shared virtual nic write to tun failed");
|
|
break;
|
|
}
|
|
}
|
|
packet = async {
|
|
match self.tun_stream.as_mut() {
|
|
Some(stream) => stream.next().await,
|
|
None => std::future::pending().await,
|
|
}
|
|
} => {
|
|
match packet {
|
|
Some(Ok(packet)) => self.state.forward_tun_packet_to_member(packet),
|
|
Some(Err(err)) => {
|
|
tracing::error!(?err, "shared virtual nic read from tun failed");
|
|
break;
|
|
}
|
|
None => break,
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
self.cleanup(true);
|
|
}
|
|
|
|
#[cfg(mobile)]
|
|
async fn run_for_mobile(mut self, first_open_sender: oneshot::Sender<Result<(), Error>>) {
|
|
match self.open_tun().await {
|
|
Ok(()) => {
|
|
if first_open_sender.send(Ok(())).is_err() {
|
|
self.cleanup(false);
|
|
return;
|
|
}
|
|
}
|
|
Err(err) => {
|
|
self.cleanup(false);
|
|
let _ = first_open_sender.send(Err(err));
|
|
return;
|
|
}
|
|
}
|
|
self.run().await;
|
|
}
|
|
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
async fn open_tun(&mut self) -> Result<(), Error> {
|
|
let mobile = self.mobile.as_ref().unwrap();
|
|
let tun_fd = mobile.tun_fd;
|
|
let tunnel = mobile
|
|
.nic
|
|
.lock()
|
|
.await
|
|
.create_dev_for_mobile(tun_fd)
|
|
.await?;
|
|
let (tun_stream, tun_sink) = tunnel.split();
|
|
self.tun_stream = Some(tun_stream);
|
|
self.tun_sink = Some(tun_sink);
|
|
tracing::info!(fd = tun_fd, "opened shared virtual nic mobile tun");
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
async fn apply_tun_update(&mut self, update: SharedVirtualNicMobileTunUpdate) -> bool {
|
|
let Some(mobile) = self.mobile.as_mut() else {
|
|
let _ = update.ack.send(Err(anyhow::anyhow!(
|
|
"shared virtual nic mobile dispatcher is not running"
|
|
)
|
|
.into()));
|
|
return true;
|
|
};
|
|
mobile.tun_fd = update.tun_fd;
|
|
self.tun_stream.take();
|
|
self.tun_sink.take();
|
|
match self.open_tun().await {
|
|
Ok(()) => {
|
|
let _ = update.ack.send(Ok(()));
|
|
true
|
|
}
|
|
Err(err) => {
|
|
tracing::error!(fd = update.tun_fd, ?err, "failed to replace mobile tun");
|
|
self.cleanup(true);
|
|
let _ = update.ack.send(Err(err));
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn cleanup(&mut self, invalidate: bool) {
|
|
self.tun_stream.take();
|
|
self.tun_sink.take();
|
|
if invalidate {
|
|
self.valid.store(false, Ordering::Release);
|
|
}
|
|
self.member_tunnel_table.detach_dispatcher();
|
|
self.state.close_all();
|
|
}
|
|
}
|
|
|
|
#[cfg(any(mobile, test))]
|
|
async fn await_mobile_open(receiver: oneshot::Receiver<Result<(), Error>>) -> Result<(), Error> {
|
|
receiver.await.map_err(|_| {
|
|
Error::from(anyhow::anyhow!(
|
|
"shared virtual nic mobile dispatcher stopped before opening TUN"
|
|
))
|
|
})?
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SharedVirtualNicDispatcherState {
|
|
members: BTreeMap<SharedVirtualNicMemberId, SharedVirtualNicMemberTunnelEntry>,
|
|
last_polled_member: SharedVirtualNicMemberId,
|
|
nat_table: SharedVirtualNicNatTable,
|
|
source_table: SharedVirtualNicSourceTable,
|
|
// Mobile VPNs can supply synthetic sources, unlike desktop subnet forwarding.
|
|
translate_unknown_sources: bool,
|
|
}
|
|
|
|
impl SharedVirtualNicDispatcherState {
|
|
fn handle_control(&mut self, control: SharedVirtualNicControl) {
|
|
match control {
|
|
SharedVirtualNicControl::Register { member_id, entry } => {
|
|
self.register(member_id, entry);
|
|
}
|
|
SharedVirtualNicControl::Unregister {
|
|
member_id,
|
|
registration_id,
|
|
} => {
|
|
self.unregister(member_id, registration_id);
|
|
}
|
|
SharedVirtualNicControl::UpdateSources {
|
|
member_id,
|
|
sources,
|
|
ack,
|
|
} => {
|
|
if let Some(previous) = self.source_table.member_sources.get(&member_id) {
|
|
self.nat_table.remove_changed_addresses(previous, &sources);
|
|
}
|
|
self.source_table.update_member_sources(member_id, sources);
|
|
let _ = ack.send(());
|
|
}
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
SharedVirtualNicControl::UpdateMobileTun(_) => {
|
|
unreachable!("mobile updates are handled by the dispatcher task")
|
|
}
|
|
SharedVirtualNicControl::Shutdown { .. } => {
|
|
unreachable!("dispatcher shutdown is handled by the dispatcher task")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn register(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
entry: SharedVirtualNicMemberTunnelEntry,
|
|
) {
|
|
let old_entry = self.members.insert(member_id, entry);
|
|
if old_entry.is_some() {
|
|
self.nat_table.remove_member(member_id);
|
|
}
|
|
drop(old_entry);
|
|
}
|
|
|
|
fn unregister(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
registration_id: SharedVirtualNicMemberRegistrationId,
|
|
) {
|
|
if self
|
|
.members
|
|
.get(&member_id)
|
|
.map(|entry| entry.registration_id)
|
|
!= Some(registration_id)
|
|
{
|
|
return;
|
|
}
|
|
|
|
let entry = self.members.remove(&member_id);
|
|
drop(entry);
|
|
self.nat_table.remove_member(member_id);
|
|
self.source_table.remove_owner(member_id);
|
|
}
|
|
|
|
fn close_member(&mut self, member_id: SharedVirtualNicMemberId) {
|
|
if let Some(entry) = self.members.get(&member_id) {
|
|
entry.close_notifier.notify_one();
|
|
self.unregister(member_id, entry.registration_id);
|
|
}
|
|
}
|
|
|
|
async fn next_member_packet(&mut self) -> (SharedVirtualNicMemberId, Option<ZCPacket>) {
|
|
poll_fn(|cx| {
|
|
// Resume after the last ready member, wrapping once to keep busy members fair.
|
|
let last = self.last_polled_member;
|
|
for (member_id, entry) in self.members.range_mut((Excluded(last), Unbounded)) {
|
|
if let Poll::Ready(packet) = Pin::new(&mut entry.stream).poll_next(cx) {
|
|
self.last_polled_member = *member_id;
|
|
return Poll::Ready((*member_id, packet.and_then(Result::ok)));
|
|
}
|
|
}
|
|
for (member_id, entry) in self.members.range_mut((Unbounded, Included(last))) {
|
|
if let Poll::Ready(packet) = Pin::new(&mut entry.stream).poll_next(cx) {
|
|
self.last_polled_member = *member_id;
|
|
return Poll::Ready((*member_id, packet.and_then(Result::ok)));
|
|
}
|
|
}
|
|
Poll::Pending
|
|
})
|
|
.await
|
|
}
|
|
|
|
fn close_all(&mut self) {
|
|
let members = std::mem::take(&mut self.members);
|
|
self.nat_table.clear();
|
|
self.source_table.clear();
|
|
|
|
for entry in members.into_values() {
|
|
entry.close_notifier.notify_one();
|
|
}
|
|
}
|
|
|
|
fn prepare_member_packet_to_tun(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
mut packet: ZCPacket,
|
|
) -> Option<ZCPacket> {
|
|
// A local socket can receive before it ever sends. Reserve its reverse
|
|
// tuple before delivering the packet, so later translations cannot take it.
|
|
if let Some(key) = SharedVirtualNicFlowKey::from_packet(&packet)
|
|
&& self
|
|
.source_table
|
|
.member_sources
|
|
.get(&member_id)
|
|
.is_some_and(|sources| sources.owns_source(key.dst))
|
|
&& !self.nat_table.reserve_reply(member_id, key)
|
|
{
|
|
return None;
|
|
}
|
|
self.nat_table.translate_reply(member_id, &mut packet);
|
|
Some(packet)
|
|
}
|
|
|
|
fn forward_tun_packet_to_member(&mut self, packet: ZCPacket) {
|
|
if !self.send_packet(packet) {
|
|
tracing::trace!("shared virtual nic dropped packet without active member");
|
|
}
|
|
}
|
|
|
|
fn send_packet(&mut self, packet: ZCPacket) -> bool {
|
|
let source_owner = self.source_table.owner_of_source(&packet, &self.members);
|
|
if self.members.len() == 1 {
|
|
let member_id = *self.members.keys().next().unwrap();
|
|
// Keep native flow reservations for later members, without creating
|
|
// translations while this member has exclusive use of the TUN.
|
|
let source_owner = source_owner
|
|
.active_member()
|
|
.or_else(|| self.translate_unknown_sources.then_some(member_id));
|
|
return self.send_packet_to_member_with_translation(
|
|
member_id,
|
|
source_owner,
|
|
packet,
|
|
false,
|
|
);
|
|
}
|
|
let preferred_destination_owner = source_owner.active_member();
|
|
let destination_owner = self.source_table.owner_of_destination(
|
|
&packet,
|
|
&self.members,
|
|
preferred_destination_owner,
|
|
);
|
|
if let Some(member_id) = destination_owner {
|
|
let translate_source = should_translate_source_for_member(
|
|
source_owner,
|
|
member_id,
|
|
self.translate_unknown_sources,
|
|
);
|
|
return self.send_packet_to_member_with_translation(
|
|
member_id,
|
|
source_owner.active_member(),
|
|
packet,
|
|
translate_source,
|
|
);
|
|
}
|
|
|
|
match source_owner {
|
|
SourceOwner::Active(member_id) => {
|
|
return self.send_packet_to_member_with_translation(
|
|
member_id,
|
|
Some(member_id),
|
|
packet,
|
|
false,
|
|
);
|
|
}
|
|
SourceOwner::Inactive => return false,
|
|
SourceOwner::None => {}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn send_packet_to_member_with_translation(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
source_owner: Option<SharedVirtualNicMemberId>,
|
|
mut packet: ZCPacket,
|
|
translate_source: bool,
|
|
) -> bool {
|
|
let Some(entry) = self.members.get_mut(&member_id) else {
|
|
return false;
|
|
};
|
|
// Check queue capacity before allocating a flow, and never await a slow member.
|
|
match Pin::new(&mut entry.sink).poll_ready(&mut Context::from_waker(noop_waker_ref())) {
|
|
Poll::Pending => return false,
|
|
Poll::Ready(Err(_)) => {
|
|
self.close_member(member_id);
|
|
return false;
|
|
}
|
|
Poll::Ready(Ok(())) => {}
|
|
}
|
|
let key = SharedVirtualNicFlowKey::from_packet(&packet);
|
|
// Forwarded subnet sources cannot collide with a member's translated address.
|
|
// Keep applying an existing translation after other members leave.
|
|
if !translate_source
|
|
&& source_owner.is_none()
|
|
&& !key.is_some_and(|key| self.nat_table.entries.contains_key(&(member_id, key)))
|
|
{
|
|
return self.send_to_member(member_id, packet);
|
|
}
|
|
let Some(key) = key else {
|
|
return false;
|
|
};
|
|
let translated_src = if translate_source {
|
|
self.source_table
|
|
.source_for_member_destination(member_id, key.dst)
|
|
.unwrap_or(key.src)
|
|
} else {
|
|
key.src
|
|
};
|
|
// Native flows need reservations too, regardless of which flow arrives first.
|
|
if !self
|
|
.nat_table
|
|
.prepare_packet(member_id, source_owner, key, &mut packet, translated_src)
|
|
{
|
|
return false;
|
|
}
|
|
self.send_to_member(member_id, packet)
|
|
}
|
|
|
|
fn send_to_member(&mut self, member_id: SharedVirtualNicMemberId, packet: ZCPacket) -> bool {
|
|
// This is the Ring's only producer, with no await since poll_ready. A consumer
|
|
// can only free capacity; if it closes, unregister also removes the new flow.
|
|
if let Err(err) =
|
|
Pin::new(&mut self.members.get_mut(&member_id).unwrap().sink).start_send(packet)
|
|
{
|
|
self.close_member(member_id);
|
|
tracing::trace!(?err, "shared member tunnel write failed");
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum SourceOwner {
|
|
Active(SharedVirtualNicMemberId),
|
|
Inactive,
|
|
None,
|
|
}
|
|
|
|
impl SourceOwner {
|
|
fn active_member(self) -> Option<SharedVirtualNicMemberId> {
|
|
match self {
|
|
Self::Active(member_id) => Some(member_id),
|
|
Self::Inactive | Self::None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn should_translate_source_for_member(
|
|
source_owner: SourceOwner,
|
|
member_id: SharedVirtualNicMemberId,
|
|
translate_unknown_sources: bool,
|
|
) -> bool {
|
|
match source_owner {
|
|
SourceOwner::Active(source) => source != member_id,
|
|
SourceOwner::Inactive => true,
|
|
SourceOwner::None => translate_unknown_sources,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn rewrite_packet_source(
|
|
packet: &mut ZCPacket,
|
|
expected_source: SharedVirtualNicFlowAddr,
|
|
new_source: SharedVirtualNicFlowAddr,
|
|
) -> bool {
|
|
rewrite_ip_addr(
|
|
packet,
|
|
expected_source,
|
|
new_source,
|
|
RewriteIpAddr::Source,
|
|
None,
|
|
)
|
|
}
|
|
|
|
fn rewrite_packet_flow(
|
|
packet: &mut ZCPacket,
|
|
original: SharedVirtualNicFlowKey,
|
|
translated: SharedVirtualNicFlowKey,
|
|
rewrite: RewriteIpAddr,
|
|
) -> bool {
|
|
if original == translated {
|
|
return true;
|
|
}
|
|
let new_port = (original.ports != translated.ports).then(|| {
|
|
let ports = translated.ports.unwrap();
|
|
match (rewrite, IpProtocol::from(translated.protocol)) {
|
|
(_, IpProtocol::Icmp | IpProtocol::Icmpv6) | (RewriteIpAddr::Source, _) => ports.src,
|
|
(RewriteIpAddr::Destination, _) => ports.dst,
|
|
}
|
|
});
|
|
let (old_addr, new_addr) = match rewrite {
|
|
RewriteIpAddr::Source => (original.src, translated.src),
|
|
RewriteIpAddr::Destination => (original.dst, translated.dst),
|
|
};
|
|
rewrite_ip_addr(packet, old_addr, new_addr, rewrite, new_port)
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum RewriteIpAddr {
|
|
Source,
|
|
Destination,
|
|
}
|
|
|
|
fn rewrite_ip_addr(
|
|
packet: &mut ZCPacket,
|
|
expected_addr: SharedVirtualNicFlowAddr,
|
|
new_addr: SharedVirtualNicFlowAddr,
|
|
rewrite: RewriteIpAddr,
|
|
new_port: Option<u16>,
|
|
) -> bool {
|
|
match (expected_addr, new_addr) {
|
|
(SharedVirtualNicFlowAddr::V4(_), SharedVirtualNicFlowAddr::V4(_)) => {
|
|
rewrite_ipv4_addr(packet, expected_addr, new_addr, rewrite, new_port)
|
|
}
|
|
(SharedVirtualNicFlowAddr::V6(_), SharedVirtualNicFlowAddr::V6(_)) => {
|
|
rewrite_ipv6_addr(packet, expected_addr, new_addr, rewrite, new_port)
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn rewrite_ipv4_addr(
|
|
packet: &mut ZCPacket,
|
|
expected_addr: SharedVirtualNicFlowAddr,
|
|
new_addr: SharedVirtualNicFlowAddr,
|
|
rewrite: RewriteIpAddr,
|
|
new_port: Option<u16>,
|
|
) -> bool {
|
|
let Some(expected_addr) = expected_addr.as_ipv4() else {
|
|
return false;
|
|
};
|
|
let Some(new_addr) = new_addr.as_ipv4() else {
|
|
return false;
|
|
};
|
|
|
|
let payload = packet.mut_payload();
|
|
let Ok(mut ipv4_packet) = Ipv4Packet::new_checked(payload) else {
|
|
return false;
|
|
};
|
|
|
|
if ipv4_packet.version() != 4 || ipv4_packet.frag_offset() != 0 || ipv4_packet.more_frags() {
|
|
return false;
|
|
}
|
|
|
|
let valid_transport = match ipv4_packet.next_header() {
|
|
IpProtocol::Tcp => TcpPacket::new_checked(ipv4_packet.payload_mut()).is_ok(),
|
|
IpProtocol::Udp => UdpPacket::new_checked(ipv4_packet.payload_mut()).is_ok(),
|
|
IpProtocol::Icmp => Icmpv4Packet::new_checked(ipv4_packet.payload_mut()).is_ok(),
|
|
_ => true,
|
|
};
|
|
if !valid_transport {
|
|
return false;
|
|
}
|
|
|
|
match rewrite {
|
|
RewriteIpAddr::Source if ipv4_packet.src_addr() == expected_addr => {
|
|
ipv4_packet.set_src_addr(new_addr);
|
|
}
|
|
RewriteIpAddr::Destination if ipv4_packet.dst_addr() == expected_addr => {
|
|
ipv4_packet.set_dst_addr(new_addr);
|
|
}
|
|
_ => return false,
|
|
}
|
|
|
|
if let Some(port) = new_port {
|
|
rewrite_transport_port(
|
|
ipv4_packet.next_header(),
|
|
ipv4_packet.payload_mut(),
|
|
port,
|
|
rewrite,
|
|
);
|
|
}
|
|
update_ipv4_transport_checksum(&mut ipv4_packet);
|
|
ipv4_packet.fill_checksum();
|
|
true
|
|
}
|
|
|
|
fn rewrite_ipv6_addr(
|
|
packet: &mut ZCPacket,
|
|
expected_addr: SharedVirtualNicFlowAddr,
|
|
new_addr: SharedVirtualNicFlowAddr,
|
|
rewrite: RewriteIpAddr,
|
|
new_port: Option<u16>,
|
|
) -> bool {
|
|
let Some(expected_addr) = expected_addr.as_ipv6() else {
|
|
return false;
|
|
};
|
|
let Some(new_addr) = new_addr.as_ipv6() else {
|
|
return false;
|
|
};
|
|
|
|
let payload = packet.mut_payload();
|
|
let Ok(mut ipv6_packet) = Ipv6Packet::new_checked(payload) else {
|
|
return false;
|
|
};
|
|
if ipv6_packet.version() != 6 {
|
|
return false;
|
|
}
|
|
let protocol = ipv6_packet.next_header();
|
|
let valid_transport = match protocol {
|
|
IpProtocol::Tcp => TcpPacket::new_checked(ipv6_packet.payload_mut()).is_ok(),
|
|
IpProtocol::Udp => UdpPacket::new_checked(ipv6_packet.payload_mut()).is_ok(),
|
|
IpProtocol::Icmpv6 => Icmpv6Packet::new_checked(ipv6_packet.payload_mut()).is_ok(),
|
|
_ => false,
|
|
};
|
|
if !valid_transport {
|
|
return false;
|
|
}
|
|
|
|
match rewrite {
|
|
RewriteIpAddr::Source if ipv6_packet.src_addr() == expected_addr => {
|
|
ipv6_packet.set_src_addr(new_addr);
|
|
}
|
|
RewriteIpAddr::Destination if ipv6_packet.dst_addr() == expected_addr => {
|
|
ipv6_packet.set_dst_addr(new_addr);
|
|
}
|
|
_ => return false,
|
|
}
|
|
|
|
if let Some(port) = new_port {
|
|
rewrite_transport_port(protocol, ipv6_packet.payload_mut(), port, rewrite);
|
|
}
|
|
let source = ipv6_packet.src_addr();
|
|
let destination = ipv6_packet.dst_addr();
|
|
match protocol {
|
|
IpProtocol::Tcp => TcpPacket::new_checked(ipv6_packet.payload_mut())
|
|
.unwrap()
|
|
.fill_checksum(&IpAddress::Ipv6(source), &IpAddress::Ipv6(destination)),
|
|
IpProtocol::Udp => UdpPacket::new_checked(ipv6_packet.payload_mut())
|
|
.unwrap()
|
|
.fill_checksum(&IpAddress::Ipv6(source), &IpAddress::Ipv6(destination)),
|
|
IpProtocol::Icmpv6 => Icmpv6Packet::new_checked(ipv6_packet.payload_mut())
|
|
.unwrap()
|
|
.fill_checksum(&source, &destination),
|
|
_ => unreachable!(),
|
|
}
|
|
true
|
|
}
|
|
|
|
// Callers have already checked the transport header before changing the address.
|
|
fn rewrite_transport_port(
|
|
protocol: IpProtocol,
|
|
payload: &mut [u8],
|
|
port: u16,
|
|
rewrite: RewriteIpAddr,
|
|
) {
|
|
match protocol {
|
|
IpProtocol::Tcp => {
|
|
let mut packet = TcpPacket::new_unchecked(payload);
|
|
match rewrite {
|
|
RewriteIpAddr::Source => packet.set_src_port(port),
|
|
RewriteIpAddr::Destination => packet.set_dst_port(port),
|
|
}
|
|
}
|
|
IpProtocol::Udp => {
|
|
let mut packet = UdpPacket::new_unchecked(payload);
|
|
match rewrite {
|
|
RewriteIpAddr::Source => packet.set_src_port(port),
|
|
RewriteIpAddr::Destination => packet.set_dst_port(port),
|
|
}
|
|
}
|
|
IpProtocol::Icmp => Icmpv4Packet::new_unchecked(payload).set_echo_ident(port),
|
|
IpProtocol::Icmpv6 => Icmpv6Packet::new_unchecked(payload).set_echo_ident(port),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn update_ipv4_transport_checksum(ipv4_packet: &mut Ipv4Packet<&mut [u8]>) {
|
|
let source = IpAddress::Ipv4(ipv4_packet.src_addr());
|
|
let destination = IpAddress::Ipv4(ipv4_packet.dst_addr());
|
|
let protocol = ipv4_packet.next_header();
|
|
let transport_payload = ipv4_packet.payload_mut();
|
|
|
|
match protocol {
|
|
IpProtocol::Tcp => {
|
|
TcpPacket::new_checked(transport_payload)
|
|
.unwrap()
|
|
.fill_checksum(&source, &destination);
|
|
}
|
|
IpProtocol::Udp => {
|
|
let mut udp_packet = UdpPacket::new_checked(transport_payload).unwrap();
|
|
if udp_packet.checksum() == 0 {
|
|
return;
|
|
}
|
|
udp_packet.fill_checksum(&source, &destination);
|
|
}
|
|
IpProtocol::Icmp => {
|
|
Icmpv4Packet::new_checked(transport_payload)
|
|
.unwrap()
|
|
.fill_checksum();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct SharedVirtualNicSourceTable {
|
|
member_sources: BTreeMap<SharedVirtualNicMemberId, SharedVirtualNicMemberSources>,
|
|
}
|
|
|
|
impl SharedVirtualNicSourceTable {
|
|
fn update_member_sources(
|
|
&mut self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
sources: SharedVirtualNicMemberSources,
|
|
) {
|
|
if sources.is_empty() {
|
|
self.member_sources.remove(&member_id);
|
|
} else {
|
|
self.member_sources.insert(member_id, sources);
|
|
}
|
|
}
|
|
|
|
fn remove_owner(&mut self, member_id: SharedVirtualNicMemberId) {
|
|
self.member_sources.remove(&member_id);
|
|
}
|
|
|
|
fn clear(&mut self) {
|
|
self.member_sources.clear();
|
|
}
|
|
|
|
fn owner_of_source(
|
|
&self,
|
|
packet: &ZCPacket,
|
|
active_members: &BTreeMap<SharedVirtualNicMemberId, SharedVirtualNicMemberTunnelEntry>,
|
|
) -> SourceOwner {
|
|
let Some((source, _)) = SharedVirtualNicFlowAddr::packet_addrs(packet) else {
|
|
return SourceOwner::None;
|
|
};
|
|
let mut found = false;
|
|
for (member_id, sources) in &self.member_sources {
|
|
if sources.owns_source(source) {
|
|
found = true;
|
|
if active_members.contains_key(member_id) {
|
|
return SourceOwner::Active(*member_id);
|
|
}
|
|
}
|
|
}
|
|
if found {
|
|
SourceOwner::Inactive
|
|
} else {
|
|
SourceOwner::None
|
|
}
|
|
}
|
|
|
|
fn owner_of_destination(
|
|
&self,
|
|
packet: &ZCPacket,
|
|
active_members: &BTreeMap<SharedVirtualNicMemberId, SharedVirtualNicMemberTunnelEntry>,
|
|
preferred_member: Option<SharedVirtualNicMemberId>,
|
|
) -> Option<SharedVirtualNicMemberId> {
|
|
let (_, dst) = SharedVirtualNicFlowAddr::packet_addrs(packet)?;
|
|
|
|
let mut first = None;
|
|
for (member_id, sources) in &self.member_sources {
|
|
if !active_members.contains_key(member_id) || !sources.has_destination(dst) {
|
|
continue;
|
|
}
|
|
if Some(*member_id) == preferred_member {
|
|
return Some(*member_id);
|
|
}
|
|
first.get_or_insert(*member_id);
|
|
}
|
|
first
|
|
}
|
|
|
|
fn source_for_member_destination(
|
|
&self,
|
|
member_id: SharedVirtualNicMemberId,
|
|
dst: SharedVirtualNicFlowAddr,
|
|
) -> Option<SharedVirtualNicFlowAddr> {
|
|
let sources = self.member_sources.get(&member_id)?;
|
|
match dst {
|
|
SharedVirtualNicFlowAddr::V4(dst) => source_for_destination(
|
|
&sources.ipv4_addresses,
|
|
&sources.ipv4_routes,
|
|
std::net::Ipv4Addr::from(dst),
|
|
)
|
|
.map(SharedVirtualNicFlowAddr::from),
|
|
SharedVirtualNicFlowAddr::V6(dst) => source_for_destination(
|
|
&sources.ipv6_addresses,
|
|
&sources.ipv6_routes,
|
|
std::net::Ipv6Addr::from(dst),
|
|
)
|
|
.map(SharedVirtualNicFlowAddr::from),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SharedVirtualNicMemberSources {
|
|
fn has_destination(&self, dst: SharedVirtualNicFlowAddr) -> bool {
|
|
match dst {
|
|
SharedVirtualNicFlowAddr::V4(dst) => {
|
|
let dst = std::net::Ipv4Addr::from(dst);
|
|
self.ipv4_addresses.iter().any(|addr| addr.contains(&dst))
|
|
|| self.ipv4_routes.iter().any(|route| route.contains(&dst))
|
|
}
|
|
SharedVirtualNicFlowAddr::V6(dst) => {
|
|
let dst = std::net::Ipv6Addr::from(dst);
|
|
self.ipv6_addresses.iter().any(|addr| addr.contains(&dst))
|
|
|| self.ipv6_routes.iter().any(|route| route.contains(&dst))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn source_for_destination<T: Inet + Ord>(
|
|
addresses: &BTreeSet<T>,
|
|
routes: &BTreeSet<T>,
|
|
dst: T::Address,
|
|
) -> Option<T::Address> {
|
|
let mut best = None;
|
|
for addr in addresses {
|
|
if addr.contains(&dst) {
|
|
update_best_source(&mut best, addr.network_length(), addr.address());
|
|
}
|
|
}
|
|
for route in routes {
|
|
if route.contains(&dst) {
|
|
let source = addresses
|
|
.iter()
|
|
.find(|addr| route.contains(&addr.address()))
|
|
.or_else(|| addresses.first());
|
|
if let Some(source) = source {
|
|
update_best_source(&mut best, route.network_length(), source.address());
|
|
}
|
|
}
|
|
}
|
|
best.map(|(_, source)| source)
|
|
}
|
|
|
|
fn update_best_source<T: Copy>(best: &mut Option<(u8, T)>, prefix: u8, source: T) {
|
|
if best
|
|
.map(|(best_prefix, _)| prefix > best_prefix)
|
|
.unwrap_or(true)
|
|
{
|
|
*best = Some((prefix, source));
|
|
}
|
|
}
|
|
|
|
// ICMP Echo sessions use only the identifier; sequence numbers stay in the packet.
|
|
fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTransportPorts> {
|
|
let (src, dst) = match IpProtocol::from(protocol) {
|
|
IpProtocol::Tcp => {
|
|
let tcp = TcpPacket::new_checked(payload).ok()?;
|
|
(tcp.src_port(), tcp.dst_port())
|
|
}
|
|
IpProtocol::Udp => {
|
|
let udp = UdpPacket::new_checked(payload).ok()?;
|
|
(udp.src_port(), udp.dst_port())
|
|
}
|
|
IpProtocol::Icmp => {
|
|
let icmp = Icmpv4Packet::new_checked(payload).ok()?;
|
|
if !matches!(
|
|
icmp.msg_type(),
|
|
Icmpv4Message::EchoRequest | Icmpv4Message::EchoReply
|
|
) {
|
|
return None;
|
|
}
|
|
(icmp.echo_ident(), 0)
|
|
}
|
|
IpProtocol::Icmpv6 => {
|
|
let icmp = Icmpv6Packet::new_checked(payload).ok()?;
|
|
if !matches!(
|
|
icmp.msg_type(),
|
|
Icmpv6Message::EchoRequest | Icmpv6Message::EchoReply
|
|
) {
|
|
return None;
|
|
}
|
|
(icmp.echo_ident(), 0)
|
|
}
|
|
_ => return None,
|
|
};
|
|
Some(SharedVirtualNicTransportPorts { src, dst })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::{
|
|
net::{Ipv4Addr, Ipv6Addr},
|
|
time::Duration,
|
|
};
|
|
|
|
use super::SharedVirtualNicFlowAddr as FlowAddr;
|
|
use super::*;
|
|
use easytier_core::tunnel::{
|
|
TunnelError, ring::create_ring_tunnel_pair, wrapper::TunnelWrapper,
|
|
};
|
|
use futures::FutureExt;
|
|
const IPV4_HEADER_MIN_LEN: usize = 20;
|
|
const IPV6_HEADER_LEN: usize = 40;
|
|
const TCP_HEADER_MIN_LEN: usize = 20;
|
|
const UDP_HEADER_LEN: usize = 8;
|
|
const ICMP_ECHO_HEADER_LEN: usize = 8;
|
|
|
|
#[tokio::test]
|
|
async fn mobile_open_handshake_waits_for_dispatcher_result() {
|
|
let (sender, receiver) = oneshot::channel();
|
|
let waiter = tokio::spawn(await_mobile_open(receiver));
|
|
|
|
tokio::task::yield_now().await;
|
|
assert!(!waiter.is_finished());
|
|
|
|
sender.send(Ok(())).unwrap();
|
|
waiter.await.unwrap().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn mobile_open_handshake_preserves_original_error() {
|
|
let (sender, receiver) = oneshot::channel();
|
|
let waiter = tokio::spawn(await_mobile_open(receiver));
|
|
let errno = 9;
|
|
|
|
sender
|
|
.send(Err(std::io::Error::from_raw_os_error(errno).into()))
|
|
.unwrap();
|
|
let err = waiter.await.unwrap().unwrap_err();
|
|
assert!(matches!(err, Error::IOError(ref io_err) if io_err.raw_os_error() == Some(errno)));
|
|
}
|
|
|
|
fn ipv6_packet(src: Ipv6Addr, dst: Ipv6Addr) -> ZCPacket {
|
|
let mut payload = vec![0; IPV6_HEADER_LEN];
|
|
let mut ipv6 = Ipv6Packet::new_unchecked(&mut payload);
|
|
ipv6.set_version(6);
|
|
ipv6.set_next_header(IpProtocol::Icmpv6);
|
|
ipv6.set_src_addr(src);
|
|
ipv6.set_dst_addr(dst);
|
|
ZCPacket::new_with_payload(&payload)
|
|
}
|
|
|
|
fn ipv4_udp_packet(src: Ipv4Addr, dst: Ipv4Addr) -> ZCPacket {
|
|
transport_packet(src, dst, IpProtocol::Udp, 1234, 5678)
|
|
}
|
|
|
|
fn ipv4_icmp_echo_packet(src: Ipv4Addr, dst: Ipv4Addr) -> ZCPacket {
|
|
echo_packet(src, dst, false, 0, 0)
|
|
}
|
|
|
|
fn ip_packet(src: FlowAddr, dst: FlowAddr, protocol: IpProtocol, payload: &[u8]) -> ZCPacket {
|
|
let header = if matches!(src, FlowAddr::V4(_)) {
|
|
IPV4_HEADER_MIN_LEN
|
|
} else {
|
|
IPV6_HEADER_LEN
|
|
};
|
|
let mut bytes = vec![0; header + payload.len()];
|
|
bytes[header..].copy_from_slice(payload);
|
|
let len = bytes.len() as u16;
|
|
match (src, dst) {
|
|
(FlowAddr::V4(src), FlowAddr::V4(dst)) => {
|
|
let mut ip = Ipv4Packet::new_unchecked(&mut bytes);
|
|
ip.set_version(4);
|
|
ip.set_header_len(header as u8);
|
|
ip.set_total_len(len);
|
|
ip.set_hop_limit(64);
|
|
ip.set_next_header(protocol);
|
|
ip.set_src_addr(src.into());
|
|
ip.set_dst_addr(dst.into());
|
|
ip.fill_checksum();
|
|
}
|
|
(FlowAddr::V6(src), FlowAddr::V6(dst)) => {
|
|
let mut ip = Ipv6Packet::new_unchecked(&mut bytes);
|
|
ip.set_version(6);
|
|
ip.set_payload_len(payload.len() as u16);
|
|
ip.set_hop_limit(64);
|
|
ip.set_next_header(protocol);
|
|
ip.set_src_addr(src.into());
|
|
ip.set_dst_addr(dst.into());
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
ZCPacket::new_with_payload(&bytes)
|
|
}
|
|
|
|
fn test_addresses(ipv6: bool) -> [FlowAddr; 4] {
|
|
if ipv6 {
|
|
[
|
|
"2001:db8:1::1",
|
|
"2001:db8:2::1",
|
|
"2001:db8:3::1",
|
|
"2001:db8:3::2",
|
|
]
|
|
.map(|ip| ip.parse::<Ipv6Addr>().unwrap().into())
|
|
} else {
|
|
[
|
|
Ipv4Addr::new(10, 231, 1, 1),
|
|
Ipv4Addr::new(10, 231, 2, 1),
|
|
Ipv4Addr::new(10, 231, 3, 1),
|
|
Ipv4Addr::new(10, 231, 3, 2),
|
|
]
|
|
.map(Into::into)
|
|
}
|
|
}
|
|
|
|
fn address_sources(address: FlowAddr) -> SharedVirtualNicMemberSources {
|
|
let mut sources = SharedVirtualNicMemberSources::default();
|
|
match address {
|
|
FlowAddr::V4(ip) => {
|
|
sources
|
|
.ipv4_addresses
|
|
.insert(Ipv4Inet::new(ip.into(), 24).unwrap());
|
|
}
|
|
FlowAddr::V6(ip) => {
|
|
sources
|
|
.ipv6_addresses
|
|
.insert(Ipv6Inet::new(ip.into(), 64).unwrap());
|
|
}
|
|
}
|
|
sources
|
|
}
|
|
|
|
fn transport_packet<A: Into<FlowAddr>>(
|
|
src: A,
|
|
dst: A,
|
|
protocol: IpProtocol,
|
|
src_port: u16,
|
|
dst_port: u16,
|
|
) -> ZCPacket {
|
|
let (src, dst) = (src.into(), dst.into());
|
|
let (src_ip, dst_ip) = match (src, dst) {
|
|
(FlowAddr::V4(src), FlowAddr::V4(dst)) => {
|
|
(IpAddress::Ipv4(src.into()), IpAddress::Ipv4(dst.into()))
|
|
}
|
|
(FlowAddr::V6(src), FlowAddr::V6(dst)) => {
|
|
(IpAddress::Ipv6(src.into()), IpAddress::Ipv6(dst.into()))
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
let mut payload = vec![
|
|
0;
|
|
if protocol == IpProtocol::Tcp {
|
|
TCP_HEADER_MIN_LEN
|
|
} else {
|
|
UDP_HEADER_LEN
|
|
}
|
|
];
|
|
match protocol {
|
|
IpProtocol::Tcp => {
|
|
let mut tcp = TcpPacket::new_unchecked(&mut payload);
|
|
tcp.set_header_len(TCP_HEADER_MIN_LEN as u8);
|
|
tcp.set_src_port(src_port);
|
|
tcp.set_dst_port(dst_port);
|
|
tcp.fill_checksum(&src_ip, &dst_ip);
|
|
}
|
|
IpProtocol::Udp => {
|
|
let mut udp = UdpPacket::new_unchecked(&mut payload);
|
|
udp.set_len(UDP_HEADER_LEN as u16);
|
|
udp.set_src_port(src_port);
|
|
udp.set_dst_port(dst_port);
|
|
udp.fill_checksum(&src_ip, &dst_ip);
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
ip_packet(src, dst, protocol, &payload)
|
|
}
|
|
|
|
fn echo_packet<A: Into<FlowAddr>>(
|
|
src: A,
|
|
dst: A,
|
|
reply: bool,
|
|
identifier: u16,
|
|
sequence: u16,
|
|
) -> ZCPacket {
|
|
let (src, dst) = (src.into(), dst.into());
|
|
let mut payload = vec![0; ICMP_ECHO_HEADER_LEN];
|
|
let protocol = match (src, dst) {
|
|
(FlowAddr::V4(_), FlowAddr::V4(_)) => {
|
|
let mut icmp = Icmpv4Packet::new_unchecked(&mut payload);
|
|
icmp.set_msg_type(if reply {
|
|
Icmpv4Message::EchoReply
|
|
} else {
|
|
Icmpv4Message::EchoRequest
|
|
});
|
|
icmp.set_echo_ident(identifier);
|
|
icmp.set_echo_seq_no(sequence);
|
|
icmp.fill_checksum();
|
|
IpProtocol::Icmp
|
|
}
|
|
(FlowAddr::V6(src), FlowAddr::V6(dst)) => {
|
|
let mut icmp = Icmpv6Packet::new_unchecked(&mut payload);
|
|
icmp.set_msg_type(if reply {
|
|
Icmpv6Message::EchoReply
|
|
} else {
|
|
Icmpv6Message::EchoRequest
|
|
});
|
|
icmp.set_echo_ident(identifier);
|
|
icmp.set_echo_seq_no(sequence);
|
|
icmp.fill_checksum(&src.into(), &dst.into());
|
|
IpProtocol::Icmpv6
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
ip_packet(src, dst, protocol, &payload)
|
|
}
|
|
|
|
fn assert_checksums(packet: &ZCPacket) {
|
|
let (src, dst, protocol, payload) = match packet.payload()[0] >> 4 {
|
|
4 => {
|
|
let ip = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
|
assert!(ip.verify_checksum());
|
|
(
|
|
IpAddress::Ipv4(ip.src_addr()),
|
|
IpAddress::Ipv4(ip.dst_addr()),
|
|
ip.next_header(),
|
|
&packet.payload()[ip.header_len() as usize..],
|
|
)
|
|
}
|
|
6 => {
|
|
let ip = Ipv6Packet::new_checked(packet.payload()).unwrap();
|
|
(
|
|
IpAddress::Ipv6(ip.src_addr()),
|
|
IpAddress::Ipv6(ip.dst_addr()),
|
|
ip.next_header(),
|
|
&packet.payload()[IPV6_HEADER_LEN..],
|
|
)
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
match protocol {
|
|
IpProtocol::Tcp => assert!(
|
|
TcpPacket::new_checked(payload)
|
|
.unwrap()
|
|
.verify_checksum(&src, &dst)
|
|
),
|
|
IpProtocol::Udp => {
|
|
let udp = UdpPacket::new_checked(payload).unwrap();
|
|
assert_ne!(udp.checksum(), 0);
|
|
assert!(udp.verify_checksum(&src, &dst));
|
|
}
|
|
IpProtocol::Icmp => assert!(
|
|
Icmpv4Packet::new_checked(payload)
|
|
.unwrap()
|
|
.verify_checksum()
|
|
),
|
|
IpProtocol::Icmpv6 => {
|
|
let (IpAddress::Ipv6(src), IpAddress::Ipv6(dst)) = (src, dst) else {
|
|
unreachable!()
|
|
};
|
|
assert!(
|
|
Icmpv6Packet::new_checked(payload)
|
|
.unwrap()
|
|
.verify_checksum(&src, &dst)
|
|
);
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn member_sources(ipv4: &[&str]) -> SharedVirtualNicMemberSources {
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(ipv4, &[], &[], &[]))
|
|
}
|
|
|
|
fn member_sources_with_ipv4_routes(
|
|
ipv4: &[&str],
|
|
ipv4_routes: &[&str],
|
|
) -> SharedVirtualNicMemberSources {
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(ipv4, &[], ipv4_routes, &[]))
|
|
}
|
|
|
|
fn member_sources_with_ipv6(ipv6: &[&str]) -> SharedVirtualNicMemberSources {
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], &[]))
|
|
}
|
|
|
|
fn member_sources_with_ipv6_routes(
|
|
ipv6: &[&str],
|
|
ipv6_routes: &[&str],
|
|
) -> SharedVirtualNicMemberSources {
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], ipv6_routes))
|
|
}
|
|
|
|
fn member_claims(
|
|
ipv4: &[&str],
|
|
ipv6: &[&str],
|
|
ipv4_routes: &[&str],
|
|
ipv6_routes: &[&str],
|
|
) -> SharedIfConfigClaims {
|
|
SharedIfConfigClaims {
|
|
ipv4_addresses: ipv4.iter().map(|addr| addr.parse().unwrap()).collect(),
|
|
ipv6_addresses: ipv6.iter().map(|addr| addr.parse().unwrap()).collect(),
|
|
ipv4_routes: ipv4_routes
|
|
.iter()
|
|
.map(|route| {
|
|
let inet = route.parse::<Ipv4Inet>().unwrap();
|
|
SharedIpv4Route::new(inet.address(), inet.network_length(), None)
|
|
})
|
|
.collect(),
|
|
ipv6_routes: ipv6_routes
|
|
.iter()
|
|
.map(|route| {
|
|
let inet = route.parse::<Ipv6Inet>().unwrap();
|
|
SharedIpv6Route::new(inet.address(), inet.network_length(), None)
|
|
})
|
|
.collect(),
|
|
mtu: None,
|
|
}
|
|
}
|
|
|
|
struct TestMember {
|
|
stream: RingStream,
|
|
_sink: RingSink,
|
|
}
|
|
|
|
impl TestMember {
|
|
fn try_recv(&mut self) -> Result<ZCPacket, ()> {
|
|
self.stream
|
|
.next()
|
|
.now_or_never()
|
|
.flatten()
|
|
.ok_or(())?
|
|
.map_err(|_| ())
|
|
}
|
|
}
|
|
|
|
fn member_channel(capacity: usize) -> (SharedVirtualNicMemberTunnelEntry, TestMember) {
|
|
let (member, dispatcher) = create_ring_socket_pair(capacity);
|
|
let (stream, sink) = split_ring_socket(dispatcher);
|
|
let (member_stream, member_sink) = split_ring_socket(member);
|
|
(
|
|
SharedVirtualNicMemberTunnelEntry {
|
|
registration_id: uuid::Uuid::from_u128(1),
|
|
stream,
|
|
sink,
|
|
close_notifier: Arc::new(Notify::new()),
|
|
},
|
|
TestMember {
|
|
stream: member_stream,
|
|
_sink: member_sink,
|
|
},
|
|
)
|
|
}
|
|
|
|
fn test_state<const N: usize>(
|
|
members: [(SharedVirtualNicMemberId, SharedVirtualNicMemberSources); N],
|
|
) -> (SharedVirtualNicDispatcherState, [TestMember; N]) {
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
let endpoints = members.map(|(id, sources)| {
|
|
let (entry, member) = member_channel(8);
|
|
state.register(id, entry);
|
|
state.source_table.update_member_sources(id, sources);
|
|
member
|
|
});
|
|
(state, endpoints)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn member_ring_polling_is_fair_and_wakes_after_pending() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
let (mut state, [mut a, mut b]) = test_state([
|
|
(first, SharedVirtualNicMemberSources::default()),
|
|
(second, SharedVirtualNicMemberSources::default()),
|
|
]);
|
|
let receive = tokio::time::timeout(Duration::from_secs(1), state.next_member_packet());
|
|
let send = async {
|
|
tokio::task::yield_now().await;
|
|
a._sink
|
|
.send(ipv4_udp_packet(Ipv4Addr::LOCALHOST, Ipv4Addr::LOCALHOST))
|
|
.await
|
|
.unwrap();
|
|
};
|
|
let (received, ()) = tokio::join!(receive, send);
|
|
assert_eq!(received.unwrap().0, first);
|
|
for _ in 0..2 {
|
|
a._sink
|
|
.send(ipv4_udp_packet(Ipv4Addr::LOCALHOST, Ipv4Addr::LOCALHOST))
|
|
.await
|
|
.unwrap();
|
|
b._sink
|
|
.send(ipv4_udp_packet(Ipv4Addr::LOCALHOST, Ipv4Addr::LOCALHOST))
|
|
.await
|
|
.unwrap();
|
|
}
|
|
for expected in [second, first, second, first] {
|
|
let (id, packet) = state.next_member_packet().await;
|
|
assert_eq!(id, expected);
|
|
assert!(packet.is_some());
|
|
}
|
|
let closed = state.members[&first].close_notifier.clone();
|
|
drop(a);
|
|
let (id, packet) = state.next_member_packet().await;
|
|
assert_eq!(id, first);
|
|
assert!(packet.is_none());
|
|
state.close_member(id);
|
|
closed.notified().await;
|
|
b._sink
|
|
.send(ipv4_udp_packet(Ipv4Addr::LOCALHOST, Ipv4Addr::LOCALHOST))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.next_member_packet().await.0, second);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn full_flow_table_drops_yield_to_other_runtime_tasks() {
|
|
let id = uuid::Uuid::from_u128(1);
|
|
let local = Ipv4Addr::new(10, 231, 1, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 1, 2);
|
|
let (entry, mut member) = member_channel(MEMBER_TUNNEL_BUFFER_SIZE);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
state.register(id, entry);
|
|
state
|
|
.source_table
|
|
.update_member_sources(id, address_sources(local.into()));
|
|
for port in 1..=FLOW_OWNER_LIMIT as u16 {
|
|
let packet = transport_packet(remote, local, IpProtocol::Udp, 5678, port);
|
|
assert!(
|
|
state
|
|
.nat_table
|
|
.reserve_reply(id, SharedVirtualNicFlowKey::from_packet(&packet).unwrap())
|
|
);
|
|
}
|
|
let packet = transport_packet(remote, local, IpProtocol::Udp, 5678, 5000);
|
|
for _ in 0..MEMBER_TUNNEL_BUFFER_SIZE {
|
|
member
|
|
._sink
|
|
.force_send(ZCPacket::new_with_payload(packet.payload()))
|
|
.unwrap();
|
|
}
|
|
let (_tun, tunnel) = create_ring_tunnel_pair();
|
|
let (tun_stream, tun_sink) = tunnel.split();
|
|
let (control_sender, control_receiver) = mpsc::unbounded_channel();
|
|
let task = SharedVirtualNicDispatcherTask {
|
|
tun_stream: Some(tun_stream),
|
|
tun_sink: Some(tun_sink),
|
|
#[cfg(any(mobile, all(test, unix)))]
|
|
mobile: None,
|
|
control_receiver,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable::default(),
|
|
valid: Arc::new(AtomicBool::new(true)),
|
|
state,
|
|
};
|
|
let handle = tokio::spawn(task.run());
|
|
tokio::task::yield_now().await;
|
|
// Refill freed slots to measure whether the dispatcher yielded before
|
|
// draining the whole Ring. No timer or unbounded producer is needed.
|
|
let mut consumed = 0;
|
|
while member
|
|
._sink
|
|
.force_send(ZCPacket::new_with_payload(packet.payload()))
|
|
.is_ok()
|
|
{
|
|
consumed += 1;
|
|
}
|
|
let (ack, rx) = oneshot::channel();
|
|
control_sender
|
|
.send(SharedVirtualNicControl::Shutdown {
|
|
invalidate: false,
|
|
ack,
|
|
})
|
|
.unwrap();
|
|
rx.await.unwrap();
|
|
handle.await.unwrap();
|
|
assert!(
|
|
consumed > 0 && consumed < MEMBER_TUNNEL_BUFFER_SIZE,
|
|
"drained {consumed} packets before yielding"
|
|
);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn start_mobile_dispatcher_for_test(
|
|
tunnel: Box<dyn Tunnel>,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable,
|
|
valid: Arc<AtomicBool>,
|
|
) -> (
|
|
SharedVirtualNicDispatcher,
|
|
mpsc::UnboundedSender<SharedVirtualNicControl>,
|
|
) {
|
|
use crate::{common::netns::NetNS, instance::virtual_nic::VirtualNicConfig};
|
|
|
|
let (tun_stream, tun_sink) = tunnel.split();
|
|
let (control_sender, control_receiver) = mpsc::unbounded_channel();
|
|
member_tunnel_table.attach_dispatcher(control_sender.clone());
|
|
let task = SharedVirtualNicDispatcherTask {
|
|
mobile: Some(SharedVirtualNicMobileTun {
|
|
nic: Arc::new(Mutex::new(VirtualNic::new(VirtualNicConfig::new(
|
|
String::new(),
|
|
1300,
|
|
NetNS::new(None),
|
|
)))),
|
|
tun_fd: -1,
|
|
}),
|
|
tun_stream: Some(tun_stream),
|
|
tun_sink: Some(tun_sink),
|
|
control_receiver,
|
|
member_tunnel_table,
|
|
valid,
|
|
state: SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources: true,
|
|
..Default::default()
|
|
},
|
|
};
|
|
let dispatcher = SharedVirtualNicDispatcher {
|
|
_task: AbortOnDropHandle::new(tokio::spawn(task.run())),
|
|
control_sender: control_sender.clone(),
|
|
};
|
|
(dispatcher, control_sender)
|
|
}
|
|
|
|
async fn register_dispatcher_test_members(
|
|
dispatcher: &SharedVirtualNicDispatcher,
|
|
member_tunnel_table: &SharedVirtualNicMemberTunnelTable,
|
|
) -> (Vec<Box<dyn Tunnel>>, Vec<Arc<Notify>>) {
|
|
let mut tunnels = Vec::new();
|
|
let mut close_notifiers = Vec::new();
|
|
for id in 1..=2 {
|
|
let member_id = uuid::Uuid::from_u128(id);
|
|
let close_notifier = Arc::new(Notify::new());
|
|
let member_tunnel = member_tunnel_table
|
|
.register(member_id, uuid::Uuid::new_v4(), close_notifier.clone())
|
|
.unwrap();
|
|
dispatcher
|
|
.update_sources(member_id, &SharedIfConfigClaims::default())
|
|
.await
|
|
.unwrap();
|
|
tunnels.push(member_tunnel);
|
|
close_notifiers.push(close_notifier);
|
|
}
|
|
(tunnels, close_notifiers)
|
|
}
|
|
|
|
fn stalled_tun_for_test() -> (Box<dyn Tunnel>, Arc<Notify>) {
|
|
let writing = Arc::new(Notify::new());
|
|
let sink_writing = writing.clone();
|
|
let tun_sink = futures::sink::unfold((), move |(), _packet: ZCPacket| {
|
|
let writing = sink_writing.clone();
|
|
async move {
|
|
writing.notify_one();
|
|
std::future::pending::<Result<(), TunnelError>>().await
|
|
}
|
|
});
|
|
let tun_stream = futures::stream::pending::<Result<ZCPacket, TunnelError>>();
|
|
(
|
|
Box::new(TunnelWrapper::new(tun_stream, tun_sink, None)),
|
|
writing,
|
|
)
|
|
}
|
|
|
|
async fn check_dispatcher_tun_write_timeout(
|
|
dispatcher: SharedVirtualNicDispatcher,
|
|
member_tunnel_table: SharedVirtualNicMemberTunnelTable,
|
|
valid: Arc<AtomicBool>,
|
|
writing: Arc<Notify>,
|
|
) {
|
|
let (members, close_notifiers) =
|
|
register_dispatcher_test_members(&dispatcher, &member_tunnel_table).await;
|
|
let (_, mut member_sink) = members[0].split();
|
|
member_sink
|
|
.send(ipv4_udp_packet(
|
|
Ipv4Addr::new(10, 231, 1, 1),
|
|
Ipv4Addr::new(203, 0, 113, 1),
|
|
))
|
|
.await
|
|
.unwrap();
|
|
tokio::time::timeout(Duration::from_secs(1), writing.notified())
|
|
.await
|
|
.unwrap();
|
|
|
|
let result = tokio::time::timeout(
|
|
DISPATCHER_TUN_WRITE_TIMEOUT + Duration::from_secs(1),
|
|
dispatcher.remove_sources(uuid::Uuid::from_u128(1)),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert!(result.is_err());
|
|
for close_notifier in close_notifiers {
|
|
tokio::time::timeout(Duration::from_secs(1), close_notifier.notified())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
assert!(!valid.load(Ordering::Acquire));
|
|
assert!(member_tunnel_table.control_sender().is_none());
|
|
tokio::time::timeout(Duration::from_secs(1), dispatcher._task)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dispatcher_tun_write_timeout_closes_members_and_source_updates() {
|
|
let (tunnel, writing) = stalled_tun_for_test();
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let dispatcher =
|
|
SharedVirtualNicDispatcher::start(tunnel, member_tunnel_table.clone(), valid.clone());
|
|
|
|
check_dispatcher_tun_write_timeout(dispatcher, member_tunnel_table, valid, writing).await;
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
async fn mobile_dispatcher_tun_write_timeout_closes_members_and_source_updates() {
|
|
let (tunnel, writing) = stalled_tun_for_test();
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let (dispatcher, _updates) =
|
|
start_mobile_dispatcher_for_test(tunnel, member_tunnel_table.clone(), valid.clone());
|
|
|
|
check_dispatcher_tun_write_timeout(dispatcher, member_tunnel_table, valid, writing).await;
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
async fn mobile_dispatcher_invalidates_shared_nic_when_tun_replacement_fails() {
|
|
let (_tun, tunnel) = create_ring_tunnel_pair();
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let (dispatcher, updates) =
|
|
start_mobile_dispatcher_for_test(tunnel, member_tunnel_table.clone(), valid.clone());
|
|
let (_members, close_notifiers) =
|
|
register_dispatcher_test_members(&dispatcher, &member_tunnel_table).await;
|
|
let (ack, receiver) = oneshot::channel();
|
|
|
|
updates
|
|
.send(SharedVirtualNicControl::UpdateMobileTun(
|
|
SharedVirtualNicMobileTunUpdate { tun_fd: -1, ack },
|
|
))
|
|
.unwrap();
|
|
|
|
assert!(
|
|
tokio::time::timeout(Duration::from_secs(1), await_mobile_open(receiver))
|
|
.await
|
|
.unwrap()
|
|
.is_err()
|
|
);
|
|
for close_notifier in close_notifiers {
|
|
tokio::time::timeout(Duration::from_secs(1), close_notifier.notified())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
assert!(!valid.load(Ordering::Acquire));
|
|
assert!(member_tunnel_table.control_sender().is_none());
|
|
assert!(
|
|
dispatcher
|
|
.remove_sources(uuid::Uuid::from_u128(1))
|
|
.await
|
|
.is_err()
|
|
);
|
|
tokio::time::timeout(Duration::from_secs(1), dispatcher._task)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
async fn mobile_dispatcher_invalidates_shared_nic_when_tun_read_closes() {
|
|
for read_error in [false, true] {
|
|
let (tun_tx, tun_rx) = mpsc::unbounded_channel();
|
|
let tun_stream = futures::stream::unfold(tun_rx, |mut receiver| async move {
|
|
receiver.recv().await.map(|packet| (packet, receiver))
|
|
});
|
|
let tun_sink = futures::sink::unfold((), |(), _packet: ZCPacket| async {
|
|
Ok::<(), TunnelError>(())
|
|
});
|
|
let tunnel = Box::new(TunnelWrapper::new(tun_stream, tun_sink, None));
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let (dispatcher, _updates) = start_mobile_dispatcher_for_test(
|
|
tunnel,
|
|
member_tunnel_table.clone(),
|
|
valid.clone(),
|
|
);
|
|
let (_members, close_notifiers) =
|
|
register_dispatcher_test_members(&dispatcher, &member_tunnel_table).await;
|
|
|
|
if read_error {
|
|
tun_tx.send(Err(TunnelError::Shutdown)).unwrap();
|
|
}
|
|
drop(tun_tx);
|
|
|
|
for close_notifier in close_notifiers {
|
|
tokio::time::timeout(Duration::from_secs(1), close_notifier.notified())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
assert!(!valid.load(Ordering::Acquire));
|
|
assert!(member_tunnel_table.control_sender().is_none());
|
|
tokio::time::timeout(Duration::from_secs(1), dispatcher._task)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn source_table_selects_ipv6_source_owner() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
let second_addr = "2001:db8::2".parse::<Ipv6Addr>().unwrap();
|
|
let dst = "2001:db8:ffff::1".parse::<Ipv6Addr>().unwrap();
|
|
let mut table = SharedVirtualNicSourceTable::default();
|
|
let (first_sender, _first_receiver) = member_channel(8);
|
|
let (second_sender, _second_receiver) = member_channel(8);
|
|
let mut members = BTreeMap::new();
|
|
members.insert(first, first_sender);
|
|
members.insert(second, second_sender);
|
|
|
|
table.update_member_sources(first, member_sources_with_ipv6(&["2001:db8::1/64"]));
|
|
table.update_member_sources(second, member_sources_with_ipv6(&["2001:db8::2/64"]));
|
|
|
|
assert_eq!(
|
|
table.owner_of_source(&ipv6_packet(second_addr, dst), &members),
|
|
SourceOwner::Active(second)
|
|
);
|
|
|
|
table.remove_owner(second);
|
|
assert_eq!(
|
|
table.owner_of_source(&ipv6_packet(second_addr, dst), &members),
|
|
SourceOwner::None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn source_table_selects_ipv4_destination_owner_from_route_claim() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
let src = Ipv4Addr::new(100, 64, 0, 1);
|
|
let dst = Ipv4Addr::new(10, 99, 0, 2);
|
|
let mut table = SharedVirtualNicSourceTable::default();
|
|
let (first_sender, _first_receiver) = member_channel(8);
|
|
let (second_sender, _second_receiver) = member_channel(8);
|
|
let mut members = BTreeMap::new();
|
|
members.insert(first, first_sender);
|
|
members.insert(second, second_sender);
|
|
|
|
table.update_member_sources(first, member_sources(&["10.231.1.1/24"]));
|
|
table.update_member_sources(
|
|
second,
|
|
member_sources_with_ipv4_routes(&["10.231.2.1/24"], &["10.99.0.0/24"]),
|
|
);
|
|
|
|
assert_eq!(
|
|
table.owner_of_destination(&ipv4_udp_packet(src, dst), &members, None),
|
|
Some(second)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_prefers_source_owner_for_shared_magic_dns() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let source_owner = uuid::Uuid::from_u128(2);
|
|
let first_ip = Ipv4Addr::new(10, 231, 1, 1);
|
|
let source_owner_ip = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote_ip = Ipv4Addr::new(100, 100, 100, 101);
|
|
let (mut state, [mut first_receiver, mut source_receiver]) = test_state([
|
|
(
|
|
first,
|
|
member_sources_with_ipv4_routes(&["10.231.1.1/24"], &["100.100.100.101/32"]),
|
|
),
|
|
(
|
|
source_owner,
|
|
member_sources_with_ipv4_routes(&["10.231.2.1/24"], &["100.100.100.101/32"]),
|
|
),
|
|
]);
|
|
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(source_owner_ip, remote_ip));
|
|
|
|
assert!(first_receiver.try_recv().is_err());
|
|
let packet = source_receiver.try_recv().unwrap();
|
|
let ipv4 = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
|
assert_eq!(ipv4.src_addr(), source_owner_ip);
|
|
assert_ne!(ipv4.src_addr(), first_ip);
|
|
assert_eq!(ipv4.dst_addr(), remote_ip);
|
|
}
|
|
|
|
#[test]
|
|
fn rewrite_ipv4_source_rejects_fragments_without_changing_packet() {
|
|
let src = Ipv4Addr::new(10, 231, 1, 1);
|
|
let dst = Ipv4Addr::new(10, 231, 2, 2);
|
|
for (more_frags, offset) in [(true, 0), (false, 8)] {
|
|
let mut packet = ipv4_udp_packet(src, dst);
|
|
let mut ipv4 = Ipv4Packet::new_checked(packet.mut_payload()).unwrap();
|
|
ipv4.set_more_frags(more_frags);
|
|
ipv4.set_frag_offset(offset);
|
|
let original = packet.payload().to_vec();
|
|
|
|
assert!(!rewrite_packet_source(
|
|
&mut packet,
|
|
FlowAddr::from(src),
|
|
FlowAddr::from(Ipv4Addr::new(10, 231, 2, 1))
|
|
));
|
|
assert_eq!(packet.payload(), original);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_prefers_source_owner_over_first_member() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let owner = uuid::Uuid::from_u128(2);
|
|
let source = "2001:db8::2".parse::<Ipv6Addr>().unwrap();
|
|
let dst = "2001:db8:ffff::1".parse::<Ipv6Addr>().unwrap();
|
|
let (mut state, [mut first_receiver, mut owner_receiver]) = test_state([
|
|
(first, SharedVirtualNicMemberSources::default()),
|
|
(owner, member_sources_with_ipv6(&["2001:db8::2/64"])),
|
|
]);
|
|
state.forward_tun_packet_to_member(ipv6_packet(source, dst));
|
|
|
|
assert!(first_receiver.try_recv().is_err());
|
|
assert!(owner_receiver.try_recv().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_forwards_external_ipv6_to_route_only_owner() {
|
|
let route_owner = uuid::Uuid::from_u128(1);
|
|
let source = "2001:db8:ffff::2".parse::<Ipv6Addr>().unwrap();
|
|
let destination = "2001:db8:100::2".parse::<Ipv6Addr>().unwrap();
|
|
let (mut state, [mut receiver]) = test_state([(
|
|
route_owner,
|
|
member_sources_with_ipv6_routes(&[], &["2001:db8:100::2/128"]),
|
|
)]);
|
|
// An exhausted local-flow table must not throttle kernel subnet forwarding.
|
|
let local = "2001:db8:100::1".parse::<Ipv6Addr>().unwrap();
|
|
for port in 1..=FLOW_OWNER_LIMIT as u16 {
|
|
let mut packet = transport_packet(local, destination, IpProtocol::Tcp, port, 80);
|
|
assert!(state.nat_table.prepare_packet(
|
|
route_owner,
|
|
Some(route_owner),
|
|
SharedVirtualNicFlowKey::from_packet(&packet).unwrap(),
|
|
&mut packet,
|
|
local.into(),
|
|
));
|
|
}
|
|
for packet in [
|
|
ipv6_packet(source, destination),
|
|
transport_packet(source, destination, IpProtocol::Tcp, 443, 22001),
|
|
] {
|
|
let unchanged = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), unchanged);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_drops_inactive_source_owner_without_fallback() {
|
|
let fallback = uuid::Uuid::from_u128(1);
|
|
let owner = uuid::Uuid::from_u128(2);
|
|
let other = uuid::Uuid::from_u128(3);
|
|
let source = "2001:db8::2".parse::<Ipv6Addr>().unwrap();
|
|
let dst = "2001:db8:ffff::1".parse::<Ipv6Addr>().unwrap();
|
|
let (mut state, [mut fallback_receiver, _owner_receiver, _other_receiver]) = test_state([
|
|
(fallback, SharedVirtualNicMemberSources::default()),
|
|
(owner, member_sources_with_ipv6(&["2001:db8::2/64"])),
|
|
(other, SharedVirtualNicMemberSources::default()),
|
|
]);
|
|
state.unregister(owner, uuid::Uuid::from_u128(1));
|
|
state.forward_tun_packet_to_member(ipv6_packet(source, dst));
|
|
|
|
assert!(fallback_receiver.try_recv().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_drops_unknown_source_and_destination_without_fallback() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let other = uuid::Uuid::from_u128(2);
|
|
let unknown_source = Ipv4Addr::new(100, 64, 0, 1);
|
|
let unknown_destination = Ipv4Addr::new(203, 0, 113, 1);
|
|
let (mut state, [mut receiver, _other_receiver]) = test_state([
|
|
(member_id, member_sources(&["10.231.1.1/24"])),
|
|
(other, SharedVirtualNicMemberSources::default()),
|
|
]);
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(unknown_source, unknown_destination));
|
|
|
|
assert!(receiver.try_recv().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_single_member_forwards_unknown_addresses_unchanged() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
for translate_unknown_sources in [false, true] {
|
|
let (sender, mut receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources,
|
|
..Default::default()
|
|
};
|
|
state.register(member_id, sender);
|
|
for packet in [
|
|
ipv4_udp_packet(Ipv4Addr::new(100, 64, 0, 1), Ipv4Addr::new(203, 0, 113, 1)),
|
|
transport_packet(
|
|
"2001:db8::1".parse::<Ipv6Addr>().unwrap(),
|
|
"2001:db8:ffff::1".parse::<Ipv6Addr>().unwrap(),
|
|
IpProtocol::Udp,
|
|
1234,
|
|
5678,
|
|
),
|
|
] {
|
|
let original = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_single_member_preserves_fragmented_packets() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
for translate_unknown_sources in [false, true] {
|
|
let (sender, mut receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources,
|
|
..Default::default()
|
|
};
|
|
state.register(member_id, sender);
|
|
state.source_table.update_member_sources(
|
|
member_id,
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(
|
|
&["10.231.1.1/24"],
|
|
&["2001:db8:1::1/64"],
|
|
&[],
|
|
&[],
|
|
)),
|
|
);
|
|
for source in [Ipv4Addr::new(10, 231, 1, 1), Ipv4Addr::new(100, 64, 0, 1)] {
|
|
for (more_frags, offset) in [(true, 0), (false, 8)] {
|
|
let mut packet = ipv4_udp_packet(source, Ipv4Addr::new(10, 231, 1, 2));
|
|
let mut ipv4 = Ipv4Packet::new_checked(packet.mut_payload()).unwrap();
|
|
ipv4.set_more_frags(more_frags);
|
|
ipv4.set_frag_offset(offset);
|
|
ipv4.fill_checksum();
|
|
let original = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
}
|
|
for source in ["2001:db8:1::1", "2001:db8:ffff::1"] {
|
|
for fragment_offset in [1u16, 8u16] {
|
|
let packet = transport_packet(
|
|
source.parse::<Ipv6Addr>().unwrap(),
|
|
"2001:db8:1::2".parse::<Ipv6Addr>().unwrap(),
|
|
IpProtocol::Udp,
|
|
1234,
|
|
5678,
|
|
);
|
|
let mut payload = packet.payload()[..IPV6_HEADER_LEN].to_vec();
|
|
payload.extend_from_slice(&[17, 0]); // Fragment's next header is UDP.
|
|
payload.extend_from_slice(&fragment_offset.to_be_bytes());
|
|
payload.extend_from_slice(&1u32.to_be_bytes());
|
|
payload.extend_from_slice(&packet.payload()[IPV6_HEADER_LEN..]);
|
|
let mut ipv6 = Ipv6Packet::new_unchecked(&mut payload);
|
|
ipv6.set_next_header(IpProtocol::from(44)); // Fragment header.
|
|
ipv6.set_payload_len((8 + UDP_HEADER_LEN) as u16);
|
|
assert!(state.send_packet(ZCPacket::new_with_payload(&payload)));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), payload);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_single_member_reserves_flows_before_another_member_joins() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
let native = Ipv4Addr::new(10, 231, 1, 1);
|
|
let source = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 1, 2);
|
|
for incoming_first in [false, true] {
|
|
let (sender, mut receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
state.register(first, sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(first, member_sources(&["10.231.1.1/24"]));
|
|
let original = ipv4_udp_packet(native, remote);
|
|
let original = original.payload().to_vec();
|
|
if incoming_first {
|
|
let packet = transport_packet(remote, native, IpProtocol::Udp, 5678, 1234);
|
|
let unchanged = packet.payload().to_vec();
|
|
assert_eq!(
|
|
state
|
|
.prepare_member_packet_to_tun(first, packet)
|
|
.unwrap()
|
|
.payload(),
|
|
unchanged
|
|
);
|
|
} else {
|
|
assert!(state.send_packet(ZCPacket::new_with_payload(&original)));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
|
|
let (sender, _second_receiver) = member_channel(8);
|
|
state.register(second, sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(second, member_sources(&["10.231.2.1/24"]));
|
|
assert!(state.send_packet(ipv4_udp_packet(source, remote)));
|
|
let translated = receiver.try_recv().unwrap();
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&translated).unwrap();
|
|
assert_eq!(wire.src, native.into());
|
|
// Keep incoming UDP replies on their service port after a member joins.
|
|
// A native outgoing flow still reserves its port against new clients.
|
|
assert_eq!(
|
|
wire.ports.unwrap().src,
|
|
if incoming_first { 1234 } else { 1235 }
|
|
);
|
|
|
|
assert!(state.send_packet(ZCPacket::new_with_payload(&original)));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), original);
|
|
for (port, destination) in [
|
|
(1234, native),
|
|
(
|
|
wire.ports.unwrap().src,
|
|
if incoming_first { native } else { source },
|
|
),
|
|
] {
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
first,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, port),
|
|
)
|
|
.unwrap();
|
|
let key = SharedVirtualNicFlowKey::from_packet(&reply).unwrap();
|
|
assert_eq!(key.dst, destination.into());
|
|
assert_eq!(key.ports.unwrap().dst, 1234);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_single_member_keeps_existing_translations_after_other_member_leaves() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let other = uuid::Uuid::from_u128(2);
|
|
let native = Ipv4Addr::new(10, 231, 1, 1);
|
|
let synthetic = Ipv4Addr::new(100, 64, 0, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 1, 2);
|
|
let (sender, mut receiver) = member_channel(8);
|
|
let (other_sender, _other_receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources: true,
|
|
..Default::default()
|
|
};
|
|
state.register(member_id, sender);
|
|
state.register(other, other_sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(member_id, member_sources(&["10.231.1.1/24"]));
|
|
assert!(state.send_packet(ipv4_udp_packet(synthetic, remote)));
|
|
let translated = receiver.try_recv().unwrap().payload().to_vec();
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(&translated).unwrap().src_addr(),
|
|
native
|
|
);
|
|
|
|
state.unregister(other, uuid::Uuid::from_u128(1));
|
|
assert!(state.send_packet(ipv4_udp_packet(synthetic, remote)));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), translated);
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
member_id,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, 1234),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(reply.payload()).unwrap().dst_addr(),
|
|
synthetic
|
|
);
|
|
|
|
let packet = transport_packet(synthetic, remote, IpProtocol::Udp, 4321, 5678);
|
|
let original = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
|
|
#[test]
|
|
fn rewrite_ipv4_flow_preserves_zero_udp_checksum() {
|
|
let source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let translated_source = Ipv4Addr::new(10, 231, 2, 1);
|
|
let destination = Ipv4Addr::new(10, 231, 2, 2);
|
|
let request = ipv4_udp_packet(source, destination);
|
|
let reply = transport_packet(destination, translated_source, IpProtocol::Udp, 5678, 4321);
|
|
let original = SharedVirtualNicFlowKey::from_packet(&request).unwrap();
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&reply)
|
|
.unwrap()
|
|
.reversed();
|
|
for (mut packet, from, to, direction) in [
|
|
(request, original, wire, RewriteIpAddr::Source),
|
|
(
|
|
reply,
|
|
wire.reversed(),
|
|
original.reversed(),
|
|
RewriteIpAddr::Destination,
|
|
),
|
|
] {
|
|
UdpPacket::new_checked(&mut packet.mut_payload()[IPV4_HEADER_MIN_LEN..])
|
|
.unwrap()
|
|
.set_checksum(0);
|
|
assert!(rewrite_packet_flow(&mut packet, from, to, direction));
|
|
assert_eq!(SharedVirtualNicFlowKey::from_packet(&packet), Some(to));
|
|
let ipv4 = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
|
assert!(ipv4.verify_checksum());
|
|
assert_eq!(
|
|
UdpPacket::new_checked(ipv4.payload()).unwrap().checksum(),
|
|
0
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn rewrite_tcp_flow_updates_checksums_and_restores_ports() {
|
|
for ipv6 in [false, true] {
|
|
let [source, _, native, remote] = test_addresses(ipv6);
|
|
let request = transport_packet(source, remote, IpProtocol::Tcp, 1234, 5678);
|
|
let reply = transport_packet(remote, native, IpProtocol::Tcp, 5678, 4321);
|
|
let original = SharedVirtualNicFlowKey::from_packet(&request).unwrap();
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&reply)
|
|
.unwrap()
|
|
.reversed();
|
|
for (mut packet, from, to, direction) in [
|
|
(request, original, wire, RewriteIpAddr::Source),
|
|
(
|
|
reply,
|
|
wire.reversed(),
|
|
original.reversed(),
|
|
RewriteIpAddr::Destination,
|
|
),
|
|
] {
|
|
assert!(rewrite_packet_flow(&mut packet, from, to, direction));
|
|
assert_eq!(SharedVirtualNicFlowKey::from_packet(&packet), Some(to));
|
|
assert_checksums(&packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_translates_wrong_local_ipv6_source_to_destination_member() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
let source_owner_ip = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
|
|
let destination_owner_ip = "2001:db8:2::1".parse::<Ipv6Addr>().unwrap();
|
|
let remote_ip = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
|
|
let (mut state, [mut source_receiver, mut destination_receiver]) = test_state([
|
|
(
|
|
source_owner,
|
|
member_sources_with_ipv6(&["2001:db8:1::1/64"]),
|
|
),
|
|
(
|
|
destination_owner,
|
|
member_sources_with_ipv6_routes(&["2001:db8:2::1/64"], &["2001:db8:99::/64"]),
|
|
),
|
|
]);
|
|
|
|
// Reserve the native tuple so the translated request must also change its port.
|
|
assert!(state.send_packet(transport_packet(
|
|
destination_owner_ip,
|
|
remote_ip,
|
|
IpProtocol::Udp,
|
|
1234,
|
|
5678
|
|
)));
|
|
destination_receiver.try_recv().unwrap();
|
|
|
|
state.forward_tun_packet_to_member(transport_packet(
|
|
source_owner_ip,
|
|
remote_ip,
|
|
IpProtocol::Udp,
|
|
1234,
|
|
5678,
|
|
));
|
|
|
|
assert!(source_receiver.try_recv().is_err());
|
|
let translated = destination_receiver.try_recv().unwrap();
|
|
let translated_ipv6 = Ipv6Packet::new_checked(translated.payload()).unwrap();
|
|
assert_eq!(translated_ipv6.src_addr(), destination_owner_ip);
|
|
assert_eq!(translated_ipv6.dst_addr(), remote_ip);
|
|
let translated_udp = UdpPacket::new_checked(translated_ipv6.payload()).unwrap();
|
|
assert_eq!(translated_udp.src_port(), 1235);
|
|
assert_ne!(translated_udp.checksum(), 0);
|
|
assert!(translated_udp.verify_checksum(
|
|
&IpAddress::Ipv6(destination_owner_ip),
|
|
&IpAddress::Ipv6(remote_ip)
|
|
));
|
|
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
transport_packet(remote_ip, destination_owner_ip, IpProtocol::Udp, 5678, 1235),
|
|
)
|
|
.unwrap();
|
|
let reply_ipv6 = Ipv6Packet::new_checked(reply.payload()).unwrap();
|
|
assert_eq!(reply_ipv6.src_addr(), remote_ip);
|
|
assert_eq!(reply_ipv6.dst_addr(), source_owner_ip);
|
|
let reply_udp = UdpPacket::new_checked(reply_ipv6.payload()).unwrap();
|
|
assert_eq!(reply_udp.dst_port(), 1234);
|
|
assert_ne!(reply_udp.checksum(), 0);
|
|
assert!(reply_udp.verify_checksum(
|
|
&IpAddress::Ipv6(remote_ip),
|
|
&IpAddress::Ipv6(source_owner_ip)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn icmp_nat_replies_keep_distinct_source_networks() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
for ipv6 in [false, true] {
|
|
let [first_source, second_source, native, remote] = test_addresses(ipv6);
|
|
let mut nat = SharedVirtualNicNatTable::default();
|
|
for (identifier, source, wire_identifier, sequence) in [
|
|
(100, first_source, 100, 1),
|
|
(200, second_source, 200, 1),
|
|
(100, second_source, 101, 1),
|
|
(100, second_source, 101, 2),
|
|
(100, second_source, 101, u16::MAX),
|
|
(100, second_source, 101, 0),
|
|
] {
|
|
let mut packet = echo_packet(source, remote, false, identifier, sequence);
|
|
let original = SharedVirtualNicFlowKey::from_packet(&packet).unwrap();
|
|
assert!(nat.prepare_packet(member_id, None, original, &mut packet, native));
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&packet).unwrap();
|
|
assert_eq!(wire.src, native);
|
|
assert_eq!(wire.ports.unwrap().src, wire_identifier);
|
|
assert_checksums(&packet);
|
|
let mut reply = echo_packet(remote, native, true, wire_identifier, sequence);
|
|
assert!(nat.translate_reply(member_id, &mut reply));
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&reply),
|
|
Some(original.reversed())
|
|
);
|
|
// Sequence numbers are payload, not part of the flow key.
|
|
let header = if ipv6 {
|
|
IPV6_HEADER_LEN
|
|
} else {
|
|
IPV4_HEADER_MIN_LEN
|
|
};
|
|
assert_eq!(
|
|
&packet.payload()[header + 6..header + 8],
|
|
&sequence.to_be_bytes()
|
|
);
|
|
assert_eq!(
|
|
&reply.payload()[header + 6..header + 8],
|
|
&sequence.to_be_bytes()
|
|
);
|
|
assert_checksums(&reply);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn rewrite_ipv6_source_rejects_extension_headers_without_changing_packet() {
|
|
let source = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
|
|
let destination = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
|
|
let mut packet = ipv6_packet(source, destination);
|
|
packet.mut_payload()[6] = 0; // Hop-by-hop options header.
|
|
let original = packet.payload().to_vec();
|
|
|
|
assert!(!rewrite_packet_source(
|
|
&mut packet,
|
|
FlowAddr::from(source),
|
|
FlowAddr::from("2001:db8:2::1".parse::<Ipv6Addr>().unwrap()),
|
|
));
|
|
assert_eq!(packet.payload(), original);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_changed_members_clear_nat_translation() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
let source_owner_ip = Ipv4Addr::new(10, 231, 1, 1);
|
|
let destination_owner_ip = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote_ip = Ipv4Addr::new(10, 231, 2, 2);
|
|
for (member_id, unregister) in [
|
|
(source_owner, true),
|
|
(destination_owner, true),
|
|
(source_owner, false),
|
|
(destination_owner, false),
|
|
] {
|
|
let (mut state, [_source_receiver, mut destination_receiver]) = test_state([
|
|
(source_owner, member_sources(&["10.231.1.1/24"])),
|
|
(destination_owner, member_sources(&["10.231.2.1/24"])),
|
|
]);
|
|
assert!(state.send_packet(ipv4_udp_packet(source_owner_ip, remote_ip)));
|
|
destination_receiver.try_recv().unwrap();
|
|
|
|
if unregister {
|
|
state.unregister(member_id, uuid::Uuid::from_u128(1));
|
|
} else {
|
|
let (ack, _rx) = oneshot::channel();
|
|
state.handle_control(SharedVirtualNicControl::UpdateSources {
|
|
member_id,
|
|
sources: member_sources(&["10.231.3.1/24"]),
|
|
ack,
|
|
});
|
|
}
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
transport_packet(remote_ip, destination_owner_ip, IpProtocol::Udp, 5678, 1234),
|
|
)
|
|
.unwrap();
|
|
let reply_ipv4 = Ipv4Packet::new_checked(reply.payload()).unwrap();
|
|
assert_eq!(reply_ipv4.src_addr(), remote_ip);
|
|
assert_eq!(reply_ipv4.dst_addr(), destination_owner_ip);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_preserves_external_ipv4_source_to_route_owner() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let route_owner = uuid::Uuid::from_u128(2);
|
|
let synthetic_source = Ipv4Addr::new(100, 64, 0, 1);
|
|
let remote_ip = Ipv4Addr::new(10, 99, 0, 2);
|
|
let (mut state, [mut first_receiver, mut route_receiver]) = test_state([
|
|
(first, member_sources(&["10.231.1.1/24"])),
|
|
(
|
|
route_owner,
|
|
member_sources_with_ipv4_routes(&["10.231.2.1/24"], &["10.99.0.0/24"]),
|
|
),
|
|
]);
|
|
|
|
// An exhausted local-flow table must not throttle kernel subnet forwarding.
|
|
let local = Ipv4Addr::new(10, 231, 2, 1);
|
|
for port in 1..=FLOW_OWNER_LIMIT as u16 {
|
|
assert!(state.send_packet(transport_packet(
|
|
local,
|
|
remote_ip,
|
|
IpProtocol::Tcp,
|
|
port,
|
|
80
|
|
)));
|
|
route_receiver.try_recv().unwrap();
|
|
}
|
|
for packet in [
|
|
ipv4_udp_packet(synthetic_source, remote_ip),
|
|
transport_packet(synthetic_source, remote_ip, IpProtocol::Tcp, 443, 22001),
|
|
] {
|
|
let unchanged = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(route_receiver.try_recv().unwrap().payload(), unchanged);
|
|
}
|
|
let incoming = transport_packet(remote_ip, synthetic_source, IpProtocol::Udp, 5678, 1234);
|
|
let unchanged = incoming.payload().to_vec();
|
|
assert_eq!(
|
|
state
|
|
.prepare_member_packet_to_tun(route_owner, incoming)
|
|
.unwrap()
|
|
.payload(),
|
|
unchanged
|
|
);
|
|
assert!(first_receiver.try_recv().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_translates_android_synthetic_icmp_source_to_destination_member() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
let synthetic_source = Ipv4Addr::new(100, 64, 0, 1);
|
|
let destination_owner_ip = Ipv4Addr::new(10, 231, 1, 1);
|
|
let remote_ip = Ipv4Addr::new(10, 231, 1, 2);
|
|
let (first_sender, mut first_receiver) = member_channel(8);
|
|
let (destination_sender, mut destination_receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState {
|
|
translate_unknown_sources: true,
|
|
..Default::default()
|
|
};
|
|
|
|
state.register(first, first_sender);
|
|
state.register(destination_owner, destination_sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(first, member_sources(&["10.231.2.1/24"]));
|
|
state
|
|
.source_table
|
|
.update_member_sources(destination_owner, member_sources(&["10.231.1.1/24"]));
|
|
|
|
state.forward_tun_packet_to_member(ipv4_icmp_echo_packet(synthetic_source, remote_ip));
|
|
|
|
assert!(first_receiver.try_recv().is_err());
|
|
let translated = destination_receiver.try_recv().unwrap();
|
|
let translated_ipv4 = Ipv4Packet::new_checked(translated.payload()).unwrap();
|
|
assert_eq!(translated_ipv4.src_addr(), destination_owner_ip);
|
|
assert_eq!(translated_ipv4.dst_addr(), remote_ip);
|
|
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
ipv4_icmp_echo_packet(remote_ip, destination_owner_ip),
|
|
)
|
|
.unwrap();
|
|
let reply_ipv4 = Ipv4Packet::new_checked(reply.payload()).unwrap();
|
|
assert_eq!(reply_ipv4.src_addr(), remote_ip);
|
|
assert_eq!(reply_ipv4.dst_addr(), synthetic_source);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_icmp_sequences_preserve_other_members_flow_capacity() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
for ipv6 in [false, true] {
|
|
let (mut state, [mut first_receiver, mut second_receiver]) = test_state([
|
|
(
|
|
first,
|
|
SharedVirtualNicMemberSources::from_claims(&member_claims(
|
|
&["10.231.1.1/24"],
|
|
&["2001:db8:1::1/64"],
|
|
&[],
|
|
&[],
|
|
)),
|
|
),
|
|
(second, member_sources(&["10.231.2.1/24"])),
|
|
]);
|
|
|
|
for sequence in 0..=FLOW_OWNER_LIMIT as u16 {
|
|
let packet = if ipv6 {
|
|
echo_packet(
|
|
"2001:db8:1::1".parse::<Ipv6Addr>().unwrap(),
|
|
"2001:db8:1::2".parse::<Ipv6Addr>().unwrap(),
|
|
false,
|
|
100,
|
|
sequence,
|
|
)
|
|
} else {
|
|
echo_packet(
|
|
Ipv4Addr::new(10, 231, 1, 1),
|
|
Ipv4Addr::new(10, 231, 1, 2),
|
|
false,
|
|
100,
|
|
sequence,
|
|
)
|
|
};
|
|
let original = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(first_receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
|
|
let packet =
|
|
ipv4_udp_packet(Ipv4Addr::new(10, 231, 2, 1), Ipv4Addr::new(10, 231, 2, 2));
|
|
let original = packet.payload().to_vec();
|
|
assert!(state.send_packet(packet));
|
|
assert_eq!(second_receiver.try_recv().unwrap().payload(), original);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_preserves_owned_ipv4_source_with_unspecified_placeholder() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let local_ip = Ipv4Addr::new(10, 231, 1, 1);
|
|
let remote_ip = Ipv4Addr::new(10, 231, 1, 2);
|
|
let (mut state, [mut receiver]) =
|
|
test_state([(member_id, member_sources(&["0.0.0.0/0", "10.231.1.1/24"]))]);
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(local_ip, remote_ip));
|
|
|
|
let packet = receiver.try_recv().unwrap();
|
|
let ipv4 = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
|
assert_eq!(ipv4.src_addr(), local_ip);
|
|
assert_eq!(ipv4.dst_addr(), remote_ip);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_ignores_stale_member_unregister() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let stale_registration = uuid::Uuid::from_u128(10);
|
|
let current_registration = uuid::Uuid::from_u128(11);
|
|
let src = "2001:db8::1".parse::<Ipv6Addr>().unwrap();
|
|
let dst = "2001:db8:ffff::1".parse::<Ipv6Addr>().unwrap();
|
|
let (sender, mut receiver) = member_channel(8);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
|
|
state.register(
|
|
member_id,
|
|
SharedVirtualNicMemberTunnelEntry {
|
|
registration_id: current_registration,
|
|
..sender
|
|
},
|
|
);
|
|
state
|
|
.source_table
|
|
.update_member_sources(member_id, member_sources_with_ipv6(&["2001:db8::1/64"]));
|
|
state.unregister(member_id, stale_registration);
|
|
state.forward_tun_packet_to_member(ipv6_packet(src, dst));
|
|
|
|
assert!(receiver.try_recv().is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dispatcher_invalidates_shared_nic_when_tun_read_fails() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let (tun_tx, tun_rx) = mpsc::unbounded_channel();
|
|
let tun_stream = futures::stream::unfold(tun_rx, |mut receiver| async move {
|
|
receiver.recv().await.map(|packet| (packet, receiver))
|
|
});
|
|
let tun_sink = futures::sink::unfold((), |(), _packet: ZCPacket| async {
|
|
Ok::<(), TunnelError>(())
|
|
});
|
|
let tunnel = TunnelWrapper::new(tun_stream, tun_sink, None);
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let dispatcher = SharedVirtualNicDispatcher::start(
|
|
Box::new(tunnel),
|
|
member_tunnel_table.clone(),
|
|
valid.clone(),
|
|
);
|
|
let close_notifier = Arc::new(Notify::new());
|
|
let _member_tunnel = member_tunnel_table
|
|
.register(member_id, uuid::Uuid::from_u128(1), close_notifier.clone())
|
|
.unwrap();
|
|
let empty_claims = SharedIfConfigClaims::default();
|
|
dispatcher
|
|
.update_sources(member_id, &empty_claims)
|
|
.await
|
|
.unwrap();
|
|
|
|
tun_tx.send(Err(TunnelError::Shutdown)).unwrap();
|
|
|
|
tokio::time::timeout(Duration::from_secs(1), close_notifier.notified())
|
|
.await
|
|
.unwrap();
|
|
assert!(!valid.load(Ordering::Acquire));
|
|
assert!(member_tunnel_table.control_sender().is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dispatcher_shutdown_for_replacement_keeps_shared_nic_valid() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let (_tun_tx, tun_rx) = mpsc::unbounded_channel();
|
|
let tun_stream = futures::stream::unfold(tun_rx, |mut receiver| async move {
|
|
receiver.recv().await.map(|packet| (packet, receiver))
|
|
});
|
|
let tun_sink = futures::sink::unfold((), |(), _packet: ZCPacket| async {
|
|
Ok::<(), TunnelError>(())
|
|
});
|
|
let tunnel = TunnelWrapper::new(tun_stream, tun_sink, None);
|
|
let member_tunnel_table = SharedVirtualNicMemberTunnelTable::default();
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let dispatcher = SharedVirtualNicDispatcher::start(
|
|
Box::new(tunnel),
|
|
member_tunnel_table.clone(),
|
|
valid.clone(),
|
|
);
|
|
let close_notifier = Arc::new(Notify::new());
|
|
let _member_tunnel = member_tunnel_table
|
|
.register(member_id, uuid::Uuid::from_u128(1), close_notifier.clone())
|
|
.unwrap();
|
|
let empty_claims = SharedIfConfigClaims::default();
|
|
dispatcher
|
|
.update_sources(member_id, &empty_claims)
|
|
.await
|
|
.unwrap();
|
|
|
|
dispatcher.shutdown_without_invalidation().await;
|
|
|
|
tokio::time::timeout(Duration::from_secs(1), close_notifier.notified())
|
|
.await
|
|
.unwrap();
|
|
assert!(valid.load(Ordering::Acquire));
|
|
assert!(member_tunnel_table.control_sender().is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cancelled_member_updates_finish_before_registration_cleanup() {
|
|
use super::super::{SharedVirtualNic, SharedVirtualNicMember};
|
|
use crate::{
|
|
common::{ifcfg::IfConfiguerTrait, netns::NetNS},
|
|
instance::virtual_nic::VirtualNicConfig,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
struct PausingIfConfiger {
|
|
claims: Arc<StdMutex<SharedIfConfigClaims>>,
|
|
pause: Arc<AtomicBool>,
|
|
applied: Arc<Notify>,
|
|
resume: Arc<Notify>,
|
|
}
|
|
|
|
impl PausingIfConfiger {
|
|
async fn after_apply(&self) {
|
|
if self.pause.swap(false, Ordering::AcqRel) {
|
|
self.applied.notify_one();
|
|
self.resume.notified().await;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl IfConfiguerTrait for PausingIfConfiger {
|
|
async fn add_ipv4_ip(
|
|
&self,
|
|
_name: &str,
|
|
address: Ipv4Addr,
|
|
prefix: u8,
|
|
) -> Result<(), Error> {
|
|
self.claims
|
|
.lock()
|
|
.unwrap()
|
|
.ipv4_addresses
|
|
.insert(Ipv4Inet::new(address, prefix).unwrap());
|
|
self.after_apply().await;
|
|
Ok(())
|
|
}
|
|
|
|
async fn remove_ip(&self, _name: &str, ip: Option<Ipv4Inet>) -> Result<(), Error> {
|
|
self.claims
|
|
.lock()
|
|
.unwrap()
|
|
.ipv4_addresses
|
|
.remove(&ip.unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
async fn add_ipv4_route(
|
|
&self,
|
|
_name: &str,
|
|
address: Ipv4Addr,
|
|
prefix: u8,
|
|
cost: Option<i32>,
|
|
) -> Result<(), Error> {
|
|
self.claims
|
|
.lock()
|
|
.unwrap()
|
|
.ipv4_routes
|
|
.insert(SharedIpv4Route::new(address, prefix, cost));
|
|
self.after_apply().await;
|
|
Ok(())
|
|
}
|
|
|
|
async fn remove_ipv4_route(
|
|
&self,
|
|
_name: &str,
|
|
address: Ipv4Addr,
|
|
prefix: u8,
|
|
) -> Result<(), Error> {
|
|
self.claims
|
|
.lock()
|
|
.unwrap()
|
|
.ipv4_routes
|
|
.retain(|route| route.address != address || route.prefix != prefix);
|
|
Ok(())
|
|
}
|
|
|
|
async fn set_mtu(&self, _name: &str, mtu: u32) -> Result<(), Error> {
|
|
self.claims.lock().unwrap().mtu = Some(mtu);
|
|
self.after_apply().await;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
for operation in ["address", "route", "mtu"] {
|
|
for pause_in_ifcfg in [false, true] {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let other_member_id = uuid::Uuid::from_u128(2);
|
|
let applied = Arc::new(Notify::new());
|
|
let resume = Arc::new(Notify::new());
|
|
let initial_claims = SharedIfConfigClaims {
|
|
mtu: Some(1500),
|
|
..Default::default()
|
|
};
|
|
let ifcfg = PausingIfConfiger {
|
|
claims: Arc::new(StdMutex::new(initial_claims.clone())),
|
|
pause: Arc::new(AtomicBool::new(pause_in_ifcfg)),
|
|
applied: applied.clone(),
|
|
resume: resume.clone(),
|
|
};
|
|
let mut shared_nic = SharedVirtualNic::new(VirtualNicConfig::new(
|
|
String::new(),
|
|
1500,
|
|
NetNS::new(None),
|
|
));
|
|
{
|
|
let mut nic = shared_nic.nic.lock().await;
|
|
nic.set_ifname_for_test("et0".to_owned());
|
|
nic.set_ifcfg_for_test(Box::new(ifcfg.clone()));
|
|
}
|
|
// A surviving member keeps the device alive after the caller exits.
|
|
shared_nic
|
|
.member_registrations
|
|
.insert(other_member_id, uuid::Uuid::new_v4());
|
|
shared_nic
|
|
.ifcfg
|
|
.apply_member_claims(other_member_id, initial_claims.clone());
|
|
let (control_sender, mut controls) = mpsc::unbounded_channel();
|
|
let ack_applied = applied.clone();
|
|
let ack_resume = resume.clone();
|
|
let task = SHARED_VIRTUAL_NIC_RUNTIME.spawn(async move {
|
|
let mut updates = 0;
|
|
while let Some(control) = controls.recv().await {
|
|
match control {
|
|
SharedVirtualNicControl::UpdateSources { ack, .. } => {
|
|
updates += 1;
|
|
if updates == 2 && !pause_in_ifcfg {
|
|
ack_applied.notify_one();
|
|
ack_resume.notified().await;
|
|
}
|
|
let _ = ack.send(());
|
|
}
|
|
SharedVirtualNicControl::Shutdown { ack, .. } => {
|
|
let _ = ack.send(());
|
|
break;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
});
|
|
shared_nic.dispatcher = Some(SharedVirtualNicDispatcher {
|
|
_task: AbortOnDropHandle::new(task),
|
|
control_sender,
|
|
});
|
|
let table = shared_nic.member_tunnel_table.clone();
|
|
let shared_nic = Arc::new(tokio::sync::Mutex::new(shared_nic));
|
|
let member = SharedVirtualNicMember::new(
|
|
member_id,
|
|
1300,
|
|
shared_nic.clone(),
|
|
Arc::new(Notify::new()),
|
|
table,
|
|
);
|
|
shared_nic
|
|
.lock()
|
|
.await
|
|
.member_registrations
|
|
.insert(member_id, member.registration.registration_id);
|
|
|
|
let caller = tokio::spawn(async move {
|
|
match operation {
|
|
"address" => member.add_ip(Ipv4Addr::new(10, 231, 1, 1), 24).await,
|
|
"route" => member.add_route(Ipv4Addr::new(10, 99, 0, 0), 24).await,
|
|
"mtu" => member.create_dev().await.map(|_| ()),
|
|
_ => unreachable!(),
|
|
}
|
|
});
|
|
tokio::time::timeout(Duration::from_secs(5), applied.notified())
|
|
.await
|
|
.unwrap();
|
|
caller.abort();
|
|
assert!(caller.await.unwrap_err().is_cancelled());
|
|
resume.notify_one();
|
|
|
|
tokio::time::timeout(Duration::from_secs(5), async {
|
|
while shared_nic
|
|
.lock()
|
|
.await
|
|
.member_registrations
|
|
.contains_key(&member_id)
|
|
{
|
|
tokio::task::yield_now().await;
|
|
}
|
|
})
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
*ifcfg.claims.lock().unwrap(),
|
|
initial_claims,
|
|
"operation={operation}, pause_in_ifcfg={pause_in_ifcfg}"
|
|
);
|
|
let mut shared_nic = shared_nic.lock().await;
|
|
assert!(shared_nic.is_valid());
|
|
assert!(!shared_nic.ifcfg.member_claims.contains_key(&member_id));
|
|
shared_nic.shutdown_dispatcher().await;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_preserves_incoming_udp_reply_ports_with_wrong_source() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
for ipv6 in [false, true] {
|
|
let [source, _, native, remote] = test_addresses(ipv6);
|
|
let source_sources = address_sources(source);
|
|
let native_sources = address_sources(native);
|
|
let incoming = transport_packet(remote, native, IpProtocol::Udp, 5678, 1234);
|
|
let outgoing = transport_packet(source, remote, IpProtocol::Udp, 1234, 5678);
|
|
let (mut state, [mut source_receiver, mut receiver]) = test_state([
|
|
(source_owner, source_sources),
|
|
(destination_owner, native_sources),
|
|
]);
|
|
|
|
let incoming_payload = incoming.payload().to_vec();
|
|
let expected_reply = SharedVirtualNicFlowKey::from_packet(&incoming)
|
|
.unwrap()
|
|
.reversed();
|
|
for _ in 0..3 {
|
|
// A wildcard UDP socket receives on B but replies with A's source IP.
|
|
let received = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
ZCPacket::new_with_payload(&incoming_payload),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(received.payload(), incoming_payload);
|
|
assert!(state.send_packet(ZCPacket::new_with_payload(outgoing.payload())));
|
|
let reply = receiver.try_recv().unwrap();
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&reply),
|
|
Some(expected_reply)
|
|
);
|
|
assert_checksums(&reply);
|
|
assert!(source_receiver.try_recv().is_err());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_reserves_native_incoming_tcp_flows_before_translation() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
for ipv6 in [false, true] {
|
|
let [source, _, native, remote] = test_addresses(ipv6);
|
|
let source_sources = address_sources(source);
|
|
let native_sources = address_sources(native);
|
|
let incoming = transport_packet(remote, native, IpProtocol::Tcp, 5678, 1234);
|
|
let outgoing = transport_packet(source, remote, IpProtocol::Tcp, 1234, 5678);
|
|
let (mut state, [_source_receiver, mut receiver]) = test_state([
|
|
(source_owner, source_sources),
|
|
(destination_owner, native_sources),
|
|
]);
|
|
|
|
let incoming_payload = incoming.payload().to_vec();
|
|
let native_reply = state
|
|
.prepare_member_packet_to_tun(destination_owner, incoming)
|
|
.unwrap();
|
|
assert_eq!(native_reply.payload(), incoming_payload);
|
|
|
|
// The native socket has not replied when another member starts a flow.
|
|
let original = SharedVirtualNicFlowKey::from_packet(&outgoing).unwrap();
|
|
assert!(state.send_packet(outgoing));
|
|
let translated = receiver.try_recv().unwrap();
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&translated).unwrap();
|
|
assert_ne!(wire.ports.unwrap().src, original.ports.unwrap().src);
|
|
|
|
let native_reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
ZCPacket::new_with_payload(&incoming_payload),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(native_reply.payload(), incoming_payload);
|
|
|
|
assert_checksums(&translated);
|
|
let translated_reply = transport_packet(
|
|
wire.dst,
|
|
wire.src,
|
|
IpProtocol::Tcp,
|
|
5678,
|
|
wire.ports.unwrap().src,
|
|
);
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(destination_owner, translated_reply)
|
|
.unwrap();
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&reply),
|
|
Some(original.reversed())
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nat_existing_outgoing_udp_mapping_precedes_incoming_reservation() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let native = Ipv4Addr::new(10, 231, 2, 1);
|
|
let other_native = Ipv4Addr::new(10, 231, 2, 3);
|
|
let remote = Ipv4Addr::new(10, 231, 2, 2);
|
|
let mut nat = SharedVirtualNicNatTable::default();
|
|
let mut outgoing = ipv4_udp_packet(source, remote);
|
|
let original = SharedVirtualNicFlowKey::from_packet(&outgoing).unwrap();
|
|
assert!(nat.prepare_packet(member_id, None, original, &mut outgoing, native.into()));
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&outgoing).unwrap();
|
|
|
|
let incoming = transport_packet(remote, other_native, IpProtocol::Udp, 5678, 1234);
|
|
assert!(nat.reserve_reply(
|
|
member_id,
|
|
SharedVirtualNicFlowKey::from_packet(&incoming).unwrap()
|
|
));
|
|
let mut outgoing = ipv4_udp_packet(source, remote);
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
original,
|
|
&mut outgoing,
|
|
other_native.into()
|
|
));
|
|
assert_eq!(SharedVirtualNicFlowKey::from_packet(&outgoing), Some(wire));
|
|
let mut reply = transport_packet(remote, native, IpProtocol::Udp, 5678, 1234);
|
|
assert!(nat.translate_reply(member_id, &mut reply));
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&reply),
|
|
Some(original.reversed())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_incoming_flow_pressure_keeps_active_reservations() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let native = Ipv4Addr::new(10, 231, 1, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 1, 2);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
state
|
|
.source_table
|
|
.update_member_sources(member_id, member_sources(&["10.231.1.1/24"]));
|
|
|
|
for port in 1..=FLOW_OWNER_LIMIT as u16 {
|
|
assert!(
|
|
state
|
|
.prepare_member_packet_to_tun(
|
|
member_id,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, port),
|
|
)
|
|
.is_some()
|
|
);
|
|
}
|
|
let new_port = FLOW_OWNER_LIMIT as u16 + 1;
|
|
assert!(
|
|
state
|
|
.prepare_member_packet_to_tun(
|
|
member_id,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, new_port),
|
|
)
|
|
.is_none()
|
|
);
|
|
assert_eq!(state.nat_table.entries.len(), FLOW_OWNER_LIMIT);
|
|
|
|
for entry in state.nat_table.entries.values_mut() {
|
|
entry.last_active = Instant::now() - FLOW_IDLE_TIMEOUT - Duration::from_secs(1);
|
|
}
|
|
// Refresh a receive-only socket before pressure reclaims the idle entries.
|
|
for port in [1, new_port] {
|
|
let incoming = transport_packet(remote, native, IpProtocol::Udp, 5678, port);
|
|
let unchanged = incoming.payload().to_vec();
|
|
assert_eq!(
|
|
state
|
|
.prepare_member_packet_to_tun(member_id, incoming)
|
|
.unwrap()
|
|
.payload(),
|
|
unchanged
|
|
);
|
|
}
|
|
assert_eq!(state.nat_table.entries.len(), 2);
|
|
let active = SharedVirtualNicFlowKey::from_packet(&transport_packet(
|
|
native,
|
|
remote,
|
|
IpProtocol::Udp,
|
|
1,
|
|
5678,
|
|
))
|
|
.unwrap();
|
|
assert!(state.nat_table.entries.contains_key(&(member_id, active)));
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_colliding_native_and_translated_flows_remain_distinct() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
let source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let native = Ipv4Addr::new(10, 231, 2, 1);
|
|
let destination = Ipv4Addr::new(10, 231, 2, 2);
|
|
for native_first in [false, true] {
|
|
let (mut state, [mut source_receiver, mut receiver]) = test_state([
|
|
(source_owner, member_sources(&["10.231.1.1/24"])),
|
|
(destination_owner, member_sources(&["10.231.2.1/24"])),
|
|
]);
|
|
let mut wire_keys = Vec::new();
|
|
for src in if native_first {
|
|
[native, source]
|
|
} else {
|
|
[source, native]
|
|
} {
|
|
let packet = ipv4_udp_packet(src, destination);
|
|
let original = SharedVirtualNicFlowKey::from_packet(&packet).unwrap();
|
|
assert!(state.send_packet(packet));
|
|
let sent = receiver.try_recv().unwrap();
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&sent).unwrap();
|
|
assert_eq!(wire.src, native.into());
|
|
assert_eq!(wire.dst, destination.into());
|
|
let ipv4 = Ipv4Packet::new_checked(sent.payload()).unwrap();
|
|
assert!(ipv4.verify_checksum());
|
|
let udp = UdpPacket::new_checked(ipv4.payload()).unwrap();
|
|
assert_ne!(udp.checksum(), 0);
|
|
assert!(
|
|
udp.verify_checksum(&IpAddress::Ipv4(native), &IpAddress::Ipv4(destination))
|
|
);
|
|
wire_keys.push((original, wire));
|
|
}
|
|
assert!(source_receiver.try_recv().is_err());
|
|
assert_ne!(wire_keys[0].1, wire_keys[1].1);
|
|
for (original, wire) in wire_keys {
|
|
let source = original.src.as_ipv4().unwrap();
|
|
assert!(state.send_packet(ipv4_udp_packet(source, destination)));
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&receiver.try_recv().unwrap()),
|
|
Some(wire)
|
|
);
|
|
for (member_id, expected) in [
|
|
(source_owner, wire.reversed()),
|
|
(destination_owner, original.reversed()),
|
|
] {
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
member_id,
|
|
transport_packet(
|
|
destination,
|
|
native,
|
|
IpProtocol::Udp,
|
|
5678,
|
|
wire.ports.unwrap().src,
|
|
),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(SharedVirtualNicFlowKey::from_packet(&reply), Some(expected));
|
|
let ipv4 = Ipv4Packet::new_checked(reply.payload()).unwrap();
|
|
assert!(ipv4.verify_checksum());
|
|
let udp = UdpPacket::new_checked(ipv4.payload()).unwrap();
|
|
assert_ne!(udp.checksum(), 0);
|
|
assert!(udp.verify_checksum(
|
|
&IpAddress::Ipv4(destination),
|
|
&IpAddress::Ipv4(expected.dst.as_ipv4().unwrap())
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_unrelated_claims_and_unregister_preserve_replies() {
|
|
let source_owner = uuid::Uuid::from_u128(1);
|
|
let destination_owner = uuid::Uuid::from_u128(2);
|
|
let unrelated = uuid::Uuid::from_u128(3);
|
|
let source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let native = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 2, 2);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
let (sender, _source_receiver) = member_channel(8);
|
|
state.register(source_owner, sender);
|
|
let (sender, mut receiver) = member_channel(8);
|
|
state.register(destination_owner, sender);
|
|
let (sender, _unrelated_receiver) = member_channel(8);
|
|
state.register(unrelated, sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(source_owner, member_sources(&["10.231.1.1/24"]));
|
|
state
|
|
.source_table
|
|
.update_member_sources(destination_owner, member_sources(&["10.231.2.1/24"]));
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(source, remote));
|
|
receiver.try_recv().unwrap();
|
|
|
|
for (member_id, sources) in [
|
|
(
|
|
unrelated,
|
|
member_sources_with_ipv4_routes(&["10.231.3.1/24"], &["192.0.2.0/24"]),
|
|
),
|
|
(
|
|
source_owner,
|
|
member_sources_with_ipv4_routes(&["10.231.1.1/24"], &["198.51.100.0/24"]),
|
|
),
|
|
(
|
|
destination_owner,
|
|
member_sources_with_ipv4_routes(
|
|
&["10.231.2.1/24", "10.231.4.1/24"],
|
|
&["203.0.113.0/24"],
|
|
),
|
|
),
|
|
] {
|
|
let (ack, mut receiver) = oneshot::channel();
|
|
state.handle_control(SharedVirtualNicControl::UpdateSources {
|
|
member_id,
|
|
sources,
|
|
ack,
|
|
});
|
|
receiver.try_recv().unwrap();
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, 1234),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(reply.payload()).unwrap().dst_addr(),
|
|
source
|
|
);
|
|
}
|
|
state.unregister(unrelated, uuid::Uuid::from_u128(1));
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
destination_owner,
|
|
transport_packet(remote, native, IpProtocol::Udp, 5678, 1234),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(reply.payload()).unwrap().dst_addr(),
|
|
source
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dispatcher_full_member_queue_drops_only_packet_without_reserving_flow() {
|
|
let slow = uuid::Uuid::from_u128(1);
|
|
let active = uuid::Uuid::from_u128(2);
|
|
let slow_ip = Ipv4Addr::new(10, 231, 1, 1);
|
|
let active_ip = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 1, 2);
|
|
let mut state = SharedVirtualNicDispatcherState::default();
|
|
let (sender, mut slow_receiver) = member_channel(8);
|
|
state.register(slow, sender);
|
|
let (sender, mut active_receiver) = member_channel(8);
|
|
state.register(active, sender);
|
|
state
|
|
.source_table
|
|
.update_member_sources(slow, member_sources(&["10.231.1.1/24"]));
|
|
state
|
|
.source_table
|
|
.update_member_sources(active, member_sources(&["10.231.2.1/24"]));
|
|
for _ in 0..8 {
|
|
assert!(state.send_packet(transport_packet(
|
|
slow_ip,
|
|
remote,
|
|
IpProtocol::Udp,
|
|
1000,
|
|
5678
|
|
)));
|
|
}
|
|
assert!(!state.send_packet(ipv4_udp_packet(active_ip, remote)));
|
|
assert_eq!(state.nat_table.entries.len(), 1);
|
|
let (ack, mut receiver) = oneshot::channel();
|
|
state.handle_control(SharedVirtualNicControl::UpdateSources {
|
|
member_id: active,
|
|
sources: member_sources(&["10.231.2.1/24"]),
|
|
ack,
|
|
});
|
|
receiver.try_recv().unwrap();
|
|
state
|
|
.forward_tun_packet_to_member(ipv4_udp_packet(active_ip, Ipv4Addr::new(10, 231, 2, 2)));
|
|
active_receiver.try_recv().unwrap();
|
|
for _ in 0..8 {
|
|
slow_receiver.try_recv().unwrap();
|
|
}
|
|
|
|
// The dropped translated flow must not steal this native flow's port.
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(slow_ip, remote));
|
|
let packet = slow_receiver.try_recv().unwrap();
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&packet)
|
|
.unwrap()
|
|
.ports
|
|
.unwrap()
|
|
.src,
|
|
1234
|
|
);
|
|
let reply = state
|
|
.prepare_member_packet_to_tun(
|
|
slow,
|
|
transport_packet(remote, slow_ip, IpProtocol::Udp, 5678, 1234),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(reply.payload()).unwrap().dst_addr(),
|
|
slow_ip
|
|
);
|
|
drop(slow_receiver);
|
|
state.forward_tun_packet_to_member(ipv4_udp_packet(slow_ip, remote));
|
|
assert!(!state.members.contains_key(&slow));
|
|
}
|
|
|
|
#[test]
|
|
fn nat_pressure_reclaims_idle_flows_but_preserves_active_reservations() {
|
|
let member_id = uuid::Uuid::from_u128(1);
|
|
let source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let native = Ipv4Addr::new(10, 231, 2, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 2, 2);
|
|
let mut nat = SharedVirtualNicNatTable::default();
|
|
for port in 1..=FLOW_OWNER_LIMIT as u16 {
|
|
let mut packet = transport_packet(source, remote, IpProtocol::Udp, port, 5678);
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
SharedVirtualNicFlowKey::from_packet(&packet).unwrap(),
|
|
&mut packet,
|
|
native.into()
|
|
));
|
|
}
|
|
let mut new_flow = transport_packet(
|
|
source,
|
|
remote,
|
|
IpProtocol::Udp,
|
|
FLOW_OWNER_LIMIT as u16 + 1,
|
|
5678,
|
|
);
|
|
let new_key = SharedVirtualNicFlowKey::from_packet(&new_flow).unwrap();
|
|
assert!(!nat.prepare_packet(member_id, None, new_key, &mut new_flow, native.into()));
|
|
let mut unsupported = ipv4_udp_packet(native, remote);
|
|
{
|
|
let mut ipv4 = Ipv4Packet::new_checked(unsupported.mut_payload()).unwrap();
|
|
ipv4.set_next_header(IpProtocol::Unknown(253));
|
|
ipv4.fill_checksum();
|
|
}
|
|
let unchanged = unsupported.payload().to_vec();
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
SharedVirtualNicFlowKey::from_packet(&unsupported).unwrap(),
|
|
&mut unsupported,
|
|
native.into(),
|
|
));
|
|
assert_eq!(unsupported.payload(), unchanged);
|
|
for entry in nat.entries.values_mut() {
|
|
entry.last_active = Instant::now() - FLOW_IDLE_TIMEOUT - Duration::from_secs(1);
|
|
}
|
|
// Activity in either direction protects the reservation during lazy reclamation.
|
|
let mut active = transport_packet(source, remote, IpProtocol::Udp, 1, 5678);
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
SharedVirtualNicFlowKey::from_packet(&active).unwrap(),
|
|
&mut active,
|
|
native.into()
|
|
));
|
|
assert!(nat.translate_reply(
|
|
member_id,
|
|
&mut transport_packet(remote, native, IpProtocol::Udp, 5678, 2)
|
|
));
|
|
assert!(nat.prepare_packet(member_id, None, new_key, &mut new_flow, native.into()));
|
|
for port in [1, 2] {
|
|
let mut native_packet = transport_packet(native, remote, IpProtocol::Udp, port, 5678);
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
SharedVirtualNicFlowKey::from_packet(&native_packet).unwrap(),
|
|
&mut native_packet,
|
|
native.into(),
|
|
));
|
|
let wire = SharedVirtualNicFlowKey::from_packet(&native_packet).unwrap();
|
|
assert_ne!(wire.ports.unwrap().src, port);
|
|
let mut reply = transport_packet(
|
|
remote,
|
|
native,
|
|
IpProtocol::Udp,
|
|
5678,
|
|
wire.ports.unwrap().src,
|
|
);
|
|
assert!(nat.translate_reply(member_id, &mut reply));
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&reply)
|
|
.unwrap()
|
|
.ports
|
|
.unwrap()
|
|
.dst,
|
|
port
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nat_identical_wire_tuples_are_scoped_to_member() {
|
|
let first = uuid::Uuid::from_u128(1);
|
|
let second = uuid::Uuid::from_u128(2);
|
|
let first_source = Ipv4Addr::new(10, 231, 1, 1);
|
|
let second_source = Ipv4Addr::new(10, 231, 2, 1);
|
|
let wire_source = Ipv4Addr::new(10, 231, 3, 1);
|
|
let remote = Ipv4Addr::new(10, 231, 3, 2);
|
|
let mut nat = SharedVirtualNicNatTable::default();
|
|
for (member_id, source) in [(first, first_source), (second, second_source)] {
|
|
let mut packet = ipv4_udp_packet(source, remote);
|
|
assert!(nat.prepare_packet(
|
|
member_id,
|
|
None,
|
|
SharedVirtualNicFlowKey::from_packet(&packet).unwrap(),
|
|
&mut packet,
|
|
wire_source.into(),
|
|
));
|
|
assert_eq!(
|
|
SharedVirtualNicFlowKey::from_packet(&packet)
|
|
.unwrap()
|
|
.ports
|
|
.unwrap()
|
|
.src,
|
|
1234
|
|
);
|
|
}
|
|
for (member_id, source) in [(first, first_source), (second, second_source)] {
|
|
let mut reply = transport_packet(remote, wire_source, IpProtocol::Udp, 5678, 1234);
|
|
assert!(nat.translate_reply(member_id, &mut reply));
|
|
assert_eq!(
|
|
Ipv4Packet::new_checked(reply.payload()).unwrap().dst_addr(),
|
|
source
|
|
);
|
|
}
|
|
}
|
|
}
|