Skip to content

mediad(turn): the relay endpoint is the Space, not the alias in front of it - #258

Merged
pierre-rouanet merged 2 commits into
mainfrom
live-turn-endpoint
Sep 14, 2026
Merged

pierre-rouanet merged 2 commits into
mainfrom
live-turn-endpoint

Conversation

@pierre-rouanet

@pierre-rouanet pierre-rouanet commented Sep 10, 2026

Copy link
Copy Markdown
Member

Problem

mediad/src/turn.rs's DEFAULT_TURN_ENDPOINT is https://turn.fastrtc.org/credentials, and
mediad.service passes no --turn-url, so that is what every microduck robot uses. It has not
resolved since at least 2026-06-04 (gradio-app/fastrtc#429,
still unanswered).

It is a dangling delegation, not an outage. The .org registry names four Route53 nameservers
for fastrtc.org, the registration is healthy and locked until 2027, and all four answer
REFUSED for the zone they are authoritative for — which is what Route53 says when the hosted
zone behind them is gone.

Two costs. Every signed-in robot offers host and srflx candidates only, so a consumer behind
symmetric NAT, CGNAT or a UDP-blocking firewall cannot reach it at all and there is no fallback,
because the fallback is the thing that is down. And a dangling Route53 delegation is a known
takeover shape: whoever wins a race to be assigned one of those four nameservers serves records
for the name, passes DNS validation for a certificate on it, and is handed the account token every
robot sends as a bearer header every five minutes.

Demos pass regardless, which is why this went unnoticed through reachy_mini #1182 and since:
two peers on one network pair on host candidates and never look at a relay.

Repro

dig turn.fastrtc.org @1.1.1.1
dig SOA fastrtc.org @ns-79.awsdns-09.com +norecurse

SERVFAIL, then REFUSED from a nameserver the registry names as authoritative.

Solution

The service never stopped answering — only the name in front of it did. fastrtc/turn-service,
the Space that alias pointed at, is up and gated on a bearer token:

curl -s -o /dev/null -w '%{http_code}\n' 'https://fastrtc-turn-service.hf.space/credentials?ttl=600'

401. So address the Space directly and take the vanity record out of the path. reachy_mini
#1408 makes the same move and measured
a relay pair carrying ~2.2 Mbps of video through turn.cloudflare.com.

turn::parse_endpoint checks the endpoint before the token can reach ithttps unless the
host is loopback, no userinfo, no query, no fragment — as a clap value_parser, so a wrong value
stops the daemon while somebody is still looking at the terminal rather than becoming a warning
every thirty seconds in a log nobody reads. Redirects need no separate guard: reqwest strips
Authorization when one crosses scheme, host or port (src/redirect.rs,
remove_sensitive_headers), so the token cannot be walked to a third-party origin. url is a new
direct dependency of mediad and was already in the lock via reqwest — one line of Cargo.lock.

Nine places in the prose reasoned from "there is no relay candidate", and two used it as the
justification for a design.
stream.rs still sends frames outbound and lan.py is still a LAN
transport — but for the reason that was always the real one, which is that a relay is metered
against the robot owner's Hugging Face allowance and the shortest path between a board and a data
centre is not through a third one. Not because the relay was down.

Verification

cargo test -p mediad — 79 pass, including three new ones on the guard: the default is a value it
accepts and survives the round trip unchanged (a default its own check rejects would stop every
robot at once), six destinations that would leak the token are refused, and loopback http still
works because every other test in the module dials one. cargo clippy -p mediad --all-targets and
cargo fmt --check clean, cargo check --workspace clean.

End to end through clap:

$ mediad --turn-url http://evil.example/credentials
error: invalid value 'http://evil.example/credentials' for '--turn-url <TURN_URL>': is plain http, which puts the account token on the wire

Verified on hardware (olducky, 2026-09-14). The drop-in that had been overriding --turn-url
was removed first, so both binaries ran on their own default, same board, same network, same
account, eleven seconds apart:

09:40:44  mediad[2483]  WARN  could not fetch relay credentials
                              GET https://turn.fastrtc.org/credentials: dns error
09:40:55  mediad[3033]  INFO  refreshed the relay credentials servers=5
                              hosts=["turn.cloudflare.com:3478?transport=udp", ...]

PID 2483 is the released build falling back to the dead default, which is what every robot in the
fleet does today. PID 3033 is this branch. systemctl show mediad -p ExecStart carries no
--turn-url, so the credentials came from the default — the one line this changes.

And it reaches the wire: mediad::pipeline: offering relay candidates relays=5 on every session
of a subsequent remote test, and a phone on 4G now connects through the console (that last step
also needed #280 — see there for why the robot's relay alone was not enough).

What this does not close, recorded in §6 and §9

  • A relay-only connectivity checkadded, .github/workflows/turn-endpoint.yml. Daily,
    one authenticated GET against DEFAULT_TURN_ENDPOINT, asserting 200 and at least one
    turn:/turns: entry. It reads the URL out of turn.rs with sed rather than keeping a copy,
    so it can never go green against an endpoint no robot uses. Scheduled and not on
    pull_request, because the failure guarded against is "nobody touched this for months" and a
    third party's outage must not block unrelated work. It fails until an HF_TOKEN secret is
    set on the repository
    — deliberately, since a check that skips itself into silence is the
    failure mode it exists to end.
  • A credentials proxy of our own. *.hf.space is {owner}-{space}, so this breaks if
    fastrtc renames or removes the Space, and there is no alias layer left to repoint. Named as
    residual risk rather than closed.
  • Quota exhaustion is invisible. When the 10 GB/month allowance is spent the proxy answers
    with no relay servers, and turn.rs logs that at info level — indistinguishable from a robot
    that was never offered one.

Branch turn-endpoint-is-dead (unmerged, no PR) recorded the DNS findings and recommended
dropping the default so a relay became opt-in. This supersedes it: opt-in coverage is zero
coverage, and the endpoint that would provide it turns out to answer. That branch can be deleted.

🤖 Generated with Claude Code

… of it

`DEFAULT_TURN_ENDPOINT` pointed at `turn.fastrtc.org`, which `fastrtc`'s own
code points at and `reachy_mini` #1182 copied. That name is a dangling
delegation rather than an outage: the `.org` registry names four Route53
nameservers for the zone, the registration is healthy and locked until 2027,
and all four nameservers answer REFUSED for the zone they are authoritative
for — what Route53 says when the hosted zone behind them is gone. The Space
that alias pointed at, `fastrtc/turn-service`, never stopped answering.

So address it directly. Every microduck robot has been offering host and srflx
candidates only, which is why a consumer behind symmetric NAT or CGNAT could
not reach one at all, and why demos passed regardless: two peers on one
network pair on host candidates and never look at a relay.

It also takes a name that can be taken over out of the path a signed-in robot
sends its account token down. A dangling Route53 delegation is a known takeover
shape, and whoever landed one of those four nameservers would serve records for
the name, pass DNS validation for a certificate on it, and be handed the token
every five minutes.

`turn::parse_endpoint` checks `--turn-url` before the token can reach it —
https unless the host is loopback, no userinfo, no query, no fragment — as a
clap `value_parser`, so a wrong value stops the daemon at argument parsing
instead of becoming a warning every thirty seconds. Redirects need no guard:
`reqwest` strips `Authorization` when one crosses scheme, host or port.

Nine places in the prose reasoned from "there is no relay candidate", and two
of them used it as the justification for a design. `stream.rs` still sends
frames outbound and `lan.py` is still a LAN transport, but for the reason that
was always the real one — a relay is metered against the robot owner's
allowance, and the shortest path between a board and a data centre is not
through a third one — rather than because the relay was down.

§6 of remote-access-design.md records what this does not close: a relay-only
connectivity check, without which nothing here would notice the endpoint dying
again, and a credentials proxy of our own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Coverage

71.62% lines on this branch, against a floor of 70%.

Per-file
Filename                              Regions    Missed Regions     Cover   Functions  Missed Functions  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
btd/src/adv.rs                             79                 0   100.00%           9                 0   100.00%          42                 0   100.00%           0                 0         -
btd/src/bluez.rs                          482               482     0.00%          34                34     0.00%         314               314     0.00%           0                 0         -
btd/src/chorale.rs                        346               246    28.90%          23                13    43.48%         230               171    25.65%           0                 0         -
btd/src/framing.rs                        253                 9    96.44%          20                 1    95.00%         120                 5    95.83%           0                 0         -
btd/src/gatt.rs                             3                 0   100.00%           1                 0   100.00%           3                 0   100.00%           0                 0         -
btd/src/link.rs                            24                 0   100.00%           3                 0   100.00%          26                 0   100.00%           0                 0         -
btd/src/main.rs                            77                77     0.00%          11                11     0.00%          77                77     0.00%           0                 0         -
btd/src/pairing.rs                        149                 2    98.66%          12                 1    91.67%          81                 1    98.77%           0                 0         -
btd/src/route.rs                          352                32    90.91%          27                 0   100.00%         351                11    96.87%           0                 0         -
btd/src/session.rs                       1218                73    94.01%          71                 5    92.96%         706                41    94.19%           0                 0         -
btd/src/upstream.rs                       397                35    91.18%          34                 8    76.47%         241                19    92.12%           0                 0         -
configd/src/bluez.rs                      586               498    15.02%          85                79     7.06%         375               326    13.07%           0                 0         -
configd/src/identity.rs                   111                 5    95.50%          12                 1    91.67%          52                 3    94.23%           0                 0         -
configd/src/logs.rs                       314                33    89.49%          31                 3    90.32%         219                36    83.56%           0                 0         -
configd/src/main.rs                       515               515     0.00%          38                38     0.00%         345               345     0.00%           0                 0         -
configd/src/net.rs                        246                12    95.12%          38                 2    94.74%         181                 4    97.79%           0                 0         -
configd/src/nm.rs                         271               213    21.40%          43                39     9.30%         171               120    29.82%           0                 0         -
configd/src/pad.rs                        379                13    96.57%          38                 2    94.74%         222                 8    96.40%           0                 0         -
configd/src/power.rs                       30                30     0.00%           6                 6     0.00%          28                28     0.00%           0                 0         -
configd/src/store.rs                      350                25    92.86%          28                 2    92.86%         173                 9    94.80%           0                 0         -
configd/src/units.rs                      183               116    36.61%          24                18    25.00%         116                79    31.90%           0                 0         -
duck-control/src/bus.rs                   587               452    23.00%          52                37    28.85%         394               307    22.08%           0                 0         -
duck-control/src/fall.rs                  173                 0   100.00%          15                 0   100.00%         108                 0   100.00%           0                 0         -
duck-control/src/imu.rs                   370                 8    97.84%          20                 0   100.00%         195                 5    97.44%           0                 0         -
duck-control/src/io.rs                    134                19    85.82%          21                 5    76.19%         120                17    85.83%           0                 0         -
duck-control/src/model.rs                 120                 1    99.17%          15                 0   100.00%          73                 0   100.00%           0                 0         -
duck-control/src/obs.rs                   263                 6    97.72%          24                 2    91.67%         177                 8    95.48%           0                 0         -
duck-control/src/policy.rs                701               560    20.11%          67                47    29.85%         452               347    23.23%           0                 0         -
duck-control/src/safety.rs                450                13    97.11%          33                 2    93.94%         292                 6    97.95%           0                 0         -
duck-control/src/sim.rs                   426                63    85.21%          32                10    68.75%         233                40    82.83%           0                 0         -
duck-detect/src/bin/duck-bench.rs         306               306     0.00%          16                16     0.00%         170               170     0.00%           0                 0         -
duck-detect/src/lib.rs                    641                15    97.66%          27                 0   100.00%         333                 8    97.60%           0                 0         -
duck-detect/src/onnx.rs                    96                96     0.00%           5                 5     0.00%          52                52     0.00%           0                 0         -
duck-detect/src/rknn.rs                   267               237    11.24%          15                11    26.67%         216               195     9.72%           0                 0         -
duck-ether/src/main.rs                    504               404    19.84%          27                16    40.74%         270               217    19.63%           0                 0         -
duck-ipc-proto/src/lib.rs                2089               163    92.20%         133                13    90.23%        1530                88    94.25%           0                 0         -
duckctl/src/main.rs                      2617              1023    60.91%         156                45    71.15%        1629               654    59.85%           0                 0         -
kinematics/src/hand.rs                    394                 2    99.49%          22                 2    90.91%         181                 2    98.90%           0                 0         -
kinematics/src/head.rs                    392                 9    97.70%          22                 0   100.00%         193                 4    97.93%           0                 0         -
kinematics/src/lib.rs                     348                10    97.13%          23                 1    95.65%         174                 7    95.98%           0                 0         -
kinematics/src/math.rs                    171                 0   100.00%          16                 0   100.00%          79                 0   100.00%           0                 0         -
kinematics/src/mjcf.rs                    233                32    86.27%          17                 1    94.12%         134                15    88.81%           0                 0         -
kinematics/src/tof.rs                     338                17    94.97%          14                 0   100.00%         184                10    94.57%           0                 0         -
mediad/src/camera.rs                      291                 5    98.28%          21                 0   100.00%         204                 5    97.55%           0                 0         -
mediad/src/config.rs                       87                 5    94.25%           7                 1    85.71%          46                 4    91.30%           0                 0         -
mediad/src/detect.rs                      293               222    24.23%          13                 9    30.77%         200               145    27.50%           0                 0         -
mediad/src/exposure.rs                    412               161    60.92%          28                 8    71.43%         279               110    60.57%           0                 0         -
mediad/src/main.rs                        376               376     0.00%          10                10     0.00%         254               254     0.00%           0                 0         -
mediad/src/pipeline.rs                   1622              1407    13.26%         108                86    20.37%        1051               902    14.18%           0                 0         -
mediad/src/producer.rs                    161                33    79.50%          20                 5    75.00%         109                20    81.65%           0                 0         -
mediad/src/relay.rs                      2217               234    89.45%         179                28    84.36%        1448               171    88.19%           0                 0         -
mediad/src/route.rs                       180                19    89.44%          12                 0   100.00%         155                 8    94.84%           0                 0         -
mediad/src/session.rs                     671                21    96.87%          37                 0   100.00%         427                18    95.78%           0                 0         -
mediad/src/stream.rs                      826               129    84.38%          55                 9    83.64%         521                87    83.30%           0                 0         -
mediad/src/turn.rs                        487                32    93.43%          44                 3    93.18%         299                17    94.31%           0                 0         -
mediad/src/upstream.rs                    263                16    93.92%          17                 1    94.12%         160                12    92.50%           0                 0         -
mediad/src/web.rs                         119                26    78.15%          15                 4    73.33%          82                17    79.27%           0                 0         -
odometry/src/anchors.rs                     5                 0   100.00%           1                 0   100.00%           3                 0   100.00%           0                 0         -
odometry/src/lib.rs                       445                80    82.02%          28                 5    82.14%         254                50    80.31%           0                 0         -
pad-imu/src/lib.rs                        608                 7    98.85%          39                 0   100.00%         372                 5    98.66%           0                 0         -
padd/src/main.rs                         1088               606    44.30%          36                13    63.89%         664               417    37.20%           0                 0         -
padd/src/tap.rs                          1253               682    45.57%          73                36    50.68%         767               407    46.94%           0                 0         -
pet-detect/src/bin/detect.rs               61                61     0.00%           1                 1     0.00%          38                38     0.00%           0                 0         -
pet-detect/src/bin/features.rs             46                46     0.00%           2                 2     0.00%          20                20     0.00%           0                 0         -
pet-detect/src/lib.rs                     362               184    49.17%          27                13    51.85%         224               122    45.54%           0                 0         -
pet-detect/src/worker.rs                  381               230    39.63%          24                14    41.67%         276               160    42.03%           0                 0         -
robotctl/src/configure.rs                1596               477    70.11%          61                12    80.33%         820               322    60.73%           0                 0         -
robotctl/src/duck.rs                     1211               144    88.11%          63                 1    98.41%         604                47    92.22%           0                 0         -
robotctl/src/imu_view.rs                  266                25    90.60%          19                 2    89.47%         173                19    89.02%           0                 0         -
robotctl/src/main.rs                     5141              2779    45.94%         282               131    53.55%        3497              1885    46.10%           0                 0         -
robotctl/src/monitor.rs                  4308               748    82.64%         222                36    83.78%        2721               477    82.47%           0                 0         -
robotctl/src/path_map.rs                  425                59    86.12%          24                 2    91.67%         223                36    83.86%           0                 0         -
robotctl/src/show.rs                      714                46    93.56%          31                 1    96.77%         509                25    95.09%           0                 0         -
robotd-params/src/edit.rs                1907               120    93.71%         114                14    87.72%         930                52    94.41%           0                 0         -
robotd-params/src/lib.rs                 1976                94    95.24%         182                 9    95.05%        1415                54    96.18%           0                 0         -
robotd-params/src/registry.rs             193                10    94.82%          15                 2    86.67%         144                17    88.19%           0                 0         -
robotd/src/chorale.rs                    1366                30    97.80%          71                 4    94.37%         826                20    97.58%           0                 0         -
robotd/src/control.rs                     450               411     8.67%          29                24    17.24%         355               307    13.52%           0                 0         -
robotd/src/intents.rs                     419                74    82.34%          48                 9    81.25%         308                49    84.09%           0                 0         -
robotd/src/main.rs                       7316              1619    77.87%         356                54    84.83%        4729              1034    78.13%           0                 0         -
robotd/src/soc.rs                          47                25    46.81%           4                 1    75.00%          30                15    50.00%           0                 0         -
robotd/src/sound.rs                       968               692    28.51%          50                34    32.00%         565               420    25.66%           0                 0         -
robotd/src/theremin.rs                    472               112    76.27%          32                12    62.50%         271                64    76.38%           0                 0         -
sounds/src/chorale/beat.rs                403                16    96.03%          26                 1    96.15%         246                12    95.12%           0                 0         -
sounds/src/chorale/midi.rs               1047                80    92.36%          44                 2    95.45%         581                43    92.60%           0                 0         -
sounds/src/chorale/mod.rs                1423                57    95.99%          83                 3    96.39%         812                41    94.95%           0                 0         -
sounds/src/chorale/text.rs                700                60    91.43%          41                12    70.73%         376                17    95.48%           0                 0         -
sounds/src/lib.rs                         167                35    79.04%          14                 6    57.14%          87                21    75.86%           0                 0         -
sounds/src/main.rs                        463               463     0.00%          20                20     0.00%         240               240     0.00%           0                 0         -
sounds/src/personality.rs                 174                 0   100.00%           6                 0   100.00%          84                 0   100.00%           0                 0         -
sounds/src/rng.rs                         173                 0   100.00%          18                 0   100.00%          96                 0   100.00%           0                 0         -
sounds/src/stream.rs                      719                 7    99.03%          46                 1    97.83%         420                 6    98.57%           0                 0         -
sounds/src/synth.rs                       353                 7    98.02%          29                 0   100.00%         197                 3    98.48%           0                 0         -
sounds/src/voices.rs                      679                 2    99.71%          26                 0   100.00%         353                 2    99.43%           0                 0         -
test-support/src/lib.rs                   272                 2    99.26%          22                 0   100.00%         167                 0   100.00%           0                 0         -
tof/src/config.rs                          42                 8    80.95%           4                 1    75.00%          23                 7    69.57%           0                 0         -
tof/src/imu.rs                            223               153    31.39%          10                 5    50.00%         137                93    32.12%           0                 0         -
tof/src/lib.rs                             88                 0   100.00%           9                 0   100.00%          54                 0   100.00%           0                 0         -
tof/src/main.rs                           770               687    10.78%          37                26    29.73%         481               437     9.15%           0                 0         -
tof/src/sensor.rs                         230               148    35.65%          22                15    31.82%         163               114    30.06%           0                 0         -
tof/src/status.rs                          76                 2    97.37%           7                 1    85.71%          58                 1    98.28%           0                 0         -
updater/src/account.rs                     87                 0   100.00%           9                 0   100.00%          76                 0   100.00%           0                 0         -
updater/src/config.rs                     436                24    94.50%          38                 4    89.47%         354                17    95.20%           0                 0         -
updater/src/engine.rs                    3265               499    84.72%         217                29    86.64%        2117               313    85.21%           0                 0         -
updater/src/faults.rs                      69                 7    89.86%           7                 0   100.00%          51                 0   100.00%           0                 0         -
updater/src/fsutil.rs                     104                30    71.15%          10                 6    40.00%          51                24    52.94%           0                 0         -
updater/src/hooks.rs                      454                15    96.70%          37                 2    94.59%         382                12    96.86%           0                 0         -
updater/src/ipc.rs                        925               333    64.00%          67                20    70.15%         569               179    68.54%           0                 0         -
updater/src/journal.rs                    832                78    90.62%          57                10    82.46%         483                53    89.03%           0                 0         -
updater/src/lib.rs                         55                16    70.91%           5                 0   100.00%          41                14    65.85%           0                 0         -
updater/src/main.rs                       550               222    59.64%          38                14    63.16%         420               151    64.05%           0                 0         -
updater/src/manifest.rs                   159                 6    96.23%          16                 0   100.00%         110                 1    99.09%           0                 0         -
updater/src/orphan.rs                     288                 6    97.92%          25                 0   100.00%         181                 3    98.34%           0                 0         -
updater/src/policy.rs                    1549               599    61.33%         124                52    58.06%         830               344    58.55%           0                 0         -
updater/src/preflight.rs                  382                16    95.81%          47                 4    91.49%         284                11    96.13%           0                 0         -
updater/src/reconcile.rs                  292                15    94.86%          25                 3    88.00%         172                 5    97.09%           0                 0         -
updater/src/robot.rs                      225                33    85.33%          37                10    72.97%         139                21    84.89%           0                 0         -
updater/src/source/github.rs              474               169    64.35%          51                26    49.02%         304                99    67.43%           0                 0         -
updater/src/source/hf_hub.rs              136                72    47.06%          20                12    40.00%          81                41    49.38%           0                 0         -
updater/src/source/http.rs                319                82    74.29%          29                11    62.07%         260                88    66.15%           0                 0         -
updater/src/source/local.rs               346                37    89.31%          36                 9    75.00%         192                26    86.46%           0                 0         -
updater/src/source/mod.rs                  39                30    23.08%           4                 2    50.00%          30                24    20.00%           0                 0         -
updater/src/spawn.rs                       94                13    86.17%           5                 0   100.00%          49                 4    91.84%           0                 0         -
updater/src/store.rs                      593                41    93.09%          43                 7    83.72%         271                42    84.50%           0                 0         -
updater/src/transcript.rs                 518                42    91.89%          31                 3    90.32%         290                22    92.41%           0                 0         -
updater/src/unix.rs                        44                 3    93.18%           3                 0   100.00%          22                 2    90.91%           0                 0         -
updater/src/verify.rs                     786                95    87.91%          55                18    67.27%         424                75    82.31%           0                 0         -
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                                   80037             22109    72.38%        5260              1407    73.25%       49993             14189    71.62%           0                 0         -

The endpoint `mediad::turn` fetched from was dead for three months before
anybody noticed, and it was already dead when the default that named it
shipped. Nothing in this repository would have caught that: the only symptom is
a warning in a journal and a missing candidate type, and every check we have
pairs two peers on one network, which never looks at a relay.

One authenticated GET, asserting 200 and at least one turn:/turns: entry.

Three deliberate choices. The URL is read out of turn.rs with sed rather than
copied, because a check carrying its own endpoint drifts from what the daemon
compiles in, and a green check on a URL no robot uses reads as proof while
proving nothing. It is scheduled rather than on pull_request: the failure being
guarded against is nobody touching this for months, which a PR trigger cannot
see, and somebody else's outage must not block unrelated work. And a missing
HF_TOKEN fails rather than skips, because a check that skips itself into
permanent silence is precisely the failure mode it exists to end.

It will fail until an HF_TOKEN secret is set on the repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pierre-rouanet
pierre-rouanet merged commit 6927cc1 into main Sep 14, 2026
4 checks passed
@pierre-rouanet
pierre-rouanet deleted the live-turn-endpoint branch September 14, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant