mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 22:22:39 +02:00
fix(nesinit,neshub): bind the media port the host forwarded
neslet now puts nestri.port= on the boot line and installs its forward and pinned masquerade for that port before the box boots. nesinit hands it to neshub as NESTRI_BIND_PORT, and neshub binds 0.0.0.0:<port> in place of iroh's random default. Every flow, the relay's included, then leaves on one public port, and the address the relay reports is the one peers can punch through to. No nestri.port= keeps the old behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6bbce800c7
commit
87092d4693
+34
-3
@@ -10,6 +10,7 @@ mod screenshot;
|
||||
mod session;
|
||||
mod ticket;
|
||||
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -28,6 +29,16 @@ struct Args {
|
||||
#[arg(long, env = "NESTRI_RELAY", default_value = "default")]
|
||||
relay: String,
|
||||
|
||||
/// The UDP port to bind for IPv4, which the host forwards to this box.
|
||||
///
|
||||
/// Set by the guest init from `nestri.port=`. The host installs its forward
|
||||
/// and its pinned masquerade for this port before the box boots, so every
|
||||
/// flow, the relay's included, leaves on one public port, and the address
|
||||
/// the relay reports is the one peers can punch through to. Unset means a
|
||||
/// port of the hub's own choosing, which only works where nothing NATs it.
|
||||
#[arg(long, env = "NESTRI_BIND_PORT")]
|
||||
bind_port: Option<u16>,
|
||||
|
||||
/// Path for the video IPC socket (nescapture → neshub)
|
||||
#[arg(
|
||||
long,
|
||||
@@ -172,6 +183,19 @@ async fn main() -> Result<()> {
|
||||
.alpns(ALPNS.iter().map(|a| a.to_vec()).collect::<Vec<_>>())
|
||||
.transport_config(crate::dgram::media_transport_config());
|
||||
|
||||
// Replaces iroh's own `0.0.0.0:0` default rather than adding a second
|
||||
// IPv4 socket; IPv6 keeps its default.
|
||||
match args.bind_port {
|
||||
Some(port) => {
|
||||
builder = builder.bind_addr(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port))?;
|
||||
tracing::info!(port, "binding the host's forwarded port");
|
||||
}
|
||||
None => tracing::info!(
|
||||
"no NESTRI_BIND_PORT, so this hub picks its own port; behind the host's NAT, \
|
||||
peers behind a NAT of their own will stay on the relay"
|
||||
),
|
||||
}
|
||||
|
||||
match args.relay.as_str() {
|
||||
"default" | "" => {
|
||||
builder = builder.relay_mode(iroh::endpoint::RelayMode::Default);
|
||||
@@ -247,11 +271,17 @@ async fn main() -> Result<()> {
|
||||
let mut waited = 0u32;
|
||||
while sock.send_to(&bytes, &cmd_path).is_err() {
|
||||
if waited == 0 {
|
||||
tracing::info!("nescapture is not listening at {} yet; waiting", cmd_path.display());
|
||||
tracing::info!(
|
||||
"nescapture is not listening at {} yet; waiting",
|
||||
cmd_path.display()
|
||||
);
|
||||
}
|
||||
waited += 1;
|
||||
if waited > 600 {
|
||||
tracing::warn!("nescapture cmd send failed at {} for a minute; dropped", cmd_path.display());
|
||||
tracing::warn!(
|
||||
"nescapture cmd send failed at {} for a minute; dropped",
|
||||
cmd_path.display()
|
||||
);
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
@@ -433,7 +463,8 @@ async fn main() -> Result<()> {
|
||||
let stats_ipc = args.stats_ipc.clone();
|
||||
let stats_tx_clone = nescope_stats_tx.clone();
|
||||
tokio::spawn({
|
||||
let (mgr, controller, cmd_tx) = (session_manager.clone(), controller.clone(), cmd_tx.clone());
|
||||
let (mgr, controller, cmd_tx) =
|
||||
(session_manager.clone(), controller.clone(), cmd_tx.clone());
|
||||
async move { ipc_listener::run_video_listener(video_ipc, mgr, controller, cmd_tx).await }
|
||||
});
|
||||
tokio::spawn({
|
||||
|
||||
@@ -494,6 +494,12 @@ impl Stack {
|
||||
if let Some(kbps) = self.video.bitrate_kbps {
|
||||
command.env("NESTRI_MAX_BITRATE", kbps.to_string());
|
||||
}
|
||||
// The port the host forwarded, from the command line, by the same
|
||||
// route: `neshub` binds it so its first packet already leaves through
|
||||
// the host's pinned masquerade. See `system::media_port`.
|
||||
if let Some(port) = crate::system::media_port() {
|
||||
command.env("NESTRI_BIND_PORT", port.to_string());
|
||||
}
|
||||
// The service's own entry last, so a service that states one of these
|
||||
// for itself wins over the defaults above.
|
||||
command.envs(service.env.iter().copied());
|
||||
@@ -911,7 +917,10 @@ mod tests {
|
||||
#[test]
|
||||
fn the_system_bus_is_waited_for_before_what_connects_to_it() {
|
||||
let bus = STACK.iter().find(|s| s.name == "dbus-system").unwrap();
|
||||
assert!(bus.ready.is_some(), "polkitd and rtkit would find no socket");
|
||||
assert!(
|
||||
bus.ready.is_some(),
|
||||
"polkitd and rtkit would find no socket"
|
||||
);
|
||||
for name in ["polkitd", "rtkit"] {
|
||||
let svc = STACK.iter().find(|s| s.name == name).unwrap();
|
||||
assert!(svc.ready_name.is_some(), "{name} is reached by name");
|
||||
|
||||
@@ -463,6 +463,36 @@ fn resolver(cmdline: &str) {
|
||||
tracing::info!(%server, "the box resolves through this");
|
||||
}
|
||||
|
||||
/// The UDP port the host forwarded to this box, which `neshub` binds.
|
||||
///
|
||||
/// The host has to choose it, not the hub: the host installs its forward and
|
||||
/// the masquerade that keeps the box's flows on one public port before the box
|
||||
/// boots, and both name this port. A hub on a port of its own choosing sends
|
||||
/// its first packets past them, and from then on the relay sees one public port
|
||||
/// while peers see another, so hole punching fails against any NAT.
|
||||
///
|
||||
/// `None` when the command line says nothing, or says something that is not a
|
||||
/// port; the hub then picks its own, as it did before the host chose one.
|
||||
pub fn media_port() -> Option<u16> {
|
||||
let cmdline = std::fs::read_to_string("/proc/cmdline").unwrap_or_default();
|
||||
port(&cmdline)
|
||||
}
|
||||
|
||||
fn port(cmdline: &str) -> Option<u16> {
|
||||
let value = parameter(cmdline, "port")?;
|
||||
match value.parse::<u16>() {
|
||||
Ok(port) if port != 0 => Some(port),
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
%value,
|
||||
"nestri.port= is not a port, so the hub picks its own and peers behind a NAT \
|
||||
stay on the relay"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read one `nestri.<key>=<value>` from a kernel command line.
|
||||
///
|
||||
/// Split out because this is the part worth asserting: the rest of `network`
|
||||
@@ -638,6 +668,19 @@ mod tests {
|
||||
assert_eq!(parameter("othernestri.ip=1.2.3.4", "ip"), None);
|
||||
}
|
||||
|
||||
/// The host's port is read as a port, and anything else leaves the hub
|
||||
/// to pick its own rather than binding something the host did not forward.
|
||||
#[test]
|
||||
fn the_media_port_is_the_hosts_or_none() {
|
||||
let cmdline = "console=hvc0 nestri.ip=10.213.0.2/24 nestri.gw=10.213.0.1 \
|
||||
nestri.dns=1.1.1.1 nestri.port=47900";
|
||||
assert_eq!(port(cmdline), Some(47900));
|
||||
assert_eq!(port("console=hvc0 nestri.ip=10.213.0.2/24"), None);
|
||||
assert_eq!(port("nestri.port=0"), None);
|
||||
assert_eq!(port("nestri.port=70000"), None);
|
||||
assert_eq!(port("nestri.port=hub"), None);
|
||||
}
|
||||
|
||||
/// Each of these is a thing that stops working, said in those terms. The
|
||||
/// same rule the early filesystems hold themselves to.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user