crates/gunnar-front/src/forward.rs
//! **The forge's public door: tcp/2222 on the front, forwarded to the
//! PRIMARY** (owner 2026-09-25: "keep the old shape" — the front is the public
//! face, `git.gunnar.rs` resolves to it, and `git push` reaches the primary's
//! git listener through it).
//!
//! The target is the verdict, not a setting: the primary's utility address out
//! of what the installer fed ([`target_of`] — `primary_utility`, or the peer
//! whose role is primary), port [`GIT_PORT`]. It is looked at again every
//! [`POLL`], and a change RE-TARGETS LIVE ([`Forward::retarget`]): the next
//! connection goes to the new primary, no restart, and connections already
//! open finish where they began. Until a primary is known a connection is
//! closed at once — refused, never parked.
//!
//! Plain TCP, byte for byte: the SSH session is end to end between the client and the
//! primary, so the host key a client sees IS the primary's. Pure Rust, no nftables, no
//! iptables. The forward itself is edda's ONE copy (`estate_front::forward`, RA5-34): it
//! refuses the front's utility address, holds a client while the primary cannot be dialled
//! yet, caps its connections and lets a silent one go ([`forge_options`]).
use crate::vm::FrontVm;
use estate_front::forward::{Forward, ForwardOptions};
use nordisk_pairing::join::JoinReply;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::sync::Arc;
use std::time::Duration;
/// The forge's port, on the front's every address and on the primary.
pub const GIT_PORT: u16 = 2222;
/// How often the fed reply is looked at again for the primary.
const POLL: Duration = Duration::from_secs(2);
/// How long a git client is held while the primary cannot be dialled yet, dialled again every
/// second. A front that just booted names its primary at once but reaches it only when its
/// tunnel is up: mock gate chain 18 (2026-10-25) measured the relay's first handshake 24 s
/// after the front's boot, and the one 10 s dial before it let the client go (`Connection reset
/// by peer`). An ssh client waits for the banner, so holding it is the kinder answer.
const DIAL_WINDOW: Duration = Duration::from_secs(60);
/// Pure: **where tcp/2222 goes, by the verdict** — the primary's OVERLAY
/// address ([`crate::primary_overlay`]: over the tunnel, WireGuard-only),
/// port [`GIT_PORT`]. `None` until the election has spoken.
pub fn target_of(reply: &JoinReply) -> Option<SocketAddrV4> {
Some(SocketAddrV4::new(crate::primary_overlay(reply)?, GIT_PORT))
}
/// Pure: **the forge door's options** — `utility` (the front's utility address) refused, a
/// client held [`DIAL_WINDOW`], edda's cap and idle time (RA5-34).
pub fn forge_options(utility: Option<Ipv4Addr>) -> ForwardOptions {
ForwardOptions {
refused: utility,
dial_window: DIAL_WINDOW,
..ForwardOptions::default()
}
}
/// **Run the forward for the life of the process**: listen on every address,
/// port [`GIT_PORT`], and follow the fed verdict. Returns at once.
pub fn start(vm: Arc<FrontVm>) {
// A32-14, stays a task: binds the forward's listeners for the life of the process (async, ends with the runtime).
tokio::spawn(async move {
let utility = appliance_pid1::net::interface_ipv4(crate::UTILITY_INTERFACE)
.ok()
.flatten();
let f = match Forward::bind_with(
SocketAddr::from((Ipv4Addr::UNSPECIFIED, GIT_PORT)),
"forge",
forge_options(utility),
)
.await
{
Ok(f) => f,
Err(e) => {
tracing::error!(error = %e, port = GIT_PORT, "front: the forge forward cannot listen — git.gunnar.rs:2222 will not answer");
return;
}
};
tracing::info!(listen = %f.local, "front: the forge forward listens; it follows the primary the installer names");
loop {
if let Some(reply) = vm.latest() {
f.retarget(target_of(&reply));
}
tokio::time::sleep(POLL).await;
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use nordisk_pairing::join::{Assigned, Peer};
use nordisk_pairing::Role;
use std::collections::BTreeMap;
fn reply(primary_utility: Option<Ipv4Addr>, peers: Vec<Peer>) -> JoinReply {
JoinReply {
estate: "gunnar.rs".into(),
me: Assigned {
name: "front".into(),
overlay: nordisk_pairing::OVERLAY_FRONT,
},
cert_pem: None,
role: Some(Role::Front),
primary_overlay: None,
primary_utility,
peers,
feed: BTreeMap::new(),
}
}
fn peer(name: &str, role: Role, utility: [u8; 4]) -> Peer {
// The overlay /32 follows the utility address's last octet (.2 main, .3 twin).
Peer {
name: name.into(),
role,
tunnel_pubkey: String::new(),
overlay: Ipv4Addr::new(10, 44, 0, utility[3]),
endpoint: None,
utility: Some(Ipv4Addr::from(utility)),
}
}
/// **WireGuard-only** (owner 2026-10-04): the target is the verdict — the
/// primary's OVERLAY address, never its utility one (refused there), never
/// the twin's.
#[test]
fn the_forward_goes_to_the_primary_the_installer_names_over_the_tunnel() {
assert_eq!(
target_of(&reply(None, vec![])),
None,
"no verdict, no target"
);
let peers = vec![
peer("twin", Role::Twin, [10, 13, 8, 3]),
peer("main", Role::Primary, [10, 13, 8, 2]),
];
assert_eq!(
target_of(&reply(None, peers.clone())),
Some(SocketAddrV4::new(Ipv4Addr::new(10, 44, 0, 2), 2222))
);
let said = JoinReply {
primary_overlay: Some(Ipv4Addr::new(10, 44, 0, 3)),
..reply(Some(Ipv4Addr::new(10, 13, 8, 9)), peers)
};
assert_eq!(
target_of(&said),
Some(SocketAddrV4::new(Ipv4Addr::new(10, 44, 0, 3), 2222)),
"the door's primary_overlay wins; primary_utility is never dialled"
);
}
/// ★ **RA5-34: the forge door is edda's one forward**, with the forge's options: the
/// utility address refused (boxes speak over the tunnel), a client held [`DIAL_WINDOW`]
/// while the primary cannot be dialled yet (mock gate chain 18), a cap and an idle time.
#[test]
fn the_forge_door_is_the_one_forward_with_the_forges_options() {
let o = forge_options(Some(Ipv4Addr::new(10, 13, 8, 1)));
assert_eq!(o.refused, Some(Ipv4Addr::new(10, 13, 8, 1)));
assert_eq!(o.dial_window, DIAL_WINDOW);
assert!(o.max_connections > 0 && o.idle > Duration::ZERO);
let code = include_str!("forward.rs");
let code = &code[..code.find("#[cfg(test)]").unwrap()];
assert!(
!code.contains(concat!("copy_", "bidirectional"))
&& !code.contains(concat!("TcpListener", "::bind")),
"a second forward is written here again"
);
}
}