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
|
||||
limiting ICMP Packet Too Big") log as `fips::ipv6tun::icmp` rather than
|
||||
`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
|
||||
and simply stops matching, so the symptom is missing log lines rather than an
|
||||
error.
|
||||
shows them. The TUN and DNS start and stop lines ("TUN device active",
|
||||
"effective MTU", "max TCP MSS", "Shutting down TUN interface", "DNS responder
|
||||
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
|
||||
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_rate_limit;
|
||||
pub mod ipv6_shim;
|
||||
pub(crate) mod lifecycle;
|
||||
pub(crate) mod outbound;
|
||||
pub mod tcp_mss;
|
||||
pub mod tun;
|
||||
|
||||
@@ -79,7 +79,8 @@ impl Node {
|
||||
// Take the TUN outbound receiver, or create a dummy channel that never
|
||||
// produces messages (when TUN is disabled). Holding the sender prevents
|
||||
// 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),
|
||||
None => {
|
||||
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
|
||||
// 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),
|
||||
None => {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
@@ -462,7 +462,7 @@ impl Node {
|
||||
mark_ipv6_ecn_ce(&mut packet);
|
||||
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) {
|
||||
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<'_> {
|
||||
let our_ipv6 = crate::FipsAddress::from_node_addr(self.node_addr()).to_ipv6();
|
||||
IcmpContext::new(
|
||||
self.supervisor.tun_tx.as_ref(),
|
||||
self.supervisor.ipv6tun.tun_tx.as_ref(),
|
||||
our_ipv6,
|
||||
&mut self.icmp_rate_limiter,
|
||||
)
|
||||
|
||||
+28
-282
@@ -11,6 +11,7 @@ use super::peering::reconcile::{
|
||||
use super::peering::retry::MAX_RETRY_CONNECTIONS_PER_TICK;
|
||||
|
||||
use crate::config::{ConnectPolicy, PeerAddress, PeerConfig};
|
||||
use crate::ipv6tun::lifecycle::{TunThreads, open_tun};
|
||||
use crate::node::acl::PeerAclContext;
|
||||
use crate::node::dataplane::PeerActionCtx;
|
||||
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::{Disconnect, DisconnectReason};
|
||||
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 std::collections::{HashMap, HashSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
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
|
||||
/// 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.
|
||||
pub(in crate::node) async fn report_exit(
|
||||
pub(crate) async fn report_exit(
|
||||
child: Child,
|
||||
body: impl std::future::Future<Output = ()>,
|
||||
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`,
|
||||
/// whether the body returned or panicked.
|
||||
pub(in crate::node) fn report_thread(
|
||||
pub(crate) fn report_thread(
|
||||
child: Child,
|
||||
body: impl FnOnce(),
|
||||
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
|
||||
// `tun_tx` via `enable_app_owned_tun`) — FIPS does no system-TUN ops;
|
||||
// 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;
|
||||
|
||||
// Worker-pool booleans + counts. Unix only — the workers issue
|
||||
@@ -1706,246 +1706,45 @@ impl Node {
|
||||
// Initialize TUN interface after transports and peers are
|
||||
// ready.
|
||||
let address = *self.identity().address();
|
||||
match TunDevice::create(&self.config().tun, address).await {
|
||||
Ok(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);
|
||||
|
||||
match open_tun(&self.config().tun, address).await {
|
||||
Some(device) => {
|
||||
// Seed the shared MSS ceiling from whatever is bound
|
||||
// right now. Both TUN threads read it live from here
|
||||
// on, so a transport binding or unbinding later moves
|
||||
// the clamp instead of leaving it at this instant's
|
||||
// value — see `crate::upper::tun::MssCeiling`.
|
||||
self.refresh_tun_mss_ceiling();
|
||||
let max_mss = self.tun_mss_ceiling.clone();
|
||||
let effective_mtu = self.effective_ipv6_mtu();
|
||||
|
||||
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(NodeError::Tun(
|
||||
crate::upper::tun::TunError::Configure(
|
||||
"failed to create shutdown pipe".into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
(fds[0], fds[1])
|
||||
let threads = TunThreads {
|
||||
ceiling: self.tun_mss_ceiling.clone(),
|
||||
effective: self.effective_ipv6_mtu(),
|
||||
path_mtu: self.path_mtu_lookup.clone(),
|
||||
channel: self.config().node.buffers.tun_channel,
|
||||
exit_tx: self.child_exit_tx.clone(),
|
||||
};
|
||||
|
||||
// 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.supervisor.ipv6tun.spawn_tun(device, threads)?;
|
||||
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 }
|
||||
}
|
||||
Err(e) => {
|
||||
None => {
|
||||
self.tun_state = TunState::Failed;
|
||||
warn!(error = %e, "Failed to initialize TUN, continuing without it");
|
||||
Event::SubstrateFailed { child }
|
||||
}
|
||||
}
|
||||
}
|
||||
Child::Dns => {
|
||||
// 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/upper/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 = 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 config = &self.context.config;
|
||||
let up = self.supervisor.ipv6tun.start_dns(
|
||||
&config.dns,
|
||||
config.peers(),
|
||||
config.node.buffers.dns_channel,
|
||||
self.child_exit_tx.clone(),
|
||||
);
|
||||
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);
|
||||
if up {
|
||||
Event::SubstrateUp { child }
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(bind = %bind, error = %e, "Failed to start DNS responder");
|
||||
} else {
|
||||
Event::SubstrateFailed { child }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(addr = %addr_str, error = %e, "Invalid dns.bind_addr; DNS responder not started");
|
||||
Event::SubstrateFailed { child }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Drain any presence edges this child's start produced *before*
|
||||
@@ -2072,20 +1871,6 @@ impl Node {
|
||||
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.
|
||||
///
|
||||
/// Shuts down TUN interface, stops I/O threads, and transitions to
|
||||
@@ -2161,15 +1946,7 @@ impl Node {
|
||||
|
||||
match child {
|
||||
Child::Dns => {
|
||||
// Stop DNS responder
|
||||
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();
|
||||
self.supervisor.ipv6tun.stop_dns();
|
||||
}
|
||||
Child::Nostr => {
|
||||
// Stop Nostr overlay discovery background work and withdraw
|
||||
@@ -2209,38 +1986,7 @@ impl Node {
|
||||
}
|
||||
Child::Tun => {
|
||||
// Shutdown TUN interface
|
||||
if let Some(name) = self.tun_name.take() {
|
||||
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();
|
||||
}
|
||||
|
||||
if self.supervisor.ipv6tun.stop_tun().await {
|
||||
self.tun_state = TunState::Disabled;
|
||||
}
|
||||
}
|
||||
@@ -2297,7 +2043,7 @@ impl Node {
|
||||
/// `Child::Dns`, which is what reaches this.
|
||||
pub(in crate::node) fn retract_child_publications(&mut self, child: Child) {
|
||||
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`].
|
||||
fn reconstruct_supervised_up(&self) -> Vec<Child> {
|
||||
let mut up: Vec<Child> = Vec::new();
|
||||
if self.supervisor.dns_task.is_some() {
|
||||
if self.supervisor.ipv6tun.dns_up() {
|
||||
up.push(Child::Dns);
|
||||
}
|
||||
if self.supervisor.nostr_rendezvous.engine().is_some() {
|
||||
@@ -2363,7 +2109,7 @@ impl Node {
|
||||
for id in self.transports.keys() {
|
||||
up.push(Child::Transport(*id));
|
||||
}
|
||||
if self.tun_name.is_some() {
|
||||
if self.supervisor.ipv6tun.tun_up() {
|
||||
up.push(Child::Tun);
|
||||
}
|
||||
up
|
||||
|
||||
@@ -103,11 +103,9 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use crate::node::NodeState;
|
||||
use crate::transport::{PacketTx, TransportId};
|
||||
use crate::upper::tun::{TunOutboundRx, TunTx};
|
||||
|
||||
/// A supervised substrate child.
|
||||
///
|
||||
@@ -395,7 +393,7 @@ impl SupervisorFsm {
|
||||
/// A supervisor seeded directly into `Running` with a known up-set.
|
||||
///
|
||||
/// 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
|
||||
/// teardown ordering is authored here regardless of how the node reached
|
||||
/// `Running`. Feeding `Event::Stop` then yields the ordered `StopChild`
|
||||
@@ -811,29 +809,10 @@ pub(crate) struct Supervisor {
|
||||
/// Packet sender for transports.
|
||||
pub(in crate::node) packet_tx: Option<PacketTx>,
|
||||
|
||||
/// TUN packet sender channel.
|
||||
pub(in crate::node) tun_tx: Option<TunTx>,
|
||||
/// Receiver for outbound packets from the TUN reader.
|
||||
pub(in crate::node) tun_outbound_rx: Option<TunOutboundRx>,
|
||||
/// 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>,
|
||||
/// TUN and DNS child handles: the TUN device name, channels, reader and
|
||||
/// writer threads and (macOS/FreeBSD) shutdown pipe, and the DNS
|
||||
/// responder task, identity receiver and bound address.
|
||||
pub(in crate::node) ipv6tun: crate::ipv6tun::lifecycle::Handles,
|
||||
|
||||
/// Sender for each UDP listen socket the transport spawn binds — its raw
|
||||
/// fd and the instance name it was configured under — armed by
|
||||
@@ -892,15 +871,7 @@ impl Supervisor {
|
||||
Self {
|
||||
state: NodeState::Created,
|
||||
packet_tx: None,
|
||||
tun_tx: None,
|
||||
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,
|
||||
ipv6tun: Default::default(),
|
||||
#[cfg(unix)]
|
||||
udp_fd_tx: None,
|
||||
nostr_rendezvous: crate::nostr::RendezvousDriver::default(),
|
||||
|
||||
+14
-11
@@ -15,7 +15,7 @@ pub(crate) mod decrypt_worker;
|
||||
#[cfg(unix)]
|
||||
pub(crate) mod encrypt_worker;
|
||||
mod handlers;
|
||||
mod lifecycle;
|
||||
pub(crate) mod lifecycle;
|
||||
pub(crate) mod metrics;
|
||||
pub(crate) mod netmon;
|
||||
pub use netmon::NetmonTrigger;
|
||||
@@ -606,8 +606,6 @@ pub struct Node {
|
||||
// === TUN Interface ===
|
||||
/// TUN device state.
|
||||
tun_state: TunState,
|
||||
/// TUN interface name (for cleanup).
|
||||
tun_name: Option<String>,
|
||||
|
||||
/// Slot the embedder installs its BLE radio into, armed by
|
||||
/// [`Self::enable_app_owned_ble_radio`]. `None` unless armed.
|
||||
@@ -913,7 +911,6 @@ impl Node {
|
||||
crate::control::snapshot::NativeSnapshot::empty(),
|
||||
)),
|
||||
tun_state,
|
||||
tun_name: None,
|
||||
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
||||
ble_radio: None,
|
||||
index_allocator: IndexAllocator::new(),
|
||||
@@ -1087,7 +1084,6 @@ impl Node {
|
||||
crate::control::snapshot::NativeSnapshot::empty(),
|
||||
)),
|
||||
tun_state,
|
||||
tun_name: None,
|
||||
#[cfg(all(ble_available, any(target_os = "android", test)))]
|
||||
ble_radio: None,
|
||||
index_allocator: IndexAllocator::new(),
|
||||
@@ -1936,7 +1932,7 @@ impl Node {
|
||||
estimated_mesh_size: self.estimated_mesh_size,
|
||||
state: self.supervisor.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(),
|
||||
connection_count: self.connection_count(),
|
||||
peer_count: self.peers.len(),
|
||||
@@ -2651,7 +2647,7 @@ impl Node {
|
||||
|
||||
/// Get the TUN interface name, if active.
|
||||
pub fn tun_name(&self) -> Option<&str> {
|
||||
self.tun_name.as_deref()
|
||||
self.supervisor.ipv6tun.tun_name.as_deref()
|
||||
}
|
||||
|
||||
// === Resource Limits ===
|
||||
@@ -3532,7 +3528,14 @@ impl Node {
|
||||
///
|
||||
/// Returns None if TUN is not active or the node hasn't been started.
|
||||
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
|
||||
@@ -3559,8 +3562,8 @@ impl Node {
|
||||
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.
|
||||
let (tun_tx, tun_rx) = std::sync::mpsc::channel();
|
||||
self.supervisor.tun_tx = Some(tun_tx);
|
||||
self.supervisor.tun_outbound_rx = Some(outbound_rx);
|
||||
self.supervisor.ipv6tun.tun_tx = Some(tun_tx);
|
||||
self.supervisor.ipv6tun.tun_outbound_rx = Some(outbound_rx);
|
||||
self.tun_state = TunState::Active;
|
||||
(outbound_tx, tun_rx)
|
||||
}
|
||||
@@ -3733,7 +3736,7 @@ impl Node {
|
||||
/// Reading live node state from a backgrounded loop is a general gap, not
|
||||
/// one this accessor tries to close.
|
||||
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
|
||||
|
||||
@@ -1857,7 +1857,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() {
|
||||
|
||||
// Inject a TUN sender so `send_icmpv6_dest_unreachable` is observable.
|
||||
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).
|
||||
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);
|
||||
for tn in nodes.iter_mut() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
tn.node.supervisor.tun_tx = Some(tx);
|
||||
tn.node.install_tun(tx);
|
||||
tun_receivers.push(rx);
|
||||
}
|
||||
|
||||
@@ -1051,7 +1051,7 @@ async fn test_tun_outbound_established_session() {
|
||||
|
||||
// Install TUN receiver on Node 1
|
||||
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
|
||||
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.
|
||||
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 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.
|
||||
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();
|
||||
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 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
|
||||
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)
|
||||
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)
|
||||
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());
|
||||
|
||||
@@ -1928,7 +1928,7 @@ async fn test_tun_outbound_3node_forwarded() {
|
||||
|
||||
// Install TUN receiver on Node 2
|
||||
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)
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
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_packet = build_ipv6_packet(&src_fips, &dst_fips, &fitting_payload);
|
||||
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
|
||||
// of forwarding.
|
||||
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;
|
||||
|
||||
@@ -2864,7 +2864,7 @@ async fn test_multihop_pmtud_heterogeneous_mtu() {
|
||||
|
||||
// Verify a fitting packet still passes through without PTB
|
||||
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_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;
|
||||
|
||||
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 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 (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();
|
||||
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 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 node1_addr = *nodes[1].node.node_addr();
|
||||
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 fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||
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 node1_addr = *nodes[1].node.node_addr();
|
||||
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();
|
||||
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 fips1 = crate::FipsAddress::from_node_addr(&node1_addr);
|
||||
let interval_ms = nodes[0]
|
||||
|
||||
@@ -3963,6 +3963,7 @@ async fn dns_responder_serves_a_proxying_embedder() {
|
||||
let identity = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
node.supervisor
|
||||
.ipv6tun
|
||||
.dns_identity_rx
|
||||
.as_mut()
|
||||
.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());
|
||||
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());
|
||||
assert_eq!(node.mesh_ifindex(), Some(expected));
|
||||
node.supervisor.ipv6tun.tun_name = Some(loopback.to_string());
|
||||
assert_eq!(node.supervisor.ipv6tun.mesh_ifindex(), Some(expected));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ x-fips-common: &fips-common
|
||||
restart: "no"
|
||||
environment:
|
||||
- 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:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ x-fips-common: &fips-common
|
||||
restart: "no"
|
||||
environment:
|
||||
- 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:
|
||||
service-a:
|
||||
|
||||
@@ -33,7 +33,7 @@ x-fips-common: &fips-common
|
||||
# the daemon, which is precisely the workaround this mechanism retires. The
|
||||
# daemon must do its own waiting here or the suite proves nothing.
|
||||
- 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:
|
||||
- ifb-net
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ rep does, set them in the invoking shell:
|
||||
`handshake`, `forwarding`, `session`, `encrypted`, `mmp`
|
||||
(via `compose-trace.yml`).
|
||||
- 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
|
||||
`testing/nat/scripts/nat-test.sh` through the
|
||||
`FIPS_NAT_EXTRA_COMPOSE` env-var hook).
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
# (where the punch packets flow)
|
||||
# - fips::node::lifecycle — daemon bootstrap, peer state
|
||||
# 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,
|
||||
# cross-init tie-breaker
|
||||
# ("Ignoring established NAT
|
||||
@@ -33,7 +36,7 @@
|
||||
# is set and the suite is nat-lan.
|
||||
|
||||
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:
|
||||
lan-a:
|
||||
|
||||
@@ -433,9 +433,10 @@ run_nat_lan() {
|
||||
# nat-test.sh at the nat-specific trace overlay via the
|
||||
# FIPS_NAT_EXTRA_COMPOSE env-var hook in nat-test.sh. The overlay
|
||||
# bumps RUST_LOG to trace on discovery::nostr, transport::udp,
|
||||
# node::lifecycle, handlers::handshake, dataplane::forwarding —
|
||||
# the modules covering the cross-init / adoption / handshake
|
||||
# path that the NAT-traversal flake exhibits. Path is repo-relative.
|
||||
# node::lifecycle, ipv6tun::lifecycle, handlers::handshake,
|
||||
# dataplane::forwarding — the modules covering the cross-init /
|
||||
# adoption / handshake path that the NAT-traversal flake exhibits.
|
||||
# Path is repo-relative.
|
||||
local -a env_args=(FIPS_NAT_SKIP_FINAL_CLEANUP=1)
|
||||
if [ -n "${FIPS_MESH_LAB_TRACE:-}" ]; then
|
||||
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
|
||||
restart: "no"
|
||||
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:
|
||||
relay:
|
||||
@@ -134,7 +134,7 @@ services:
|
||||
entrypoint:
|
||||
- /usr/local/bin/nat-node-entrypoint.sh
|
||||
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
|
||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||
- ROUTE_VIA=172.31.1.254
|
||||
@@ -160,7 +160,7 @@ services:
|
||||
entrypoint:
|
||||
- /usr/local/bin/nat-node-entrypoint.sh
|
||||
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
|
||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||
- ROUTE_VIA=172.31.2.254
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
entrypoint:
|
||||
- /usr/local/bin/nat-node-entrypoint.sh
|
||||
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
|
||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||
- ROUTE_VIA=172.31.1.254
|
||||
@@ -212,7 +212,7 @@ services:
|
||||
entrypoint:
|
||||
- /usr/local/bin/nat-node-entrypoint.sh
|
||||
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
|
||||
- ROUTE_SUBNET=${NAT_WAN_PREFIX:-172.31.254}.0/24
|
||||
- ROUTE_VIA=172.31.2.254
|
||||
|
||||
Reference in New Issue
Block a user