mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
Move the TUN and DNS child start and stop bodies into ipv6tun
Bringing the TUN device and the .fips DNS responder up and taking them down is host-side work, but the bodies sat inline in the node's supervisor arms, and their handles were eight loose fields on the supervisor. Move the bodies to ipv6tun::lifecycle and gather the handles into one Handles struct there, held by the supervisor. The supervisor arms, their order and the child-exit reporting are unchanged; each arm now calls into ipv6tun. The TUN start is two calls so the node can refresh its MSS ceiling between them, exactly where it did before: open_tun creates and logs the device, then spawn_tun creates the macOS/FreeBSD shutdown pipe and starts the writer and reader threads. A failure to create the device still continues without a TUN, and a pipe or writer failure still fails the node's start. stop_tun and stop_dns carry the teardown unchanged, including the shutdown-pipe write that wakes the reader on macOS and FreeBSD. The TUN device name moves into Handles as well, so the teardown up-set can ask ipv6tun whether each child is up. A TUN counts as up when it has a device name, not when it has a sender, so an app-owned TUN still produces no TUN teardown; DNS counts as up while its task handle exists. Node::tun_name, tun_tx, dns_local_addr and enable_app_owned_tun keep their behaviour and now read or write the handles. Node::mesh_ifindex had no caller left outside a test and is replaced by the same method on Handles. Tests install a TUN sender through a test-only Node::install_tun. The moved log lines now log under fips::ipv6tun::lifecycle instead of fips::node::lifecycle. Add that target to the NAT harness and its trace overlay, and to the harnesses that relied on fips::node=debug, and note the rename in the changelog.
This commit is contained in:
+7
-3
@@ -336,9 +336,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
ICMPv6 Packet Too Big debug lines ("Sending ICMP Packet Too Big", "Rate
|
ICMPv6 Packet Too Big debug lines ("Sending ICMP Packet Too Big", "Rate
|
||||||
limiting ICMP Packet Too Big") log as `fips::ipv6tun::icmp` rather than
|
limiting ICMP Packet Too Big") log as `fips::ipv6tun::icmp` rather than
|
||||||
`fips::node::handlers::session`, so a `fips::node=debug` filter no longer
|
`fips::node::handlers::session`, so a `fips::node=debug` filter no longer
|
||||||
shows them. An existing `RUST_LOG` filter naming an old target still parses
|
shows them. The TUN and DNS start and stop lines ("TUN device active",
|
||||||
and simply stops matching, so the symptom is missing log lines rather than an
|
"effective MTU", "max TCP MSS", "Shutting down TUN interface", "DNS responder
|
||||||
error.
|
started", "DNS responder stopped" and their failure warnings) log as
|
||||||
|
`fips::ipv6tun::lifecycle` rather than `fips::node::lifecycle`, so a filter
|
||||||
|
on `fips::node::lifecycle` or `fips::node` no longer selects them. An
|
||||||
|
existing `RUST_LOG` filter naming an old target still parses and simply
|
||||||
|
stops matching, so the symptom is missing log lines rather than an error.
|
||||||
Update `RUST_LOG` filters, journal-watch recipes and any log-scraping alert
|
Update `RUST_LOG` filters, journal-watch recipes and any log-scraping alert
|
||||||
accordingly. The library path `fips::upper` still resolves.
|
accordingly. The library path `fips::upper` still resolves.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,402 @@
|
|||||||
|
//! Start and stop of the TUN and DNS children.
|
||||||
|
//!
|
||||||
|
//! The node's supervisor decides when each child starts and stops, and in
|
||||||
|
//! what order. The bodies that bring the TUN device and the `.fips` DNS
|
||||||
|
//! responder up and take them down live here, together with the handles
|
||||||
|
//! they leave behind for the node to drive and, later, to tear down.
|
||||||
|
|
||||||
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
use super::config::{DnsConfig, TunConfig};
|
||||||
|
use super::dns::{DnsIdentityRx, bind_dns_socket, lookup_mesh_ifindex, run_dns_responder};
|
||||||
|
use super::tun::{
|
||||||
|
MssCeiling, TunDevice, TunError, TunOutboundRx, TunTx, run_tun_reader, shutdown_tun_interface,
|
||||||
|
};
|
||||||
|
use crate::FipsAddress;
|
||||||
|
use crate::config::PeerConfig;
|
||||||
|
use crate::hosts::{DEFAULT_HOSTS_PATH, HostMap, HostMapReloader};
|
||||||
|
use crate::node::lifecycle::supervisor::Child;
|
||||||
|
use crate::node::lifecycle::{report_exit, report_thread};
|
||||||
|
use crate::node::path_mtu::PathMtuLookup;
|
||||||
|
|
||||||
|
/// Runtime handles of the TUN and DNS children, held by the node's
|
||||||
|
/// supervisor.
|
||||||
|
///
|
||||||
|
/// Every field is empty until its child starts. The TUN channels are the
|
||||||
|
/// exception: an embedder that owns the TUN installs them before start
|
||||||
|
/// through `Node::enable_app_owned_tun`, without a device name.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct Handles {
|
||||||
|
/// Kernel name of the TUN device this node created, used to delete or
|
||||||
|
/// down it at teardown. Set only while a system TUN is up; an app-owned
|
||||||
|
/// TUN leaves it unset.
|
||||||
|
pub(crate) tun_name: Option<String>,
|
||||||
|
/// TUN packet sender channel.
|
||||||
|
pub(crate) tun_tx: Option<TunTx>,
|
||||||
|
/// Receiver for outbound packets from the TUN reader.
|
||||||
|
pub(crate) tun_outbound_rx: Option<TunOutboundRx>,
|
||||||
|
/// TUN reader thread handle.
|
||||||
|
pub(crate) tun_reader_handle: Option<JoinHandle<()>>,
|
||||||
|
/// TUN writer thread handle.
|
||||||
|
pub(crate) tun_writer_handle: Option<JoinHandle<()>>,
|
||||||
|
/// Shutdown pipe: writing to this fd unblocks the TUN reader thread on
|
||||||
|
/// macOS and FreeBSD. On Linux, deleting the interface via netlink
|
||||||
|
/// serves the same purpose.
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
pub(crate) tun_shutdown_fd: Option<std::os::unix::io::RawFd>,
|
||||||
|
|
||||||
|
/// Receiver for resolved identities from the DNS responder.
|
||||||
|
pub(crate) dns_identity_rx: Option<DnsIdentityRx>,
|
||||||
|
/// DNS responder task handle.
|
||||||
|
pub(crate) dns_task: Option<tokio::task::JoinHandle<()>>,
|
||||||
|
/// Address the DNS responder actually bound, read back from the socket
|
||||||
|
/// after `bind` so a port-0 config resolves to the assigned port. `Some`
|
||||||
|
/// only while the responder is up; published to embedders through
|
||||||
|
/// [`Node::dns_local_addr`](crate::Node::dns_local_addr).
|
||||||
|
pub(crate) dns_local_addr: Option<SocketAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the TUN threads take from the node when they start.
|
||||||
|
pub(crate) struct TunThreads {
|
||||||
|
/// Shared TCP MSS ceiling, already refreshed by the node. Both threads
|
||||||
|
/// read it per packet from then on.
|
||||||
|
pub(crate) ceiling: MssCeiling,
|
||||||
|
/// The node's effective IPv6 MTU at start, logged with the ceiling.
|
||||||
|
pub(crate) effective: u16,
|
||||||
|
/// Per-destination path MTU, read by the clamp in both threads.
|
||||||
|
pub(crate) path_mtu: PathMtuLookup,
|
||||||
|
/// Capacity of the host-to-mesh channel (`node.buffers.tun_channel`).
|
||||||
|
pub(crate) channel: usize,
|
||||||
|
/// Sender each TUN thread reports `Child::Tun` on when it exits.
|
||||||
|
pub(crate) exit_tx: Option<Sender<Child>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the TUN device, logging it, or log the failure and return `None`.
|
||||||
|
///
|
||||||
|
/// A failure here is not fatal to the node: it continues without a TUN.
|
||||||
|
pub(crate) async fn open_tun(config: &TunConfig, address: FipsAddress) -> Option<TunDevice> {
|
||||||
|
match TunDevice::create(config, address).await {
|
||||||
|
Ok(device) => {
|
||||||
|
info!("TUN device active:");
|
||||||
|
info!(" name: {}", device.name());
|
||||||
|
info!(" address: {}", device.address());
|
||||||
|
info!(" mtu: {}", device.mtu());
|
||||||
|
Some(device)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(error = %e, "Failed to initialize TUN, continuing without it");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handles {
|
||||||
|
/// Whether the TUN child is up.
|
||||||
|
///
|
||||||
|
/// Follows the device name, not the TUN sender: an app-owned TUN
|
||||||
|
/// installs the sender but creates no device, so there is no TUN child
|
||||||
|
/// to tear down.
|
||||||
|
pub(crate) fn tun_up(&self) -> bool {
|
||||||
|
self.tun_name.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the DNS child is up: its responder task handle exists.
|
||||||
|
pub(crate) fn dns_up(&self) -> bool {
|
||||||
|
self.dns_task.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the index of the mesh TUN device this node actually created.
|
||||||
|
///
|
||||||
|
/// Reads the device name recorded when the TUN was brought up, which is
|
||||||
|
/// the kernel's name rather than the configured one. Returns `None` when
|
||||||
|
/// no TUN is up, which disables the DNS responder's mesh-interface
|
||||||
|
/// filter: with no mesh interface there is no mesh exposure to defend.
|
||||||
|
/// An app-owned TUN also leaves the name unset, so the filter stays off
|
||||||
|
/// there even though a mesh interface exists.
|
||||||
|
pub(crate) fn mesh_ifindex(&self) -> Option<u32> {
|
||||||
|
self.tun_name.as_deref().and_then(lookup_mesh_ifindex)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the TUN reader and writer threads on an opened device and keep
|
||||||
|
/// their handles.
|
||||||
|
///
|
||||||
|
/// An error here (the macOS/FreeBSD shutdown pipe, or duplicating the
|
||||||
|
/// device fd for the writer) is fatal to the node's start, unlike a
|
||||||
|
/// failure to create the device.
|
||||||
|
pub(crate) fn spawn_tun(
|
||||||
|
&mut self,
|
||||||
|
device: TunDevice,
|
||||||
|
threads: TunThreads,
|
||||||
|
) -> Result<(), TunError> {
|
||||||
|
let TunThreads {
|
||||||
|
ceiling: max_mss,
|
||||||
|
effective: effective_mtu,
|
||||||
|
path_mtu: path_mtu_lookup,
|
||||||
|
channel: tun_channel_size,
|
||||||
|
exit_tx,
|
||||||
|
} = threads;
|
||||||
|
let mtu = device.mtu();
|
||||||
|
let name = device.name().to_string();
|
||||||
|
let our_addr = *device.address();
|
||||||
|
|
||||||
|
info!("effective MTU: {} bytes", effective_mtu);
|
||||||
|
debug!(
|
||||||
|
" max TCP MSS: {} bytes",
|
||||||
|
max_mss.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
);
|
||||||
|
|
||||||
|
// On macOS and FreeBSD, create a shutdown pipe. Writing to it
|
||||||
|
// unblocks the reader thread's select() loop without closing
|
||||||
|
// the TUN fd (which would cause a double-close when TunDevice
|
||||||
|
// drops). Linux instead unblocks the reader by deleting the
|
||||||
|
// interface; on macOS/FreeBSD downing the interface does not
|
||||||
|
// wake a blocked read.
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
let (shutdown_read_fd, shutdown_write_fd) = {
|
||||||
|
let mut fds = [0i32; 2];
|
||||||
|
if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
|
||||||
|
return Err(TunError::Configure("failed to create shutdown pipe".into()));
|
||||||
|
}
|
||||||
|
(fds[0], fds[1])
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create writer (dups the fd for independent write access).
|
||||||
|
// Pass path_mtu_lookup so inbound SYN-ACK clamp can read
|
||||||
|
// per-destination path MTU learned via discovery.
|
||||||
|
let (writer, tun_tx) = device.create_writer(max_mss.clone(), path_mtu_lookup.clone())?;
|
||||||
|
|
||||||
|
// Spawn writer thread. On exit, including a panic,
|
||||||
|
// it self-reports `Child::Tun` (sync context →
|
||||||
|
// `blocking_send`); TUN is one compound child, so
|
||||||
|
// both threads reporting is fine (the FSM de-dups
|
||||||
|
// via `up.remove`).
|
||||||
|
let writer_child_tx = exit_tx.clone();
|
||||||
|
let writer_handle = thread::spawn(move || {
|
||||||
|
report_thread(Child::Tun, move || writer.run(), writer_child_tx.as_ref());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clone tun_tx for the reader
|
||||||
|
let reader_tun_tx = tun_tx.clone();
|
||||||
|
|
||||||
|
// Create outbound channel for TUN reader → Node
|
||||||
|
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
|
||||||
|
|
||||||
|
// Spawn reader thread. Like the writer, it
|
||||||
|
// self-reports `Child::Tun` on exit or panic (sync
|
||||||
|
// context → `blocking_send`). Exactly one cfg
|
||||||
|
// variant compiles, so the exit sender is moved
|
||||||
|
// into that closure.
|
||||||
|
let reader_child_tx = exit_tx;
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
let reader_handle = thread::spawn(move || {
|
||||||
|
report_thread(
|
||||||
|
Child::Tun,
|
||||||
|
move || {
|
||||||
|
run_tun_reader(
|
||||||
|
device,
|
||||||
|
mtu,
|
||||||
|
our_addr,
|
||||||
|
reader_tun_tx,
|
||||||
|
outbound_tx,
|
||||||
|
max_mss,
|
||||||
|
path_mtu_lookup,
|
||||||
|
shutdown_read_fd,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
reader_child_tx.as_ref(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
|
||||||
|
let reader_handle = thread::spawn(move || {
|
||||||
|
report_thread(
|
||||||
|
Child::Tun,
|
||||||
|
move || {
|
||||||
|
run_tun_reader(
|
||||||
|
device,
|
||||||
|
mtu,
|
||||||
|
our_addr,
|
||||||
|
reader_tun_tx,
|
||||||
|
outbound_tx,
|
||||||
|
max_mss,
|
||||||
|
path_mtu_lookup,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
reader_child_tx.as_ref(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.tun_name = Some(name);
|
||||||
|
self.tun_tx = Some(tun_tx);
|
||||||
|
self.tun_outbound_rx = Some(outbound_rx);
|
||||||
|
self.tun_reader_handle = Some(reader_handle);
|
||||||
|
self.tun_writer_handle = Some(writer_handle);
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
{
|
||||||
|
self.tun_shutdown_fd = Some(shutdown_write_fd);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the TUN child: close the writer's channel, delete or down the
|
||||||
|
/// interface, wake the reader, and join both threads.
|
||||||
|
///
|
||||||
|
/// Returns whether there was a TUN child to stop. With no device name
|
||||||
|
/// (never started, or app-owned) it does nothing, and an app-owned
|
||||||
|
/// sender is left in place.
|
||||||
|
pub(crate) async fn stop_tun(&mut self) -> bool {
|
||||||
|
let Some(name) = self.tun_name.take() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
info!(name = %name, "Shutting down TUN interface");
|
||||||
|
|
||||||
|
// Drop the tun_tx to signal the writer to stop
|
||||||
|
self.tun_tx.take();
|
||||||
|
|
||||||
|
// Delete the interface (on Linux, causes reader to get
|
||||||
|
// EFAULT; on macOS/FreeBSD this downs it — the kernel
|
||||||
|
// destroys the device once the reader closes the fd).
|
||||||
|
if let Err(e) = shutdown_tun_interface(&name).await {
|
||||||
|
warn!(name = %name, error = %e, "Failed to shutdown TUN interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS and FreeBSD, signal the reader thread to exit by
|
||||||
|
// writing to the shutdown pipe. The reader's select() will
|
||||||
|
// wake up and break.
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let Some(fd) = self.tun_shutdown_fd.take() {
|
||||||
|
unsafe {
|
||||||
|
libc::write(fd, b"x".as_ptr() as *const libc::c_void, 1);
|
||||||
|
libc::close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for threads to finish
|
||||||
|
if let Some(handle) = self.tun_reader_handle.take() {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
if let Some(handle) = self.tun_writer_handle.take() {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the `.fips` DNS responder and keep its handles, returning
|
||||||
|
/// whether it came up. A failure is logged and is not fatal to the node.
|
||||||
|
///
|
||||||
|
/// `peers` seeds the responder's own hosts map, which it reloads from
|
||||||
|
/// the hosts file on its own; `channel` is the capacity of the identity
|
||||||
|
/// channel back to the node (`node.buffers.dns_channel`). Reads the TUN
|
||||||
|
/// device name for the mesh-interface filter, so the TUN child, when
|
||||||
|
/// enabled, starts first.
|
||||||
|
pub(crate) fn start_dns(
|
||||||
|
&mut self,
|
||||||
|
config: &DnsConfig,
|
||||||
|
peers: &[PeerConfig],
|
||||||
|
channel: usize,
|
||||||
|
exit_tx: Option<Sender<Child>>,
|
||||||
|
) -> bool {
|
||||||
|
// Initialize DNS responder (independent of TUN).
|
||||||
|
//
|
||||||
|
// Default bind_addr is "::1" (IPv6 loopback). The shipped
|
||||||
|
// fips-dns-setup configures systemd-resolved via a global
|
||||||
|
// /etc/systemd/resolved.conf.d/fips.conf drop-in pointing at
|
||||||
|
// [::1]:5354, which sidesteps a Linux IPV6_PKTINFO behaviour
|
||||||
|
// where self-destined traffic to fips0's address is attributed
|
||||||
|
// to fips0 in PKTINFO and gets silently dropped by the
|
||||||
|
// mesh-interface filter in src/ipv6tun/dns.rs.
|
||||||
|
//
|
||||||
|
// For mesh-reachable resolution (rare), set bind_addr: "::"
|
||||||
|
// in fips.yaml. The mesh-interface filter remains active to
|
||||||
|
// prevent hosts-file alias enumeration in that mode.
|
||||||
|
// `IPV6_V6ONLY=0` is set explicitly so IPv4 clients on
|
||||||
|
// 127.0.0.1 still reach us regardless of kernel sysctl
|
||||||
|
// defaults — but only when bind is on a wildcard / IPv6 path.
|
||||||
|
let addr_str = config.bind_addr();
|
||||||
|
match addr_str.parse::<IpAddr>() {
|
||||||
|
Ok(ip) => {
|
||||||
|
let bind = SocketAddr::new(ip, config.port());
|
||||||
|
match bind_dns_socket(bind) {
|
||||||
|
Ok(socket) => {
|
||||||
|
// Read the bound address back off the socket
|
||||||
|
// rather than reusing `bind`: a port-0 config
|
||||||
|
// resolves to the kernel-assigned port here,
|
||||||
|
// and this is the address an embedder that
|
||||||
|
// proxies queries to us has to dial.
|
||||||
|
let local_addr = socket.local_addr().unwrap_or(bind);
|
||||||
|
let (identity_tx, identity_rx) = tokio::sync::mpsc::channel(channel);
|
||||||
|
let dns_ttl = config.ttl();
|
||||||
|
let base_hosts = HostMap::from_peer_configs(peers);
|
||||||
|
let hosts_path = PathBuf::from(DEFAULT_HOSTS_PATH);
|
||||||
|
let reloader = HostMapReloader::new(base_hosts, hosts_path);
|
||||||
|
// Resolve the TUN ifindex so the responder can
|
||||||
|
// drop queries arriving on the mesh interface
|
||||||
|
// Without this, the `::` bind exposes the
|
||||||
|
// hosts file's alias space to any mesh peer.
|
||||||
|
// The name comes from the device the TUN
|
||||||
|
// path actually created, not the configured
|
||||||
|
// one: macOS and FreeBSD assign utunN/tunN
|
||||||
|
// of their own choosing and the configured
|
||||||
|
// name resolves to nothing there, which left
|
||||||
|
// the filter permanently off.
|
||||||
|
let mesh_ifindex = self.mesh_ifindex();
|
||||||
|
if self.tun_name.is_some() && mesh_ifindex.is_none() {
|
||||||
|
warn!(
|
||||||
|
device = ?self.tun_name,
|
||||||
|
"Mesh interface index unresolved; DNS mesh filter disabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
bind = %local_addr,
|
||||||
|
hosts = reloader.hosts().len(),
|
||||||
|
mesh_ifindex = ?mesh_ifindex,
|
||||||
|
"DNS responder started for .fips domain (auto-reload enabled)"
|
||||||
|
);
|
||||||
|
// Self-report on exit so the supervisor FSM
|
||||||
|
// routes health when the DNS task dies at
|
||||||
|
// runtime. The responder never returns, so
|
||||||
|
// in practice that is a panic. On a
|
||||||
|
// deliberate stop the task is `.abort()`ed,
|
||||||
|
// which drops the report with it; even if
|
||||||
|
// one fired, the FSM ignores it outside
|
||||||
|
// `Running`.
|
||||||
|
let handle = tokio::spawn(report_exit(
|
||||||
|
Child::Dns,
|
||||||
|
run_dns_responder(socket, identity_tx, dns_ttl, reloader, mesh_ifindex),
|
||||||
|
exit_tx,
|
||||||
|
));
|
||||||
|
self.dns_identity_rx = Some(identity_rx);
|
||||||
|
self.dns_task = Some(handle);
|
||||||
|
self.dns_local_addr = Some(local_addr);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(bind = %bind, error = %e, "Failed to start DNS responder");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(addr = %addr_str, error = %e, "Invalid dns.bind_addr; DNS responder not started");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the DNS responder and retract its published address.
|
||||||
|
pub(crate) fn stop_dns(&mut self) {
|
||||||
|
// Stop DNS responder
|
||||||
|
if let Some(handle) = self.dns_task.take() {
|
||||||
|
handle.abort();
|
||||||
|
debug!("DNS responder stopped");
|
||||||
|
}
|
||||||
|
// Retract the published address in the same step that kills
|
||||||
|
// the listener, so an embedder polling `dns_local_addr()`
|
||||||
|
// never dials a socket that is already gone.
|
||||||
|
self.dns_local_addr.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ pub mod dns;
|
|||||||
pub mod icmp;
|
pub mod icmp;
|
||||||
pub mod icmp_rate_limit;
|
pub mod icmp_rate_limit;
|
||||||
pub mod ipv6_shim;
|
pub mod ipv6_shim;
|
||||||
|
pub(crate) mod lifecycle;
|
||||||
pub(crate) mod outbound;
|
pub(crate) mod outbound;
|
||||||
pub mod tcp_mss;
|
pub mod tcp_mss;
|
||||||
pub mod tun;
|
pub mod tun;
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ impl Node {
|
|||||||
// Take the TUN outbound receiver, or create a dummy channel that never
|
// Take the TUN outbound receiver, or create a dummy channel that never
|
||||||
// produces messages (when TUN is disabled). Holding the sender prevents
|
// produces messages (when TUN is disabled). Holding the sender prevents
|
||||||
// the channel from closing.
|
// the channel from closing.
|
||||||
let (mut tun_outbound_rx, _tun_guard) = match self.supervisor.tun_outbound_rx.take() {
|
let (mut tun_outbound_rx, _tun_guard) = match self.supervisor.ipv6tun.tun_outbound_rx.take()
|
||||||
|
{
|
||||||
Some(rx) => (rx, None),
|
Some(rx) => (rx, None),
|
||||||
None => {
|
None => {
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||||
@@ -89,7 +90,8 @@ impl Node {
|
|||||||
|
|
||||||
// Take the DNS identity receiver, or create a dummy channel (when DNS
|
// Take the DNS identity receiver, or create a dummy channel (when DNS
|
||||||
// is disabled). Same pattern as TUN outbound.
|
// is disabled). Same pattern as TUN outbound.
|
||||||
let (mut dns_identity_rx, _dns_guard) = match self.supervisor.dns_identity_rx.take() {
|
let (mut dns_identity_rx, _dns_guard) = match self.supervisor.ipv6tun.dns_identity_rx.take()
|
||||||
|
{
|
||||||
Some(rx) => (rx, None),
|
Some(rx) => (rx, None),
|
||||||
None => {
|
None => {
|
||||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ impl Node {
|
|||||||
mark_ipv6_ecn_ce(&mut packet);
|
mark_ipv6_ecn_ce(&mut packet);
|
||||||
self.metrics().congestion.ce_received.inc();
|
self.metrics().congestion.ce_received.inc();
|
||||||
}
|
}
|
||||||
if let Some(tun_tx) = &self.supervisor.tun_tx {
|
if let Some(tun_tx) = &self.supervisor.ipv6tun.tun_tx {
|
||||||
if let Err(e) = tun_tx.send(packet) {
|
if let Err(e) = tun_tx.send(packet) {
|
||||||
debug!(error = %e, "Failed to deliver decompressed IPv6 packet to TUN");
|
debug!(error = %e, "Failed to deliver decompressed IPv6 packet to TUN");
|
||||||
}
|
}
|
||||||
@@ -3131,7 +3131,7 @@ impl Node {
|
|||||||
pub(in crate::node) fn host_icmp(&mut self) -> IcmpContext<'_> {
|
pub(in crate::node) fn host_icmp(&mut self) -> IcmpContext<'_> {
|
||||||
let our_ipv6 = crate::FipsAddress::from_node_addr(self.node_addr()).to_ipv6();
|
let our_ipv6 = crate::FipsAddress::from_node_addr(self.node_addr()).to_ipv6();
|
||||||
IcmpContext::new(
|
IcmpContext::new(
|
||||||
self.supervisor.tun_tx.as_ref(),
|
self.supervisor.ipv6tun.tun_tx.as_ref(),
|
||||||
our_ipv6,
|
our_ipv6,
|
||||||
&mut self.icmp_rate_limiter,
|
&mut self.icmp_rate_limiter,
|
||||||
)
|
)
|
||||||
|
|||||||
+31
-285
@@ -11,6 +11,7 @@ use super::peering::reconcile::{
|
|||||||
use super::peering::retry::MAX_RETRY_CONNECTIONS_PER_TICK;
|
use super::peering::retry::MAX_RETRY_CONNECTIONS_PER_TICK;
|
||||||
|
|
||||||
use crate::config::{ConnectPolicy, PeerAddress, PeerConfig};
|
use crate::config::{ConnectPolicy, PeerAddress, PeerConfig};
|
||||||
|
use crate::ipv6tun::lifecycle::{TunThreads, open_tun};
|
||||||
use crate::node::acl::PeerAclContext;
|
use crate::node::acl::PeerAclContext;
|
||||||
use crate::node::dataplane::PeerActionCtx;
|
use crate::node::dataplane::PeerActionCtx;
|
||||||
use crate::nostr::{BootstrapEvent, NostrRendezvous};
|
use crate::nostr::{BootstrapEvent, NostrRendezvous};
|
||||||
@@ -19,11 +20,10 @@ use crate::peer::machine::{HandshakeCrypto, PeerEvent, PeerMachine};
|
|||||||
use crate::proto::fmp::wire::build_msg1;
|
use crate::proto::fmp::wire::build_msg1;
|
||||||
use crate::proto::fmp::{Disconnect, DisconnectReason};
|
use crate::proto::fmp::{Disconnect, DisconnectReason};
|
||||||
use crate::transport::{Link, LinkDirection, LinkId, TransportAddr, TransportId, packet_channel};
|
use crate::transport::{Link, LinkDirection, LinkId, TransportAddr, TransportId, packet_channel};
|
||||||
use crate::upper::tun::{TunDevice, TunState, run_tun_reader, shutdown_tun_interface};
|
use crate::upper::tun::TunState;
|
||||||
use crate::{NodeAddr, PeerIdentity};
|
use crate::{NodeAddr, PeerIdentity};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ fn socket_addr_families_compatible(local: SocketAddr, remote: SocketAddr) -> boo
|
|||||||
/// A panic would otherwise unwind past the report and leave the node healthy
|
/// A panic would otherwise unwind past the report and leave the node healthy
|
||||||
/// with the child gone. Aborting the task still reports nothing: the abort
|
/// with the child gone. Aborting the task still reports nothing: the abort
|
||||||
/// drops this whole future, so a deliberate stop does not read as a death.
|
/// drops this whole future, so a deliberate stop does not read as a death.
|
||||||
pub(in crate::node) async fn report_exit(
|
pub(crate) async fn report_exit(
|
||||||
child: Child,
|
child: Child,
|
||||||
body: impl std::future::Future<Output = ()>,
|
body: impl std::future::Future<Output = ()>,
|
||||||
tx: Option<tokio::sync::mpsc::Sender<Child>>,
|
tx: Option<tokio::sync::mpsc::Sender<Child>>,
|
||||||
@@ -66,7 +66,7 @@ pub(in crate::node) async fn report_exit(
|
|||||||
|
|
||||||
/// Run a supervised child's thread body, then report the child's exit on `tx`,
|
/// Run a supervised child's thread body, then report the child's exit on `tx`,
|
||||||
/// whether the body returned or panicked.
|
/// whether the body returned or panicked.
|
||||||
pub(in crate::node) fn report_thread(
|
pub(crate) fn report_thread(
|
||||||
child: Child,
|
child: Child,
|
||||||
body: impl FnOnce(),
|
body: impl FnOnce(),
|
||||||
tx: Option<&tokio::sync::mpsc::Sender<Child>>,
|
tx: Option<&tokio::sync::mpsc::Sender<Child>>,
|
||||||
@@ -1473,7 +1473,7 @@ impl Node {
|
|||||||
// No Tun child when the TUN is app-owned (the embedder pre-set
|
// No Tun child when the TUN is app-owned (the embedder pre-set
|
||||||
// `tun_tx` via `enable_app_owned_tun`) — FIPS does no system-TUN ops;
|
// `tun_tx` via `enable_app_owned_tun`) — FIPS does no system-TUN ops;
|
||||||
// the channels installed before `start` carry both directions.
|
// the channels installed before `start` carry both directions.
|
||||||
let tun = self.config().tun.enabled && self.supervisor.tun_tx.is_none();
|
let tun = self.config().tun.enabled && self.supervisor.ipv6tun.tun_tx.is_none();
|
||||||
let dns = self.config().dns.enabled;
|
let dns = self.config().dns.enabled;
|
||||||
|
|
||||||
// Worker-pool booleans + counts. Unix only — the workers issue
|
// Worker-pool booleans + counts. Unix only — the workers issue
|
||||||
@@ -1706,244 +1706,43 @@ impl Node {
|
|||||||
// Initialize TUN interface after transports and peers are
|
// Initialize TUN interface after transports and peers are
|
||||||
// ready.
|
// ready.
|
||||||
let address = *self.identity().address();
|
let address = *self.identity().address();
|
||||||
match TunDevice::create(&self.config().tun, address).await {
|
match open_tun(&self.config().tun, address).await {
|
||||||
Ok(device) => {
|
Some(device) => {
|
||||||
let mtu = device.mtu();
|
|
||||||
let name = device.name().to_string();
|
|
||||||
let our_addr = *device.address();
|
|
||||||
|
|
||||||
info!("TUN device active:");
|
|
||||||
info!(" name: {}", name);
|
|
||||||
info!(" address: {}", device.address());
|
|
||||||
info!(" mtu: {}", mtu);
|
|
||||||
|
|
||||||
// Seed the shared MSS ceiling from whatever is bound
|
// Seed the shared MSS ceiling from whatever is bound
|
||||||
// right now. Both TUN threads read it live from here
|
// right now. Both TUN threads read it live from here
|
||||||
// on, so a transport binding or unbinding later moves
|
// on, so a transport binding or unbinding later moves
|
||||||
// the clamp instead of leaving it at this instant's
|
// the clamp instead of leaving it at this instant's
|
||||||
// value — see `crate::upper::tun::MssCeiling`.
|
// value — see `crate::upper::tun::MssCeiling`.
|
||||||
self.refresh_tun_mss_ceiling();
|
self.refresh_tun_mss_ceiling();
|
||||||
let max_mss = self.tun_mss_ceiling.clone();
|
let threads = TunThreads {
|
||||||
let effective_mtu = self.effective_ipv6_mtu();
|
ceiling: self.tun_mss_ceiling.clone(),
|
||||||
|
effective: self.effective_ipv6_mtu(),
|
||||||
info!("effective MTU: {} bytes", effective_mtu);
|
path_mtu: self.path_mtu_lookup.clone(),
|
||||||
debug!(
|
channel: self.config().node.buffers.tun_channel,
|
||||||
" max TCP MSS: {} bytes",
|
exit_tx: self.child_exit_tx.clone(),
|
||||||
max_mss.load(std::sync::atomic::Ordering::Relaxed)
|
|
||||||
);
|
|
||||||
|
|
||||||
// On macOS and FreeBSD, create a shutdown pipe. Writing to it
|
|
||||||
// unblocks the reader thread's select() loop without closing
|
|
||||||
// the TUN fd (which would cause a double-close when TunDevice
|
|
||||||
// drops). Linux instead unblocks the reader by deleting the
|
|
||||||
// interface; on macOS/FreeBSD downing the interface does not
|
|
||||||
// wake a blocked read.
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
let (shutdown_read_fd, shutdown_write_fd) = {
|
|
||||||
let mut fds = [0i32; 2];
|
|
||||||
if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
|
|
||||||
return Err(NodeError::Tun(
|
|
||||||
crate::upper::tun::TunError::Configure(
|
|
||||||
"failed to create shutdown pipe".into(),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
(fds[0], fds[1])
|
|
||||||
};
|
};
|
||||||
|
self.supervisor.ipv6tun.spawn_tun(device, threads)?;
|
||||||
// Create writer (dups the fd for independent write access).
|
|
||||||
// Pass path_mtu_lookup so inbound SYN-ACK clamp can read
|
|
||||||
// per-destination path MTU learned via discovery.
|
|
||||||
let (writer, tun_tx) = device
|
|
||||||
.create_writer(max_mss.clone(), self.path_mtu_lookup.clone())?;
|
|
||||||
|
|
||||||
// Spawn writer thread. On exit, including a panic,
|
|
||||||
// it self-reports `Child::Tun` (sync context →
|
|
||||||
// `blocking_send`); TUN is one compound child, so
|
|
||||||
// both threads reporting is fine (the FSM de-dups
|
|
||||||
// via `up.remove`).
|
|
||||||
let writer_child_tx = self.child_exit_tx.clone();
|
|
||||||
let writer_handle = thread::spawn(move || {
|
|
||||||
report_thread(
|
|
||||||
Child::Tun,
|
|
||||||
move || writer.run(),
|
|
||||||
writer_child_tx.as_ref(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clone tun_tx for the reader
|
|
||||||
let reader_tun_tx = tun_tx.clone();
|
|
||||||
|
|
||||||
// Create outbound channel for TUN reader → Node
|
|
||||||
let tun_channel_size = self.config().node.buffers.tun_channel;
|
|
||||||
let (outbound_tx, outbound_rx) =
|
|
||||||
tokio::sync::mpsc::channel(tun_channel_size);
|
|
||||||
|
|
||||||
// Spawn reader thread. Like the writer, it
|
|
||||||
// self-reports `Child::Tun` on exit or panic (sync
|
|
||||||
// context → `blocking_send`). Exactly one cfg
|
|
||||||
// variant compiles, so the single clone is moved
|
|
||||||
// into that closure.
|
|
||||||
let path_mtu_lookup = self.path_mtu_lookup.clone();
|
|
||||||
let reader_child_tx = self.child_exit_tx.clone();
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
let reader_handle = thread::spawn(move || {
|
|
||||||
report_thread(
|
|
||||||
Child::Tun,
|
|
||||||
move || {
|
|
||||||
run_tun_reader(
|
|
||||||
device,
|
|
||||||
mtu,
|
|
||||||
our_addr,
|
|
||||||
reader_tun_tx,
|
|
||||||
outbound_tx,
|
|
||||||
max_mss,
|
|
||||||
path_mtu_lookup,
|
|
||||||
shutdown_read_fd,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
reader_child_tx.as_ref(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
|
|
||||||
let reader_handle = thread::spawn(move || {
|
|
||||||
report_thread(
|
|
||||||
Child::Tun,
|
|
||||||
move || {
|
|
||||||
run_tun_reader(
|
|
||||||
device,
|
|
||||||
mtu,
|
|
||||||
our_addr,
|
|
||||||
reader_tun_tx,
|
|
||||||
outbound_tx,
|
|
||||||
max_mss,
|
|
||||||
path_mtu_lookup,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
reader_child_tx.as_ref(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.tun_state = TunState::Active;
|
self.tun_state = TunState::Active;
|
||||||
self.tun_name = Some(name);
|
|
||||||
self.supervisor.tun_tx = Some(tun_tx);
|
|
||||||
self.supervisor.tun_outbound_rx = Some(outbound_rx);
|
|
||||||
self.supervisor.tun_reader_handle = Some(reader_handle);
|
|
||||||
self.supervisor.tun_writer_handle = Some(writer_handle);
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
{
|
|
||||||
self.supervisor.tun_shutdown_fd = Some(shutdown_write_fd);
|
|
||||||
}
|
|
||||||
Event::SubstrateUp { child }
|
Event::SubstrateUp { child }
|
||||||
}
|
}
|
||||||
Err(e) => {
|
None => {
|
||||||
self.tun_state = TunState::Failed;
|
self.tun_state = TunState::Failed;
|
||||||
warn!(error = %e, "Failed to initialize TUN, continuing without it");
|
|
||||||
Event::SubstrateFailed { child }
|
Event::SubstrateFailed { child }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Child::Dns => {
|
Child::Dns => {
|
||||||
// Initialize DNS responder (independent of TUN).
|
let config = &self.context.config;
|
||||||
//
|
let up = self.supervisor.ipv6tun.start_dns(
|
||||||
// Default bind_addr is "::1" (IPv6 loopback). The shipped
|
&config.dns,
|
||||||
// fips-dns-setup configures systemd-resolved via a global
|
config.peers(),
|
||||||
// /etc/systemd/resolved.conf.d/fips.conf drop-in pointing at
|
config.node.buffers.dns_channel,
|
||||||
// [::1]:5354, which sidesteps a Linux IPV6_PKTINFO behaviour
|
self.child_exit_tx.clone(),
|
||||||
// where self-destined traffic to fips0's address is attributed
|
);
|
||||||
// to fips0 in PKTINFO and gets silently dropped by the
|
if up {
|
||||||
// mesh-interface filter in src/upper/dns.rs.
|
Event::SubstrateUp { child }
|
||||||
//
|
} else {
|
||||||
// For mesh-reachable resolution (rare), set bind_addr: "::"
|
Event::SubstrateFailed { child }
|
||||||
// in fips.yaml. The mesh-interface filter remains active to
|
|
||||||
// prevent hosts-file alias enumeration in that mode.
|
|
||||||
// `IPV6_V6ONLY=0` is set explicitly so IPv4 clients on
|
|
||||||
// 127.0.0.1 still reach us regardless of kernel sysctl
|
|
||||||
// defaults — but only when bind is on a wildcard / IPv6 path.
|
|
||||||
let addr_str = self.config().dns.bind_addr();
|
|
||||||
match addr_str.parse::<std::net::IpAddr>() {
|
|
||||||
Ok(ip) => {
|
|
||||||
let bind = std::net::SocketAddr::new(ip, self.config().dns.port());
|
|
||||||
match crate::ipv6tun::dns::bind_dns_socket(bind) {
|
|
||||||
Ok(socket) => {
|
|
||||||
// Read the bound address back off the socket
|
|
||||||
// rather than reusing `bind`: a port-0 config
|
|
||||||
// resolves to the kernel-assigned port here,
|
|
||||||
// and this is the address an embedder that
|
|
||||||
// proxies queries to us has to dial.
|
|
||||||
let local_addr = socket.local_addr().unwrap_or(bind);
|
|
||||||
let dns_channel_size = self.config().node.buffers.dns_channel;
|
|
||||||
let (identity_tx, identity_rx) =
|
|
||||||
tokio::sync::mpsc::channel(dns_channel_size);
|
|
||||||
let dns_ttl = self.config().dns.ttl();
|
|
||||||
let base_hosts =
|
|
||||||
crate::upper::hosts::HostMap::from_peer_configs(
|
|
||||||
self.config().peers(),
|
|
||||||
);
|
|
||||||
let hosts_path = std::path::PathBuf::from(
|
|
||||||
crate::upper::hosts::DEFAULT_HOSTS_PATH,
|
|
||||||
);
|
|
||||||
let reloader = crate::upper::hosts::HostMapReloader::new(
|
|
||||||
base_hosts, hosts_path,
|
|
||||||
);
|
|
||||||
// Resolve the TUN ifindex so the responder can
|
|
||||||
// drop queries arriving on the mesh interface
|
|
||||||
// Without this, the `::` bind exposes the
|
|
||||||
// hosts file's alias space to any mesh peer.
|
|
||||||
// The name comes from the device the TUN
|
|
||||||
// path actually created, not the configured
|
|
||||||
// one: macOS and FreeBSD assign utunN/tunN
|
|
||||||
// of their own choosing and the configured
|
|
||||||
// name resolves to nothing there, which left
|
|
||||||
// the filter permanently off.
|
|
||||||
let mesh_ifindex = self.mesh_ifindex();
|
|
||||||
if self.tun_name.is_some() && mesh_ifindex.is_none() {
|
|
||||||
warn!(
|
|
||||||
device = ?self.tun_name,
|
|
||||||
"Mesh interface index unresolved; DNS mesh filter disabled"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
info!(
|
|
||||||
bind = %local_addr,
|
|
||||||
hosts = reloader.hosts().len(),
|
|
||||||
mesh_ifindex = ?mesh_ifindex,
|
|
||||||
"DNS responder started for .fips domain (auto-reload enabled)"
|
|
||||||
);
|
|
||||||
// Self-report on exit so the supervisor FSM
|
|
||||||
// routes health when the DNS task dies at
|
|
||||||
// runtime. The responder never returns, so
|
|
||||||
// in practice that is a panic. On a
|
|
||||||
// deliberate stop the task is `.abort()`ed,
|
|
||||||
// which drops the report with it; even if
|
|
||||||
// one fired, the FSM ignores it outside
|
|
||||||
// `Running`.
|
|
||||||
let dns_child_tx = self.child_exit_tx.clone();
|
|
||||||
let handle = tokio::spawn(report_exit(
|
|
||||||
Child::Dns,
|
|
||||||
crate::upper::dns::run_dns_responder(
|
|
||||||
socket,
|
|
||||||
identity_tx,
|
|
||||||
dns_ttl,
|
|
||||||
reloader,
|
|
||||||
mesh_ifindex,
|
|
||||||
),
|
|
||||||
dns_child_tx,
|
|
||||||
));
|
|
||||||
self.supervisor.dns_identity_rx = Some(identity_rx);
|
|
||||||
self.supervisor.dns_task = Some(handle);
|
|
||||||
self.supervisor.dns_local_addr = Some(local_addr);
|
|
||||||
Event::SubstrateUp { child }
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(bind = %bind, error = %e, "Failed to start DNS responder");
|
|
||||||
Event::SubstrateFailed { child }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(addr = %addr_str, error = %e, "Invalid dns.bind_addr; DNS responder not started");
|
|
||||||
Event::SubstrateFailed { child }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2072,20 +1871,6 @@ impl Node {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the index of the mesh TUN device this node actually created.
|
|
||||||
///
|
|
||||||
/// Reads the device name recorded when the TUN was brought up, which is
|
|
||||||
/// the kernel's name rather than the configured one. Returns `None` when
|
|
||||||
/// no TUN is up, which disables the DNS responder's mesh-interface
|
|
||||||
/// filter: with no mesh interface there is no mesh exposure to defend.
|
|
||||||
/// An app-owned TUN also leaves the name unset, so the filter stays off
|
|
||||||
/// there even though a mesh interface exists.
|
|
||||||
pub(crate) fn mesh_ifindex(&self) -> Option<u32> {
|
|
||||||
self.tun_name
|
|
||||||
.as_deref()
|
|
||||||
.and_then(crate::ipv6tun::dns::lookup_mesh_ifindex)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop the node.
|
/// Stop the node.
|
||||||
///
|
///
|
||||||
/// Shuts down TUN interface, stops I/O threads, and transitions to
|
/// Shuts down TUN interface, stops I/O threads, and transitions to
|
||||||
@@ -2161,15 +1946,7 @@ impl Node {
|
|||||||
|
|
||||||
match child {
|
match child {
|
||||||
Child::Dns => {
|
Child::Dns => {
|
||||||
// Stop DNS responder
|
self.supervisor.ipv6tun.stop_dns();
|
||||||
if let Some(handle) = self.supervisor.dns_task.take() {
|
|
||||||
handle.abort();
|
|
||||||
debug!("DNS responder stopped");
|
|
||||||
}
|
|
||||||
// Retract the published address in the same step that kills
|
|
||||||
// the listener, so an embedder polling `dns_local_addr()`
|
|
||||||
// never dials a socket that is already gone.
|
|
||||||
self.supervisor.dns_local_addr.take();
|
|
||||||
}
|
}
|
||||||
Child::Nostr => {
|
Child::Nostr => {
|
||||||
// Stop Nostr overlay discovery background work and withdraw
|
// Stop Nostr overlay discovery background work and withdraw
|
||||||
@@ -2209,38 +1986,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
Child::Tun => {
|
Child::Tun => {
|
||||||
// Shutdown TUN interface
|
// Shutdown TUN interface
|
||||||
if let Some(name) = self.tun_name.take() {
|
if self.supervisor.ipv6tun.stop_tun().await {
|
||||||
info!(name = %name, "Shutting down TUN interface");
|
|
||||||
|
|
||||||
// Drop the tun_tx to signal the writer to stop
|
|
||||||
self.supervisor.tun_tx.take();
|
|
||||||
|
|
||||||
// Delete the interface (on Linux, causes reader to get
|
|
||||||
// EFAULT; on macOS/FreeBSD this downs it — the kernel
|
|
||||||
// destroys the device once the reader closes the fd).
|
|
||||||
if let Err(e) = shutdown_tun_interface(&name).await {
|
|
||||||
warn!(name = %name, error = %e, "Failed to shutdown TUN interface");
|
|
||||||
}
|
|
||||||
|
|
||||||
// On macOS and FreeBSD, signal the reader thread to exit by
|
|
||||||
// writing to the shutdown pipe. The reader's select() will
|
|
||||||
// wake up and break.
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
if let Some(fd) = self.supervisor.tun_shutdown_fd.take() {
|
|
||||||
unsafe {
|
|
||||||
libc::write(fd, b"x".as_ptr() as *const libc::c_void, 1);
|
|
||||||
libc::close(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for threads to finish
|
|
||||||
if let Some(handle) = self.supervisor.tun_reader_handle.take() {
|
|
||||||
let _ = handle.join();
|
|
||||||
}
|
|
||||||
if let Some(handle) = self.supervisor.tun_writer_handle.take() {
|
|
||||||
let _ = handle.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tun_state = TunState::Disabled;
|
self.tun_state = TunState::Disabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2297,7 +2043,7 @@ impl Node {
|
|||||||
/// `Child::Dns`, which is what reaches this.
|
/// `Child::Dns`, which is what reaches this.
|
||||||
pub(in crate::node) fn retract_child_publications(&mut self, child: Child) {
|
pub(in crate::node) fn retract_child_publications(&mut self, child: Child) {
|
||||||
if matches!(child, Child::Dns) {
|
if matches!(child, Child::Dns) {
|
||||||
self.supervisor.dns_local_addr.take();
|
self.supervisor.ipv6tun.dns_local_addr.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2351,7 +2097,7 @@ impl Node {
|
|||||||
/// stops them. Shared by [`Self::stop`] and [`Self::enter_drain`].
|
/// stops them. Shared by [`Self::stop`] and [`Self::enter_drain`].
|
||||||
fn reconstruct_supervised_up(&self) -> Vec<Child> {
|
fn reconstruct_supervised_up(&self) -> Vec<Child> {
|
||||||
let mut up: Vec<Child> = Vec::new();
|
let mut up: Vec<Child> = Vec::new();
|
||||||
if self.supervisor.dns_task.is_some() {
|
if self.supervisor.ipv6tun.dns_up() {
|
||||||
up.push(Child::Dns);
|
up.push(Child::Dns);
|
||||||
}
|
}
|
||||||
if self.supervisor.nostr_rendezvous.engine().is_some() {
|
if self.supervisor.nostr_rendezvous.engine().is_some() {
|
||||||
@@ -2363,7 +2109,7 @@ impl Node {
|
|||||||
for id in self.transports.keys() {
|
for id in self.transports.keys() {
|
||||||
up.push(Child::Transport(*id));
|
up.push(Child::Transport(*id));
|
||||||
}
|
}
|
||||||
if self.tun_name.is_some() {
|
if self.supervisor.ipv6tun.tun_up() {
|
||||||
up.push(Child::Tun);
|
up.push(Child::Tun);
|
||||||
}
|
}
|
||||||
up
|
up
|
||||||
|
|||||||
@@ -103,11 +103,9 @@
|
|||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::JoinHandle;
|
|
||||||
|
|
||||||
use crate::node::NodeState;
|
use crate::node::NodeState;
|
||||||
use crate::transport::{PacketTx, TransportId};
|
use crate::transport::{PacketTx, TransportId};
|
||||||
use crate::upper::tun::{TunOutboundRx, TunTx};
|
|
||||||
|
|
||||||
/// A supervised substrate child.
|
/// A supervised substrate child.
|
||||||
///
|
///
|
||||||
@@ -395,7 +393,7 @@ impl SupervisorFsm {
|
|||||||
/// A supervisor seeded directly into `Running` with a known up-set.
|
/// A supervisor seeded directly into `Running` with a known up-set.
|
||||||
///
|
///
|
||||||
/// The teardown driver (`stop()`) reconstructs the up-set from observed
|
/// The teardown driver (`stop()`) reconstructs the up-set from observed
|
||||||
/// runtime presence (`dns_task.is_some()`, transports keys, etc.) rather
|
/// runtime presence (`ipv6tun.dns_up()`, transports keys, etc.) rather
|
||||||
/// than relying on a live machine persisted across start/stop, so that
|
/// than relying on a live machine persisted across start/stop, so that
|
||||||
/// teardown ordering is authored here regardless of how the node reached
|
/// teardown ordering is authored here regardless of how the node reached
|
||||||
/// `Running`. Feeding `Event::Stop` then yields the ordered `StopChild`
|
/// `Running`. Feeding `Event::Stop` then yields the ordered `StopChild`
|
||||||
@@ -811,29 +809,10 @@ pub(crate) struct Supervisor {
|
|||||||
/// Packet sender for transports.
|
/// Packet sender for transports.
|
||||||
pub(in crate::node) packet_tx: Option<PacketTx>,
|
pub(in crate::node) packet_tx: Option<PacketTx>,
|
||||||
|
|
||||||
/// TUN packet sender channel.
|
/// TUN and DNS child handles: the TUN device name, channels, reader and
|
||||||
pub(in crate::node) tun_tx: Option<TunTx>,
|
/// writer threads and (macOS/FreeBSD) shutdown pipe, and the DNS
|
||||||
/// Receiver for outbound packets from the TUN reader.
|
/// responder task, identity receiver and bound address.
|
||||||
pub(in crate::node) tun_outbound_rx: Option<TunOutboundRx>,
|
pub(in crate::node) ipv6tun: crate::ipv6tun::lifecycle::Handles,
|
||||||
/// TUN reader thread handle.
|
|
||||||
pub(in crate::node) tun_reader_handle: Option<JoinHandle<()>>,
|
|
||||||
/// TUN writer thread handle.
|
|
||||||
pub(in crate::node) tun_writer_handle: Option<JoinHandle<()>>,
|
|
||||||
/// Shutdown pipe: writing to this fd unblocks the TUN reader thread on
|
|
||||||
/// macOS and FreeBSD. On Linux, deleting the interface via netlink
|
|
||||||
/// serves the same purpose.
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
pub(in crate::node) tun_shutdown_fd: Option<std::os::unix::io::RawFd>,
|
|
||||||
|
|
||||||
/// Receiver for resolved identities from the DNS responder.
|
|
||||||
pub(in crate::node) dns_identity_rx: Option<crate::upper::dns::DnsIdentityRx>,
|
|
||||||
/// DNS responder task handle.
|
|
||||||
pub(in crate::node) dns_task: Option<tokio::task::JoinHandle<()>>,
|
|
||||||
/// Address the DNS responder actually bound, read back from the socket
|
|
||||||
/// after `bind` so a port-0 config resolves to the assigned port. `Some`
|
|
||||||
/// only while the responder is up; published to embedders through
|
|
||||||
/// [`Node::dns_local_addr`](crate::Node::dns_local_addr).
|
|
||||||
pub(in crate::node) dns_local_addr: Option<std::net::SocketAddr>,
|
|
||||||
|
|
||||||
/// Sender for each UDP listen socket the transport spawn binds — its raw
|
/// Sender for each UDP listen socket the transport spawn binds — its raw
|
||||||
/// fd and the instance name it was configured under — armed by
|
/// fd and the instance name it was configured under — armed by
|
||||||
@@ -892,15 +871,7 @@ impl Supervisor {
|
|||||||
Self {
|
Self {
|
||||||
state: NodeState::Created,
|
state: NodeState::Created,
|
||||||
packet_tx: None,
|
packet_tx: None,
|
||||||
tun_tx: None,
|
ipv6tun: Default::default(),
|
||||||
tun_outbound_rx: None,
|
|
||||||
tun_reader_handle: None,
|
|
||||||
tun_writer_handle: None,
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
|
||||||
tun_shutdown_fd: None,
|
|
||||||
dns_identity_rx: None,
|
|
||||||
dns_task: None,
|
|
||||||
dns_local_addr: None,
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
udp_fd_tx: None,
|
udp_fd_tx: None,
|
||||||
nostr_rendezvous: crate::nostr::RendezvousDriver::default(),
|
nostr_rendezvous: crate::nostr::RendezvousDriver::default(),
|
||||||
|
|||||||
+14
-11
@@ -15,7 +15,7 @@ pub(crate) mod decrypt_worker;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) mod encrypt_worker;
|
pub(crate) mod encrypt_worker;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod lifecycle;
|
pub(crate) mod lifecycle;
|
||||||
pub(crate) mod metrics;
|
pub(crate) mod metrics;
|
||||||
pub(crate) mod netmon;
|
pub(crate) mod netmon;
|
||||||
pub use netmon::NetmonTrigger;
|
pub use netmon::NetmonTrigger;
|
||||||
@@ -606,8 +606,6 @@ pub struct Node {
|
|||||||
// === TUN Interface ===
|
// === TUN Interface ===
|
||||||
/// TUN device state.
|
/// TUN device state.
|
||||||
tun_state: TunState,
|
tun_state: TunState,
|
||||||
/// TUN interface name (for cleanup).
|
|
||||||
tun_name: Option<String>,
|
|
||||||
|
|
||||||
/// Slot the embedder installs its BLE radio into, armed by
|
/// Slot the embedder installs its BLE radio into, armed by
|
||||||
/// [`Self::enable_app_owned_ble_radio`]. `None` unless armed.
|
/// [`Self::enable_app_owned_ble_radio`]. `None` unless armed.
|
||||||
@@ -913,7 +911,6 @@ impl Node {
|
|||||||
crate::control::snapshot::NativeSnapshot::empty(),
|
crate::control::snapshot::NativeSnapshot::empty(),
|
||||||
)),
|
)),
|
||||||
tun_state,
|
tun_state,
|
||||||
tun_name: None,
|
|
||||||
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
||||||
ble_radio: None,
|
ble_radio: None,
|
||||||
index_allocator: IndexAllocator::new(),
|
index_allocator: IndexAllocator::new(),
|
||||||
@@ -1087,7 +1084,6 @@ impl Node {
|
|||||||
crate::control::snapshot::NativeSnapshot::empty(),
|
crate::control::snapshot::NativeSnapshot::empty(),
|
||||||
)),
|
)),
|
||||||
tun_state,
|
tun_state,
|
||||||
tun_name: None,
|
|
||||||
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
||||||
ble_radio: None,
|
ble_radio: None,
|
||||||
index_allocator: IndexAllocator::new(),
|
index_allocator: IndexAllocator::new(),
|
||||||
@@ -1936,7 +1932,7 @@ impl Node {
|
|||||||
estimated_mesh_size: self.estimated_mesh_size,
|
estimated_mesh_size: self.estimated_mesh_size,
|
||||||
state: self.supervisor.state,
|
state: self.supervisor.state,
|
||||||
tun_state: self.tun_state,
|
tun_state: self.tun_state,
|
||||||
tun_name: self.tun_name.clone(),
|
tun_name: self.supervisor.ipv6tun.tun_name.clone(),
|
||||||
effective_ipv6_mtu: self.effective_ipv6_mtu(),
|
effective_ipv6_mtu: self.effective_ipv6_mtu(),
|
||||||
connection_count: self.connection_count(),
|
connection_count: self.connection_count(),
|
||||||
peer_count: self.peers.len(),
|
peer_count: self.peers.len(),
|
||||||
@@ -2651,7 +2647,7 @@ impl Node {
|
|||||||
|
|
||||||
/// Get the TUN interface name, if active.
|
/// Get the TUN interface name, if active.
|
||||||
pub fn tun_name(&self) -> Option<&str> {
|
pub fn tun_name(&self) -> Option<&str> {
|
||||||
self.tun_name.as_deref()
|
self.supervisor.ipv6tun.tun_name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Resource Limits ===
|
// === Resource Limits ===
|
||||||
@@ -3532,7 +3528,14 @@ impl Node {
|
|||||||
///
|
///
|
||||||
/// Returns None if TUN is not active or the node hasn't been started.
|
/// Returns None if TUN is not active or the node hasn't been started.
|
||||||
pub fn tun_tx(&self) -> Option<&TunTx> {
|
pub fn tun_tx(&self) -> Option<&TunTx> {
|
||||||
self.supervisor.tun_tx.as_ref()
|
self.supervisor.ipv6tun.tun_tx.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a TUN packet sender, standing in for the TUN writer, so a
|
||||||
|
/// test can read what the node delivers toward the host.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn install_tun(&mut self, tun_tx: TunTx) {
|
||||||
|
self.supervisor.ipv6tun.tun_tx = Some(tun_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set up an **app-owned TUN**: rather than FIPS creating a system TUN
|
/// Set up an **app-owned TUN**: rather than FIPS creating a system TUN
|
||||||
@@ -3559,8 +3562,8 @@ impl Node {
|
|||||||
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
|
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
|
||||||
// mesh → app: the node writes inbound packets to `tun_tx`; the app pulls.
|
// mesh → app: the node writes inbound packets to `tun_tx`; the app pulls.
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
self.supervisor.tun_tx = Some(tun_tx);
|
self.supervisor.ipv6tun.tun_tx = Some(tun_tx);
|
||||||
self.supervisor.tun_outbound_rx = Some(outbound_rx);
|
self.supervisor.ipv6tun.tun_outbound_rx = Some(outbound_rx);
|
||||||
self.tun_state = TunState::Active;
|
self.tun_state = TunState::Active;
|
||||||
(outbound_tx, tun_rx)
|
(outbound_tx, tun_rx)
|
||||||
}
|
}
|
||||||
@@ -3733,7 +3736,7 @@ impl Node {
|
|||||||
/// Reading live node state from a backgrounded loop is a general gap, not
|
/// Reading live node state from a backgrounded loop is a general gap, not
|
||||||
/// one this accessor tries to close.
|
/// one this accessor tries to close.
|
||||||
pub fn dns_local_addr(&self) -> Option<std::net::SocketAddr> {
|
pub fn dns_local_addr(&self) -> Option<std::net::SocketAddr> {
|
||||||
self.supervisor.dns_local_addr
|
self.supervisor.ipv6tun.dns_local_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle that wakes the medium-change detector (`node.netmon.*`) now
|
/// A handle that wakes the medium-change detector (`node.netmon.*`) now
|
||||||
|
|||||||
@@ -1857,7 +1857,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() {
|
|||||||
|
|
||||||
// Inject a TUN sender so `send_icmpv6_dest_unreachable` is observable.
|
// Inject a TUN sender so `send_icmpv6_dest_unreachable` is observable.
|
||||||
let (tun_tx, tun_rx) = mpsc::channel::<Vec<u8>>();
|
let (tun_tx, tun_rx) = mpsc::channel::<Vec<u8>>();
|
||||||
node.supervisor.tun_tx = Some(tun_tx);
|
node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Build a target identity (the unreachable destination).
|
// Build a target identity (the unreachable destination).
|
||||||
let target_identity = Identity::generate();
|
let target_identity = Identity::generate();
|
||||||
|
|||||||
+19
-19
@@ -637,7 +637,7 @@ async fn test_session_100_nodes() {
|
|||||||
let mut tun_receivers: Vec<mpsc::Receiver<Vec<u8>>> = Vec::with_capacity(NUM_NODES);
|
let mut tun_receivers: Vec<mpsc::Receiver<Vec<u8>>> = Vec::with_capacity(NUM_NODES);
|
||||||
for tn in nodes.iter_mut() {
|
for tn in nodes.iter_mut() {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
tn.node.supervisor.tun_tx = Some(tx);
|
tn.node.install_tun(tx);
|
||||||
tun_receivers.push(rx);
|
tun_receivers.push(rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1051,7 +1051,7 @@ async fn test_tun_outbound_established_session() {
|
|||||||
|
|
||||||
// Install TUN receiver on Node 1
|
// Install TUN receiver on Node 1
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[1].node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Build and inject an IPv6 packet
|
// Build and inject an IPv6 packet
|
||||||
let test_payload = b"data-plane-test-12345";
|
let test_payload = b"data-plane-test-12345";
|
||||||
@@ -1133,7 +1133,7 @@ async fn rekey_cutover_preserves_data_plane() {
|
|||||||
|
|
||||||
// node 1's TUN receiver observes decoded plaintext.
|
// node 1's TUN receiver observes decoded plaintext.
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[1].node.install_tun(tun_tx);
|
||||||
let src_fips = crate::FipsAddress::from_node_addr(&node0_addr);
|
let src_fips = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||||
let dst_fips = crate::FipsAddress::from_node_addr(&node1_addr);
|
let dst_fips = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||||
|
|
||||||
@@ -1298,9 +1298,9 @@ async fn rekey_pair_with_held_msg2() -> HeldMsg2Pair {
|
|||||||
|
|
||||||
// Each node's TUN receiver observes the plaintext the other one sent.
|
// Each node's TUN receiver observes the plaintext the other one sent.
|
||||||
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun0_tx);
|
nodes[0].node.install_tun(tun0_tx);
|
||||||
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun1_tx);
|
nodes[1].node.install_tun(tun1_tx);
|
||||||
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||||
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||||
|
|
||||||
@@ -1823,7 +1823,7 @@ async fn test_tun_outbound_triggers_session_initiation() {
|
|||||||
|
|
||||||
// Install TUN receiver on Node 1
|
// Install TUN receiver on Node 1
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[1].node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Build and inject an IPv6 packet (identity cache populated at peer promotion)
|
// Build and inject an IPv6 packet (identity cache populated at peer promotion)
|
||||||
let test_payload = b"trigger-session-test";
|
let test_payload = b"trigger-session-test";
|
||||||
@@ -1876,7 +1876,7 @@ async fn test_tun_outbound_unknown_destination() {
|
|||||||
|
|
||||||
// Install TUN receiver on Node 0 (for ICMPv6 response)
|
// Install TUN receiver on Node 0 (for ICMPv6 response)
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[0].node.install_tun(tun_tx);
|
||||||
|
|
||||||
let src_fips = crate::FipsAddress::from_node_addr(nodes[0].node.node_addr());
|
let src_fips = crate::FipsAddress::from_node_addr(nodes[0].node.node_addr());
|
||||||
|
|
||||||
@@ -1928,7 +1928,7 @@ async fn test_tun_outbound_3node_forwarded() {
|
|||||||
|
|
||||||
// Install TUN receiver on Node 2
|
// Install TUN receiver on Node 2
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[2].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[2].node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Build and inject an IPv6 packet (triggers session initiation to Node 2)
|
// Build and inject an IPv6 packet (triggers session initiation to Node 2)
|
||||||
let test_payload = b"forwarded-data-plane";
|
let test_payload = b"forwarded-data-plane";
|
||||||
@@ -1973,7 +1973,7 @@ async fn test_tun_outbound_pending_queue_flush() {
|
|||||||
|
|
||||||
// Install TUN receiver on Node 1
|
// Install TUN receiver on Node 1
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[1].node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Send 5 packets before any session exists
|
// Send 5 packets before any session exists
|
||||||
let mut packets = Vec::new();
|
let mut packets = Vec::new();
|
||||||
@@ -2624,7 +2624,7 @@ async fn test_tun_outbound_path_mtu_generates_ptb() {
|
|||||||
|
|
||||||
// Install TUN receiver on source node to capture ICMPv6 PTB
|
// Install TUN receiver on source node to capture ICMPv6 PTB
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[0].node.install_tun(tun_tx);
|
||||||
|
|
||||||
// Build an IPv6 packet that fits local MTU but exceeds path MTU
|
// Build an IPv6 packet that fits local MTU but exceeds path MTU
|
||||||
let reduced_ipv6_mtu = crate::upper::icmp::effective_ipv6_mtu(reduced_mtu) as usize;
|
let reduced_ipv6_mtu = crate::upper::icmp::effective_ipv6_mtu(reduced_mtu) as usize;
|
||||||
@@ -2681,7 +2681,7 @@ async fn test_tun_outbound_path_mtu_generates_ptb() {
|
|||||||
|
|
||||||
// Verify a packet that fits within path MTU passes through (no PTB)
|
// Verify a packet that fits within path MTU passes through (no PTB)
|
||||||
let (tun_tx2, tun_rx2) = std::sync::mpsc::channel();
|
let (tun_tx2, tun_rx2) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx2);
|
nodes[0].node.install_tun(tun_tx2);
|
||||||
let fitting_payload = vec![0u8; reduced_ipv6_mtu - 41]; // fits within path MTU
|
let fitting_payload = vec![0u8; reduced_ipv6_mtu - 41]; // fits within path MTU
|
||||||
let fitting_packet = build_ipv6_packet(&src_fips, &dst_fips, &fitting_payload);
|
let fitting_packet = build_ipv6_packet(&src_fips, &dst_fips, &fitting_payload);
|
||||||
assert!(fitting_packet.len() <= reduced_ipv6_mtu);
|
assert!(fitting_packet.len() <= reduced_ipv6_mtu);
|
||||||
@@ -2820,7 +2820,7 @@ async fn test_multihop_pmtud_heterogeneous_mtu() {
|
|||||||
// should check PathMtuState and generate ICMPv6 PTB on TUN instead
|
// should check PathMtuState and generate ICMPv6 PTB on TUN instead
|
||||||
// of forwarding.
|
// of forwarding.
|
||||||
let (tun_tx2, tun_rx2) = std::sync::mpsc::channel();
|
let (tun_tx2, tun_rx2) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx2);
|
nodes[0].node.install_tun(tun_tx2);
|
||||||
|
|
||||||
nodes[0].node.handle_tun_outbound(ipv6_packet.clone()).await;
|
nodes[0].node.handle_tun_outbound(ipv6_packet.clone()).await;
|
||||||
|
|
||||||
@@ -2864,7 +2864,7 @@ async fn test_multihop_pmtud_heterogeneous_mtu() {
|
|||||||
|
|
||||||
// Verify a fitting packet still passes through without PTB
|
// Verify a fitting packet still passes through without PTB
|
||||||
let (tun_tx3, tun_rx3) = std::sync::mpsc::channel();
|
let (tun_tx3, tun_rx3) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx3);
|
nodes[0].node.install_tun(tun_tx3);
|
||||||
|
|
||||||
let fitting_payload = vec![0xCDu8; 600 - 40]; // 600-byte IPv6 packet, well within 694
|
let fitting_payload = vec![0xCDu8; 600 - 40]; // 600-byte IPv6 packet, well within 694
|
||||||
let fitting_packet = build_ipv6_packet(&src_fips, &dst_fips, &fitting_payload);
|
let fitting_packet = build_ipv6_packet(&src_fips, &dst_fips, &fitting_payload);
|
||||||
@@ -3188,7 +3188,7 @@ async fn test_forged_mtu_exceeded_of_zero_does_not_blackhole_the_session() {
|
|||||||
nodes[0].node.handle_mtu_exceeded(&reporter, &inner).await;
|
nodes[0].node.handle_mtu_exceeded(&reporter, &inner).await;
|
||||||
|
|
||||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun_tx);
|
nodes[0].node.install_tun(tun_tx);
|
||||||
|
|
||||||
let payload = vec![0u8; 560];
|
let payload = vec![0u8; 560];
|
||||||
let ipv6_packet = build_ipv6_packet(&src_fips, &dst_fips, &payload);
|
let ipv6_packet = build_ipv6_packet(&src_fips, &dst_fips, &payload);
|
||||||
@@ -5377,9 +5377,9 @@ async fn a_lost_initial_msg3_is_resent_and_the_responder_completes_the_session()
|
|||||||
let node1_addr = *nodes[1].node.node_addr();
|
let node1_addr = *nodes[1].node.node_addr();
|
||||||
|
|
||||||
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun0_tx);
|
nodes[0].node.install_tun(tun0_tx);
|
||||||
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun1_tx);
|
nodes[1].node.install_tun(tun1_tx);
|
||||||
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||||
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||||
|
|
||||||
@@ -5432,7 +5432,7 @@ async fn an_initiator_stops_resending_msg3_once_a_responder_frame_authenticates(
|
|||||||
let node0_addr = *nodes[0].node.node_addr();
|
let node0_addr = *nodes[0].node.node_addr();
|
||||||
let node1_addr = *nodes[1].node.node_addr();
|
let node1_addr = *nodes[1].node.node_addr();
|
||||||
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun0_tx);
|
nodes[0].node.install_tun(tun0_tx);
|
||||||
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||||
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||||
let interval_ms = nodes[0]
|
let interval_ms = nodes[0]
|
||||||
@@ -5487,9 +5487,9 @@ async fn a_resent_msg3_reaching_an_established_responder_is_refused_and_the_sess
|
|||||||
let node0_addr = *nodes[0].node.node_addr();
|
let node0_addr = *nodes[0].node.node_addr();
|
||||||
let node1_addr = *nodes[1].node.node_addr();
|
let node1_addr = *nodes[1].node.node_addr();
|
||||||
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
let (tun0_tx, tun0_rx) = std::sync::mpsc::channel();
|
||||||
nodes[0].node.supervisor.tun_tx = Some(tun0_tx);
|
nodes[0].node.install_tun(tun0_tx);
|
||||||
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
let (tun1_tx, tun1_rx) = std::sync::mpsc::channel();
|
||||||
nodes[1].node.supervisor.tun_tx = Some(tun1_tx);
|
nodes[1].node.install_tun(tun1_tx);
|
||||||
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
let fips0 = crate::FipsAddress::from_node_addr(&node0_addr);
|
||||||
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
let fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||||
let interval_ms = nodes[0]
|
let interval_ms = nodes[0]
|
||||||
|
|||||||
@@ -3963,6 +3963,7 @@ async fn dns_responder_serves_a_proxying_embedder() {
|
|||||||
let identity = tokio::time::timeout(
|
let identity = tokio::time::timeout(
|
||||||
std::time::Duration::from_secs(2),
|
std::time::Duration::from_secs(2),
|
||||||
node.supervisor
|
node.supervisor
|
||||||
|
.ipv6tun
|
||||||
.dns_identity_rx
|
.dns_identity_rx
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("responder installed the identity receiver")
|
.expect("responder installed the identity receiver")
|
||||||
@@ -4707,8 +4708,8 @@ fn mesh_filter_resolves_the_live_tun_device_rather_than_the_configured_name() {
|
|||||||
config.tun.name = Some("fips-absent-dev".to_string());
|
config.tun.name = Some("fips-absent-dev".to_string());
|
||||||
let mut node = Node::new(config).unwrap();
|
let mut node = Node::new(config).unwrap();
|
||||||
|
|
||||||
assert_eq!(node.mesh_ifindex(), None);
|
assert_eq!(node.supervisor.ipv6tun.mesh_ifindex(), None);
|
||||||
|
|
||||||
node.tun_name = Some(loopback.to_string());
|
node.supervisor.ipv6tun.tun_name = Some(loopback.to_string());
|
||||||
assert_eq!(node.mesh_ifindex(), Some(expected));
|
assert_eq!(node.supervisor.ipv6tun.mesh_ifindex(), Some(expected));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ x-fips-common: &fips-common
|
|||||||
restart: "no"
|
restart: "no"
|
||||||
environment:
|
environment:
|
||||||
- FIPS_TEST_MODE=default
|
- FIPS_TEST_MODE=default
|
||||||
- RUST_LOG=info,fips::node=debug,fips::ipv6tun::icmp=debug
|
- RUST_LOG=info,fips::node=debug,fips::ipv6tun::icmp=debug,fips::ipv6tun::lifecycle=debug
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ x-fips-common: &fips-common
|
|||||||
restart: "no"
|
restart: "no"
|
||||||
environment:
|
environment:
|
||||||
- FIPS_TEST_MODE=default
|
- FIPS_TEST_MODE=default
|
||||||
- RUST_LOG=info,fips::node=debug,fips::ipv6tun::icmp=debug
|
- RUST_LOG=info,fips::node=debug,fips::ipv6tun::icmp=debug,fips::ipv6tun::lifecycle=debug
|
||||||
|
|
||||||
services:
|
services:
|
||||||
service-a:
|
service-a:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ x-fips-common: &fips-common
|
|||||||
# the daemon, which is precisely the workaround this mechanism retires. The
|
# the daemon, which is precisely the workaround this mechanism retires. The
|
||||||
# daemon must do its own waiting here or the suite proves nothing.
|
# daemon must do its own waiting here or the suite proves nothing.
|
||||||
- FIPS_TEST_MODE=default
|
- FIPS_TEST_MODE=default
|
||||||
- RUST_LOG=info,fips::transport::ethernet=debug,fips::node=debug,fips::ipv6tun::icmp=debug
|
- RUST_LOG=info,fips::transport::ethernet=debug,fips::node=debug,fips::ipv6tun::icmp=debug,fips::ipv6tun::lifecycle=debug
|
||||||
networks:
|
networks:
|
||||||
- ifb-net
|
- ifb-net
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ rep does, set them in the invoking shell:
|
|||||||
`handshake`, `forwarding`, `session`, `encrypted`, `mmp`
|
`handshake`, `forwarding`, `session`, `encrypted`, `mmp`
|
||||||
(via `compose-trace.yml`).
|
(via `compose-trace.yml`).
|
||||||
- nat-lan — `fips::nostr`, `transport::udp`,
|
- nat-lan — `fips::nostr`, `transport::udp`,
|
||||||
`node::lifecycle`, `handlers::handshake`, `dataplane::forwarding`
|
`node::lifecycle`, `ipv6tun::lifecycle`, `handlers::handshake`,
|
||||||
|
`dataplane::forwarding`
|
||||||
(via `compose-trace-nat.yml`, picked up by
|
(via `compose-trace-nat.yml`, picked up by
|
||||||
`testing/nat/scripts/nat-test.sh` through the
|
`testing/nat/scripts/nat-test.sh` through the
|
||||||
`FIPS_NAT_EXTRA_COMPOSE` env-var hook).
|
`FIPS_NAT_EXTRA_COMPOSE` env-var hook).
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
# (where the punch packets flow)
|
# (where the punch packets flow)
|
||||||
# - fips::node::lifecycle — daemon bootstrap, peer state
|
# - fips::node::lifecycle — daemon bootstrap, peer state
|
||||||
# machine, adoption transitions
|
# machine, adoption transitions
|
||||||
|
# - fips::ipv6tun::lifecycle — TUN and DNS child start/stop
|
||||||
|
# (formerly logged under
|
||||||
|
# node::lifecycle)
|
||||||
# - fips::node::handlers::handshake — Noise handshake msg1/2/3,
|
# - fips::node::handlers::handshake — Noise handshake msg1/2/3,
|
||||||
# cross-init tie-breaker
|
# cross-init tie-breaker
|
||||||
# ("Ignoring established NAT
|
# ("Ignoring established NAT
|
||||||
@@ -33,7 +36,7 @@
|
|||||||
# is set and the suite is nat-lan.
|
# is set and the suite is nat-lan.
|
||||||
|
|
||||||
x-trace-rust-log: &trace-rust-log
|
x-trace-rust-log: &trace-rust-log
|
||||||
RUST_LOG: "info,fips::nostr=trace,fips::transport::udp=trace,fips::node::lifecycle=trace,fips::node::handlers::handshake=trace,fips::node::dataplane::forwarding=trace"
|
RUST_LOG: "info,fips::nostr=trace,fips::transport::udp=trace,fips::node::lifecycle=trace,fips::ipv6tun::lifecycle=trace,fips::node::handlers::handshake=trace,fips::node::dataplane::forwarding=trace"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
lan-a:
|
lan-a:
|
||||||
|
|||||||
@@ -433,9 +433,10 @@ run_nat_lan() {
|
|||||||
# nat-test.sh at the nat-specific trace overlay via the
|
# nat-test.sh at the nat-specific trace overlay via the
|
||||||
# FIPS_NAT_EXTRA_COMPOSE env-var hook in nat-test.sh. The overlay
|
# FIPS_NAT_EXTRA_COMPOSE env-var hook in nat-test.sh. The overlay
|
||||||
# bumps RUST_LOG to trace on discovery::nostr, transport::udp,
|
# bumps RUST_LOG to trace on discovery::nostr, transport::udp,
|
||||||
# node::lifecycle, handlers::handshake, dataplane::forwarding —
|
# node::lifecycle, ipv6tun::lifecycle, handlers::handshake,
|
||||||
# the modules covering the cross-init / adoption / handshake
|
# dataplane::forwarding — the modules covering the cross-init /
|
||||||
# path that the NAT-traversal flake exhibits. Path is repo-relative.
|
# adoption / handshake path that the NAT-traversal flake exhibits.
|
||||||
|
# Path is repo-relative.
|
||||||
local -a env_args=(FIPS_NAT_SKIP_FINAL_CLEANUP=1)
|
local -a env_args=(FIPS_NAT_SKIP_FINAL_CLEANUP=1)
|
||||||
if [ -n "${FIPS_MESH_LAB_TRACE:-}" ]; then
|
if [ -n "${FIPS_MESH_LAB_TRACE:-}" ]; then
|
||||||
env_args+=(FIPS_NAT_EXTRA_COMPOSE=testing/mesh-lab/compose-trace-nat.yml)
|
env_args+=(FIPS_NAT_EXTRA_COMPOSE=testing/mesh-lab/compose-trace-nat.yml)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ x-fips-common: &fips-common
|
|||||||
- net.ipv6.conf.all.disable_ipv6=0
|
- net.ipv6.conf.all.disable_ipv6=0
|
||||||
restart: "no"
|
restart: "no"
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug
|
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug,fips::ipv6tun::lifecycle=debug
|
||||||
|
|
||||||
services:
|
services:
|
||||||
relay:
|
relay:
|
||||||
@@ -134,7 +134,7 @@ services:
|
|||||||
entrypoint:
|
entrypoint:
|
||||||
- /usr/local/bin/nat-node-entrypoint.sh
|
- /usr/local/bin/nat-node-entrypoint.sh
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug
|
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug,fips::ipv6tun::lifecycle=debug
|
||||||
- DATA_IF=eth0
|
- DATA_IF=eth0
|
||||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||||
- ROUTE_VIA=172.31.1.254
|
- ROUTE_VIA=172.31.1.254
|
||||||
@@ -160,7 +160,7 @@ services:
|
|||||||
entrypoint:
|
entrypoint:
|
||||||
- /usr/local/bin/nat-node-entrypoint.sh
|
- /usr/local/bin/nat-node-entrypoint.sh
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug
|
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug,fips::ipv6tun::lifecycle=debug
|
||||||
- DATA_IF=eth0
|
- DATA_IF=eth0
|
||||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||||
- ROUTE_VIA=172.31.2.254
|
- ROUTE_VIA=172.31.2.254
|
||||||
@@ -186,7 +186,7 @@ services:
|
|||||||
entrypoint:
|
entrypoint:
|
||||||
- /usr/local/bin/nat-node-entrypoint.sh
|
- /usr/local/bin/nat-node-entrypoint.sh
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug
|
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug,fips::ipv6tun::lifecycle=debug
|
||||||
- DATA_IF=eth0
|
- DATA_IF=eth0
|
||||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||||
- ROUTE_VIA=172.31.1.254
|
- ROUTE_VIA=172.31.1.254
|
||||||
@@ -212,7 +212,7 @@ services:
|
|||||||
entrypoint:
|
entrypoint:
|
||||||
- /usr/local/bin/nat-node-entrypoint.sh
|
- /usr/local/bin/nat-node-entrypoint.sh
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug
|
- RUST_LOG=info,fips::nostr=debug,fips::node::lifecycle=debug,fips::ipv6tun::lifecycle=debug
|
||||||
- DATA_IF=eth0
|
- DATA_IF=eth0
|
||||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||||
- ROUTE_VIA=172.31.2.254
|
- ROUTE_VIA=172.31.2.254
|
||||||
|
|||||||
Reference in New Issue
Block a user