summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/MESHBAY_DESIGN.md1
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py19
-rw-r--r--packages/meshbay-node/tests/test_answer_host_log.py30
3 files changed, 43 insertions, 7 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 0160b97f..b720623a 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -4818,7 +4818,6 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows.
| **A failed download leaves its in-flight chunks rejecting unhandled** | When `pipelinedDownload` throws, the other promises of its window are abandoned and each prints "Uncaught (in promise)" in the console. Noise, but in the log a dropped connection is read from |
| **A queued transfer logs "no answer" every minute** | The lease watchdog re-asks after 60 s of silence whatever the last answer was, so a lease the node has answered `queued` and that has not moved prints "no answer for transfer ... asking again" once a minute for as long as it waits, which reads as a fault |
| **"Prune stale entries" does not touch the index** | The Node page's maintenance button runs `prune_index_cache`, which drops rows of the hash cache and nothing else. An operator who sees a deleted file still listed reaches for it, and nothing changes: only the reconcile removes an index entry, and nothing lets the operator ask for one |
-| **The node logs `host: none` for every answer** | `webrtc_server.py` counts host candidates by `" typ host "` with a space after it, and aiortc ends those lines at `typ host`. Every answer in ten days of the test node's log read `host: none`, and it is the first thing read when a member reports "DataChannel closed": it sent the 2026-10-16 diagnosis the wrong way. A one-line fix and a test |
| **Only the group page explains a refused local network** | `transport.js` names it (`local_network_blocked`, Android 17 and Chrome 155), and `group-page.js` says what to change. Search and the other screens that open a connection show it as a node that cannot be reached |
| **A reconnect refused the local network says nothing** | A phone connected on mobile data that walks onto the node's Wi-Fi without the permission loses the connection when Android drops mobile data, and `_reconnectLoop` retries with the reason in the console and nowhere else |
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
index fea642a5..3a9c2fa5 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py
@@ -85,6 +85,18 @@ MAX_PEER_SESSIONS_PER_ACCOUNT = MAX_PEER_SESSIONS // 2
UNAUTHENTICATED_SESSION_TIMEOUT = 60 # seconds
+def _host_addresses(sdp: str) -> set[str]:
+ """The host addresses an SDP offers. A candidate line ends at `typ host`
+ when nothing follows it, as aiortc writes them: matched by field, not by
+ `" typ host "`, which found none and logged `host: none` for every answer."""
+ addresses: set[str] = set()
+ for line in sdp.splitlines():
+ parts = line.split()
+ if line.startswith("a=candidate:") and parts[6:8] == ["typ", "host"]:
+ addresses.add(parts[4])
+ return addresses
+
+
class WebRTCPeerSession(
DispatchMixin, AdminMixin, AdmissionMixin, BlobsMixin, ChatMixin, FilesMixin, GroupOpsMixin,
HandshakeMixin, NodeOpsMixin, TransferMixin, UploadMixin,
@@ -404,12 +416,7 @@ class WebRTCTransport:
# anything that peer could route to at all — on a NAT'd host or a VM the
# only host candidate is an address no one else can reach, and the log
# otherwise looks identical to a working connection.
- host_addrs: set[str] = set()
- for line in answer_sdp.splitlines():
- if line.startswith("a=candidate:") and " typ host " in line:
- parts = line.split()
- if len(parts) > 5:
- host_addrs.add(parts[4])
+ host_addrs = _host_addresses(answer_sdp)
log.info(
"WebRTC answer ready for peer=%s (ICE gather %.2fs, host: %s, %d srflx)",
peer_id, time.monotonic() - gather_start,
diff --git a/packages/meshbay-node/tests/test_answer_host_log.py b/packages/meshbay-node/tests/test_answer_host_log.py
new file mode 100644
index 00000000..cde4e54b
--- /dev/null
+++ b/packages/meshbay-node/tests/test_answer_host_log.py
@@ -0,0 +1,30 @@
+"""
+The node's log names the host addresses it put in its answer.
+
+It is the first thing read when a member reports "DataChannel closed", and it
+read `host: none` for every answer: the match wanted `" typ host "` with a space
+after it, and aiortc ends a host candidate line at `typ host`. The addresses
+below are the test node's of 2026-10-16.
+"""
+
+from meshbay_node.transport.webrtc_server import _host_addresses
+
+ANSWER = "\r\n".join([
+ "v=0",
+ "m=application 9 UDP/DTLS/SCTP webrtc-datachannel",
+ "a=candidate:7d8e0b5c 1 udp 2130706431 192.168.1.22 40125 typ host",
+ "a=candidate:3f1a9e22 1 udp 2130706431 2a01:cb15:80e8:de00:d455:bcab:c324:4d72 40125 typ host",
+ "a=candidate:c0a8c8fe 1 udp 2130706431 192.168.200.254 40125 typ host",
+ "a=candidate:9b0f1d47 1 udp 1694498815 90.112.206.172 40125 typ srflx raddr 192.168.1.22 rport 40125",
+ "a=end-of-candidates",
+]) + "\r\n"
+
+
+def test_host_candidates_ending_the_line_are_counted():
+ assert _host_addresses(ANSWER) == {
+ "192.168.1.22", "2a01:cb15:80e8:de00:d455:bcab:c324:4d72", "192.168.200.254"}
+
+
+def test_other_candidates_and_lines_are_not():
+ assert _host_addresses("a=candidate:1 1 udp 1 90.112.206.172 9 typ srflx\r\n"
+ "a=ice-ufrag:typ host\r\n") == set()