Skip to content

ts_control_serde: netmap deserialization fails on a peer's NextDNS split-DNS URL field #400

Description

@a1ecbr0wn

Summary

After a Device successfully registers and is authorized (AuthState::Authorized), the netmap update stream immediately fails to deserialize and the ControlRunner actor crashes, tearing down the whole Device with an opaque Error::Internal(Actor).

Environment

  • tailscale = "0.5.0"
  • Real, ordinary tailnet (not a fresh/empty one) with several existing devices, at least one of which has NextDNS split-DNS configured for that specific device (producing a per-device NextDNS block-page URL in its DNS config, of the form https://dns.nextdns.io/<id>?device_id=...&device_name=...&device_model=...&device_ip=...).

Observed error

ERROR ts_control::client::map_stream: deserializing netmap error=invalid type: string "https://dns.nextdns.io/<redacted>?device_id=<redacted>&device_name=<redacted>&device_model=&device_ip=<redacted>", expected a borrowed string at line 1 column 33219
ERROR actor.lifecycle{actor.name="ts_runtime::control_runner::ControlRunner" ...}:actor.handle_message{... kameo::message::StreamMessage<Arc<ts_control::client::map_stream::StateUpdate>, (), ()>}: ts_runtime::control_runner: state update stream terminated
Error: Internal(Actor)

"expected a borrowed string" (rather than a plain type mismatch) suggests this is a zero-copy (&str) deserialization target that can't borrow this particular string value directly from the input buffer — possibly because of how it's escaped/encoded in the JSON payload, or because the value straddles a buffer boundary. It reproduces consistently against this tailnet.

Impact

Any tailnet containing a device with this kind of NextDNS-linked DNS config field appears to make the whole Device unusable — Device::new() (or the first netmap update shortly after) crashes outright rather than skipping/tolerating the one field it can't parse. This is a harder blocker than #383 (registration error classification), since it happens even after successful authorization, on otherwise-unremarkable real-world tailnet state.

Suggested fix direction

Netmap/state deserialization has had similar fragility before (#123, closed in 0.3.0 for a different field) — it may be worth deserializing peer/DNS metadata fields more defensively in general (e.g. accepting an owned String instead of requiring a zero-copy &str borrow, or tolerating/skipping unparseable optional fields) rather than fixing this one field in isolation.

Repro

Happy to provide more detail/a minimal repro binary if useful — this was hit via a small throwaway Device::new() + tcp_listen() test program, not a customized setup.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions