Feature: per-site-resource opt-out from advertising the destination as a client route (alias-address-only mode)
- Dominant language
- TypeScript
- Stars
- 22.8k
- Forks
- 783
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 47
Description
### Summary
There is currently no way to tell Pangolin *"serve this site resource through its alias address, but do not put its destination in the client's route table."* For a deployment with many host/ssh site resources on a LAN the client already reaches another way, that missing knob is the difference between the tunnel being additive and the tunnel taking over the LAN.
I'd like a per-resource boolean — `advertiseDestination` (default `true`, i.e. today's behaviour) — honoured by `generateRemoteSubnets()`.
### Why the current behaviour is a problem
In `generateRemoteSubnets()` (`ee-1.19.4`):
```js
if (sr.mode === "host" || sr.mode === "ssh") {
const parseResult = ipSchema.safeParse(sr.destination);
return parseResult.success; // -> `${sr.destination}/32`
}
```
`buildSiteConfigurationForOlmClient()` hands the result to the client as `remoteSubnets`, and the client installs one route per prefix. So **every** host/ssh site resource with a bare-IP destination becomes a `/32` on the client, unconditionally.
In our case that is ~158 `/32` routes for LAN addresses. On macOS the route table selects by **longest prefix before metric**, so those `/32`s beat every less-specific route the machine already had for the same LAN — an SD-WAN route and a Tailscale route in our case. There is no client-side setting that reorders it, because there is nothing to reorder: a `/32` simply wins. The result is that connecting the client silently blackholes paths that worked a moment earlier, for hosts the tunnel was never meant to take over.
This is not about *reachability* — the resources work. It is that the client cannot express "I want this resource, but I already have a better path to its address."
### Why the existing workarounds don't cover it
- **`aliasAddress` already proves the concept.** `generateSubnetProxyTargetV2()` emits `destPrefix = /32, rewriteTo: `, which is a complete, collision-free path to the resource that does not require the destination to be routed at all. Notably, the `jitMode` branch of `buildSiteConfigurationForOlmClient()` already has `remoteSubnets` commented out and sends only `aliases` — so JIT connections *already* work purely off alias addresses. The requested flag is essentially "let a non-JIT client opt into that same shape, per resource."
- **A non-IP destination** (hostname) does skip the route, since `ipSchema.safeParse` fails — but that changes `rewriteTo` into a hostname and makes the data path depend on name resolution inside the site. That's a side effect of a validation check, not a supported switch.
- **`mode: "cidr"` aggregation** would collapse the routes, but CIDR resources can't carry the per-host access grants that the per-resource model provides. Trading the grant model to reduce route count isn't acceptable for us.
- **Client-side route withdrawal** works and is what we're shipping in the meantime, but it is a second writer racing the server's own config — exactly the kind of thing that turns into a churn loop. A supported server-side flag retires it.
### Proposed change
Add a nullable boolean column to `siteResources` (default `true`) and honour it in the filter:
```js
if (sr.mode === "host" || sr.mode === "ssh") {
if (sr.advertiseDestination === false) return false;
const parseResult = ipSchema.safeParse(sr.destination);
return parseResult.success;
}
```
`generateSubnetProxyTargetV2()` would keep emitting the alias target unchanged, so the resource stays fully reachable via `aliasAddress`. Defaulting to `true` makes it a no-op for every existing deployment.
Exposing it on the site-resource create/update API (and the UI checkbox, "Advertise destination as a client route") would be enough for our use; we drive resources through the Integration API.
### Environment
- Pangolin `ee-1.19.4`
- ~158 host/ssh site resources with bare-IP LAN destinations
- macOS client, with pre-existing SD-WAN and Tailscale routes covering the same LAN
Happy to put up a PR if the shape above is acceptable — the change looks small and additive, but I'd rather agree the column name and default first than guess.
Contributor guide
Assessment
This issue has not been assessed yet.