mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
lan port forwarding
This commit is contained in:
@@ -18,6 +18,7 @@ use ssh_key::public::Ed25519PublicKey;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::config::spec::{PackagePointerSpec, SystemPointerSpec};
|
||||
use crate::install::progress::InstallProgress;
|
||||
use crate::net::forward::LanPortForwards;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
||||
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
||||
@@ -34,6 +35,7 @@ pub struct Database {
|
||||
pub server_info: ServerInfo,
|
||||
#[model]
|
||||
pub package_data: AllPackageData,
|
||||
pub lan_port_forwards: LanPortForwards,
|
||||
pub ui: Value,
|
||||
}
|
||||
impl Database {
|
||||
@@ -83,6 +85,7 @@ impl Database {
|
||||
zram: false,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
lan_port_forwards: LanPortForwards::new(),
|
||||
ui: serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json"))
|
||||
.unwrap(),
|
||||
}
|
||||
|
||||
@@ -95,16 +95,13 @@ impl OsApi for Manager {
|
||||
let mut secrets = self.seed.ctx.secret_store.acquire().await?;
|
||||
let mut tx = secrets.begin().await?;
|
||||
|
||||
svc.add_lan(&mut tx, id.clone(), external_port, internal_port, false)
|
||||
let addr = svc
|
||||
.add_lan(&mut tx, id.clone(), external_port, internal_port, false)
|
||||
.await
|
||||
.map_err(|e| eyre!("Could not add to local: {e:?}"))?;
|
||||
let key = Key::for_interface(&mut tx, Some((self.seed.manifest.id.clone(), id)))
|
||||
.await
|
||||
.map_err(|e| eyre!("Could not get network name: {e:?}"))?
|
||||
.local_address();
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(helpers::Address(key))
|
||||
Ok(helpers::Address(addr))
|
||||
}
|
||||
async fn bind_onion(
|
||||
&self,
|
||||
@@ -125,16 +122,13 @@ impl OsApi for Manager {
|
||||
let mut secrets = self.seed.ctx.secret_store.acquire().await?;
|
||||
let mut tx = secrets.begin().await?;
|
||||
|
||||
svc.add_tor(&mut tx, id.clone(), external_port, internal_port)
|
||||
let addr = svc
|
||||
.add_tor(&mut tx, id.clone(), external_port, internal_port)
|
||||
.await
|
||||
.map_err(|e| eyre!("Could not add to tor: {e:?}"))?;
|
||||
let key = Key::for_interface(&mut tx, Some((self.seed.manifest.id.clone(), id)))
|
||||
.await
|
||||
.map_err(|e| eyre!("Could not get network name: {e:?}"))?
|
||||
.tor_address()
|
||||
.to_string();
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(helpers::Address(key))
|
||||
Ok(helpers::Address(addr))
|
||||
}
|
||||
async fn unbind_local(&self, id: InterfaceId, external: u16) -> Result<(), Report> {
|
||||
let ip = try_get_running_ip(&self.seed)
|
||||
|
||||
192
backend/src/net/forward.rs
Normal file
192
backend/src/net/forward.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use id_pool::IdPool;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::util::Invoke;
|
||||
use crate::Error;
|
||||
|
||||
pub const START9_BRIDGE_IFACE: &str = "br-start9";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct LanPortForwards {
|
||||
pool: IdPool,
|
||||
allocated: BTreeMap<PackageId, BTreeMap<u16, u16>>,
|
||||
}
|
||||
impl LanPortForwards {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: IdPool::new_ranged(32768..u16::MAX),
|
||||
allocated: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn alloc(&mut self, package: PackageId, port: u16) -> Option<u16> {
|
||||
if let Some(res) = self.allocated.get(&package).and_then(|a| a.get(&port)) {
|
||||
Some(*res)
|
||||
} else if let Some(res) = self.pool.request_id() {
|
||||
let mut ports = self.allocated.remove(&package).unwrap_or_default();
|
||||
ports.insert(port, res);
|
||||
self.allocated.insert(package, ports);
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn dealloc(&mut self, package: &PackageId) {
|
||||
for port in self
|
||||
.allocated
|
||||
.remove(package)
|
||||
.into_iter()
|
||||
.flat_map(|p| p.into_values())
|
||||
{
|
||||
self.pool.return_id(port).unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LpfController {
|
||||
forwards: Mutex<BTreeMap<u16, BTreeMap<SocketAddr, Weak<()>>>>,
|
||||
}
|
||||
impl LpfController {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
forwards: Mutex::new(BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
pub async fn add(&self, port: u16, addr: SocketAddr) -> Result<Arc<()>, Error> {
|
||||
let mut writable = self.forwards.lock().await;
|
||||
let (prev, mut forward) = if let Some(forward) = writable.remove(&port) {
|
||||
(
|
||||
forward.keys().next().cloned(),
|
||||
forward
|
||||
.into_iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
(None, BTreeMap::new())
|
||||
};
|
||||
let rc = Arc::new(());
|
||||
forward.insert(addr, Arc::downgrade(&rc));
|
||||
let next = forward.keys().next().cloned();
|
||||
if !forward.is_empty() {
|
||||
writable.insert(port, forward);
|
||||
}
|
||||
|
||||
update_forward(port, prev, next).await?;
|
||||
Ok(rc)
|
||||
}
|
||||
pub async fn gc(&self, port: u16) -> Result<(), Error> {
|
||||
let mut writable = self.forwards.lock().await;
|
||||
let (prev, forward) = if let Some(forward) = writable.remove(&port) {
|
||||
(
|
||||
forward.keys().next().cloned(),
|
||||
forward
|
||||
.into_iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
(None, BTreeMap::new())
|
||||
};
|
||||
let next = forward.keys().next().cloned();
|
||||
if !forward.is_empty() {
|
||||
writable.insert(port, forward);
|
||||
}
|
||||
|
||||
update_forward(port, prev, next).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_forward(
|
||||
port: u16,
|
||||
prev: Option<SocketAddr>,
|
||||
next: Option<SocketAddr>,
|
||||
) -> Result<(), Error> {
|
||||
if prev != next {
|
||||
if let Some(prev) = prev {
|
||||
unforward(START9_BRIDGE_IFACE, port, prev).await?;
|
||||
}
|
||||
if let Some(next) = next {
|
||||
forward(START9_BRIDGE_IFACE, port, next).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// iptables -I FORWARD -o br-start9 -p tcp -d 172.18.0.2 --dport 8333 -j ACCEPT
|
||||
// iptables -t nat -I PREROUTING -p tcp --dport 32768 -j DNAT --to 172.18.0.2:8333
|
||||
async fn forward(iface: &str, port: u16, addr: SocketAddr) -> Result<(), Error> {
|
||||
Command::new("iptables")
|
||||
.arg("-I")
|
||||
.arg("FORWARD")
|
||||
.arg("-o")
|
||||
.arg(iface)
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("-d")
|
||||
.arg(addr.ip().to_string())
|
||||
.arg("--dport")
|
||||
.arg(addr.port().to_string())
|
||||
.arg("-j")
|
||||
.arg("ACCEPT")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("iptables")
|
||||
.arg("-t")
|
||||
.arg("nat")
|
||||
.arg("-I")
|
||||
.arg("PREROUTING")
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("--dport")
|
||||
.arg(port.to_string())
|
||||
.arg("-j")
|
||||
.arg("DNAT")
|
||||
.arg("--to")
|
||||
.arg(addr.to_string())
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// iptables -D FORWARD -o br-start9 -p tcp -d 172.18.0.2 --dport 8333 -j ACCEPT
|
||||
// iptables -t nat -D PREROUTING -p tcp --dport 32768 -j DNAT --to 172.18.0.2:8333
|
||||
async fn unforward(iface: &str, port: u16, addr: SocketAddr) -> Result<(), Error> {
|
||||
Command::new("iptables")
|
||||
.arg("-D")
|
||||
.arg("FORWARD")
|
||||
.arg("-o")
|
||||
.arg(iface)
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("-d")
|
||||
.arg(addr.ip().to_string())
|
||||
.arg("--dport")
|
||||
.arg(addr.port().to_string())
|
||||
.arg("-j")
|
||||
.arg("ACCEPT")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Command::new("iptables")
|
||||
.arg("-t")
|
||||
.arg("nat")
|
||||
.arg("-D")
|
||||
.arg("PREROUTING")
|
||||
.arg("-p")
|
||||
.arg("tcp")
|
||||
.arg("--dport")
|
||||
.arg(port.to_string())
|
||||
.arg("-j")
|
||||
.arg("DNAT")
|
||||
.arg("--to")
|
||||
.arg(addr.to_string())
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use crate::Error;
|
||||
|
||||
pub mod dhcp;
|
||||
pub mod dns;
|
||||
pub mod forward;
|
||||
pub mod interface;
|
||||
pub mod keys;
|
||||
#[cfg(feature = "avahi")]
|
||||
|
||||
@@ -4,12 +4,14 @@ use std::sync::{Arc, Weak};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use models::InterfaceId;
|
||||
use patch_db::{DbHandle, LockType, PatchDb};
|
||||
use sqlx::PgExecutor;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::error::ErrorCollection;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::net::dns::DnsController;
|
||||
use crate::net::forward::LpfController;
|
||||
use crate::net::keys::Key;
|
||||
#[cfg(feature = "avahi")]
|
||||
use crate::net::mdns::MdnsController;
|
||||
@@ -26,6 +28,7 @@ pub struct NetController {
|
||||
pub(super) mdns: MdnsController,
|
||||
pub(super) vhost: VHostController,
|
||||
pub(super) dns: DnsController,
|
||||
pub(super) lpf: LpfController,
|
||||
pub(super) ssl: Arc<SslManager>,
|
||||
pub(super) os_bindings: Vec<Arc<()>>,
|
||||
}
|
||||
@@ -47,6 +50,7 @@ impl NetController {
|
||||
mdns: MdnsController::init().await?,
|
||||
vhost: VHostController::new(ssl.clone()),
|
||||
dns: DnsController::init(dns_bind).await?,
|
||||
lpf: LpfController::new(),
|
||||
ssl,
|
||||
os_bindings: Vec::new(),
|
||||
};
|
||||
@@ -155,6 +159,7 @@ impl NetController {
|
||||
controller: Arc::downgrade(self),
|
||||
tor: BTreeMap::new(),
|
||||
lan: BTreeMap::new(),
|
||||
lpf: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -204,6 +209,15 @@ impl NetController {
|
||||
self.mdns.gc(key.base_address()).await?;
|
||||
self.vhost.gc(Some(key.local_address()), external).await
|
||||
}
|
||||
|
||||
async fn add_lpf(&self, external: u16, target: SocketAddr) -> Result<Arc<()>, Error> {
|
||||
self.lpf.add(external, target).await
|
||||
}
|
||||
|
||||
async fn remove_lpf(&self, external: u16, rcs: Vec<Arc<()>>) -> Result<(), Error> {
|
||||
drop(rcs);
|
||||
self.lpf.gc(external).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetService {
|
||||
@@ -213,6 +227,7 @@ pub struct NetService {
|
||||
controller: Weak<NetController>,
|
||||
tor: BTreeMap<(InterfaceId, u16), (Key, Vec<Arc<()>>)>,
|
||||
lan: BTreeMap<(InterfaceId, u16), (Key, Vec<Arc<()>>)>,
|
||||
lpf: BTreeMap<u16, (u16, Vec<Arc<()>>)>,
|
||||
}
|
||||
impl NetService {
|
||||
#[instrument(skip(self))]
|
||||
@@ -231,7 +246,7 @@ impl NetService {
|
||||
id: InterfaceId,
|
||||
external: u16,
|
||||
internal: u16,
|
||||
) -> Result<(), Error>
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
@@ -248,7 +263,7 @@ impl NetService {
|
||||
.await?,
|
||||
);
|
||||
self.tor.insert(tor_idx, tor);
|
||||
Ok(())
|
||||
Ok(key.tor_address().to_string())
|
||||
}
|
||||
pub async fn remove_tor(&mut self, id: InterfaceId, external: u16) -> Result<(), Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
@@ -264,11 +279,12 @@ impl NetService {
|
||||
external: u16,
|
||||
internal: u16,
|
||||
connect_ssl: bool,
|
||||
) -> Result<(), Error>
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: PgExecutor<'a>,
|
||||
{
|
||||
let key = Key::for_interface(secrets, Some((self.id.clone(), id.clone()))).await?;
|
||||
let addr = key.local_address();
|
||||
let ctrl = self.net_controller()?;
|
||||
let lan_idx = (id, external);
|
||||
let mut lan = self
|
||||
@@ -286,7 +302,7 @@ impl NetService {
|
||||
.await?,
|
||||
);
|
||||
self.lan.insert(lan_idx, lan);
|
||||
Ok(())
|
||||
Ok(addr)
|
||||
}
|
||||
pub async fn remove_lan(&mut self, id: InterfaceId, external: u16) -> Result<(), Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
@@ -295,6 +311,35 @@ impl NetService {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn add_lpf(&mut self, db: &PatchDb, internal: u16) -> Result<u16, Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
let mut db = db.handle();
|
||||
let lpf_model = crate::db::DatabaseModel::new().lan_port_forwards();
|
||||
lpf_model.lock(&mut db, LockType::Write).await?; // TODO: replace all this with an RMW
|
||||
let mut lpf = lpf_model.get_mut(&mut db).await?;
|
||||
let external = lpf.alloc(self.id.clone(), internal).ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("No ephemeral ports available"),
|
||||
crate::ErrorKind::Network,
|
||||
)
|
||||
})?;
|
||||
lpf.save(&mut db).await?;
|
||||
drop(db);
|
||||
let rc = ctrl.add_lpf(external, (self.ip, internal).into()).await?;
|
||||
let (_, mut lpfs) = self.lpf.remove(&internal).unwrap_or_default();
|
||||
lpfs.push(rc);
|
||||
self.lpf.insert(internal, (external, lpfs));
|
||||
|
||||
Ok(external)
|
||||
}
|
||||
pub async fn remove_lpf(&mut self, db: &PatchDb, internal: u16) -> Result<(), Error> {
|
||||
let ctrl = self.net_controller()?;
|
||||
if let Some((external, rcs)) = self.lpf.remove(&internal) {
|
||||
ctrl.remove_lpf(external, rcs).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn export_cert<Ex>(
|
||||
&self,
|
||||
secrets: &mut Ex,
|
||||
@@ -330,6 +375,9 @@ impl NetService {
|
||||
for ((_, external), (key, rcs)) in std::mem::take(&mut self.tor) {
|
||||
errors.handle(ctrl.remove_tor(&key, external, rcs).await);
|
||||
}
|
||||
for (_, (external, rcs)) in std::mem::take(&mut self.lpf) {
|
||||
errors.handle(ctrl.remove_lpf(external, rcs).await);
|
||||
}
|
||||
std::mem::take(&mut self.dns);
|
||||
errors.handle(ctrl.dns.gc(Some(self.id.clone()), self.ip).await);
|
||||
self.ip = Ipv4Addr::new(0, 0, 0, 0);
|
||||
@@ -356,6 +404,7 @@ impl Drop for NetService {
|
||||
controller: Default::default(),
|
||||
tor: Default::default(),
|
||||
lan: Default::default(),
|
||||
lpf: Default::default(),
|
||||
},
|
||||
);
|
||||
tokio::spawn(async move { svc.remove_all().await.unwrap() });
|
||||
|
||||
Reference in New Issue
Block a user