docs(crafty): "You are not invited" is an empty allowlist -- and it proves RakNet works
gate / gate (pull_request) Successful in 1m36s
gate / conformance (pull_request) Has been skipped

New operator observation: a Bedrock client got "Unable to connect / You are not
invited to play on this server". That was put to me as evidence that NetherNet
signalling is working, that the refusal may have come from Microsoft rather than
our server, and that BDS players therefore never traverse crafty-l4-udp.
Measured read-only at 21:56, all three are wrong.

  allow-list=true            (server.properties line 37)
  allowlist.json == []       (3 bytes, mtime 20:51, never edited since create)
  online-mode=true
  transport=raknet           (line 71; file mtime STILL 21:33:31)
  UDP 19132 bound            (/proc/net/udp 00000000:4ABC, pid 47006)
  TCP 19132 ABSENT           (no 4ABC in /proc/net/tcp or tcp6)
  bedrock_server pid 47006, same process since 21:34, never restarted

1. transport is still raknet -- nobody reverted it, the binary did not rewrite it
   (mtime unchanged, audit.log records no later edit, same pid). NetherNet is NOT
   in use and cannot be what served this client.
2. NetherNet could not have served it in principle: it signals over TCP on
   server-port, and there is no TCP 19132 listener in the pod at all.
3. So the refusal came from OUR server, over RakNet, over 19132/udp, through the
   VIP. The 19132-19134/UDP exposure is the opposite of inert -- it carried both
   the earlier pong and this refusal.
4. Cause is certain and trivial: allow-list=true with an empty allowlist.json
   refuses every player. The shipped how_to says so, and it keys on Xbox Gamertag.

The requested log correlation cannot be done from disk: K3s-Bedrock's log_path in
crafty.sqlite is the empty string and Dedicated_Server.txt is 0 bytes -- Crafty
persists no BDS stdout at all, the console is panel-UI-only. commander.log
21:39-21:56 holds only homelab crash-detection and raknet_ping lines. That silence
is evidence about Crafty's logging, not about the refusal's origin.

Adds a README section on the join authorization model: the measured table, the
proof the refusal is ours, the "console is not on disk" caveat, gamertag/XUID
identity, and three operator routes (console `allowlist add`, edit-then-`allowlist
reload`, or allow-list=false -- discouraged, since the game port is deliberately
un-gated so the allowlist is the only join control). Notes allowlist.json is PVC
state that resets to [] on a server recreate. Options table and human test 4 now
check the allowlist before blaming the network.

Ranking unchanged at the top: keep transport=raknet, add gamertags. Still zero
manifest change. NetherNet would ADD work (TCP 19132 in crafty-l4-tcp and in
crafty-allow-ingress, plus server-udp-ports) and admit not one extra player.

Read-only throughout: allowlist.json, server.properties and the DB were read; no
console command issued, no kubectl mutation, no flux reconcile.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-29 21:59:15 -05:00
1 parent 8697702d51
commit cd2617e5b3
2 files changed
+207 -5

No files matched your search

+90 -5
View File
@@ -181,6 +181,89 @@ one `None` terminator entry as missing. Every field that matters (edition, MOTD,
name, player count, max players, uuid, game mode, both ports) parsed correctly. **Do not chase this
warning.** The failure signature is the *other* message, `Got timeout`.
### "You are not invited to play on this server" — the join gate is the allowlist, not the network (MEASURED 2026-09-29 21:56)
A Bedrock client reported **"Unable to connect / You are not invited to play on this server"**. That is
an **authorization** refusal, and it is the expected, guaranteed behaviour of this server's current
configuration. Measured:
| Property / file | Value | Source |
|---|---|---|
| `allow-list` | **`true`** (line 37) | `server.properties` |
| `allowlist.json` | **`[]`** — 3 bytes, mtime 20:51, never edited since server create | `cat allowlist.json` |
| `online-mode` | `true` (line 31) | `server.properties` |
| `transport` | **`raknet`** (line 71), file mtime **still 21:33:31** | `server.properties` |
| TCP 19132 listener | **none** (`NO-TCP-4ABC`) | `grep 4ABC /proc/net/tcp /proc/net/tcp6` |
| UDP 19132 listener | present, `00000000:4ABC`, pid 47006 | `grep 4ABC /proc/net/udp` |
**`allow-list=true` with an empty `allowlist.json` refuses every player, unconditionally.** The shipped
`bedrock_server_how_to.html` states it: *"If the `allow-list` property is enabled in server.properties
then the server will only allow selected users to connect. To allow a user to connect you need to know
their Xbox Live **Gamertag**."* An empty list selects nobody. This is not a network problem, not a
transport problem, and not a platform problem — and no amount of Service or policy work will change it.
**This is also positive evidence that the RakNet path works end to end.** A refusal authored by *our*
server required the client to reach our server and get an answer back. The only listener on 19132 is
the **RakNet UDP** socket — there is **no TCP 19132 listener at all**, and NetherNet requires a TCP
signalling socket on `server-port`. So the client cannot have used NetherNet against this server; it
arrived over `19132/udp`, through `crafty-l4-udp` and the VIP. **The 19132-19134/UDP exposure is not
inert — it is the path that carried both the pong and this refusal.** *(Observed: the listeners, the
absent TCP socket, `transport=raknet`. Reasoned from those: that the refusal traversed that path. Not
captured: the actual player flow — see the caveat below.)*
#### Caveat: BDS console output is NOT on disk, so a join attempt cannot be correlated from the filesystem
`K3s-Bedrock`'s `log_path` in `crafty.sqlite` is the **empty string**, and `Dedicated_Server.txt` is
**0 bytes**. Crafty does not persist a Bedrock server's stdout anywhere on the PVC — the console exists
only in the panel UI. `commander.log` between 21:39 and 21:56 contains nothing but `homelab`
crash-detection lines and the `raknet_ping` chatter: **no join, no gamertag, no XUID, no allowlist
denial.** That absence is **not** evidence about where the refusal came from; it is evidence that
Crafty logs no BDS stdout at all. **To see a rejected join you must watch the panel console live.**
UNVERIFIED, and only observable in the UI.
#### How a Bedrock player actually gets authorized here
Identity is an **Xbox Live Gamertag** (`online-mode=true` forces XBL auth, which is what gives BDS a
gamertag/XUID to check). `allowlist.json` is a JSON array; per the vendor doc the keys are `name` (the
gamertag), optional `xuid` (*"populated when someone with a matching name connects"*), and
`ignoresPlayerLimit`. So the operator adds **one entry per player, keyed on gamertag** — there is no
group, role, or Keycloak integration, and **none of this touches the oauth2-proxy/Keycloak gate**,
which protects only the web panel.
Three ways to do it, all **operator actions**:
1. **Preferred — the panel console.** In Crafty, open `K3s-Bedrock` → Console and send:
`allowlist add <Gamertag>` (quote it if it contains a space: `allowlist add "Example Name"`).
This writes `allowlist.json` **and** takes effect immediately, with no restart.
2. **Edit the file, then reload.** Write `/crafty/servers/c5837301-…/allowlist.json`, then send
`allowlist reload` in the console — *"After you've modified the file you need to run the command
`allowlist reload` to make sure that the server knows about your new change."* Shape:
```json
[
{ "ignoresPlayerLimit": false, "name": "FirstPlayerGamertag" },
{ "ignoresPlayerLimit": false, "name": "SecondPlayerGamertag" }
]
```
3. **Turn the allowlist off entirely** — `allow-list=false` in `server.properties` (restart), or
`allowlist off` in the console. Note the vendor's warning: `allowlist on|off` *"does not change the
value in the server.properties file"*, so a console `off` silently reverts on the next start.
**Not recommended:** the game port is deliberately un-gated at the network layer (see *Game-port L4
exposure*), so the allowlist is the **only** thing deciding who may join. Keep it `true` and enumerate
players.
> **`allowlist.json` is application state on the PVC.** It is not in git, Flux cannot restore it, and a
> server recreate resets it to `[]` — i.e. back to refusing everyone. Same class of problem as
> `transport=raknet`. Treat both as backup-relevant.
#### Why this does not change the transport recommendation
`transport=raknet` is still correct and still the recommendation. The allowlist refusal is orthogonal —
it would refuse players identically under NetherNet. Switching to NetherNet would *add* work (TCP 19132
in `crafty-l4-tcp` **and** in `crafty-allow-ingress`, plus `server-udp-ports` pinned and exposed) and
would not admit a single extra player. The ranking below stands, with the allowlist as the live blocker.
#### Decision: `transport=raknet` stays; the 19132-19134/UDP exposure is correct and live
No manifest change is needed or wanted. `service-l4.yaml`, `networkpolicy.yaml` and the
@@ -190,11 +273,11 @@ recommended** today:
| Option | Manifest consequence | Verdict |
|---|---|---|
| **Keep `transport=raknet`** (recommended) | **none** — the existing Service, policy and ports are already correct and now proven | **Take this.** Zero change, works today, vanilla parity, Bedrock clients join a real Bedrock world. |
| **Keep `transport=raknet`** (recommended) | **none** — the existing Service, policy and ports are already correct and now proven end-to-end | **Take this.** Zero change, reachable today, vanilla parity, Bedrock clients join a real Bedrock world. The remaining blocker is the **allowlist**, not the transport (above). |
| **GeyserMC + Floodgate** on the `homelab` Paper server | none either (Geyser also binds `19132/udp`), but it would **collide** with the live BDS on 19132 — one of them must move | Hold in reserve. Only worth it if RakNet is really removed, or if you *want* one shared world. Cost: Bedrock players join the **Java** world, not a Bedrock world; Java-only mechanics and some content differ; it is another moving part to patch. |
| **Pin/downgrade BDS** to a pre-nethernet release | none | Unnecessary — the *current* build already speaks RakNet. Also fragile: Microsoft removes old BDS downloads, and Crafty's Bedrock installer tracks latest. UNVERIFIED whether Crafty can pin a version at all. |
| **A third-party server** (PocketMine-MP, Nukkit) | none | No. Loses vanilla parity and the world format; a large change to solve a problem that is not occurring. |
| **Accept NetherNet** | 19132-19134/UDP becomes **inert**; nethernet would instead need **TCP** 19132 added to `crafty-l4-tcp` *and* to `crafty-allow-ingress`, plus `server-udp-ports` pinned and exposed for the negotiated media path | No. Strictly more manifest work than the option that already works, and the WebRTC media path behind a shared VIP and SNAT is unproven here. |
| **Accept NetherNet** | 19132-19134/UDP becomes **inert**; nethernet would instead need **TCP** 19132 added to `crafty-l4-tcp` *and* to `crafty-allow-ingress`, plus `server-udp-ports` pinned and exposed for the negotiated media path | No — and note it is **not currently in use**: `transport=raknet` and there is no TCP 19132 listener. Strictly more manifest work than the option that already works, it admits no extra player (the allowlist is the gate), and WebRTC media behind a shared VIP and SNAT is unproven here. |
#### If RakNet is ever really removed
@@ -267,9 +350,11 @@ Gateway VIP). The sidecar→Crafty hop is pod-local (`127.0.0.1`) and not subjec
pong from off-cluster. **Ignore the `TRANSPORT TYPE ERROR` banner in the console and ignore
Crafty's `too few fields` warning — both appear on a fully working server.** The authoritative
test is a RakNet Unconnected Ping to `192.168.2.22:19132` returning a `0x1c` packet whose MOTD
string starts `MCPE;`. Then **Add Server** by hand in a Windows/mobile client at
`crafty-mc.home.keithjackson.dev` port `19132`. It will never show up in the LAN/Friends tab, and a
console cannot reach it.
string starts `MCPE;`. **Then check the allowlist** — `allow-list=true` with `allowlist.json == []`
refuses every player with *"You are not invited to play on this server"* even though the server is
fully reachable (see *the join gate* above); add the player's **Xbox Gamertag** first. Then
**Add Server** by hand in a Windows/mobile client at `crafty-mc.home.keithjackson.dev` port `19132`.
It will never show up in the LAN/Friends tab, and a console cannot reach it.
## Runtime state: the servers themselves are NOT in git