mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +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
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
BUILD_FLAGS="--release"
|
BUILD_FLAGS="--release"
|
||||||
else
|
else
|
||||||
if [ "$PROFILE" != "debug"]; then
|
if [ "$PROFILE" != "debug" ]; then
|
||||||
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
PROFILE=debug
|
PROFILE=debug
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ use crate::db::model::Database;
|
|||||||
use crate::db::model::package::AllPackageData;
|
use crate::db::model::package::AllPackageData;
|
||||||
use crate::net::acme::AcmeProvider;
|
use crate::net::acme::AcmeProvider;
|
||||||
use crate::net::host::Host;
|
use crate::net::host::Host;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, Bindings, DerivedAddressInfo, NetInfo};
|
use crate::net::host::binding::{
|
||||||
use crate::net::utils::ipv6_is_local;
|
AddSslOptions, BindInfo, BindOptions, Bindings, DerivedAddressInfo, NetInfo,
|
||||||
|
};
|
||||||
use crate::net::vhost::AlpnInfo;
|
use crate::net::vhost::AlpnInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgress;
|
use crate::progress::FullProgress;
|
||||||
@@ -91,7 +92,7 @@ impl Public {
|
|||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
public_domains: BTreeMap::new(),
|
public_domains: BTreeMap::new(),
|
||||||
private_domains: BTreeSet::new(),
|
private_domains: BTreeMap::new(),
|
||||||
},
|
},
|
||||||
wifi: WifiInfo {
|
wifi: WifiInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -242,44 +243,12 @@ pub struct DnsSettings {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NetworkInterfaceInfo {
|
pub struct NetworkInterfaceInfo {
|
||||||
pub name: Option<InternedString>,
|
pub name: Option<InternedString>,
|
||||||
#[ts(skip)]
|
|
||||||
pub public: Option<bool>,
|
|
||||||
pub secure: Option<bool>,
|
pub secure: Option<bool>,
|
||||||
pub ip_info: Option<Arc<IpInfo>>,
|
pub ip_info: Option<Arc<IpInfo>>,
|
||||||
#[serde(default, rename = "type")]
|
#[serde(default, rename = "type")]
|
||||||
pub gateway_type: Option<GatewayType>,
|
pub gateway_type: Option<GatewayType>,
|
||||||
}
|
}
|
||||||
impl NetworkInterfaceInfo {
|
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 {
|
pub fn secure(&self) -> bool {
|
||||||
self.secure.unwrap_or(false)
|
self.secure.unwrap_or(false)
|
||||||
}
|
}
|
||||||
@@ -316,7 +285,20 @@ pub enum NetworkInterfaceType {
|
|||||||
Loopback,
|
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)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum GatewayType {
|
pub enum GatewayType {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ impl SignatureAuthContext for RpcContext {
|
|||||||
.as_network()
|
.as_network()
|
||||||
.as_host()
|
.as_host()
|
||||||
.as_private_domains()
|
.as_private_domains()
|
||||||
.de()
|
.keys()
|
||||||
.map(|k| k.into_iter())
|
.map(|k| k.into_iter())
|
||||||
.transpose(),
|
.transpose(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use color_eyre::eyre::eyre;
|
|||||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||||
use hickory_server::authority::{AuthorityObject, Catalog, MessageResponseBuilder};
|
use hickory_server::authority::{AuthorityObject, Catalog, MessageResponseBuilder};
|
||||||
use hickory_server::proto::op::{Header, ResponseCode};
|
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::resolver::config::{ResolverConfig, ResolverOpts};
|
||||||
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||||
use hickory_server::store::forwarder::{ForwardAuthority, ForwardConfig};
|
use hickory_server::store::forwarder::{ForwardAuthority, ForwardConfig};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
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 {
|
for (iface, info) in res {
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
iface,
|
iface,
|
||||||
@@ -64,7 +64,6 @@ pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|ip_info| ip_info.device_type)
|
.and_then(|ip_info| ip_info.device_type)
|
||||||
.map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")),
|
.map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")),
|
||||||
info.public(),
|
|
||||||
info.ip_info.as_ref().map_or_else(
|
info.ip_info.as_ref().map_or_else(
|
||||||
|| "<DISCONNECTED>".to_owned(),
|
|| "<DISCONNECTED>".to_owned(),
|
||||||
|ip_info| ip_info
|
|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_about("about.show-gateways-startos-can-listen-on")
|
||||||
.with_call_remote::<CliContext>(),
|
.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(
|
.subcommand(
|
||||||
"forget",
|
"forget",
|
||||||
from_fn_async(forget_iface)
|
from_fn_async(forget_iface)
|
||||||
@@ -134,40 +117,6 @@ async fn list_interfaces(
|
|||||||
Ok(ctx.net_controller.net_iface.watcher.ip_info())
|
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)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
|
||||||
struct ForgetGatewayParams {
|
struct ForgetGatewayParams {
|
||||||
#[arg(help = "help.arg.gateway-id")]
|
#[arg(help = "help.arg.gateway-id")]
|
||||||
@@ -910,12 +859,11 @@ async fn watch_ip(
|
|||||||
|
|
||||||
write_to.send_if_modified(
|
write_to.send_if_modified(
|
||||||
|m: &mut OrdMap<GatewayId, NetworkInterfaceInfo>| {
|
|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)
|
.get(&iface)
|
||||||
.map_or((None, None, None, None, None), |i| {
|
.map_or((None, None, None, None), |i| {
|
||||||
(
|
(
|
||||||
i.name.clone(),
|
i.name.clone(),
|
||||||
i.public,
|
|
||||||
i.secure,
|
i.secure,
|
||||||
i.gateway_type,
|
i.gateway_type,
|
||||||
i.ip_info
|
i.ip_info
|
||||||
@@ -929,7 +877,6 @@ async fn watch_ip(
|
|||||||
iface.clone(),
|
iface.clone(),
|
||||||
NetworkInterfaceInfo {
|
NetworkInterfaceInfo {
|
||||||
name,
|
name,
|
||||||
public,
|
|
||||||
secure,
|
secure,
|
||||||
ip_info: Some(ip_info.clone()),
|
ip_info: Some(ip_info.clone()),
|
||||||
gateway_type,
|
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> {
|
pub async fn forget(&self, interface: &GatewayId) -> Result<(), Error> {
|
||||||
let mut sub = self
|
let mut sub = self
|
||||||
.db
|
.db
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::util::serde::{HandlerExtSerde, display_serializable};
|
|||||||
pub struct HostAddress {
|
pub struct HostAddress {
|
||||||
pub address: InternedString,
|
pub address: InternedString,
|
||||||
pub public: Option<PublicDomainConfig>,
|
pub public: Option<PublicDomainConfig>,
|
||||||
pub private: bool,
|
pub private: Option<BTreeSet<GatewayId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
@@ -53,7 +53,7 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
|||||||
for domain in &public {
|
for domain in &public {
|
||||||
check_domain(&mut domains, domain.clone())?;
|
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) {
|
if !public.contains(&domain) {
|
||||||
check_domain(&mut domains, domain)?;
|
check_domain(&mut domains, domain)?;
|
||||||
}
|
}
|
||||||
@@ -63,13 +63,13 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
|||||||
host.as_public_domains_mut()
|
host.as_public_domains_mut()
|
||||||
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||||
host.as_private_domains_mut()
|
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()?;
|
let public = host.as_public_domains().keys()?;
|
||||||
for domain in &public {
|
for domain in &public {
|
||||||
check_domain(&mut domains, domain.clone())?;
|
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) {
|
if !public.contains(&domain) {
|
||||||
check_domain(&mut domains, domain)?;
|
check_domain(&mut domains, domain)?;
|
||||||
}
|
}
|
||||||
@@ -146,21 +146,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc => "ADDRESS", "PUBLIC", "ACME PROVIDER"]);
|
todo!("find a good way to represent this");
|
||||||
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"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table.print_tty(false)?;
|
table.print_tty(false)?;
|
||||||
|
|
||||||
@@ -248,18 +234,20 @@ pub async fn remove_public_domain<Kind: HostApiKind>(
|
|||||||
pub struct AddPrivateDomainParams {
|
pub struct AddPrivateDomainParams {
|
||||||
#[arg(help = "help.arg.fqdn")]
|
#[arg(help = "help.arg.fqdn")]
|
||||||
pub fqdn: InternedString,
|
pub fqdn: InternedString,
|
||||||
|
pub gateway: GatewayId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_private_domain<Kind: HostApiKind>(
|
pub async fn add_private_domain<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
AddPrivateDomainParams { fqdn }: AddPrivateDomainParams,
|
AddPrivateDomainParams { fqdn, gateway }: AddPrivateDomainParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_private_domains_mut()
|
.as_private_domains_mut()
|
||||||
.mutate(|d| Ok(d.insert(fqdn)))?;
|
.upsert(&fqdn, || Ok(BTreeSet::new()))?
|
||||||
|
.mutate(|d| Ok(d.insert(gateway)))?;
|
||||||
handle_duplicates(db)
|
handle_duplicates(db)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -7,6 +8,7 @@ use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_f
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::HostId;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::prelude::Map;
|
use crate::db::prelude::Map;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
@@ -16,7 +18,6 @@ use crate::net::vhost::AlpnInfo;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::FromStrParser;
|
use crate::util::FromStrParser;
|
||||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
use crate::HostId;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -49,31 +50,36 @@ impl FromStr for BindId {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
pub struct DerivedAddressInfo {
|
pub struct DerivedAddressInfo {
|
||||||
/// User-controlled: private addresses the user has disabled
|
/// User override: enable these addresses (only for public IP & port)
|
||||||
pub private_disabled: BTreeSet<HostnameInfo>,
|
pub enabled: BTreeSet<SocketAddr>,
|
||||||
/// User-controlled: public addresses the user has enabled
|
/// User override: disable these addresses (only for domains and private IP & port)
|
||||||
pub public_enabled: BTreeSet<HostnameInfo>,
|
pub disabled: BTreeSet<(InternedString, u16)>,
|
||||||
/// COMPUTED: NetServiceData::update — all possible addresses for this binding
|
/// COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||||
pub possible: BTreeSet<HostnameInfo>,
|
pub available: BTreeSet<HostnameInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DerivedAddressInfo {
|
impl DerivedAddressInfo {
|
||||||
/// Returns addresses that are currently enabled.
|
/// Returns addresses that are currently enabled after applying overrides.
|
||||||
/// Private addresses are enabled by default (disabled if in private_disabled).
|
/// Default: public IPs are disabled, everything else is enabled.
|
||||||
/// Public addresses are disabled by default (enabled if in public_enabled).
|
/// Explicit `enabled`/`disabled` overrides take precedence.
|
||||||
pub fn enabled(&self) -> BTreeSet<&HostnameInfo> {
|
pub fn enabled(&self) -> BTreeSet<&HostnameInfo> {
|
||||||
self.possible
|
self.available
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|h| {
|
.filter(|h| {
|
||||||
if h.public {
|
if h.public && h.metadata.is_ip() {
|
||||||
self.public_enabled.contains(h)
|
// 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 {
|
} 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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
@@ -199,9 +205,9 @@ impl BindInfo {
|
|||||||
options,
|
options,
|
||||||
net: lan,
|
net: lan,
|
||||||
addresses: DerivedAddressInfo {
|
addresses: DerivedAddressInfo {
|
||||||
private_disabled: addresses.private_disabled,
|
enabled: addresses.enabled,
|
||||||
public_enabled: addresses.public_enabled,
|
disabled: addresses.disabled,
|
||||||
possible: BTreeSet::new(),
|
available: BTreeSet::new(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -328,17 +334,27 @@ pub async fn set_address_enabled<Kind: HostApiKind>(
|
|||||||
.as_bindings_mut()
|
.as_bindings_mut()
|
||||||
.mutate(|b| {
|
.mutate(|b| {
|
||||||
let bind = b.get_mut(&internal_port).or_not_found(internal_port)?;
|
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 {
|
if enabled {
|
||||||
bind.addresses.public_enabled.insert(address.clone());
|
bind.addresses.enabled.insert(sa);
|
||||||
} else {
|
} else {
|
||||||
bind.addresses.public_enabled.remove(&address);
|
bind.addresses.enabled.remove(&sa);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if enabled {
|
||||||
bind.addresses.private_disabled.remove(&address);
|
bind.addresses.disabled.remove(&key);
|
||||||
} else {
|
} else {
|
||||||
bind.addresses.private_disabled.insert(address.clone());
|
bind.addresses.disabled.insert(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -3,19 +3,23 @@ use std::future::Future;
|
|||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl::OrdMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use patch_db::DestructureMut;
|
||||||
use rpc_toolkit::{Context, Empty, HandlerExt, OrEmpty, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, Empty, HandlerExt, OrEmpty, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||||
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
|
use crate::net::host::binding::{BindInfo, BindOptions, Bindings, binding};
|
||||||
|
use crate::net::service_interface::{HostnameInfo, HostnameMetadata};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{HostId, PackageId};
|
use crate::{GatewayId, HostId, PackageId};
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
pub mod binding;
|
pub mod binding;
|
||||||
@@ -27,7 +31,7 @@ pub mod binding;
|
|||||||
pub struct Host {
|
pub struct Host {
|
||||||
pub bindings: Bindings,
|
pub bindings: Bindings,
|
||||||
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||||
pub private_domains: BTreeSet<InternedString>,
|
pub private_domains: BTreeMap<InternedString, BTreeSet<GatewayId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Host> for Host {
|
impl AsRef<Host> for Host {
|
||||||
@@ -45,20 +49,183 @@ impl Host {
|
|||||||
.map(|(address, config)| HostAddress {
|
.map(|(address, config)| HostAddress {
|
||||||
address: address.clone(),
|
address: address.clone(),
|
||||||
public: Some(config.clone()),
|
public: Some(config.clone()),
|
||||||
private: self.private_domains.contains(address),
|
private: self.private_domains.get(address).cloned(),
|
||||||
})
|
})
|
||||||
.chain(
|
.chain(
|
||||||
self.private_domains
|
self.private_domains
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| !self.public_domains.contains_key(*a))
|
.filter(|(domain, _)| !self.public_domains.contains_key(*domain))
|
||||||
.map(|address| HostAddress {
|
.map(|(domain, gateways)| HostAddress {
|
||||||
address: address.clone(),
|
address: domain.clone(),
|
||||||
public: None,
|
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)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::net::gateway::NetworkInterfaceController;
|
|||||||
use crate::net::host::address::HostAddress;
|
use crate::net::host::address::HostAddress;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||||
use crate::net::host::{Host, Hosts, host_for};
|
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::socks::SocksController;
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
||||||
@@ -261,10 +261,7 @@ impl NetServiceData {
|
|||||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||||
let binds = self.binds.entry(id.clone()).or_default();
|
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 net_ifaces = ctrl.net_iface.watcher.ip_info();
|
||||||
let hostname = server_info.as_hostname().de()?;
|
|
||||||
let host_addresses: Vec<_> = host.addresses().collect();
|
let host_addresses: Vec<_> = host.addresses().collect();
|
||||||
|
|
||||||
// Collect private DNS entries (domains without public config)
|
// 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() {
|
for (_port, bind) in host.bindings.iter_mut() {
|
||||||
if !bind.enabled {
|
if !bind.enabled {
|
||||||
continue;
|
continue;
|
||||||
@@ -286,7 +283,83 @@ impl NetServiceData {
|
|||||||
continue;
|
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
|
for (gateway_id, info) in net_ifaces
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, info)| {
|
.filter(|(_, info)| {
|
||||||
@@ -296,111 +369,97 @@ impl NetServiceData {
|
|||||||
})
|
})
|
||||||
.filter(|(_, info)| info.ip_info.is_some())
|
.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(|_| {
|
let port = bind.net.assigned_port.filter(|_| {
|
||||||
bind.options.secure.map_or(false, |s| {
|
bind.options.secure.map_or(false, |s| {
|
||||||
!(s.ssl && bind.options.add_ssl.is_some()) || info.secure()
|
!(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 {
|
if let Some(ip_info) = &info.ip_info {
|
||||||
let public = info.public();
|
let public = info.public();
|
||||||
|
// WAN IP (public)
|
||||||
if let Some(wan_ip) = ip_info.wan_ip {
|
if let Some(wan_ip) = ip_info.wan_ip {
|
||||||
bind.addresses.possible.insert(HostnameInfo {
|
let host_str = InternedString::from_display(&wan_ip);
|
||||||
gateway: gateway.clone(),
|
if let Some(p) = port {
|
||||||
public: true,
|
bind.addresses.available.insert(HostnameInfo {
|
||||||
hostname: IpHostname::Ipv4 {
|
ssl: false,
|
||||||
value: wan_ip,
|
public: true,
|
||||||
port,
|
host: host_str.clone(),
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
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 {
|
for ipnet in &ip_info.subnets {
|
||||||
match ipnet {
|
match ipnet {
|
||||||
IpNet::V4(net) => {
|
IpNet::V4(net) => {
|
||||||
if !public {
|
if !public {
|
||||||
bind.addresses.possible.insert(HostnameInfo {
|
let host_str = InternedString::from_display(&net.addr());
|
||||||
gateway: gateway.clone(),
|
if let Some(p) = port {
|
||||||
public,
|
bind.addresses.available.insert(HostnameInfo {
|
||||||
hostname: IpHostname::Ipv4 {
|
ssl: false,
|
||||||
value: net.addr(),
|
public: false,
|
||||||
port,
|
host: host_str.clone(),
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
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) => {
|
IpNet::V6(net) => {
|
||||||
bind.addresses.possible.insert(HostnameInfo {
|
let is_public = public && !ipv6_is_local(net.addr());
|
||||||
gateway: gateway.clone(),
|
let host_str = InternedString::from_display(&net.addr());
|
||||||
public: public && !ipv6_is_local(net.addr()),
|
if let Some(p) = port {
|
||||||
hostname: IpHostname::Ipv6 {
|
bind.addresses.available.insert(HostnameInfo {
|
||||||
value: net.addr(),
|
ssl: false,
|
||||||
scope_id: ip_info.scope_id,
|
public: is_public,
|
||||||
port,
|
host: host_str.clone(),
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
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
|
let server_private_ips: BTreeSet<IpAddr> = enabled_addresses
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| !a.public)
|
.filter(|a| !a.public)
|
||||||
.filter_map(|a| {
|
.flat_map(|a| a.metadata.gateways())
|
||||||
net_ifaces
|
.filter_map(|gw| net_ifaces.get(gw).and_then(|info| info.ip_info.as_ref()))
|
||||||
.get(&a.gateway.id)
|
|
||||||
.and_then(|info| info.ip_info.as_ref())
|
|
||||||
})
|
|
||||||
.flat_map(|ip_info| ip_info.subnets.iter().map(|s| s.addr()))
|
.flat_map(|ip_info| ip_info.subnets.iter().map(|s| s.addr()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -465,32 +521,36 @@ impl NetServiceData {
|
|||||||
|
|
||||||
// Domain vhosts: group by (domain, ssl_port), merge public/private sets
|
// Domain vhosts: group by (domain, ssl_port), merge public/private sets
|
||||||
for addr_info in &enabled_addresses {
|
for addr_info in &enabled_addresses {
|
||||||
if let IpHostname::Domain {
|
if !addr_info.ssl {
|
||||||
value: domain,
|
continue;
|
||||||
ssl_port: Some(domain_ssl_port),
|
}
|
||||||
..
|
match &addr_info.metadata {
|
||||||
} = &addr_info.hostname
|
HostnameMetadata::PublicDomain { .. }
|
||||||
{
|
| HostnameMetadata::PrivateDomain { .. } => {}
|
||||||
let key = (Some(domain.clone()), *domain_ssl_port);
|
_ => continue,
|
||||||
let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
|
}
|
||||||
public: BTreeSet::new(),
|
let domain = &addr_info.host;
|
||||||
private: BTreeSet::new(),
|
let domain_ssl_port = addr_info.port.unwrap_or(443);
|
||||||
acme: host_addresses
|
let key = (Some(domain.clone()), domain_ssl_port);
|
||||||
.iter()
|
let target = vhosts.entry(key).or_insert_with(|| ProxyTarget {
|
||||||
.find(|a| &a.address == domain)
|
public: BTreeSet::new(),
|
||||||
.and_then(|a| a.public.as_ref())
|
private: BTreeSet::new(),
|
||||||
.and_then(|p| p.acme.clone()),
|
acme: host_addresses
|
||||||
addr,
|
.iter()
|
||||||
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
.find(|a| a.address == *domain)
|
||||||
connect_ssl: connect_ssl
|
.and_then(|a| a.public.as_ref())
|
||||||
.clone()
|
.and_then(|p| p.acme.clone()),
|
||||||
.map(|_| ctrl.tls_client_config.clone()),
|
addr,
|
||||||
});
|
add_x_forwarded_headers: ssl.add_x_forwarded_headers,
|
||||||
if addr_info.public {
|
connect_ssl: connect_ssl.clone().map(|_| ctrl.tls_client_config.clone()),
|
||||||
target.public.insert(addr_info.gateway.id.clone());
|
});
|
||||||
} else {
|
if addr_info.public {
|
||||||
// Add interface IPs for this gateway to private set
|
for gw in addr_info.metadata.gateways() {
|
||||||
if let Some(info) = net_ifaces.get(&addr_info.gateway.id) {
|
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 {
|
if let Some(ip_info) = &info.ip_info {
|
||||||
for subnet in &ip_info.subnets {
|
for subnet in &ip_info.subnets {
|
||||||
target.private.insert(subnet.addr());
|
target.private.insert(subnet.addr());
|
||||||
@@ -512,16 +572,14 @@ impl NetServiceData {
|
|||||||
let fwd_public: BTreeSet<GatewayId> = enabled_addresses
|
let fwd_public: BTreeSet<GatewayId> = enabled_addresses
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| a.public)
|
.filter(|a| a.public)
|
||||||
.map(|a| a.gateway.id.clone())
|
.flat_map(|a| a.metadata.gateways())
|
||||||
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
let fwd_private: BTreeSet<IpAddr> = enabled_addresses
|
let fwd_private: BTreeSet<IpAddr> = enabled_addresses
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| !a.public)
|
.filter(|a| !a.public)
|
||||||
.filter_map(|a| {
|
.flat_map(|a| a.metadata.gateways())
|
||||||
net_ifaces
|
.filter_map(|gw| net_ifaces.get(gw).and_then(|i| i.ip_info.as_ref()))
|
||||||
.get(&a.gateway.id)
|
|
||||||
.and_then(|i| i.ip_info.as_ref())
|
|
||||||
})
|
|
||||||
.flat_map(|ip| ip.subnets.iter().map(|s| s.addr()))
|
.flat_map(|ip| ip.subnets.iter().map(|s| s.addr()))
|
||||||
.collect();
|
.collect();
|
||||||
forwards.insert(
|
forwards.insert(
|
||||||
@@ -634,8 +692,8 @@ impl NetServiceData {
|
|||||||
for (port, bind) in host.bindings.0 {
|
for (port, bind) in host.bindings.0 {
|
||||||
if let Some(b) = bindings.as_idx_mut(&port) {
|
if let Some(b) = bindings.as_idx_mut(&port) {
|
||||||
b.as_addresses_mut()
|
b.as_addresses_mut()
|
||||||
.as_possible_mut()
|
.as_available_mut()
|
||||||
.ser(&bind.addresses.possible)?;
|
.ser(&bind.addresses.available)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
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 serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HostnameInfo {
|
pub struct HostnameInfo {
|
||||||
pub gateway: GatewayInfo,
|
pub ssl: bool,
|
||||||
pub public: 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 {
|
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 {
|
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,
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ pub async fn add_tunnel(
|
|||||||
iface.clone(),
|
iface.clone(),
|
||||||
NetworkInterfaceInfo {
|
NetworkInterfaceInfo {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
public: None,
|
|
||||||
secure: None,
|
secure: None,
|
||||||
ip_info: None,
|
ip_info: None,
|
||||||
gateway_type,
|
gateway_type,
|
||||||
@@ -193,15 +192,12 @@ pub async fn remove_tunnel(
|
|||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
for host in all_hosts(db) {
|
for host in all_hosts(db) {
|
||||||
let host = host?;
|
let host = host?;
|
||||||
host.as_bindings_mut().mutate(|b| {
|
host.as_private_domains_mut().mutate(|d| {
|
||||||
Ok(b.values_mut().for_each(|v| {
|
for gateways in d.values_mut() {
|
||||||
v.addresses
|
gateways.remove(&id);
|
||||||
.private_disabled
|
}
|
||||||
.retain(|h| h.gateway.id != id);
|
d.retain(|_, gateways| !gateways.is_empty());
|
||||||
v.addresses
|
Ok(())
|
||||||
.public_enabled
|
|
||||||
.retain(|h| h.gateway.id != id);
|
|
||||||
}))
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ pub async fn get_ssl_certificate(
|
|||||||
Ok(m.as_public_domains()
|
Ok(m.as_public_domains()
|
||||||
.keys()?
|
.keys()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().keys()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_bindings()
|
m.as_bindings()
|
||||||
.de()?
|
.de()?
|
||||||
.values()
|
.values()
|
||||||
.flat_map(|b| b.addresses.possible.iter().cloned())
|
.flat_map(|b| b.addresses.available.iter().cloned())
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<InternedString>>())
|
.collect::<Vec<InternedString>>())
|
||||||
@@ -182,12 +182,12 @@ pub async fn get_ssl_key(
|
|||||||
Ok(m.as_public_domains()
|
Ok(m.as_public_domains()
|
||||||
.keys()?
|
.keys()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(m.as_private_domains().de()?)
|
.chain(m.as_private_domains().keys()?)
|
||||||
.chain(
|
.chain(
|
||||||
m.as_bindings()
|
m.as_bindings()
|
||||||
.de()?
|
.de()?
|
||||||
.values()
|
.values()
|
||||||
.flat_map(|b| b.addresses.possible.iter().cloned())
|
.flat_map(|b| b.addresses.available.iter().cloned())
|
||||||
.map(|h| h.to_san_hostname()),
|
.map(|h| h.to_san_hostname()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<InternedString>>())
|
.collect::<Vec<InternedString>>())
|
||||||
|
|||||||
@@ -164,6 +164,19 @@ fn migrate_host(host: Option<&mut Value>) {
|
|||||||
// Remove hostnameInfo from host
|
// Remove hostnameInfo from host
|
||||||
host.remove("hostnameInfo");
|
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"
|
// 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()) {
|
if let Some(bindings) = host.get_mut("bindings").and_then(|b| b.as_object_mut()) {
|
||||||
for (_, binding) in bindings.iter_mut() {
|
for (_, binding) in bindings.iter_mut() {
|
||||||
@@ -173,9 +186,9 @@ fn migrate_host(host: Option<&mut Value>) {
|
|||||||
binding_obj.insert(
|
binding_obj.insert(
|
||||||
"addresses".into(),
|
"addresses".into(),
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"privateDisabled": [],
|
"enabled": [],
|
||||||
"publicEnabled": [],
|
"disabled": [],
|
||||||
"possible": []
|
"available": []
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ export type DerivedAddressInfo = {
|
|||||||
/**
|
/**
|
||||||
* User-controlled: private addresses the user has disabled
|
* User-controlled: private addresses the user has disabled
|
||||||
*/
|
*/
|
||||||
privateDisabled: Array<HostnameInfo>
|
enabled: Array<HostnameInfo>
|
||||||
/**
|
/**
|
||||||
* User-controlled: public addresses the user has enabled
|
* User-controlled: public addresses the user has enabled
|
||||||
*/
|
*/
|
||||||
publicEnabled: Array<HostnameInfo>
|
disabled: Array<HostnameInfo>
|
||||||
/**
|
/**
|
||||||
* COMPUTED: NetServiceData::update — all possible addresses for this binding
|
* COMPUTED: NetServiceData::update — all possible addresses for this binding
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -226,12 +226,16 @@ function filterRec(
|
|||||||
return hostnames
|
return hostnames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDefaultEnabled(h: HostnameInfo): boolean {
|
||||||
|
return !(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))
|
||||||
|
}
|
||||||
|
|
||||||
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
|
||||||
return addr.possible.filter((h) =>
|
return addr.possible.filter((h) => {
|
||||||
h.public
|
if (addr.enabled.some((e) => deepEqual(e, h))) return true
|
||||||
? addr.publicEnabled.some((e) => deepEqual(e, h))
|
if (addr.disabled.some((d) => deepEqual(d, h))) return false
|
||||||
: !addr.privateDisabled.some((d) => deepEqual(d, h)),
|
return isDefaultEnabled(h)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filledAddress = (
|
export const filledAddress = (
|
||||||
|
|||||||
@@ -257,9 +257,9 @@ export class InterfaceService {
|
|||||||
if (!binding) return []
|
if (!binding) return []
|
||||||
const addr = binding.addresses
|
const addr = binding.addresses
|
||||||
const enabled = addr.possible.filter(h =>
|
const enabled = addr.possible.filter(h =>
|
||||||
h.public
|
addr.enabled.some(e => utils.deepEqual(e, h)) ||
|
||||||
? addr.publicEnabled.some(e => utils.deepEqual(e, h))
|
(!addr.disabled.some(d => utils.deepEqual(d, h)) &&
|
||||||
: !addr.privateDisabled.some(d => utils.deepEqual(d, h)),
|
!(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))),
|
||||||
)
|
)
|
||||||
return enabled.filter(
|
return enabled.filter(
|
||||||
h =>
|
h =>
|
||||||
|
|||||||
@@ -134,9 +134,12 @@ export default class ServiceInterfaceRoute {
|
|||||||
gateways:
|
gateways:
|
||||||
gateways.map(g => ({
|
gateways.map(g => ({
|
||||||
enabled:
|
enabled:
|
||||||
(g.public
|
(binding?.addresses.enabled.some(a => a.gateway.id === g.id) ||
|
||||||
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
(!binding?.addresses.disabled.some(a => a.gateway.id === g.id) &&
|
||||||
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
binding?.addresses.possible.some(a =>
|
||||||
|
a.gateway.id === g.id &&
|
||||||
|
!(a.public && (a.hostname.kind === 'ipv4' || a.hostname.kind === 'ipv6'))
|
||||||
|
))) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})) || [],
|
})) || [],
|
||||||
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
publicDomains: getPublicDomains(host.publicDomains, gateways),
|
||||||
|
|||||||
@@ -95,9 +95,12 @@ export default class StartOsUiComponent {
|
|||||||
),
|
),
|
||||||
gateways: gateways.map(g => ({
|
gateways: gateways.map(g => ({
|
||||||
enabled:
|
enabled:
|
||||||
(g.public
|
(binding?.addresses.enabled.some(a => a.gateway.id === g.id) ||
|
||||||
? binding?.addresses.publicEnabled.some(a => a.gateway.id === g.id)
|
(!binding?.addresses.disabled.some(a => a.gateway.id === g.id) &&
|
||||||
: !binding?.addresses.privateDisabled.some(a => a.gateway.id === g.id)) ?? false,
|
binding?.addresses.possible.some(a =>
|
||||||
|
a.gateway.id === g.id &&
|
||||||
|
!(a.public && (a.hostname.kind === 'ipv4' || a.hostname.kind === 'ipv6'))
|
||||||
|
))) ?? false,
|
||||||
...g,
|
...g,
|
||||||
})),
|
})),
|
||||||
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
publicDomains: getPublicDomains(network.host.publicDomains, gateways),
|
||||||
|
|||||||
@@ -2128,8 +2128,8 @@ export namespace Mock {
|
|||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [
|
possible: [
|
||||||
{
|
{
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
@@ -2214,8 +2214,8 @@ export namespace Mock {
|
|||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [],
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
@@ -2237,8 +2237,8 @@ export namespace Mock {
|
|||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [],
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export const mockPatchData: DataModel = {
|
|||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [
|
possible: [
|
||||||
{
|
{
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
@@ -516,8 +516,8 @@ export const mockPatchData: DataModel = {
|
|||||||
assignedSslPort: 443,
|
assignedSslPort: 443,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [
|
possible: [
|
||||||
{
|
{
|
||||||
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
gateway: { id: 'eth0', name: 'Ethernet', public: false },
|
||||||
@@ -602,8 +602,8 @@ export const mockPatchData: DataModel = {
|
|||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [],
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
@@ -625,8 +625,8 @@ export const mockPatchData: DataModel = {
|
|||||||
assignedSslPort: null,
|
assignedSslPort: null,
|
||||||
},
|
},
|
||||||
addresses: {
|
addresses: {
|
||||||
privateDisabled: [],
|
enabled: [],
|
||||||
publicEnabled: [],
|
disabled: [],
|
||||||
possible: [],
|
possible: [],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
Reference in New Issue
Block a user