d127e29fac
Replaces it with why the remaining segments have no eligible hosts: two are appliance-only and three have no IPv6 enabled yet, pending the firewall-policy pass that SLAAC on a client segment would require.
175 lines
7.5 KiB
Markdown
175 lines
7.5 KiB
Markdown
# ESH IPv6 naming scheme
|
||
|
||
Every ESH LAN carries an **eight-hex-digit phrase** as the first half of the
|
||
interface identifier. Picked 2026-08-18/19. This file is the canonical record.
|
||
|
||
> **Why this file exists.** The scheme originally lived as a single line in
|
||
> `persistent-memory.md` and was silently deleted by a `memory: snapshot`
|
||
> commit (`837fa36`). Recovering it took a hunt through session transcripts to
|
||
> find the commit that had held it. A naming convention is not temporal state —
|
||
> it belongs in a document, so it now is one.
|
||
|
||
## The names
|
||
|
||
| network | hex | reads as |
|
||
|---|---|---|
|
||
| `Default` | **`4BA5:3417`** | A BASE FOR IT |
|
||
| `esh-mgmt` | **`15DA:B055`** | IS DA BOSS |
|
||
| `esh-server` | **`4411:B105`** | FOR ALL BIOS |
|
||
| `esh-userland` | **`CAFE:4411`** | CAFE FOR ALL |
|
||
| `esh-iot` | **`4DBA:D107`** | FOR DA BAD IOT |
|
||
| `esh-cameras` | **`1533:FACE5`** | I SEE FACES |
|
||
| *(reserved)* DMZ | **`4411:DBAD`** | FOR ALL DA BAD |
|
||
|
||
The DMZ name is **claimed against a network that does not exist yet** — there is
|
||
no DMZ on the ESH UDM. Do not reuse it.
|
||
|
||
Substitutions are the standard hexspeak set: `0`→O, `1`→I/L, `5`→S, plus letters
|
||
that are already native hex (`A`–`F`). Anything outside `0-9a-f` is not
|
||
expressible — `b0ss` and `c00l` do **not** work, which is why the set above uses
|
||
`B055` and avoids `c00l` entirely.
|
||
|
||
House style, arrived at rather than designed: **eight digits, and a complete
|
||
phrase rather than a single word.** Words are allowed to straddle the group
|
||
boundary (`4DBA:D107` is `4·D·BAD·107`); the phrase reads through the colon.
|
||
|
||
## Address structure
|
||
|
||
```
|
||
2607:73c0:402:1d02 : 4411:b105 : 50 : 45
|
||
└──── ISP /64 ────┘ └ segment ─┘ └ 10.0.50.45 ┘
|
||
```
|
||
|
||
- **Prefix** — Cityside's, not ours to name. ESH holds a `/56`
|
||
(`2607:73c0:402:1d00::/56`, 256 × /64); the subnet id is assigned by UniFi's
|
||
`ipv6_pd_prefixid`. `esh-cameras` is `1d00`, `esh-server` is `1d02`.
|
||
- **Segment word pair** — 32 bits, from the table above.
|
||
- **Host** — the last two IPv4 octets, written as literal digits so they read
|
||
straight off the address. `10.0.50.45` → `:50:45`.
|
||
|
||
The scheme lives entirely in the **interface identifier**, so it is
|
||
**delegation-size independent**. It works identically on a `/56`, a `/48`, or
|
||
NH3's single `/64`. It never competes with the subnet id, which is far too small
|
||
to hold a word (8 bits at ESH — two hex digits).
|
||
|
||
Note: `4411:b105:50:45` fills all four host groups, so there is **no `::`** in
|
||
these addresses. Writing `...::4411:b105:50:45` is malformed and will be
|
||
rejected.
|
||
|
||
## What can and cannot carry a name
|
||
|
||
| slot | nameable? |
|
||
|---|---|
|
||
| `/64` subnet id (`ipv6_pd_prefixid`) | **No** — 8 bits at ESH, two hex digits, no room for a word |
|
||
| the gateway's own address | **No** — fixed at `::1` by UniFi, no field for it |
|
||
| a UniFi *client* reservation | **No** — UniFi has no IPv6 equivalent of `use_fixedip` |
|
||
| **a host taking its own address** | **Yes** — this is the one that works |
|
||
|
||
⚠ **The original note concluded these names could never appear in a `dig` or
|
||
`ip -6` output. That is wrong.** The first three rows are correct, but they only
|
||
establish that *UniFi* cannot assign the address. A Linux host can simply take
|
||
one within its own advertised prefix, and the router gets no vote. Appliances
|
||
with no shell — cameras, most IoT — genuinely cannot, so `1533:FACE5` and
|
||
`4DBA:D107` are likely to stay documentation-only.
|
||
|
||
## Applying it to a host
|
||
|
||
Do **not** use an `iface … inet6 static` stanza: on Debian that sets
|
||
`accept_ra=0`, killing SLAAC and the IPv6 default route — a good way to strand a
|
||
headless box. Use an `if-up.d` hook that derives the live prefix instead.
|
||
|
||
Live example, `/etc/network/if-up.d/ipv6-scheme-addr` on `esh-docker-vm`:
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
[ "$IFACE" = ens18 ] || exit 0
|
||
(
|
||
i=0
|
||
while [ $i -lt 30 ]; do
|
||
PFX=$(ip -6 -o addr show dev "$IFACE" scope global 2>/dev/null \
|
||
| awk '{print $4}' | cut -d/ -f1 | head -1 | cut -d: -f1-4)
|
||
if [ -n "$PFX" ]; then
|
||
ip -6 addr replace "${PFX}:4411:b105:50:45/64" dev "$IFACE" && exit 0
|
||
fi
|
||
sleep 2
|
||
i=$((i + 1))
|
||
done
|
||
) >/dev/null 2>&1 &
|
||
exit 0
|
||
```
|
||
|
||
Three deliberate properties:
|
||
|
||
- **The prefix is derived, never hardcoded** — self-heals if Cityside
|
||
re-delegates.
|
||
- **Backgrounded with a retry** — SLAAC may not have landed when `if-up.d` runs,
|
||
and a hook that blocks or fails would stall interface bring-up.
|
||
- **Additive** — `/etc/network/interfaces` already sources `interfaces.d/`;
|
||
nothing existing is edited, and removal is one `rm`.
|
||
|
||
Remaining gap: a *mid-life* prefix change is only picked up at the next
|
||
interface-up. A timer would close it; not worth building until the prefix is
|
||
observed to actually move.
|
||
|
||
## Deployed
|
||
|
||
All three Linux hosts on `esh-server` now carry the segment name, with the last
|
||
two groups reading straight off their IPv4 address:
|
||
|
||
| host | address | v4 | applied via |
|
||
|---|---|---|---|
|
||
| `esh-docker-vm` (AdGuard) | `2607:73c0:402:1d02:4411:b105:50:45` | 10.0.50.45 | `if-up.d` on `ens18` |
|
||
| `esh-pve-nas` | `2607:73c0:402:1d02:4411:b105:50:55` | 10.0.50.55 | `if-up.d` on `vmbr0` |
|
||
| `esh-vm-db` | `2607:73c0:402:1d02:4411:b105:50:60` | 10.0.50.60 | `if-up.d` on `ens18` |
|
||
|
||
`esh-docker-vm`'s is load-bearing, not decorative: the ESH UDM advertises an
|
||
IPv6 resolver to clients via RDNSS, macOS prefers it over the DHCPv4-supplied
|
||
one, so whatever sits there is what resolves `*.internal` for every Mac on the
|
||
network. It previously pointed at AdGuard's **MAC-derived SLAAC address**, which
|
||
would have broken if that VM's NIC ever changed. Both `esh-userland` and
|
||
`esh-server` now advertise the scheme address instead
|
||
(`dhcpdv6_dns_auto=false` + `dhcpdv6_dns_1=<address>`), verified on the wire by
|
||
soliciting an RA and parsing option type 25.
|
||
|
||
### ⚠ Proxmox bridges need `accept_ra=2` or SLAAC never runs
|
||
|
||
`esh-pve-nas` had **link-local only** despite `accept_ra=1`, `autoconf=1` and
|
||
IPv6 enabled — every sysctl looked correct. The cause: **`vmbr0.forwarding = 1`**
|
||
(Proxmox sets per-interface forwarding on bridges), and the kernel ignores RAs on
|
||
a forwarding interface unless `accept_ra` is explicitly **`2`**. `accept_ra=1`
|
||
means "accept only if not forwarding", so it silently did nothing.
|
||
|
||
Fixed in `/etc/sysctl.d/60-ipv6-accept-ra.conf` on that host:
|
||
|
||
```
|
||
net.ipv6.conf.vmbr0.accept_ra = 2
|
||
net.ipv6.conf.vmbr0.accept_ra_defrtr = 0
|
||
```
|
||
|
||
`accept_ra_defrtr=0` is deliberate — it takes the advertised **prefix** (so
|
||
SLAAC configures an address) while **declining the default route**, so a
|
||
hypervisor gains an IPv6 identity with no change to its routing behaviour.
|
||
Verified after: SLAAC address present, v6 default routes still **0**, v4 intact.
|
||
|
||
Expect the same on any other Proxmox node when its LAN gets IPv6.
|
||
|
||
### Getting into a host with no direct root
|
||
|
||
`esh-vm-db` refuses key auth for `root` and `infra-ops`, and `lkraven`'s sudo
|
||
wants a password. It is VMID 101 on `esh-pve`, and the **QEMU guest agent** runs
|
||
as uid 0 inside it, so the hook was installed with:
|
||
|
||
```
|
||
qm guest exec 101 -- /bin/sh -c 'echo <base64> | base64 -d > /etc/network/if-up.d/... '
|
||
```
|
||
|
||
base64 because quoting a multi-line script through two SSH layers mangles it.
|
||
Worth remembering as the general path for guests whose credentials are not
|
||
vaulted.
|
||
|
||
Every Linux host on `esh-server` now carries its name. The remaining ESH
|
||
segments have no eligible hosts: `esh-cameras` and `esh-iot` are appliances
|
||
with no shell, and `esh-mgmt`, `esh-userland` and `Default` are still
|
||
`ipv6_interface_type: none` pending the firewall-policy pass — enabling SLAAC
|
||
there gives every client a globally reachable address.
|