lan port forwarding

This commit is contained in:
Aiden McClelland
2023-02-19 22:30:07 -07:00
parent 4dfdf2f92f
commit f5430f9151
7 changed files with 281 additions and 17 deletions

View File

@@ -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(),
}

View File

@@ -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
View 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(())
}

View File

@@ -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")]

View File

@@ -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() });