feat: cascade address enable/disable to related bindings on same gateway

This commit is contained in:
Aiden McClelland
2026-03-26 15:16:08 -06:00
parent f3d2782f18
commit dce0f075ce
2 changed files with 135 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ use crate::hostname::ServerHostname;
use crate::net::acme::AcmeProvider;
use crate::net::gateway::{CheckDnsParams, CheckPortParams, CheckPortRes, check_dns, check_port};
use crate::net::host::{HostApiKind, all_hosts};
use crate::net::service_interface::HostnameMetadata;
use crate::prelude::*;
use crate::util::serde::{HandlerExtSerde, display_serializable};
@@ -246,8 +247,50 @@ pub async fn add_public_domain<Kind: HostApiKind>(
.and_then(|a| a.port)
.ok_or_else(|| Error::new(eyre!("no public address found for {fqdn} on port {internal_port}"), ErrorKind::NotFound))?;
// Disable the domain on all other bindings
// On the target binding, enable the WAN IPv4 and all
// public domains on the same gateway+port (no SNI without SSL).
host.as_bindings_mut().mutate(|b| {
if let Some(bind) = b.get_mut(&internal_port) {
let non_ssl_port = bind.addresses.available.iter().find_map(|a| {
if a.ssl || !a.public || a.hostname != fqdn {
return None;
}
if let HostnameMetadata::PublicDomain { gateway: gw } = &a.metadata {
if *gw == gateway {
return a.port;
}
}
None
});
if let Some(dp) = non_ssl_port {
for a in &bind.addresses.available {
if a.ssl || !a.public {
continue;
}
if let HostnameMetadata::Ipv4 { gateway: gw } = &a.metadata {
if *gw == gateway {
if let Some(sa) = a.to_socket_addr() {
if sa.port() == dp {
bind.addresses.enabled.insert(sa);
}
}
}
}
}
for a in &bind.addresses.available {
if a.ssl {
continue;
}
if let HostnameMetadata::PublicDomain { gateway: gw } = &a.metadata {
if *gw == gateway && a.port == Some(dp) {
bind.addresses.disabled.remove(&(a.hostname.clone(), dp));
}
}
}
}
}
// Disable the domain on all other bindings
for (&port, bind) in b.iter_mut() {
if port == internal_port {
continue;

View File

@@ -13,7 +13,7 @@ use crate::context::{CliContext, RpcContext};
use crate::db::prelude::Map;
use crate::net::forward::AvailablePorts;
use crate::net::host::HostApiKind;
use crate::net::service_interface::HostnameInfo;
use crate::net::service_interface::{HostnameInfo, HostnameMetadata};
use crate::net::vhost::AlpnInfo;
use crate::prelude::*;
use crate::util::FromStrParser;
@@ -344,6 +344,41 @@ pub async fn set_address_enabled<Kind: HostApiKind>(
} else {
bind.addresses.enabled.remove(&sa);
}
// Non-SSL Ipv4: cascade to PublicDomains on same gateway
if !address.ssl {
if let HostnameMetadata::Ipv4 { gateway } =
&address.metadata
{
let port = sa.port();
for a in &bind.addresses.available {
if a.ssl {
continue;
}
if let HostnameMetadata::PublicDomain {
gateway: gw,
} = &a.metadata
{
if gw == gateway
&& a.port.unwrap_or(80) == port
{
let k = (
a.hostname.clone(),
a.port.unwrap_or(80),
);
if enabled {
bind.addresses
.disabled
.remove(&k);
} else {
bind.addresses
.disabled
.insert(k);
}
}
}
}
}
}
} else {
// Domains and private IPs: toggle via (host, port) in `disabled` set
let port = address.port.unwrap_or(if address.ssl { 443 } else { 80 });
@@ -353,6 +388,61 @@ pub async fn set_address_enabled<Kind: HostApiKind>(
} else {
bind.addresses.disabled.insert(key);
}
// Non-SSL PublicDomain: cascade to Ipv4 + other PublicDomains on same gateway
if !address.ssl {
if let HostnameMetadata::PublicDomain { gateway } =
&address.metadata
{
for a in &bind.addresses.available {
if a.ssl {
continue;
}
match &a.metadata {
HostnameMetadata::Ipv4 { gateway: gw }
if a.public
&& gw == gateway =>
{
if let Some(sa) =
a.to_socket_addr()
{
if sa.port() == port {
if enabled {
bind.addresses
.enabled
.insert(sa);
} else {
bind.addresses
.enabled
.remove(&sa);
}
}
}
}
HostnameMetadata::PublicDomain {
gateway: gw,
} if gw == gateway => {
let dp = a.port.unwrap_or(80);
if dp == port {
let k = (
a.hostname.clone(),
dp,
);
if enabled {
bind.addresses
.disabled
.remove(&k);
} else {
bind.addresses
.disabled
.insert(k);
}
}
}
_ => {}
}
}
}
}
}
Ok(())
})