Skip to content

console: offer a relay of this page's own, for a consumer with no IPv4 - #280

Merged
pierre-rouanet merged 1 commit into
mainfrom
console-offers-its-own-relay
Sep 14, 2026
Merged

pierre-rouanet merged 1 commit into
mainfrom
console-offers-its-own-relay

Conversation

@pierre-rouanet

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

Copy link
Copy Markdown
Member

Stacked on #258 — base is live-turn-endpoint, because this uses the endpoint that PR
established and amends the §6 text it rewrote. Review after it, or say the word and I'll squash
the two together.

Problem

#258 fixed the robot's relay endpoint and proved it on hardware: offering relay candidates relays=5, every session. A phone on 4G still could not connect.

Six sessions on olducky between 09:43 and 09:45, all identical:

INFO  mediad::pipeline: offering relay candidates peer="…" relays=5
ERROR gst: failed to resolve: Error resolving "<uuid>.local"
WARN  gst: Ice connection state in session … failed

The .local failures are the browser's mDNS host candidates, unusable off-LAN and expected. The
relay candidate is the one that was supposed to work, and did not.

Cause

The iPhone has no IPv4 socket, and an ICE candidate is a bare literal.

On a mobile network it reaches a hostname through DNS64/NAT64. The STUN server sees the
carrier's translator and reports an IPv4 reflexive address back, so the phone gathers a candidate
that looks like IPv4 — 92.184.109.200 in this case. But there is no synthesis for a bare
literal, and the robot's relay candidate is exactly that: 141.101.90.1, on a board with no
global IPv6 at all.

robot global IPv6 addresses:  0
ip -6 route get 2606:4700::1111 → Network is unreachable
turn.cloudflare.com → 141.101.90.1  /  2a06:98c1:3200::1

So there was no pair either end could actually use, and the session negotiated perfectly and
carried nothing — the same symptom a missing relay gives, from a different cause, which is why
#258 did not move it.

Fix

The page mints relay credentials for itself. Only the consumer's own allocation bridges this:
turn.cloudflare.com is a name, so the phone resolves it over IPv6, and the relayed address
Cloudflare hands back is IPv4 — which the robot can reach.

With the visitor's HF token, not the robot's. The console Space already signs the visitor in
with hf_oauth, so the token is there; and a robot's metered allowance should go on being
watched rather than on watching. reachy_mini#1408's own doc note recommends the same.

refreshRelays runs on the connect path, which is already async and already says
"connecting…". newPeerConnection reads what it stored and never fetches — the same rule the
robot follows in its consumer-added handler, because that handler cannot wait without delaying
every session, including the LAN ones that will never use a relay. A LAN session asks for no
credentials at all.

Verification

Confirmed from the phone before this was written. Robot credentials pasted into
Trickle ICE in mobile
Safari on 4G gathered a relay candidate with an IPv4 address — where the phone's own candidates
had paired with nothing across six attempts. That is the mechanism this change automates.

Script syntax checked with node --check.

Verified end to end (2026-09-14). Published to pollen-robotics/microduck-console — live
build 9aed222/f02fb229 2026-09-14T10:08Z — and an iPhone on 4G, WiFi off, connects to olducky
through the console and carries video. The same phone, the same robot and the same rendezvous
failed six times in a row before this, with the robot offering relays=5 every time.

Note the Space now serves this branch rather than main until this merges.

What this changes in the design

§6's "only the robot offers one" is amended rather than deleted. The claim holds for a Python
consumer, which is what it was written about, and fails for a browser whose address family does
not overlap the robot's. One relay candidate is still enough; it just is not always the robot's.

Also still open from #258 and unaddressed here: nothing in CI would notice either endpoint dying,
and *.hf.space is {owner}-{space} with no alias left to repoint.

🤖 Generated with Claude Code

§6 argued that only the robot needs to offer a relay candidate: a connection
needs one and not two, and `aiortc`'s TURN client does not work, so a Python
consumer can never be that side. Both halves are true. The conclusion assumed
something it did not check — that the two ends can address each other at all.

An iPhone on a mobile network cannot. It has no IPv4 socket: it reaches a
hostname through DNS64/NAT64, the STUN server reports an IPv4 reflexive address
back, and the phone gathers a candidate saying so. But an ICE candidate is a
bare literal, and the robot's relay candidate is a bare IPv4 literal on a board
with no global IPv6 at all, which that phone cannot send a packet to.

Measured on olducky over 4G: six sessions, `offering relay candidates relays=5`
every one, `Ice connection state ... failed` every one, eight seconds apart.
The same symptom a missing relay gives, and a different cause — which is why
the robot-side endpoint fix did not move it.

So the page mints credentials for itself. Only its own allocation bridges this:
`turn.cloudflare.com` is a name, so it resolves over IPv6, and the relayed
address Cloudflare hands back is IPv4, which the robot can reach. Confirmed
from the phone before this was written — those same credentials in a Trickle
ICE page gathered a `relay` candidate with an IPv4 address over 4G, where the
robot's own candidates had paired with nothing.

With the visitor's token rather than the robot's, which is the right way round
twice: a robot's allowance should go on being watched rather than on watching,
and a browser signed in with `hf_oauth` already holds a token of its own.

`refreshRelays` runs on the connect path, which is already asynchronous and
already says "connecting…". `newPeerConnection` reads what it stored and never
fetches — the same rule the robot follows in `consumer-added`, and for the same
reason: that handler cannot wait without delaying every session, LAN sessions
included, which will never use a relay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage

71.76% 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                          348                32    90.80%          27                 0   100.00%         347                11    96.83%           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                2074               163    92.14%         133                13    90.23%        1519                88    94.21%           0                 0         -
duckctl/src/main.rs                      2473               998    59.64%         146                44    69.86%        1551               644    58.48%           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                       90                 5    94.44%           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                        377               377     0.00%          10                10     0.00%         249               249     0.00%           0                 0         -
mediad/src/pipeline.rs                   1551              1360    12.31%         102                84    17.65%        1000               868    13.20%           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                       176                19    89.20%          12                 0   100.00%         151                 8    94.70%           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/lib.rs                       321                 5    98.44%          22                 1    95.45%         192                 7    96.35%           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%         659               412    37.48%           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                1197               359    70.01%          47                12    74.47%         632               254    59.81%           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                     5088              2726    46.42%         277               126    54.51%        3467              1855    46.50%           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                1825               120    93.42%         111                14    87.39%         892                52    94.17%           0                 0         -
robotd-params/src/lib.rs                 1877                99    94.73%         176                11    93.75%        1360                67    95.07%           0                 0         -
robotd-params/src/registry.rs             188                10    94.68%          13                 2    84.62%         141                17    87.94%           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                       7308              1611    77.96%         356                54    84.83%        4724              1028    78.24%           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                    3239               473    85.40%         215                27    87.44%        2092               288    86.23%           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                        900               308    65.78%          67                20    70.15%         557               167    70.02%           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                    1532               582    62.01%         121                49    59.50%         819               333    59.34%           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                                   78960             21721    72.49%        5202              1392    73.24%       49403             13953    71.76%           0                 0         -

Base automatically changed from live-turn-endpoint to main September 14, 2026 10:25
@pierre-rouanet
pierre-rouanet merged commit 4d40c85 into main Sep 14, 2026
4 checks passed
@pierre-rouanet
pierre-rouanet deleted the console-offers-its-own-relay branch September 14, 2026 10:26
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