feat: refactor NetService to watch DB and reconcile network state

- NetService sync task now uses PatchDB DbWatch instead of being called
  directly after DB mutations
- Read gateways from DB instead of network interface context when
  updating host addresses
- gateway sync updates all host addresses in the DB
- Add Watch<u64> channel for callers to wait on sync completion
- Fix ts-rs codegen bug with #[ts(skip)] on flattened Plugin field
- Update SDK getServiceInterface.ts for new HostnameInfo shape
- Remove unnecessary HTTPS redirect in static_server.rs
- Fix tunnel/api.rs to filter for WAN IPv4 address
This commit is contained in:
Aiden McClelland
2026-02-13 16:21:57 -07:00
parent 3765465618
commit 49d4da03ca
12 changed files with 5767 additions and 3489 deletions

View File

@@ -7,4 +7,4 @@ export type HostnameMetadata =
| { kind: 'ipv6'; gateway: GatewayId; scopeId: number }
| { kind: 'private-domain'; gateways: Array<GatewayId> }
| { kind: 'public-domain'; gateway: GatewayId }
| ({ kind: 'plugin'; package: PackageId } & { [key in string]?: unknown })
| { kind: 'plugin'; package: PackageId }

View File

@@ -49,19 +49,20 @@ type VisibilityFilter<V extends 'public' | 'private'> = V extends 'public'
: never
type KindFilter<K extends FilterKinds> = K extends 'mdns'
?
| (HostnameInfo & { hostname: { kind: 'local' } })
| (HostnameInfo & { metadata: { kind: 'private-domain' } })
| KindFilter<Exclude<K, 'mdns'>>
: K extends 'domain'
?
| (HostnameInfo & { hostname: { kind: 'domain' } })
| (HostnameInfo & { metadata: { kind: 'private-domain' } })
| (HostnameInfo & { metadata: { kind: 'public-domain' } })
| KindFilter<Exclude<K, 'domain'>>
: K extends 'ipv4'
?
| (HostnameInfo & { hostname: { kind: 'ipv4' } })
| (HostnameInfo & { metadata: { kind: 'ipv4' } })
| KindFilter<Exclude<K, 'ipv4'>>
: K extends 'ipv6'
?
| (HostnameInfo & { hostname: { kind: 'ipv6' } })
| (HostnameInfo & { metadata: { kind: 'ipv6' } })
| KindFilter<Exclude<K, 'ipv6'>>
: K extends 'ip'
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
@@ -108,10 +109,7 @@ type FormatReturnTy<
export type Filled<F extends Filter = {}> = {
hostnames: HostnameInfo[]
toUrls: (h: HostnameInfo) => {
url: UrlString | null
sslUrl: UrlString | null
}
toUrl: (h: HostnameInfo) => UrlString
format: <Format extends Formats = 'urlstring'>(
format?: Format,
@@ -152,37 +150,29 @@ const unique = <A>(values: A[]) => Array.from(new Set(values))
export const addressHostToUrl = (
{ scheme, sslScheme, username, suffix }: AddressInfo,
hostname: HostnameInfo,
): { url: UrlString | null; sslUrl: UrlString | null } => {
const res = []
const fmt = (scheme: string | null, host: HostnameInfo, port: number) => {
): UrlString => {
const effectiveScheme = hostname.ssl ? sslScheme : scheme
let host: string
if (hostname.metadata.kind === 'ipv6') {
host = IPV6_LINK_LOCAL.contains(hostname.host)
? `[${hostname.host}%${hostname.metadata.scopeId}]`
: `[${hostname.host}]`
} else {
host = hostname.host
}
let portStr = ''
if (hostname.port !== null) {
const excludePort =
scheme &&
scheme in knownProtocols &&
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
let hostname
if (host.hostname.kind === 'domain') {
hostname = host.hostname.value
} else if (host.hostname.kind === 'ipv6') {
hostname = IPV6_LINK_LOCAL.contains(host.hostname.value)
? `[${host.hostname.value}%${host.hostname.scopeId}]`
: `[${host.hostname.value}]`
} else {
hostname = host.hostname.value
}
return `${scheme ? `${scheme}://` : ''}${
username ? `${username}@` : ''
}${hostname}${excludePort ? '' : `:${port}`}${suffix}`
effectiveScheme &&
effectiveScheme in knownProtocols &&
hostname.port ===
knownProtocols[effectiveScheme as keyof typeof knownProtocols]
.defaultPort
if (!excludePort) portStr = `:${hostname.port}`
}
let url = null
if (hostname.hostname.port !== null) {
url = fmt(scheme, hostname, hostname.hostname.port)
}
let sslUrl = null
if (hostname.hostname.sslPort !== null) {
sslUrl = fmt(sslScheme, hostname, hostname.hostname.sslPort)
}
return { url, sslUrl }
return `${effectiveScheme ? `${effectiveScheme}://` : ''}${
username ? `${username}@` : ''
}${host}${portStr}${suffix}`
}
function filterRec(
@@ -209,15 +199,19 @@ function filterRec(
hostnames = hostnames.filter(
(h) =>
invert !==
((kind.has('mdns') && h.hostname.kind === 'local') ||
(kind.has('domain') && h.hostname.kind === 'domain') ||
(kind.has('ipv4') && h.hostname.kind === 'ipv4') ||
(kind.has('ipv6') && h.hostname.kind === 'ipv6') ||
((kind.has('mdns') &&
h.metadata.kind === 'private-domain' &&
h.host.endsWith('.local')) ||
(kind.has('domain') &&
(h.metadata.kind === 'private-domain' ||
h.metadata.kind === 'public-domain')) ||
(kind.has('ipv4') && h.metadata.kind === 'ipv4') ||
(kind.has('ipv6') && h.metadata.kind === 'ipv6') ||
(kind.has('localhost') &&
['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) ||
['localhost', '127.0.0.1', '::1'].includes(h.host)) ||
(kind.has('link-local') &&
h.hostname.kind === 'ipv6' &&
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))),
h.metadata.kind === 'ipv6' &&
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.host)))),
)
}
@@ -226,15 +220,26 @@ function filterRec(
return hostnames
}
function isDefaultEnabled(h: HostnameInfo): boolean {
return !(h.public && (h.hostname.kind === 'ipv4' || h.hostname.kind === 'ipv6'))
function isPublicIp(h: HostnameInfo): boolean {
return h.public && (h.metadata.kind === 'ipv4' || h.metadata.kind === 'ipv6')
}
function enabledAddresses(addr: DerivedAddressInfo): HostnameInfo[] {
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)
return addr.available.filter((h) => {
if (isPublicIp(h)) {
// Public IPs: disabled by default, explicitly enabled via SocketAddr string
if (h.port === null) return true
const sa =
h.metadata.kind === 'ipv6'
? `[${h.host}]:${h.port}`
: `${h.host}:${h.port}`
return addr.enabled.includes(sa)
} else {
// Everything else: enabled by default, explicitly disabled via [host, port] tuple
return !addr.disabled.some(
([host, port]) => host === h.host && port === (h.port ?? 0),
)
}
})
}
@@ -242,11 +247,7 @@ export const filledAddress = (
host: Host,
addressInfo: AddressInfo,
): FilledAddressInfo => {
const toUrls = addressHostToUrl.bind(null, addressInfo)
const toUrlArray = (h: HostnameInfo) => {
const u = toUrls(h)
return [u.url, u.sslUrl].filter((u) => u !== null)
}
const toUrl = addressHostToUrl.bind(null, addressInfo)
const binding = host.bindings[addressInfo.internalPort]
const hostnames = binding ? enabledAddresses(binding.addresses) : []
@@ -266,11 +267,11 @@ export const filledAddress = (
return {
...addressInfo,
hostnames,
toUrls,
toUrl,
format: <Format extends Formats = 'urlstring'>(format?: Format) => {
let res: FormatReturnTy<{}, Format>[] = hostnames as any
if (format === 'hostname-info') return res
const urls = hostnames.flatMap(toUrlArray)
const urls = hostnames.map(toUrl)
if (format === 'url') res = urls.map((u) => new URL(u)) as any
else res = urls as any
return res