mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
feat: NAT hairpinning, DNS static servers, clear service error on install
- Add POSTROUTING MASQUERADE rules for container and host hairpin NAT - Allow bridge subnet containers to reach private forwards via LAN IPs - Pass bridge_subnet env var from forward.rs to forward-port script - Use DB-configured static DNS servers in resolver with DB watcher - Fall back to resolv.conf servers when no static servers configured - Clear service error state when install/update completes successfully - Remove completed TODO items
This commit is contained in:
@@ -11,7 +11,8 @@ use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use hickory_server::authority::{AuthorityObject, Catalog, MessageResponseBuilder};
|
||||
use hickory_server::proto::op::{Header, ResponseCode};
|
||||
use hickory_server::proto::rr::{Name, Record, RecordType};
|
||||
use hickory_server::resolver::config::{ResolverConfig, ResolverOpts};
|
||||
use hickory_server::proto::xfer::Protocol;
|
||||
use hickory_server::resolver::config::{NameServerConfig, ResolverConfig, ResolverOpts};
|
||||
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||
use hickory_server::store::forwarder::{ForwardAuthority, ForwardConfig};
|
||||
use hickory_server::{ServerFuture, resolver as hickory_resolver};
|
||||
@@ -241,22 +242,65 @@ impl Resolver {
|
||||
let mut prev = crate::util::serde::hash_serializable::<sha2::Sha256, _>(&(
|
||||
ResolverConfig::new(),
|
||||
ResolverOpts::default(),
|
||||
Option::<std::collections::VecDeque<SocketAddr>>::None,
|
||||
))
|
||||
.unwrap_or_default();
|
||||
loop {
|
||||
if let Err(e) = async {
|
||||
let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||
.boxed();
|
||||
while let Some(conf) = stream.try_next().await? {
|
||||
let (config, mut opts) =
|
||||
hickory_resolver::system_conf::parse_resolv_conf(conf)
|
||||
.with_kind(ErrorKind::ParseSysInfo)?;
|
||||
opts.timeout = Duration::from_secs(30);
|
||||
let hash = crate::util::serde::hash_serializable::<sha2::Sha256, _>(
|
||||
&(&config, &opts),
|
||||
)?;
|
||||
if hash != prev {
|
||||
let res: Result<(), Error> = async {
|
||||
let mut file_stream =
|
||||
file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||
.boxed();
|
||||
let mut static_sub = db
|
||||
.subscribe(
|
||||
"/public/serverInfo/network/dns/staticServers"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let mut last_config: Option<(ResolverConfig, ResolverOpts)> = None;
|
||||
loop {
|
||||
let got_file = tokio::select! {
|
||||
res = file_stream.try_next() => {
|
||||
let conf = res?
|
||||
.ok_or_else(|| Error::new(
|
||||
eyre!("resolv.conf stream ended"),
|
||||
ErrorKind::Network,
|
||||
))?;
|
||||
let (config, mut opts) =
|
||||
hickory_resolver::system_conf::parse_resolv_conf(conf)
|
||||
.with_kind(ErrorKind::ParseSysInfo)?;
|
||||
opts.timeout = Duration::from_secs(30);
|
||||
last_config = Some((config, opts));
|
||||
true
|
||||
}
|
||||
_ = static_sub.recv() => false,
|
||||
};
|
||||
let Some((ref config, ref opts)) = last_config else {
|
||||
continue;
|
||||
};
|
||||
let static_servers: Option<
|
||||
std::collections::VecDeque<SocketAddr>,
|
||||
> = db
|
||||
.peek()
|
||||
.await
|
||||
.as_public()
|
||||
.as_server_info()
|
||||
.as_network()
|
||||
.as_dns()
|
||||
.as_static_servers()
|
||||
.de()?;
|
||||
let hash =
|
||||
crate::util::serde::hash_serializable::<sha2::Sha256, _>(&(
|
||||
config,
|
||||
opts,
|
||||
&static_servers,
|
||||
))?;
|
||||
if hash == prev {
|
||||
prev = hash;
|
||||
continue;
|
||||
}
|
||||
if got_file {
|
||||
db.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
@@ -275,44 +319,55 @@ impl Resolver {
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
let auth: Vec<Arc<dyn AuthorityObject>> = vec![Arc::new(
|
||||
ForwardAuthority::builder_tokio(ForwardConfig {
|
||||
name_servers: from_value(Value::Array(
|
||||
config
|
||||
.name_servers()
|
||||
.into_iter()
|
||||
.skip(4)
|
||||
.map(to_value)
|
||||
.collect::<Result<_, Error>>()?,
|
||||
))?,
|
||||
options: Some(opts),
|
||||
})
|
||||
.build()
|
||||
.map_err(|e| Error::new(eyre!("{e}"), ErrorKind::Network))?,
|
||||
)];
|
||||
{
|
||||
let mut guard = tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
catalog.write(),
|
||||
}
|
||||
let forward_servers =
|
||||
if let Some(servers) = &static_servers {
|
||||
servers
|
||||
.iter()
|
||||
.flat_map(|addr| {
|
||||
[
|
||||
NameServerConfig::new(*addr, Protocol::Udp),
|
||||
NameServerConfig::new(*addr, Protocol::Tcp),
|
||||
]
|
||||
})
|
||||
.map(|n| to_value(&n))
|
||||
.collect::<Result<_, Error>>()?
|
||||
} else {
|
||||
config
|
||||
.name_servers()
|
||||
.into_iter()
|
||||
.skip(4)
|
||||
.map(to_value)
|
||||
.collect::<Result<_, Error>>()?
|
||||
};
|
||||
let auth: Vec<Arc<dyn AuthorityObject>> = vec![Arc::new(
|
||||
ForwardAuthority::builder_tokio(ForwardConfig {
|
||||
name_servers: from_value(Value::Array(forward_servers))?,
|
||||
options: Some(opts.clone()),
|
||||
})
|
||||
.build()
|
||||
.map_err(|e| Error::new(eyre!("{e}"), ErrorKind::Network))?,
|
||||
)];
|
||||
{
|
||||
let mut guard = tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
catalog.write(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("net.dns.timeout-updating-catalog")),
|
||||
ErrorKind::Timeout,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::new(
|
||||
eyre!("{}", t!("net.dns.timeout-updating-catalog")),
|
||||
ErrorKind::Timeout,
|
||||
)
|
||||
})?;
|
||||
guard.upsert(Name::root().into(), auth);
|
||||
drop(guard);
|
||||
}
|
||||
})?;
|
||||
guard.upsert(Name::root().into(), auth);
|
||||
drop(guard);
|
||||
}
|
||||
prev = hash;
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
tracing::error!("{e}");
|
||||
tracing::debug!("{e:?}");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
@@ -3,18 +3,16 @@ use std::net::{IpAddr, SocketAddrV4};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use ipnet::IpNet;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use iddqd::{IdOrdItem, IdOrdMap};
|
||||
use rand::Rng;
|
||||
use imbl::OrdMap;
|
||||
use ipnet::{IpNet, Ipv4Net};
|
||||
use rand::Rng;
|
||||
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::GatewayId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::public::NetworkInterfaceInfo;
|
||||
use crate::prelude::*;
|
||||
@@ -22,6 +20,7 @@ use crate::util::Invoke;
|
||||
use crate::util::future::NonDetachingJoinHandle;
|
||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||
use crate::util::sync::Watch;
|
||||
use crate::{GatewayId, HOST_IP};
|
||||
|
||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||
const EPHEMERAL_PORT_START: u16 = 49152;
|
||||
@@ -254,7 +253,12 @@ pub async fn add_iptables_rule(nat: bool, undo: bool, args: &[&str]) -> Result<(
|
||||
if nat {
|
||||
cmd.arg("-t").arg("nat");
|
||||
}
|
||||
let exists = cmd.arg("-C").args(args).invoke(ErrorKind::Network).await.is_ok();
|
||||
let exists = cmd
|
||||
.arg("-C")
|
||||
.args(args)
|
||||
.invoke(ErrorKind::Network)
|
||||
.await
|
||||
.is_ok();
|
||||
if undo != !exists {
|
||||
let mut cmd = Command::new("iptables");
|
||||
if nat {
|
||||
@@ -444,14 +448,13 @@ impl InterfaceForwardEntry {
|
||||
continue;
|
||||
}
|
||||
|
||||
let src_filter =
|
||||
if reqs.public_gateways.contains(gw_id) {
|
||||
None
|
||||
} else if reqs.private_ips.contains(&IpAddr::V4(ip)) {
|
||||
Some(subnet.trunc())
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let src_filter = if reqs.public_gateways.contains(gw_id) {
|
||||
None
|
||||
} else if reqs.private_ips.contains(&IpAddr::V4(ip)) {
|
||||
Some(subnet.trunc())
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
keep.insert(addr);
|
||||
let fwd_rc = port_forward
|
||||
@@ -713,7 +716,14 @@ async fn forward(
|
||||
.env("dip", target.ip().to_string())
|
||||
.env("dprefix", target_prefix.to_string())
|
||||
.env("sport", source.port().to_string())
|
||||
.env("dport", target.port().to_string());
|
||||
.env("dport", target.port().to_string())
|
||||
.env(
|
||||
"bridge_subnet",
|
||||
Ipv4Net::new(HOST_IP.into(), 24)
|
||||
.with_kind(ErrorKind::ParseNetAddress)?
|
||||
.trunc()
|
||||
.to_string(),
|
||||
);
|
||||
if let Some(subnet) = src_filter {
|
||||
cmd.env("src_subnet", subnet.to_string());
|
||||
}
|
||||
|
||||
@@ -587,6 +587,7 @@ impl Service {
|
||||
entry.as_developer_key_mut().ser(&Pem::new(developer_key))?;
|
||||
entry.as_icon_mut().ser(&icon)?;
|
||||
entry.as_registry_mut().ser(registry)?;
|
||||
entry.as_status_info_mut().as_error_mut().ser(&None)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user