Relay's log never leaves the phone unless its owner sends it. When one arrives, this is how to read it β and, for anyone adding a log line, what the format is promising.
0.00 INFO service Starting sharing
0.01 INFO link Reply path checked before any PC asked probe=192.168.1.1 advertised=192.168.1.5 verdict=leaves-by-the-link
8.01 ERROR link No usable Wi-Fi, hotspot or USB link waited_ms=8000 looks=8 vpn=true links="rndis0(no-ipv4) wlan0(192.168.1.5 wifi score-3) tun0(10.8.0.2 vpn)"
| Column | Meaning |
|---|---|
| time | Seconds since the app's process started β not since sharing started, whatever an older build's header claimed |
| level | INFO Β· WARN Β· ERROR |
| area | service Β· link Β· pairing Β· tunnel Β· update |
| message | A sentence, with no values in it |
| fields | key=value, quoted when the value contains a space |
The report's header carries Lockdown: on / off / unknown β Android's "Block
connections without VPN". unknown means the platform would not answer, not
that it is off; see vpn-compat.md.
The last line of the report counts the errors and warnings, because a report is usually read by somebody scrolling a chat on a phone.
A value in a sentence has to be reworded every time it appears, so it cannot be
searched for. advertised=192.168.1.5 is the same string in every line that
mentions it; "the address we advertised was 192.168.1.5" is not.
The single most useful thing in a report about a connection that will not start. It names every interface, including the ones Relay deliberately refuses, with why:
| Verdict | Meaning | What to tell the user |
|---|---|---|
no-ipv4 |
Up, no address yet | Wait and retry β USB tethering looks exactly like this for a few seconds after it is switched on |
down |
Present, not up | Plug the cable in / turn the hotspot on |
cellular |
The carrier's own link | No PC can route to it; join a Wi-Fi or turn the hotspot on |
vpn |
A tun interface | Ignored by design; a PC cannot reach the phone through it |
wifi hotspot usb |
Advertisable, with its score-N |
The highest score is the one in the QR |
This exists because a report once said No usable Wi-Fi or hotspot interface found seven times, and answering it took a message to the user in another
country. Their cable was plugged in with tethering just switched on β the
no-ipv4 case β which one line would have said.
Reply path checked β¦ appears twice: once at start-up against a stand-in
address, once for the real PC once it asks. Both are informational only.
The probe asks the kernel which interface a datagram would leave by. It cannot
ask whether the packet arrives, and it has been observed saying
verdict=would-leave-by-vpn on a session that paired a PC and carried traffic
sixty seconds later. Triage with it; never conclude from it. What does mean the
replies are lost is the PC_GOT_NO_REPLY warning β see
vpn-compat.md.
Not with ping. Relay forwards TCP and UDP and nothing else β there is no
unprivileged way for an Android app to send ICMP, and a SOCKS5 upstream
(upstream.go) cannot carry it at all. So a ping to a public address is not a
test of anything, and until 2.8.9 it was worse than that: the gVisor stack has
to be promiscuous for the forwarders to see packets addressed to the internet,
and that made it answer echo requests for every address in the world from
inside the phone. ping 1.1.1.1 could not fail.
That cost a real user an evening. Their report read:
ping 1.1.1.1 -> Reply ... time=49ms TTL=64
tracert 1.1.1.1 -> 1 315 ms 291 ms 55 ms one.one.one.one [1.1.1.1]
nslookup google.com -> DNS request timed out
TTL=64 and a one-hop traceroute are the tell. A real 1.1.1.1 is dozens of
hops away and answers with a TTL in the forties; a responder that is one hop
away and ignores TTL is a local stack. Since 2.8.9 those requests are dropped, so
a ping that fails means "Relay does not carry ping", not "the internet is down".
Ask the two questions separately instead, from the PC while connected:
| Question | Command | A working tunnel |
|---|---|---|
| Is the tunnel itself alive? | ping 10.13.37.1 |
replies β this is Relay's own end, and the only address it answers for |
| Does TCP cross? | curl.exe -sS --max-time 15 -o NUL -w "%{http_code}\n" https://1.1.1.1/ |
200 |
| Does UDP cross? | nslookup google.com 8.8.8.8 |
an address |
| Whose internet is it? | curl.exe -sS --max-time 20 https://www.cloudflare.com/cdn-cgi/trace |
ip= the phone's exit, and warp=on if the phone's VPN is being shared |
Read them in that order and stop at the first failure β TCP working while UDP
does not is a different fault from neither working, and the phone's VPN is the
usual reason for the first. Read ip= and the laptop's own ip= from the
same service or the comparison means nothing; two "what is my IP" endpoints
do not have to agree, and one that disagreed cost a published claim a correction.
- Pick the level by what a reader should do:
ERRORis "this is why it did not work",WARNis "working, but not the way it should be",INFOis the rest. - Put every value in a field, not in the sentence.
- Where a failure has context worth capturing, capture it at the moment of the
failure β
LinkSnapshot.take()costs one enumeration and is the difference between a report that can be answered and one that needs a conversation. - Do not log anything that is not already on the device. Nothing here is uploaded, and the report says so; keep that true.