diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index c228bb32a..574fc8a9b 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -19,8 +19,8 @@ use crate::account::AccountInfo; use crate::db::model::package::AllPackageData; use crate::net::acme::AcmeProvider; use crate::net::forward::START9_BRIDGE_IFACE; -use crate::net::host::Host; use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo}; +use crate::net::host::Host; use crate::net::utils::ipv6_is_local; use crate::net::vhost::AlpnInfo; use crate::prelude::*; @@ -283,11 +283,9 @@ impl NetworkInterfaceInfo { }) .collect::>(); if !ip4s.is_empty() { - return ip4s.iter().all(|ip4| { - ip4.is_loopback() - // || (ip4.is_private() && !ip4.octets().starts_with(&[10, 59])) // reserving 10.59 for public wireguard configurations - || ip4.is_link_local() - }); + 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() { diff --git a/core/startos/src/net/gateway.rs b/core/startos/src/net/gateway.rs index fdd1bab0c..0bd7f4938 100644 --- a/core/startos/src/net/gateway.rs +++ b/core/startos/src/net/gateway.rs @@ -863,6 +863,16 @@ impl NetworkInterfaceController { ) -> Result<(), Error> { tracing::debug!("syncronizing {info:?} to db"); + db.mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_network_mut() + .as_gateways_mut() + .ser(info) + }) + .await + .result?; + let ntp: BTreeSet<_> = info .values() .filter_map(|i| i.ip_info.as_ref()) diff --git a/core/startos/src/net/host/address.rs b/core/startos/src/net/host/address.rs index 03957c767..f01ef3bc9 100644 --- a/core/startos/src/net/host/address.rs +++ b/core/startos/src/net/host/address.rs @@ -3,17 +3,17 @@ use std::collections::BTreeSet; use clap::Parser; use imbl_value::InternedString; use models::GatewayId; -use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::net::acme::AcmeProvider; -use crate::net::host::{HostApiKind, all_hosts}; +use crate::net::host::{all_hosts, HostApiKind}; use crate::net::tor::OnionAddress; use crate::prelude::*; -use crate::util::serde::{HandlerExtSerde, display_serializable}; +use crate::util::serde::{display_serializable, HandlerExtSerde}; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] @@ -73,8 +73,8 @@ fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> { Ok(()) } -pub fn address_api() --> ParentHandler { +pub fn address_api( +) -> ParentHandler { ParentHandler::::new() .subcommand( "domain", @@ -200,7 +200,7 @@ pub fn address_api() #[derive(Deserialize, Serialize, Parser)] pub struct AddPublicDomainParams { - pub domain: InternedString, + pub fqdn: InternedString, #[arg(long)] pub acme: Option, pub gateway: GatewayId, @@ -209,7 +209,7 @@ pub struct AddPublicDomainParams { pub async fn add_public_domain( ctx: RpcContext, AddPublicDomainParams { - ref domain, + ref fqdn, acme, gateway, }: AddPublicDomainParams, @@ -231,7 +231,7 @@ pub async fn add_public_domain( Kind::host_for(&inheritance, db)? .as_public_domains_mut() - .insert(domain, &PublicDomainConfig { acme, gateway })?; + .insert(fqdn, &PublicDomainConfig { acme, gateway })?; check_duplicates(db) }) .await @@ -266,19 +266,19 @@ pub async fn remove_public_domain( #[derive(Deserialize, Serialize, Parser)] pub struct AddPrivateDomainParams { - pub domain: InternedString, + pub fqdn: InternedString, } pub async fn add_private_domain( ctx: RpcContext, - AddPrivateDomainParams { domain }: AddPrivateDomainParams, + AddPrivateDomainParams { fqdn }: AddPrivateDomainParams, inheritance: Kind::Inheritance, ) -> Result<(), Error> { ctx.db .mutate(|db| { Kind::host_for(&inheritance, db)? .as_private_domains_mut() - .mutate(|d| Ok(d.insert(domain)))?; + .mutate(|d| Ok(d.insert(fqdn)))?; check_duplicates(db) }) .await diff --git a/core/startos/src/net/host/binding.rs b/core/startos/src/net/host/binding.rs index 75508aed4..96b49abf9 100644 --- a/core/startos/src/net/host/binding.rs +++ b/core/startos/src/net/host/binding.rs @@ -1,11 +1,11 @@ use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; -use clap::Parser; use clap::builder::ValueParserFactory; +use clap::Parser; use imbl::OrdSet; use models::{FromStrParser, GatewayId, HostId}; -use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -16,7 +16,7 @@ use crate::net::gateway::InterfaceFilter; use crate::net::host::HostApiKind; use crate::net::vhost::AlpnInfo; use crate::prelude::*; -use crate::util::serde::{HandlerExtSerde, display_serializable}; +use crate::util::serde::{display_serializable, HandlerExtSerde}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)] #[ts(export)] @@ -136,9 +136,9 @@ impl BindInfo { impl InterfaceFilter for NetInfo { fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool { if info.public() { - self.public_enabled.contains(id) + dbg!(self.public_enabled.contains(id)) } else { - !self.private_disabled.contains(id) + dbg!(!self.private_disabled.contains(id)) } } } @@ -169,8 +169,8 @@ pub struct AddSslOptions { pub alpn: Option, } -pub fn binding() --> ParentHandler { +pub fn binding( +) -> ParentHandler { ParentHandler::::new() .subcommand( "list", diff --git a/sdk/base/lib/util/ip.ts b/sdk/base/lib/util/ip.ts index 6d2bd72b6..a631b6b23 100644 --- a/sdk/base/lib/util/ip.ts +++ b/sdk/base/lib/util/ip.ts @@ -31,6 +31,9 @@ export class IpAddress { throw new Error("invalid ip address") } } + static parse(address: string): IpAddress { + return new IpAddress(address) + } isIpv4(): boolean { return this.octets.length === 4 } @@ -49,6 +52,9 @@ export class IpNet extends IpAddress { super(address) this.prefix = Number(prefixStr) } + static parse(ipnet: string): IpNet { + return new IpNet(ipnet) + } contains(address: string | IpAddress): boolean { if (typeof address === "string") address = new IpAddress(address) if (this.octets.length !== address.octets.length) return false @@ -75,3 +81,5 @@ export const PRIVATE_IPV4_RANGES = [ ] export const IPV6_LINK_LOCAL = new IpNet("fe80::/10") + +export const CGNAT = new IpNet("100.64.0.0/10") diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts index d0b511369..4e6a6ac79 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts @@ -241,7 +241,7 @@ export class PublicDomainService { default: '', disabled: data.gateways .filter( - g => !g.ipInfo.wanIp || g.ipInfo.wanIp.split('.').at(-1) === '100', + g => !g.ipInfo?.wanIp || utils.CGNAT.contains(g.ipInfo?.wanIp), ) .map(g => g.id), })), diff --git a/web/projects/ui/src/app/services/gateway.service.ts b/web/projects/ui/src/app/services/gateway.service.ts index 0c4df5629..2aca98a67 100644 --- a/web/projects/ui/src/app/services/gateway.service.ts +++ b/web/projects/ui/src/app/services/gateway.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core' import { PatchDB } from 'patch-db-client' -import { T } from '@start9labs/start-sdk' +import { T, utils } from '@start9labs/start-sdk' import { map } from 'rxjs/operators' import { DataModel } from './patch-db/data-model' import { toSignal } from '@angular/core/rxjs-interop' @@ -8,7 +8,10 @@ import { toSignal } from '@angular/core/rxjs-interop' export type GatewayPlus = T.NetworkInterfaceInfo & { id: string ipInfo: T.IpInfo + subnets: utils.IpNet[] lanIpv4: string[] + wanIp?: utils.IpAddress + public: boolean } @Injectable() @@ -20,16 +23,20 @@ export class GatewayService { map(gateways => Object.entries(gateways) .filter(([_, val]) => !!val?.ipInfo) - .map( - ([id, val]) => - ({ - ...val, - id, - lanIpv4: val?.ipInfo?.subnets - .filter(s => !s.includes('::')) - .map(s => s.split('/')[0]), - }) as GatewayPlus, - ), + .map(([id, val]) => { + const subnets = + val?.ipInfo?.subnets.map(s => utils.IpNet.parse(s)) ?? [] + return { + ...val, + id, + subnets, + lanIpv4: subnets.filter(s => s.isIpv4()).map(s => s.address), + public: val?.public ?? subnets.some(s => s.isPublic()), + wanIp: + val?.ipInfo?.wanIp && + utils.IpAddress.parse(val?.ipInfo?.wanIp), + } as GatewayPlus + }), ), ), )