mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
wip refactor
This commit is contained in:
663
core/Cargo.lock
generated
663
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ PROFILE=${PROFILE:-debug}
|
||||
if [ "${PROFILE}" = "release" ]; then
|
||||
BUILD_FLAGS="--release"
|
||||
else
|
||||
if [ "$PROFILE" != "debug"]; then
|
||||
if [ "$PROFILE" != "debug" ]; then
|
||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||
PROFILE=debug
|
||||
fi
|
||||
|
||||
@@ -20,8 +20,9 @@ use crate::db::model::Database;
|
||||
use crate::db::model::package::AllPackageData;
|
||||
use crate::net::acme::AcmeProvider;
|
||||
use crate::net::host::Host;
|
||||
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, Bindings, DerivedAddressInfo, NetInfo};
|
||||
use crate::net::utils::ipv6_is_local;
|
||||
use crate::net::host::binding::{
|
||||
AddSslOptions, BindInfo, BindOptions, Bindings, DerivedAddressInfo, NetInfo,
|
||||
};
|
||||
use crate::net::vhost::AlpnInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::FullProgress;
|
||||
@@ -91,7 +92,7 @@ impl Public {
|
||||
.collect(),
|
||||
),
|
||||
public_domains: BTreeMap::new(),
|
||||
private_domains: BTreeSet::new(),
|
||||
private_domains: BTreeMap::new(),
|
||||
},
|
||||
wifi: WifiInfo {
|
||||
enabled: true,
|
||||
@@ -242,44 +243,12 @@ pub struct DnsSettings {
|
||||
#[ts(export)]
|
||||
pub struct NetworkInterfaceInfo {
|
||||
pub name: Option<InternedString>,
|
||||
#[ts(skip)]
|
||||
pub public: Option<bool>,
|
||||
pub secure: Option<bool>,
|
||||
pub ip_info: Option<Arc<IpInfo>>,
|
||||
#[serde(default, rename = "type")]
|
||||
pub gateway_type: Option<GatewayType>,
|
||||
}
|
||||
impl NetworkInterfaceInfo {
|
||||
pub fn public(&self) -> bool {
|
||||
self.public.unwrap_or_else(|| {
|
||||
!self.ip_info.as_ref().map_or(true, |ip_info| {
|
||||
let ip4s = ip_info
|
||||
.subnets
|
||||
.iter()
|
||||
.filter_map(|ipnet| {
|
||||
if let IpAddr::V4(ip4) = ipnet.addr() {
|
||||
Some(ip4)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
if !ip4s.is_empty() {
|
||||
return ip4s
|
||||
.iter()
|
||||
.all(|ip4| ip4.is_loopback() || ip4.is_private() || ip4.is_link_local());
|
||||
}
|
||||
ip_info.subnets.iter().all(|ipnet| {
|
||||
if let IpAddr::V6(ip6) = ipnet.addr() {
|
||||
ipv6_is_local(ip6)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn secure(&self) -> bool {
|
||||
self.secure.unwrap_or(false)
|
||||
}
|
||||
@@ -316,7 +285,20 @@ pub enum NetworkInterfaceType {
|
||||
Loopback,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, clap::ValueEnum)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
TS,
|
||||
clap::ValueEnum,
|
||||
)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum GatewayType {
|
||||
|
||||
@@ -71,7 +71,7 @@ impl SignatureAuthContext for RpcContext {
|
||||
.as_network()
|
||||
.as_host()
|
||||
.as_private_domains()
|
||||
.de()
|
||||
.keys()
|
||||
.map(|k| k.into_iter())
|
||||
.transpose(),
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ use color_eyre::eyre::eyre;
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use hickory_server::authority::{AuthorityObject, Catalog, MessageResponseBuilder};
|
||||
use hickory_server::proto::op::{Header, ResponseCode};
|
||||
use hickory_server::proto::rr::{LowerName, Name, Record, RecordType};
|
||||
use hickory_server::proto::rr::{Name, Record, RecordType};
|
||||
use hickory_server::resolver::config::{ResolverConfig, ResolverOpts};
|
||||
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||
use hickory_server::store::forwarder::{ForwardAuthority, ForwardConfig};
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc => "INTERFACE", "TYPE", "PUBLIC", "ADDRESSES", "WAN IP"]);
|
||||
table.add_row(row![bc => "INTERFACE", "TYPE", "ADDRESSES", "WAN IP"]);
|
||||
for (iface, info) in res {
|
||||
table.add_row(row![
|
||||
iface,
|
||||
@@ -64,7 +64,6 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
.as_ref()
|
||||
.and_then(|ip_info| ip_info.device_type)
|
||||
.map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")),
|
||||
info.public(),
|
||||
info.ip_info.as_ref().map_or_else(
|
||||
|| "<DISCONNECTED>".to_owned(),
|
||||
|ip_info| ip_info
|
||||
@@ -94,22 +93,6 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_about("about.show-gateways-startos-can-listen-on")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"set-public",
|
||||
from_fn_async(set_public)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("about.indicate-gateway-inbound-access-from-wan")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"unset-public",
|
||||
from_fn_async(unset_public)
|
||||
.with_metadata("sync_db", Value::Bool(true))
|
||||
.no_display()
|
||||
.with_about("about.allow-gateway-infer-inbound-access-from-wan")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"forget",
|
||||
from_fn_async(forget_iface)
|
||||
@@ -134,40 +117,6 @@ async fn list_interfaces(
|
||||
Ok(ctx.net_controller.net_iface.watcher.ip_info())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct NetworkInterfaceSetPublicParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
#[arg(help = "help.arg.is-public")]
|
||||
public: Option<bool>,
|
||||
}
|
||||
|
||||
async fn set_public(
|
||||
ctx: RpcContext,
|
||||
NetworkInterfaceSetPublicParams { gateway, public }: NetworkInterfaceSetPublicParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.net_controller
|
||||
.net_iface
|
||||
.set_public(&gateway, Some(public.unwrap_or(true)))
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct UnsetPublicParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
gateway: GatewayId,
|
||||
}
|
||||
|
||||
async fn unset_public(
|
||||
ctx: RpcContext,
|
||||
UnsetPublicParams { gateway }: UnsetPublicParams,
|
||||
) -> Result<(), Error> {
|
||||
ctx.net_controller
|
||||
.net_iface
|
||||
.set_public(&gateway, None)
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||
struct ForgetGatewayParams {
|
||||
#[arg(help = "help.arg.gateway-id")]
|
||||
@@ -910,12 +859,11 @@ async fn watch_ip(
|
||||
|
||||
write_to.send_if_modified(
|
||||
|m: &mut OrdMap<GatewayId, NetworkInterfaceInfo>| {
|
||||
let (name, public, secure, gateway_type, prev_wan_ip) = m
|
||||
let (name, secure, gateway_type, prev_wan_ip) = m
|
||||
.get(&iface)
|
||||
.map_or((None, None, None, None, None), |i| {
|
||||
.map_or((None, None, None, None), |i| {
|
||||
(
|
||||
i.name.clone(),
|
||||
i.public,
|
||||
i.secure,
|
||||
i.gateway_type,
|
||||
i.ip_info
|
||||
@@ -929,7 +877,6 @@ async fn watch_ip(
|
||||
iface.clone(),
|
||||
NetworkInterfaceInfo {
|
||||
name,
|
||||
public,
|
||||
secure,
|
||||
ip_info: Some(ip_info.clone()),
|
||||
gateway_type,
|
||||
@@ -1192,43 +1139,6 @@ impl NetworkInterfaceController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_public(
|
||||
&self,
|
||||
interface: &GatewayId,
|
||||
public: Option<bool>,
|
||||
) -> Result<(), Error> {
|
||||
let mut sub = self
|
||||
.db
|
||||
.subscribe(
|
||||
"/public/serverInfo/network/gateways"
|
||||
.parse::<JsonPointer<_, _>>()
|
||||
.with_kind(ErrorKind::Database)?,
|
||||
)
|
||||
.await;
|
||||
let mut err = None;
|
||||
let changed = self.watcher.ip_info.send_if_modified(|ip_info| {
|
||||
let prev = std::mem::replace(
|
||||
&mut match ip_info.get_mut(interface).or_not_found(interface) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
.public,
|
||||
public,
|
||||
);
|
||||
prev != public
|
||||
});
|
||||
if let Some(e) = err {
|
||||
return Err(e);
|
||||
}
|
||||
if changed {
|
||||
sub.recv().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn forget(&self, interface: &GatewayId) -> Result<(), Error> {
|
||||
let mut sub = self
|
||||
.db
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||
pub struct HostAddress {
|
||||
pub address: InternedString,
|
||||
pub public: Option<PublicDomainConfig>,
|
||||
pub private: bool,
|
||||
pub private: Option<BTreeSet<GatewayId>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||
@@ -53,7 +53,7 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||
for domain in &public {
|
||||
check_domain(&mut domains, domain.clone())?;
|
||||
}
|
||||
for domain in host.as_private_domains().de()? {
|
||||
for domain in host.as_private_domains().keys()? {
|
||||
if !public.contains(&domain) {
|
||||
check_domain(&mut domains, domain)?;
|
||||
}
|
||||
@@ -63,13 +63,13 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||
host.as_public_domains_mut()
|
||||
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||
host.as_private_domains_mut()
|
||||
.mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?;
|
||||
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||
|
||||
let public = host.as_public_domains().keys()?;
|
||||
for domain in &public {
|
||||
check_domain(&mut domains, domain.clone())?;
|
||||
}
|
||||
for domain in host.as_private_domains().de()? {
|
||||
for domain in host.as_private_domains().keys()? {
|
||||
if !public.contains(&domain) {
|
||||
check_domain(&mut domains, domain)?;
|
||||
}
|
||||
@@ -146,21 +146,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||
}
|
||||
|
||||
let mut table = Table::new();
|
||||
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
||||
for entry in &res {
|
||||
if let Some(PublicDomainConfig { gateway, acme }) = &entry.public {
|
||||
table.add_row(row![
|
||||
entry.address,
|
||||
&format!(
|
||||
"{} ({gateway})",
|
||||
if entry.private { "YES" } else { "ONLY" }
|
||||
),
|
||||
acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE")
|
||||
]);
|
||||
} else {
|
||||
table.add_row(row![entry.address, &format!("NO"), "N/A"]);
|
||||
}
|
||||
}
|
||||
todo!("find a good way to represent this");
|
||||
|
||||
table.print_tty(false)?;
|
||||
|
||||
@@ -248,18 +234,20 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
|
||||
pub struct AddPrivateDomainParams {
|
||||
#[arg(help = "help.arg.fqdn")]
|
||||
pub fqdn: InternedString,
|
||||
pub gateway: GatewayId,
|
||||
}
|
||||
|
||||
pub async fn add_private_domain<Kind: HostApiKind>(
|
||||
ctx: RpcContext,
|
||||
AddPrivateDomainParams { fqdn }: AddPrivateDomainParams,
|
||||
AddPrivateDomainParams { fqdn, gateway }: AddPrivateDomainParams,
|
||||
inheritance: Kind::Inheritance,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
Kind::host_for(&inheritance, db)?
|
||||
.as_private_domains_mut()
|
||||
.mutate(|d| Ok(d.insert(fqdn)))?;
|
||||
.upsert(&fqdn, || Ok(BTreeSet::new()))?
|
||||
.mutate(|d| Ok(d.insert(gateway)))?;
|
||||
handle_duplicates(db)
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
@@ -7,6 +8,7 @@ use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_f
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::HostId;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::prelude::Map;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
@@ -16,7 +18,6 @@ use crate::net::vhost::AlpnInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::util::FromStrParser;
|
||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||
use crate::HostId;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
@@ -49,31 +50,36 @@ impl FromStr for BindId {
|
||||
#[ts(export)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DerivedAddressInfo {
|
||||
/// User-controlled: private addresses the user has disabled
|
||||
pub private_disabled: BTreeSet<HostnameInfo>,
|
||||
/// User-controlled: public addresses the user has enabled
|
||||
pub public_enabled: BTreeSet<HostnameInfo>,
|
||||
/// User override: enable these addresses (only for public IP & port)
|
||||
pub enabled: BTreeSet<SocketAddr>,
|
||||
/// User override: disable these addresses (only for domains and private IP & port)
|
||||
pub disabled: BTreeSet<(InternedString, u16)>,
|
||||
/// COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||
pub possible: BTreeSet<HostnameInfo>,
|
||||
pub available: BTreeSet<HostnameInfo>,
|
||||
}
|
||||
|
||||
impl DerivedAddressInfo {
|
||||
/// Returns addresses that are currently enabled.
|
||||
/// Private addresses are enabled by default (disabled if in private_disabled).
|
||||
/// Public addresses are disabled by default (enabled if in public_enabled).
|
||||
/// Returns addresses that are currently enabled after applying overrides.
|
||||
/// Default: public IPs are disabled, everything else is enabled.
|
||||
/// Explicit `enabled`/`disabled` overrides take precedence.
|
||||
pub fn enabled(&self) -> BTreeSet<&HostnameInfo> {
|
||||
self.possible
|
||||
self.available
|
||||
.iter()
|
||||
.filter(|h| {
|
||||
if h.public {
|
||||
self.public_enabled.contains(h)
|
||||
if h.public && h.metadata.is_ip() {
|
||||
// Public IPs: disabled by default, explicitly enabled via SocketAddr
|
||||
h.to_socket_addr().map_or(
|
||||
true, // should never happen, but would rather see them if it does
|
||||
|sa| self.enabled.contains(&sa),
|
||||
)
|
||||
} else {
|
||||
!self.private_disabled.contains(h)
|
||||
!self
|
||||
.disabled
|
||||
.contains(&(h.host.clone(), h.port.unwrap_or_default())) // disablable addresses will always have a port
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
@@ -199,9 +205,9 @@ impl BindInfo {
|
||||
options,
|
||||
net: lan,
|
||||
addresses: DerivedAddressInfo {
|
||||
private_disabled: addresses.private_disabled,
|
||||
public_enabled: addresses.public_enabled,
|
||||
possible: BTreeSet::new(),
|
||||
enabled: addresses.enabled,
|
||||
disabled: addresses.disabled,
|
||||
available: BTreeSet::new(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -328,17 +334,27 @@ pub async fn set_address_enabled<Kind: HostApiKind>(
|
||||
.as_bindings_mut()
|
||||
.mutate(|b| {
|
||||
let bind = b.get_mut(&internal_port).or_not_found(internal_port)?;
|
||||
if address.public {
|
||||
if address.public && address.metadata.is_ip() {
|
||||
// Public IPs: toggle via SocketAddr in `enabled` set
|
||||
let sa = address.to_socket_addr().ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("cannot convert address to socket addr"),
|
||||
ErrorKind::InvalidRequest,
|
||||
)
|
||||
})?;
|
||||
if enabled {
|
||||
bind.addresses.public_enabled.insert(address.clone());
|
||||
bind.addresses.enabled.insert(sa);
|
||||
} else {
|
||||
bind.addresses.public_enabled.remove(&address);
|
||||
bind.addresses.enabled.remove(&sa);
|
||||
}
|
||||
} else {
|
||||
// Domains and private IPs: toggle via (host, port) in `disabled` set
|
||||
let port = address.port.unwrap_or(if address.ssl { 443 } else { 80 });
|
||||
let key = (address.host.clone(), port);
|
||||
if enabled {
|
||||
bind.addresses.private_disabled.remove(&address);
|
||||
bind.addresses.disabled.remove(&key);
|
||||
} else {
|
||||
bind.addresses.private_disabled.insert(address.clone());
|
||||
bind.addresses.disabled.insert(key);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -3,19 +3,23 @@ use std::future::Future;
|
||||
use std::panic::RefUnwindSafe;
|
||||
|
||||
use clap::Parser;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use patch_db::DestructureMut;
|
||||
use rpc_toolkit::{Context, Empty, HandlerExt, OrEmpty, ParentHandler, from_fn_async};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::db::model::public::NetworkInterfaceInfo;
|
||||
use crate::net::forward::AvailablePorts;
|
||||
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
|
||||
use crate::net::service_interface::{HostnameInfo, HostnameMetadata};
|
||||
use crate::prelude::*;
|
||||
use crate::{HostId, PackageId};
|
||||
use crate::{GatewayId, HostId, PackageId};
|
||||
|
||||
pub mod address;
|
||||
pub mod binding;
|
||||
@@ -27,7 +31,7 @@ pub mod binding;
|
||||
pub struct Host {
|
||||
pub bindings: Bindings,
|
||||
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||
pub private_domains: BTreeSet<InternedString>,
|
||||
pub private_domains: BTreeMap<InternedString, BTreeSet<GatewayId>>,
|
||||
}
|
||||
|
||||
impl AsRef<Host> for Host {
|
||||
@@ -45,20 +49,183 @@ impl Host {
|
||||
.map(|(address, config)| HostAddress {
|
||||
address: address.clone(),
|
||||
public: Some(config.clone()),
|
||||
private: self.private_domains.contains(address),
|
||||
private: self.private_domains.get(address).cloned(),
|
||||
})
|
||||
.chain(
|
||||
self.private_domains
|
||||
.iter()
|
||||
.filter(|a| !self.public_domains.contains_key(*a))
|
||||
.map(|address| HostAddress {
|
||||
address: address.clone(),
|
||||
.filter(|(domain, _)| !self.public_domains.contains_key(*domain))
|
||||
.map(|(domain, gateways)| HostAddress {
|
||||
address: domain.clone(),
|
||||
public: None,
|
||||
private: true,
|
||||
private: Some(gateways.clone()),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Model<Host> {
|
||||
pub fn update_addresses(
|
||||
&mut self,
|
||||
gateways: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||
available_ports: &AvailablePorts,
|
||||
) -> Result<(), Error> {
|
||||
let this = self.destructure_mut();
|
||||
for (_, bind) in this.bindings.as_entries_mut()? {
|
||||
let net = bind.as_net().de()?;
|
||||
let opt = bind.as_options().de()?;
|
||||
let mut available = BTreeSet::new();
|
||||
for (gid, g) in gateways {
|
||||
let Some(ip_info) = &g.ip_info else {
|
||||
continue;
|
||||
};
|
||||
let gateway_secure = g.secure();
|
||||
for subnet in &ip_info.subnets {
|
||||
let host = InternedString::from_display(&subnet.addr());
|
||||
let metadata = if subnet.addr().is_ipv4() {
|
||||
HostnameMetadata::Ipv4 {
|
||||
gateway: gid.clone(),
|
||||
}
|
||||
} else {
|
||||
HostnameMetadata::Ipv6 {
|
||||
gateway: gid.clone(),
|
||||
scope_id: ip_info.scope_id,
|
||||
}
|
||||
};
|
||||
if let Some(port) = net.assigned_port.filter(|_| {
|
||||
opt.secure
|
||||
.map_or(gateway_secure, |s| !(s.ssl && opt.add_ssl.is_some()))
|
||||
}) {
|
||||
available.insert(HostnameInfo {
|
||||
ssl: opt.secure.map_or(false, |s| s.ssl),
|
||||
public: false,
|
||||
host: host.clone(),
|
||||
port: Some(port),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(port) = net.assigned_ssl_port {
|
||||
available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: false,
|
||||
host: host.clone(),
|
||||
port: Some(port),
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(wan_ip) = &ip_info.wan_ip {
|
||||
let host = InternedString::from_display(&wan_ip);
|
||||
let metadata = HostnameMetadata::Ipv4 {
|
||||
gateway: gid.clone(),
|
||||
};
|
||||
if let Some(port) = net.assigned_port.filter(|_| {
|
||||
opt.secure.map_or(
|
||||
false, // the public internet is never secure
|
||||
|s| !(s.ssl && opt.add_ssl.is_some()),
|
||||
)
|
||||
}) {
|
||||
available.insert(HostnameInfo {
|
||||
ssl: opt.secure.map_or(false, |s| s.ssl),
|
||||
public: true,
|
||||
host: host.clone(),
|
||||
port: Some(port),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(port) = net.assigned_ssl_port {
|
||||
available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: true,
|
||||
host: host.clone(),
|
||||
port: Some(port),
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (domain, info) in this.public_domains.de()? {
|
||||
let metadata = HostnameMetadata::PublicDomain {
|
||||
gateway: info.gateway.clone(),
|
||||
};
|
||||
if let Some(port) = net.assigned_port.filter(|_| {
|
||||
opt.secure.map_or(
|
||||
false, // the public internet is never secure
|
||||
|s| !(s.ssl && opt.add_ssl.is_some()),
|
||||
)
|
||||
}) {
|
||||
available.insert(HostnameInfo {
|
||||
ssl: opt.secure.map_or(false, |s| s.ssl),
|
||||
public: true,
|
||||
host: domain.clone(),
|
||||
port: Some(port),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(mut port) = net.assigned_ssl_port {
|
||||
if let Some(preferred) = opt
|
||||
.add_ssl
|
||||
.as_ref()
|
||||
.map(|s| s.preferred_external_port)
|
||||
.filter(|p| available_ports.is_ssl(*p))
|
||||
{
|
||||
port = preferred;
|
||||
}
|
||||
available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: true,
|
||||
host: domain.clone(),
|
||||
port: Some(port),
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (domain, domain_gateways) in this.private_domains.de()? {
|
||||
if let Some(port) = net.assigned_port.filter(|_| {
|
||||
opt.secure
|
||||
.map_or(true, |s| !(s.ssl && opt.add_ssl.is_some()))
|
||||
}) {
|
||||
let gateways = if opt.secure.is_some() {
|
||||
domain_gateways.clone()
|
||||
} else {
|
||||
domain_gateways
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|g| gateways.get(g).map_or(false, |g| g.secure()))
|
||||
.collect()
|
||||
};
|
||||
available.insert(HostnameInfo {
|
||||
ssl: opt.secure.map_or(false, |s| s.ssl),
|
||||
public: true,
|
||||
host: domain.clone(),
|
||||
port: Some(port),
|
||||
metadata: HostnameMetadata::PrivateDomain { gateways },
|
||||
});
|
||||
}
|
||||
if let Some(mut port) = net.assigned_ssl_port {
|
||||
if let Some(preferred) = opt
|
||||
.add_ssl
|
||||
.as_ref()
|
||||
.map(|s| s.preferred_external_port)
|
||||
.filter(|p| available_ports.is_ssl(*p))
|
||||
{
|
||||
port = preferred;
|
||||
}
|
||||
available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: true,
|
||||
host: domain.clone(),
|
||||
port: Some(port),
|
||||
metadata: HostnameMetadata::PrivateDomain {
|
||||
gateways: domain_gateways,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
bind.as_addresses_mut().as_available_mut().ser(&available)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
#[model = "Model<Self>"]
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::net::gateway::NetworkInterfaceController;
|
||||
use crate::net::host::address::HostAddress;
|
||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||
use crate::net::host::{Host, Hosts, host_for};
|
||||
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname};
|
||||
use crate::net::service_interface::{HostnameInfo, HostnameMetadata};
|
||||
use crate::net::socks::SocksController;
|
||||
use crate::net::utils::ipv6_is_local;
|
||||
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
||||
@@ -261,10 +261,7 @@ impl NetServiceData {
|
||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||
let binds = self.binds.entry(id.clone()).or_default();
|
||||
|
||||
let peek = ctrl.db.peek().await;
|
||||
let server_info = peek.as_public().as_server_info();
|
||||
let net_ifaces = ctrl.net_iface.watcher.ip_info();
|
||||
let hostname = server_info.as_hostname().de()?;
|
||||
let host_addresses: Vec<_> = host.addresses().collect();
|
||||
|
||||
// Collect private DNS entries (domains without public config)
|
||||
@@ -277,7 +274,7 @@ impl NetServiceData {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 1: Compute possible addresses ──
|
||||
// ── Phase 1: Compute available addresses ──
|
||||
for (_port, bind) in host.bindings.iter_mut() {
|
||||
if !bind.enabled {
|
||||
continue;
|
||||
@@ -286,7 +283,83 @@ impl NetServiceData {
|
||||
continue;
|
||||
}
|
||||
|
||||
bind.addresses.possible.clear();
|
||||
bind.addresses.available.clear();
|
||||
|
||||
// Domain port: non-SSL port for domains (secure-filtered, gateway-independent)
|
||||
let domain_base_port = bind.net.assigned_port.filter(|_| {
|
||||
bind.options
|
||||
.secure
|
||||
.map_or(false, |s| !s.ssl || bind.options.add_ssl.is_none())
|
||||
});
|
||||
let (domain_port, domain_ssl_port) = if bind
|
||||
.options
|
||||
.add_ssl
|
||||
.as_ref()
|
||||
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
||||
{
|
||||
(None, Some(443))
|
||||
} else {
|
||||
(domain_base_port, bind.net.assigned_ssl_port)
|
||||
};
|
||||
|
||||
// Domain addresses
|
||||
for HostAddress {
|
||||
address, public, ..
|
||||
} in host_addresses.iter().cloned()
|
||||
{
|
||||
// Public domain entry
|
||||
if let Some(pub_config) = &public {
|
||||
let metadata = HostnameMetadata::PublicDomain {
|
||||
gateway: pub_config.gateway.clone(),
|
||||
};
|
||||
if let Some(p) = domain_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: false,
|
||||
public: true,
|
||||
host: address.clone(),
|
||||
port: Some(p),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(sp) = domain_ssl_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: true,
|
||||
host: address.clone(),
|
||||
port: Some(sp),
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Private domain entry
|
||||
if let Some(gateways) = host.private_domains.get(&address) {
|
||||
if !gateways.is_empty() {
|
||||
let metadata = HostnameMetadata::PrivateDomain {
|
||||
gateways: gateways.clone(),
|
||||
};
|
||||
if let Some(p) = domain_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: false,
|
||||
public: false,
|
||||
host: address.clone(),
|
||||
port: Some(p),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
}
|
||||
if let Some(sp) = domain_ssl_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: false,
|
||||
host: address.clone(),
|
||||
port: Some(sp),
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IP addresses (per-gateway)
|
||||
for (gateway_id, info) in net_ifaces
|
||||
.iter()
|
||||
.filter(|(_, info)| {
|
||||
@@ -296,111 +369,97 @@ impl NetServiceData {
|
||||
})
|
||||
.filter(|(_, info)| info.ip_info.is_some())
|
||||
{
|
||||
let gateway = GatewayInfo {
|
||||
id: gateway_id.clone(),
|
||||
name: info
|
||||
.name
|
||||
.clone()
|
||||
.or_else(|| info.ip_info.as_ref().map(|i| i.name.clone()))
|
||||
.unwrap_or_else(|| gateway_id.clone().into()),
|
||||
public: info.public(),
|
||||
};
|
||||
let port = bind.net.assigned_port.filter(|_| {
|
||||
bind.options.secure.map_or(false, |s| {
|
||||
!(s.ssl && bind.options.add_ssl.is_some()) || info.secure()
|
||||
})
|
||||
});
|
||||
// .local addresses (private only, non-public, non-wireguard gateways)
|
||||
if !info.public()
|
||||
&& info.ip_info.as_ref().map_or(false, |i| {
|
||||
i.device_type != Some(NetworkInterfaceType::Wireguard)
|
||||
})
|
||||
{
|
||||
bind.addresses.possible.insert(HostnameInfo {
|
||||
gateway: gateway.clone(),
|
||||
public: false,
|
||||
hostname: IpHostname::Local {
|
||||
value: InternedString::from_display(&{
|
||||
let hostname = &hostname;
|
||||
lazy_format!("{hostname}.local")
|
||||
}),
|
||||
port,
|
||||
ssl_port: bind.net.assigned_ssl_port,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Domain addresses
|
||||
for HostAddress {
|
||||
address,
|
||||
public,
|
||||
private,
|
||||
} in host_addresses.iter().cloned()
|
||||
{
|
||||
let private = private && !info.public();
|
||||
let public =
|
||||
public.as_ref().map_or(false, |p| &p.gateway == gateway_id);
|
||||
if public || private {
|
||||
let (domain_port, domain_ssl_port) = if bind
|
||||
.options
|
||||
.add_ssl
|
||||
.as_ref()
|
||||
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
||||
{
|
||||
(None, Some(443))
|
||||
} else {
|
||||
(port, bind.net.assigned_ssl_port)
|
||||
};
|
||||
bind.addresses.possible.insert(HostnameInfo {
|
||||
gateway: gateway.clone(),
|
||||
public,
|
||||
hostname: IpHostname::Domain {
|
||||
value: address.clone(),
|
||||
port: domain_port,
|
||||
ssl_port: domain_ssl_port,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// IP addresses
|
||||
|
||||
if let Some(ip_info) = &info.ip_info {
|
||||
let public = info.public();
|
||||
// WAN IP (public)
|
||||
if let Some(wan_ip) = ip_info.wan_ip {
|
||||
bind.addresses.possible.insert(HostnameInfo {
|
||||
gateway: gateway.clone(),
|
||||
public: true,
|
||||
hostname: IpHostname::Ipv4 {
|
||||
value: wan_ip,
|
||||
port,
|
||||
ssl_port: bind.net.assigned_ssl_port,
|
||||
},
|
||||
});
|
||||
let host_str = InternedString::from_display(&wan_ip);
|
||||
if let Some(p) = port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: false,
|
||||
public: true,
|
||||
host: host_str.clone(),
|
||||
port: Some(p),
|
||||
metadata: HostnameMetadata::Ipv4 {
|
||||
gateway: gateway_id.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
if let Some(sp) = bind.net.assigned_ssl_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: true,
|
||||
host: host_str,
|
||||
port: Some(sp),
|
||||
metadata: HostnameMetadata::Ipv4 {
|
||||
gateway: gateway_id.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// Subnet IPs
|
||||
for ipnet in &ip_info.subnets {
|
||||
match ipnet {
|
||||
IpNet::V4(net) => {
|
||||
if !public {
|
||||
bind.addresses.possible.insert(HostnameInfo {
|
||||
gateway: gateway.clone(),
|
||||
public,
|
||||
hostname: IpHostname::Ipv4 {
|
||||
value: net.addr(),
|
||||
port,
|
||||
ssl_port: bind.net.assigned_ssl_port,
|
||||
},
|
||||
});
|
||||
let host_str = InternedString::from_display(&net.addr());
|
||||
if let Some(p) = port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: false,
|
||||
public: false,
|
||||
host: host_str.clone(),
|
||||
port: Some(p),
|
||||
metadata: HostnameMetadata::Ipv4 {
|
||||
gateway: gateway_id.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
if let Some(sp) = bind.net.assigned_ssl_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: false,
|
||||
host: host_str,
|
||||
port: Some(sp),
|
||||
metadata: HostnameMetadata::Ipv4 {
|
||||
gateway: gateway_id.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
IpNet::V6(net) => {
|
||||
bind.addresses.possible.insert(HostnameInfo {
|
||||
gateway: gateway.clone(),
|
||||
public: public && !ipv6_is_local(net.addr()),
|
||||
hostname: IpHostname::Ipv6 {
|
||||
value: net.addr(),
|
||||
scope_id: ip_info.scope_id,
|
||||
port,
|
||||
ssl_port: bind.net.assigned_ssl_port,
|
||||
},
|
||||
});
|
||||
let is_public = public && !ipv6_is_local(net.addr());
|
||||
let host_str = InternedString::from_display(&net.addr());
|
||||
if let Some(p) = port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: false,
|
||||
public: is_public,
|
||||
host: host_str.clone(),
|
||||
port: Some(p),
|
||||
metadata: HostnameMetadata::Ipv6 {
|
||||
gateway: gateway_id.clone(),
|
||||
scope_id: ip_info.scope_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
if let Some(sp) = bind.net.assigned_ssl_port {
|
||||
bind.addresses.available.insert(HostnameInfo {
|
||||
ssl: true,
|
||||
public: is_public,
|
||||
host: host_str,
|
||||
port: Some(sp),
|
||||
metadata: HostnameMetadata::Ipv6 {
|
||||
gateway: gateway_id.clone(),
|
||||
scope_id: ip_info.scope_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -435,11 +494,8 @@ impl NetServiceData {
|
||||
let server_private_ips: BTreeSet<IpAddr> = enabled_addresses
|
||||
.iter()
|
||||
.filter(|a| !a.public)
|
||||
.filter_map(|a| {
|
||||
net_ifaces
|
||||
.get(&a.gateway.id)
|
||||
.and_then(|info| info.ip_info.as_ref())
|
||||
})
|
||||
.flat_map(|a| a.metadata.gateways())
|
||||
.filter_map(|gw| net_ifaces.get(gw).and_then(|info| info.ip_info.as_ref()))
|
||||
.flat_map(|ip_info| ip_info.subnets.iter().map(|s| s.addr()))
|
||||
.collect();
|
||||
|
||||
@@ -465,32 +521,36 @@ impl NetServiceData {
|
||||
|
||||
// Domain vhosts: group by (domain, ssl_port), merge public/private sets
|
||||
for addr_info in &enabled_addresses {
|
||||
if let IpHostname::Domain {
|
||||
value: domain,
|
||||
ssl_port: Some(domain_ssl_port),
|
||||
..
|
||||
} = &addr_info.hostname
|
||||
{
|
||||
let key = (Some(domain.clone()), *domain_ssl_port);
|
||||
let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
|
||||
public: BTreeSet::new(),
|
||||
private: BTreeSet::new(),
|
||||
acme: host_addresses
|
||||
.iter()
|
||||
.find(|a| &a.address == domain)
|
||||
.and_then(|a| a.public.as_ref())
|
||||
.and_then(|p| p.acme.clone()),
|
||||
addr,
|
||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||
connect_ssl: connect_ssl
|
||||
.clone()
|
||||
.map(|_| ctrl.tls_client_config.clone()),
|
||||
});
|
||||
if addr_info.public {
|
||||
target.public.insert(addr_info.gateway.id.clone());
|
||||
} else {
|
||||
// Add interface IPs for this gateway to private set
|
||||
if let Some(info) = net_ifaces.get(&addr_info.gateway.id) {
|
||||
if !addr_info.ssl {
|
||||
continue;
|
||||
}
|
||||
match &addr_info.metadata {
|
||||
HostnameMetadata::PublicDomain { .. }
|
||||
| HostnameMetadata::PrivateDomain { .. } => {}
|
||||
_ => continue,
|
||||
}
|
||||
let domain = &addr_info.host;
|
||||
let domain_ssl_port = addr_info.port.unwrap_or(443);
|
||||
let key = (Some(domain.clone()), domain_ssl_port);
|
||||
let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
|
||||
public: BTreeSet::new(),
|
||||
private: BTreeSet::new(),
|
||||
acme: host_addresses
|
||||
.iter()
|
||||
.find(|a| a.address == *domain)
|
||||
.and_then(|a| a.public.as_ref())
|
||||
.and_then(|p| p.acme.clone()),
|
||||
addr,
|
||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||
connect_ssl: connect_ssl.clone().map(|_| ctrl.tls_client_config.clone()),
|
||||
});
|
||||
if addr_info.public {
|
||||
for gw in addr_info.metadata.gateways() {
|
||||
target.public.insert(gw.clone());
|
||||
}
|
||||
} else {
|
||||
for gw in addr_info.metadata.gateways() {
|
||||
if let Some(info) = net_ifaces.get(gw) {
|
||||
if let Some(ip_info) = &info.ip_info {
|
||||
for subnet in &ip_info.subnets {
|
||||
target.private.insert(subnet.addr());
|
||||
@@ -512,16 +572,14 @@ impl NetServiceData {
|
||||
let fwd_public: BTreeSet<GatewayId> = enabled_addresses
|
||||
.iter()
|
||||
.filter(|a| a.public)
|
||||
.map(|a| a.gateway.id.clone())
|
||||
.flat_map(|a| a.metadata.gateways())
|
||||
.cloned()
|
||||
.collect();
|
||||
let fwd_private: BTreeSet<IpAddr> = enabled_addresses
|
||||
.iter()
|
||||
.filter(|a| !a.public)
|
||||
.filter_map(|a| {
|
||||
net_ifaces
|
||||
.get(&a.gateway.id)
|
||||
.and_then(|i| i.ip_info.as_ref())
|
||||
})
|
||||
.flat_map(|a| a.metadata.gateways())
|
||||
.filter_map(|gw| net_ifaces.get(gw).and_then(|i| i.ip_info.as_ref()))
|
||||
.flat_map(|ip| ip.subnets.iter().map(|s| s.addr()))
|
||||
.collect();
|
||||
forwards.insert(
|
||||
@@ -634,8 +692,8 @@ impl NetServiceData {
|
||||
for (port, bind) in host.bindings.0 {
|
||||
if let Some(b) = bindings.as_idx_mut(&port) {
|
||||
b.as_addresses_mut()
|
||||
.as_possible_mut()
|
||||
.ser(&bind.addresses.possible)?;
|
||||
.as_available_mut()
|
||||
.ser(&bind.addresses.available)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,22 +1,74 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::collections::BTreeSet;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use imbl_value::InternedString;
|
||||
use imbl_value::{InOMap, InternedString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{GatewayId, HostId, ServiceInterfaceId};
|
||||
use crate::prelude::*;
|
||||
use crate::{GatewayId, HostId, PackageId, ServiceInterfaceId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HostnameInfo {
|
||||
pub gateway: GatewayInfo,
|
||||
pub ssl: bool,
|
||||
pub public: bool,
|
||||
pub hostname: IpHostname,
|
||||
pub host: InternedString,
|
||||
pub port: Option<u16>,
|
||||
pub metadata: HostnameMetadata,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(rename_all_fields = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum HostnameMetadata {
|
||||
Ipv4 {
|
||||
gateway: GatewayId,
|
||||
},
|
||||
Ipv6 {
|
||||
gateway: GatewayId,
|
||||
scope_id: u32,
|
||||
},
|
||||
PrivateDomain {
|
||||
gateways: BTreeSet<GatewayId>,
|
||||
},
|
||||
PublicDomain {
|
||||
gateway: GatewayId,
|
||||
},
|
||||
Plugin {
|
||||
package: PackageId,
|
||||
#[serde(flatten)]
|
||||
extra: InOMap<InternedString, Value>,
|
||||
},
|
||||
}
|
||||
|
||||
impl HostnameInfo {
|
||||
pub fn to_socket_addr(&self) -> Option<SocketAddr> {
|
||||
let ip = self.host.parse().ok()?;
|
||||
Some(SocketAddr::new(ip, self.port?))
|
||||
}
|
||||
|
||||
pub fn to_san_hostname(&self) -> InternedString {
|
||||
self.hostname.to_san_hostname()
|
||||
self.host.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl HostnameMetadata {
|
||||
pub fn is_ip(&self) -> bool {
|
||||
matches!(self, Self::Ipv4 { .. } | Self::Ipv6 { .. })
|
||||
}
|
||||
|
||||
pub fn gateways(&self) -> Box<dyn Iterator<Item = &GatewayId> + '_> {
|
||||
match self {
|
||||
Self::Ipv4 { gateway }
|
||||
| Self::Ipv6 { gateway, .. }
|
||||
| Self::PublicDomain { gateway } => Box::new(std::iter::once(gateway)),
|
||||
Self::PrivateDomain { gateways } => Box::new(gateways.iter()),
|
||||
Self::Plugin { .. } => Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,48 +81,6 @@ pub struct GatewayInfo {
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all_fields = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum IpHostname {
|
||||
Ipv4 {
|
||||
value: Ipv4Addr,
|
||||
port: Option<u16>,
|
||||
ssl_port: Option<u16>,
|
||||
},
|
||||
Ipv6 {
|
||||
value: Ipv6Addr,
|
||||
#[serde(default)]
|
||||
scope_id: u32,
|
||||
port: Option<u16>,
|
||||
ssl_port: Option<u16>,
|
||||
},
|
||||
Local {
|
||||
#[ts(type = "string")]
|
||||
value: InternedString,
|
||||
port: Option<u16>,
|
||||
ssl_port: Option<u16>,
|
||||
},
|
||||
Domain {
|
||||
#[ts(type = "string")]
|
||||
value: InternedString,
|
||||
port: Option<u16>,
|
||||
ssl_port: Option<u16>,
|
||||
},
|
||||
}
|
||||
impl IpHostname {
|
||||
pub fn to_san_hostname(&self) -> InternedString {
|
||||
match self {
|
||||
Self::Ipv4 { value, .. } => InternedString::from_display(value),
|
||||
Self::Ipv6 { value, .. } => InternedString::from_display(value),
|
||||
Self::Local { value, .. } => value.clone(),
|
||||
Self::Domain { value, .. } => value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -82,7 +82,6 @@ pub async fn add_tunnel(
|
||||
iface.clone(),
|
||||
NetworkInterfaceInfo {
|
||||
name: Some(name),
|
||||
public: None,
|
||||
secure: None,
|
||||
ip_info: None,
|
||||
gateway_type,
|
||||
@@ -193,15 +192,12 @@ pub async fn remove_tunnel(
|
||||
.mutate(|db| {
|
||||
for host in all_hosts(db) {
|
||||
let host = host?;
|
||||
host.as_bindings_mut().mutate(|b| {
|
||||
Ok(b.values_mut().for_each(|v| {
|
||||
v.addresses
|
||||
.private_disabled
|
||||
.retain(|h| h.gateway.id != id);
|
||||
v.addresses
|
||||
.public_enabled
|
||||
.retain(|h| h.gateway.id != id);
|
||||
}))
|
||||
host.as_private_domains_mut().mutate(|d| {
|
||||
for gateways in d.values_mut() {
|
||||
gateways.remove(&id);
|
||||
}
|
||||
d.retain(|_, gateways| !gateways.is_empty());
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,12 +58,12 @@ pub async fn get_ssl_certificate(
|
||||
Ok(m.as_public_domains()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.chain(m.as_private_domains().de()?)
|
||||
.chain(m.as_private_domains().keys()?)
|
||||
.chain(
|
||||
m.as_bindings()
|
||||
.de()?
|
||||
.values()
|
||||
.flat_map(|b| b.addresses.possible.iter().cloned())
|
||||
.flat_map(|b| b.addresses.available.iter().cloned())
|
||||
.map(|h| h.to_san_hostname()),
|
||||
)
|
||||
.collect::<Vec<InternedString>>())
|
||||
@@ -182,12 +182,12 @@ pub async fn get_ssl_key(
|
||||
Ok(m.as_public_domains()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.chain(m.as_private_domains().de()?)
|
||||
.chain(m.as_private_domains().keys()?)
|
||||
.chain(
|
||||
m.as_bindings()
|
||||
.de()?
|
||||
.values()
|
||||
.flat_map(|b| b.addresses.possible.iter().cloned())
|
||||
.flat_map(|b| b.addresses.available.iter().cloned())
|
||||
.map(|h| h.to_san_hostname()),
|
||||
)
|
||||
.collect::<Vec<InternedString>>())
|
||||
|
||||
@@ -164,6 +164,19 @@ fn migrate_host(host: Option<&mut Value>) {
|
||||
// Remove hostnameInfo from host
|
||||
host.remove("hostnameInfo");
|
||||
|
||||
// Migrate privateDomains from array to object (BTreeSet -> BTreeMap<_, BTreeSet<GatewayId>>)
|
||||
if let Some(private_domains) = host.get("privateDomains").and_then(|v| v.as_array()).cloned() {
|
||||
let mut new_pd: Value = serde_json::json!({}).into();
|
||||
for domain in private_domains {
|
||||
if let Some(d) = domain.as_str() {
|
||||
if let Some(obj) = new_pd.as_object_mut() {
|
||||
obj.insert(d.into(), serde_json::json!([]).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
host.insert("privateDomains".into(), new_pd);
|
||||
}
|
||||
|
||||
// For each binding: add "addresses" field, remove gateway-level fields from "net"
|
||||
if let Some(bindings) = host.get_mut("bindings").and_then(|b| b.as_object_mut()) {
|
||||
for (_, binding) in bindings.iter_mut() {
|
||||
@@ -173,9 +186,9 @@ fn migrate_host(host: Option<&mut Value>) {
|
||||
binding_obj.insert(
|
||||
"addresses".into(),
|
||||
serde_json::json!({
|
||||
"privateDisabled": [],
|
||||
"publicEnabled": [],
|
||||
"possible": []
|
||||
"enabled": [],
|
||||
"disabled": [],
|
||||
"available": []
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -5,11 +5,11 @@ export type DerivedAddressInfo = {
|
||||
/**
|
||||
* User-controlled: private addresses the user has disabled
|
||||
*/
|
||||
privateDisabled: Array<HostnameInfo>
|
||||
enabled: Array<HostnameInfo>
|
||||
/**
|
||||
* User-controlled: public addresses the user has enabled
|
||||
*/
|
||||
publicEnabled: Array<HostnameInfo>
|
||||
disabled: Array<HostnameInfo>
|
||||
/**
|
||||
* COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||
*/
|
||||
|
||||
@@ -226,12 +226,16 @@ function filterRec(
|
||||
return hostnames
|
||||
}
|
||||
|
||||
function isDefaultEnabled(h: HostnameInfo): boolean {
|
||||
return !(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))
|
||||
}
|
||||
|
||||
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
||||
return addr.possible.filter((h) =>
|
||||
h.public
|
||||
? addr.publicEnabled.some((e) => deepEqual(e, h))
|
||||
: !addr.privateDisabled.some((d) => deepEqual(d, h)),
|
||||
)
|
||||
return addr.possible.filter((h) => {
|
||||
if (addr.enabled.some((e) => deepEqual(e, h))) return true
|
||||
if (addr.disabled.some((d) => deepEqual(d, h))) return false
|
||||
return isDefaultEnabled(h)
|
||||
})
|
||||
}
|
||||
|
||||
export const filledAddress = (
|
||||
|
||||
@@ -257,9 +257,9 @@ export class InterfaceService {
|
||||
if (!binding) return []
|
||||
const addr = binding.addresses
|
||||
const enabled = addr.possible.filter(h =>
|
||||
h.public
|
||||
? addr.publicEnabled.some(e => utils.deepEqual(e, h))
|
||||
: !addr.privateDisabled.some(d => utils.deepEqual(d, h)),
|
||||
addr.enabled.some(e => utils.deepEqual(e, h)) ||
|
||||
(!addr.disabled.some(d => utils.deepEqual(d, h)) &&
|
||||
!(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))),
|
||||
)
|
||||
return enabled.filter(
|
||||
h =>
|
||||
|
||||
@@ -134,9 +134,12 @@ export default class ServiceInterfaceRoute {
|
||||
gateways:
|
||||
gateways.map(g => ({
|
||||
enabled:
|
||||
(g.public
|
||||
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
||||
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
||||
(binding?.addresses.enabled.some(a => a.gateway.id === g.id) ||
|
||||
(!binding?.addresses.disabled.some(a => a.gateway.id === g.id) &&
|
||||
binding?.addresses.possible.some(a =>
|
||||
a.gateway.id === g.id &&
|
||||
!(a.public && (a.hostname.kind === 'ipv4' || a.hostname.kind === 'ipv6'))
|
||||
))) ?? false,
|
||||
...g,
|
||||
})) || [],
|
||||
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
||||
|
||||
@@ -95,9 +95,12 @@ export default class StartOsUiComponent {
|
||||
),
|
||||
gateways: gateways.map(g => ({
|
||||
enabled:
|
||||
(g.public
|
||||
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
||||
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
||||
(binding?.addresses.enabled.some(a => a.gateway.id === g.id) ||
|
||||
(!binding?.addresses.disabled.some(a => a.gateway.id === g.id) &&
|
||||
binding?.addresses.possible.some(a =>
|
||||
a.gateway.id === g.id &&
|
||||
!(a.public && (a.hostname.kind === 'ipv4' || a.hostname.kind === 'ipv6'))
|
||||
))) ?? false,
|
||||
...g,
|
||||
})),
|
||||
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
||||
|
||||
@@ -2128,8 +2128,8 @@ export namespace Mock {
|
||||
assignedSslPort: 443,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [
|
||||
{
|
||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||
@@ -2214,8 +2214,8 @@ export namespace Mock {
|
||||
assignedSslPort: null,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [],
|
||||
},
|
||||
options: {
|
||||
@@ -2237,8 +2237,8 @@ export namespace Mock {
|
||||
assignedSslPort: null,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [],
|
||||
},
|
||||
options: {
|
||||
|
||||
@@ -40,8 +40,8 @@ export const mockPatchData: DataModel = {
|
||||
assignedSslPort: 443,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [
|
||||
{
|
||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||
@@ -516,8 +516,8 @@ export const mockPatchData: DataModel = {
|
||||
assignedSslPort: 443,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [
|
||||
{
|
||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||
@@ -602,8 +602,8 @@ export const mockPatchData: DataModel = {
|
||||
assignedSslPort: null,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [],
|
||||
},
|
||||
options: {
|
||||
@@ -625,8 +625,8 @@ export const mockPatchData: DataModel = {
|
||||
assignedSslPort: null,
|
||||
},
|
||||
addresses: {
|
||||
privateDisabled: [],
|
||||
publicEnabled: [],
|
||||
enabled: [],
|
||||
disabled: [],
|
||||
possible: [],
|
||||
},
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user