remoteRF-server 0.1.64__tar.gz → 0.1.65__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/PKG-INFO +1 -1
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/pyproject.toml +1 -1
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/host/host_tunnel_server.py +243 -103
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/README.md +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/setup.cfg +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/device_manager.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/grpc_server.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/serverrf_cli.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/top_level.txt +0 -0
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/host/host_tunnel_server.py
RENAMED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
import threading
|
|
6
7
|
import queue
|
|
7
8
|
import time
|
|
8
9
|
import secrets
|
|
10
|
+
import traceback
|
|
9
11
|
from dataclasses import dataclass
|
|
10
12
|
from typing import Dict, Optional, Tuple, Any, List, Set, Iterator
|
|
11
13
|
from concurrent.futures import Future
|
|
@@ -35,6 +37,35 @@ from .host_directory_store import (
|
|
|
35
37
|
import hashlib
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# Always-on logging (no levels to configure; INFO/WARN/ERROR always print)
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
def _ts() -> str:
|
|
45
|
+
t = time.time()
|
|
46
|
+
sec = int(t)
|
|
47
|
+
ms = int((t - sec) * 1000)
|
|
48
|
+
lt = time.localtime(sec)
|
|
49
|
+
return time.strftime("%H:%M:%S", lt) + f".{ms:03d}"
|
|
50
|
+
|
|
51
|
+
def _log(level: str, msg: str) -> None:
|
|
52
|
+
tn = threading.current_thread().name
|
|
53
|
+
print(f"{_ts()} {level:<5} [{tn}] hostrf.host_tunnel_server: {msg}", file=sys.stderr, flush=True)
|
|
54
|
+
|
|
55
|
+
def info(msg: str) -> None:
|
|
56
|
+
_log("INFO", msg)
|
|
57
|
+
|
|
58
|
+
def warn(msg: str) -> None:
|
|
59
|
+
_log("WARN", msg)
|
|
60
|
+
|
|
61
|
+
def error(msg: str) -> None:
|
|
62
|
+
_log("ERROR", msg)
|
|
63
|
+
|
|
64
|
+
def exception(msg: str) -> None:
|
|
65
|
+
_log("ERROR", msg)
|
|
66
|
+
traceback.print_exc(limit=80, file=sys.stderr)
|
|
67
|
+
|
|
68
|
+
|
|
38
69
|
def host_key(host_id: str) -> str:
|
|
39
70
|
h = hashlib.sha256((host_id or "").encode("utf-8")).hexdigest()
|
|
40
71
|
return h[:16] # short stable key
|
|
@@ -87,28 +118,42 @@ class HostSession:
|
|
|
87
118
|
self.inflight_lock = threading.Lock()
|
|
88
119
|
self.last_heartbeat_ms = now_ms()
|
|
89
120
|
self.alive = True
|
|
121
|
+
self.outbound_max = int(outbound_max)
|
|
122
|
+
|
|
123
|
+
info(f"[session] created host_id={self.host_id} outbound_max={self.outbound_max}")
|
|
90
124
|
|
|
91
125
|
def send(self, frame: host_tunnel_pb2.HostFrame, *, timeout: float = 2.0) -> bool:
|
|
92
126
|
if not self.alive:
|
|
127
|
+
warn(f"[session] send refused: host_id={self.host_id} (session not alive) frame={frame.WhichOneof('msg')}")
|
|
93
128
|
return False
|
|
129
|
+
which = frame.WhichOneof("msg")
|
|
94
130
|
try:
|
|
95
131
|
self.out_q.put(frame, timeout=timeout)
|
|
132
|
+
info(f"[session] queued outbound frame host_id={self.host_id} type={which}")
|
|
96
133
|
return True
|
|
97
134
|
except queue.Full:
|
|
135
|
+
warn(f"[session] outbound queue FULL host_id={self.host_id} max={self.outbound_max} drop type={which}")
|
|
136
|
+
return False
|
|
137
|
+
except Exception:
|
|
138
|
+
exception(f"[session] send exception host_id={self.host_id} type={which}")
|
|
98
139
|
return False
|
|
99
140
|
|
|
100
141
|
def close(self) -> None:
|
|
142
|
+
if not self.alive:
|
|
143
|
+
return
|
|
144
|
+
info(f"[session] closing host_id={self.host_id}")
|
|
101
145
|
self.alive = False
|
|
102
146
|
try:
|
|
103
147
|
self.out_q.put_nowait(None)
|
|
104
148
|
except Exception:
|
|
105
|
-
|
|
149
|
+
exception(f"[session] close: failed to enqueue sentinel host_id={self.host_id}")
|
|
106
150
|
|
|
107
151
|
with self.inflight_lock:
|
|
108
|
-
for
|
|
152
|
+
for rid, fut in list(self.inflight.items()):
|
|
109
153
|
if not fut.done():
|
|
110
|
-
fut.set_exception(RuntimeError(f"Host '{self.host_id}' disconnected"))
|
|
154
|
+
fut.set_exception(RuntimeError(f"Host '{self.host_id}' disconnected (req_id={rid})"))
|
|
111
155
|
self.inflight.clear()
|
|
156
|
+
info(f"[session] closed host_id={self.host_id}")
|
|
112
157
|
|
|
113
158
|
|
|
114
159
|
class HostTunnelRegistry:
|
|
@@ -119,14 +164,6 @@ class HostTunnelRegistry:
|
|
|
119
164
|
- host_directory.env (append-only CSV lists)
|
|
120
165
|
- host_directory_meta.env (upsert metadata; never deletes keys)
|
|
121
166
|
|
|
122
|
-
Fast per-RPC routing helpers:
|
|
123
|
-
- is_host_device(device_id) -> bool
|
|
124
|
-
- is_host_device_active(device_id) -> bool (cached TTL)
|
|
125
|
-
- get_host_for_device(device_id) -> Optional[str]
|
|
126
|
-
|
|
127
|
-
UI/diagnostics:
|
|
128
|
-
- device_directory_cached() -> {device_id: (host_id, device_info, is_active)} (cached TTL)
|
|
129
|
-
|
|
130
167
|
Enforces:
|
|
131
168
|
- device_id is globally unique across hosts (rejects conflicting host announce)
|
|
132
169
|
"""
|
|
@@ -137,12 +174,12 @@ class HostTunnelRegistry:
|
|
|
137
174
|
# live sessions
|
|
138
175
|
self._hosts: Dict[str, HostSession] = {} # host_id -> session
|
|
139
176
|
|
|
140
|
-
#
|
|
141
|
-
self._routes: Dict[str, DeviceRoute] = {}
|
|
142
|
-
self._host_devices: Dict[str, Set[str]] = {}
|
|
143
|
-
self._device_infos: Dict[str, host_tunnel_pb2.DeviceInfo] = {}
|
|
177
|
+
# directory state
|
|
178
|
+
self._routes: Dict[str, DeviceRoute] = {} # device_id -> route(host_id, local_id)
|
|
179
|
+
self._host_devices: Dict[str, Set[str]] = {} # host_id -> set(device_id)
|
|
180
|
+
self._device_infos: Dict[str, host_tunnel_pb2.DeviceInfo] = {} # device_id -> DeviceInfo
|
|
144
181
|
|
|
145
|
-
# status
|
|
182
|
+
# status
|
|
146
183
|
self._host_status: Dict[str, HostStatus] = {}
|
|
147
184
|
self._device_status: Dict[str, DeviceStatus] = {}
|
|
148
185
|
|
|
@@ -157,23 +194,18 @@ class HostTunnelRegistry:
|
|
|
157
194
|
self._active_hosts_cache_ts_ms: int = 0
|
|
158
195
|
self._active_hosts_cache_ttl_ms: int = int(active_cache_ttl_ms)
|
|
159
196
|
|
|
160
|
-
self._dir_cache: Dict[str, Tuple[str, Any, bool]] = {}
|
|
197
|
+
self._dir_cache: Dict[str, Tuple[str, Tuple[str, Any, bool], bool]] = {} # not used externally
|
|
161
198
|
self._dir_cache_ts_ms: int = 0
|
|
162
199
|
self._dir_cache_ttl_ms: int = int(dir_cache_ttl_ms)
|
|
163
200
|
|
|
201
|
+
info(f"[registry] init cfg_dir={cfg}")
|
|
164
202
|
self._load_persisted()
|
|
165
203
|
|
|
166
|
-
# ---------------------------
|
|
167
|
-
# caching helpers
|
|
168
|
-
# ---------------------------
|
|
169
|
-
|
|
170
204
|
def _invalidate_caches_locked(self) -> None:
|
|
171
|
-
# must be called under self._lock
|
|
172
205
|
self._active_hosts_cache_ts_ms = 0
|
|
173
206
|
self._dir_cache_ts_ms = 0
|
|
174
207
|
|
|
175
208
|
def _refresh_active_hosts_cache_locked(self, now: int) -> None:
|
|
176
|
-
# must be called under self._lock
|
|
177
209
|
if self._active_hosts_cache_ts_ms and (now - self._active_hosts_cache_ts_ms) < self._active_hosts_cache_ttl_ms:
|
|
178
210
|
return
|
|
179
211
|
self._active_hosts_cache = {hid for hid, sess in self._hosts.items() if sess is not None and sess.alive}
|
|
@@ -184,19 +216,14 @@ class HostTunnelRegistry:
|
|
|
184
216
|
# ---------------------------
|
|
185
217
|
|
|
186
218
|
def _load_persisted(self) -> None:
|
|
187
|
-
"""
|
|
188
|
-
Rebuild directory state from repo_root/.config/*.env
|
|
189
|
-
|
|
190
|
-
On boot, everything starts offline until hosts reconnect.
|
|
191
|
-
This restores *directory + metadata* only (routes/devices/labels).
|
|
192
|
-
"""
|
|
193
219
|
kv_lists = self._env_lists.read_kv()
|
|
194
220
|
kv_meta = self._env_meta.read_kv()
|
|
195
221
|
|
|
196
222
|
known_hosts = csv_split(kv_lists.get("KNOWN_HOSTS", ""))
|
|
197
223
|
known_devices = csv_split(kv_lists.get("KNOWN_DEVICES", ""))
|
|
198
224
|
|
|
199
|
-
|
|
225
|
+
info(f"[registry] load persisted: known_hosts={len(known_hosts)} known_devices={len(known_devices)}")
|
|
226
|
+
|
|
200
227
|
with self._lock:
|
|
201
228
|
self._routes.clear()
|
|
202
229
|
self._host_devices.clear()
|
|
@@ -204,22 +231,18 @@ class HostTunnelRegistry:
|
|
|
204
231
|
self._host_status.clear()
|
|
205
232
|
self._device_status.clear()
|
|
206
233
|
|
|
207
|
-
# seed known hosts as offline
|
|
208
234
|
for hid in known_hosts:
|
|
209
235
|
if hid:
|
|
210
236
|
self._host_status[hid] = HostStatus(host_id=hid, online=False, last_seen_ms=0)
|
|
211
237
|
self._host_devices.setdefault(hid, set())
|
|
212
238
|
|
|
213
|
-
# collect devices that appear anywhere
|
|
214
239
|
devices_from_host_lists: Set[str] = set()
|
|
215
240
|
for k, v in kv_lists.items():
|
|
216
241
|
if k.startswith("HOST_") and k.endswith("_DEVICES"):
|
|
217
242
|
devices_from_host_lists.update(csv_split(v))
|
|
218
243
|
|
|
219
|
-
# meta augments those devices; it can’t introduce new raw device ids safely.
|
|
220
244
|
all_devices: List[str] = sorted(set(known_devices) | devices_from_host_lists)
|
|
221
245
|
|
|
222
|
-
# build device entries offline
|
|
223
246
|
for device_id in all_devices:
|
|
224
247
|
if not device_id:
|
|
225
248
|
continue
|
|
@@ -260,6 +283,8 @@ class HostTunnelRegistry:
|
|
|
260
283
|
|
|
261
284
|
self._invalidate_caches_locked()
|
|
262
285
|
|
|
286
|
+
info(f"[registry] persisted directory loaded: routes={len(self._routes)} hosts={len(self._host_status)} devices={len(self._device_status)}")
|
|
287
|
+
|
|
263
288
|
# ---------------------------
|
|
264
289
|
# persistence write helpers
|
|
265
290
|
# ---------------------------
|
|
@@ -275,24 +300,23 @@ class HostTunnelRegistry:
|
|
|
275
300
|
self._env_meta.upsert_kv(f"HOST_{hk}_VERSION", version)
|
|
276
301
|
|
|
277
302
|
def persist_host_meta_from_hello(self, hello: Any) -> None:
|
|
278
|
-
"""
|
|
279
|
-
Safe helper so HostTunnelServicer can persist from HostHello without needing to know field names.
|
|
280
|
-
"""
|
|
281
303
|
try:
|
|
282
304
|
hid = str(getattr(hello, "host_id", "") or "").strip()
|
|
283
305
|
ver = str(getattr(hello, "version", "") or "").strip()
|
|
284
306
|
if hid:
|
|
285
307
|
self._persist_host_meta(hid, version=ver)
|
|
308
|
+
info(f"[registry] persisted host meta from hello: host_id={hid} version={ver!r}")
|
|
286
309
|
except Exception:
|
|
287
|
-
|
|
310
|
+
exception("[registry] persist_host_meta_from_hello failed")
|
|
288
311
|
|
|
289
312
|
def _persist_host(self, host_id: str) -> None:
|
|
290
313
|
self._env_lists.append_to_csv_list("KNOWN_HOSTS", host_id)
|
|
314
|
+
info(f"[registry] persisted host list: host_id={host_id}")
|
|
291
315
|
|
|
292
316
|
def _persist_device(self, host_id: str, device_id: str, local_id: int, device_info: Any) -> None:
|
|
293
317
|
self._env_lists.append_to_csv_list("KNOWN_DEVICES", device_id)
|
|
294
318
|
hk = host_key(host_id)
|
|
295
|
-
self._env_meta.set_kv_if_absent(f"HOST_{hk}_ID", host_id)
|
|
319
|
+
self._env_meta.set_kv_if_absent(f"HOST_{hk}_ID", host_id)
|
|
296
320
|
self._env_lists.append_to_csv_list(f"HOST_{hk}_DEVICES", device_id)
|
|
297
321
|
|
|
298
322
|
dk = sanitize_env_key(device_id)
|
|
@@ -310,14 +334,18 @@ class HostTunnelRegistry:
|
|
|
310
334
|
if kind:
|
|
311
335
|
self._env_meta.upsert_kv(f"DEVICE_{dk}_KIND", kind)
|
|
312
336
|
|
|
337
|
+
info(f"[registry] persisted device: host_id={host_id} device_id={device_id} local_id={int(local_id)} label={label!r} serial={serial!r} kind={kind!r}")
|
|
338
|
+
|
|
313
339
|
# ---------------------------
|
|
314
340
|
# directory/status mutators
|
|
315
341
|
# ---------------------------
|
|
316
342
|
|
|
317
343
|
def register_host(self, host_id: str, session: HostSession) -> None:
|
|
344
|
+
info(f"[registry] register_host: host_id={host_id}")
|
|
318
345
|
with self._lock:
|
|
319
346
|
old = self._hosts.get(host_id)
|
|
320
347
|
if old is not None:
|
|
348
|
+
warn(f"[registry] register_host: replacing existing session host_id={host_id}")
|
|
321
349
|
old.close()
|
|
322
350
|
|
|
323
351
|
self._hosts[host_id] = session
|
|
@@ -331,7 +359,10 @@ class HostTunnelRegistry:
|
|
|
331
359
|
self._persist_host(host_id)
|
|
332
360
|
self._invalidate_caches_locked()
|
|
333
361
|
|
|
362
|
+
info(f"[registry] register_host done: host_id={host_id} online=True")
|
|
363
|
+
|
|
334
364
|
def drop_host(self, host_id: str) -> None:
|
|
365
|
+
info(f"[registry] drop_host: host_id={host_id}")
|
|
335
366
|
with self._lock:
|
|
336
367
|
sess = self._hosts.pop(host_id, None)
|
|
337
368
|
if sess is not None:
|
|
@@ -342,7 +373,6 @@ class HostTunnelRegistry:
|
|
|
342
373
|
hs.last_seen_ms = now_ms()
|
|
343
374
|
self._host_status[host_id] = hs
|
|
344
375
|
|
|
345
|
-
# mark that host's devices offline (keep routes)
|
|
346
376
|
for did in self._host_devices.get(host_id, set()):
|
|
347
377
|
ds = self._device_status.get(did)
|
|
348
378
|
if ds is not None:
|
|
@@ -351,6 +381,8 @@ class HostTunnelRegistry:
|
|
|
351
381
|
|
|
352
382
|
self._invalidate_caches_locked()
|
|
353
383
|
|
|
384
|
+
info(f"[registry] drop_host done: host_id={host_id} online=False")
|
|
385
|
+
|
|
354
386
|
def heartbeat(self, host_id: str, unix_ms: int = 0) -> None:
|
|
355
387
|
now = int(unix_ms) or now_ms()
|
|
356
388
|
with self._lock:
|
|
@@ -367,12 +399,12 @@ class HostTunnelRegistry:
|
|
|
367
399
|
|
|
368
400
|
self._invalidate_caches_locked()
|
|
369
401
|
|
|
402
|
+
info(f"[registry] heartbeat: host_id={host_id} unix_ms={now}")
|
|
403
|
+
|
|
370
404
|
def announce_devices(self, host_id: str, devices: list) -> None:
|
|
371
|
-
""
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
"""
|
|
375
|
-
# conflict check first (no partial apply)
|
|
405
|
+
info(f"[registry] announce_devices: host_id={host_id} n={len(devices)}")
|
|
406
|
+
|
|
407
|
+
# conflict check (no partial apply)
|
|
376
408
|
with self._lock:
|
|
377
409
|
for d in devices:
|
|
378
410
|
device_id = str(getattr(d, "device_id", "")).strip()
|
|
@@ -380,6 +412,7 @@ class HostTunnelRegistry:
|
|
|
380
412
|
continue
|
|
381
413
|
existing = self._routes.get(device_id)
|
|
382
414
|
if existing is not None and existing.host_id != host_id:
|
|
415
|
+
warn(f"[registry] device_id conflict: device_id={device_id} existing_host={existing.host_id} new_host={host_id}")
|
|
383
416
|
raise DeviceIdConflictError(
|
|
384
417
|
device_id=device_id,
|
|
385
418
|
existing_host=existing.host_id,
|
|
@@ -393,6 +426,7 @@ class HostTunnelRegistry:
|
|
|
393
426
|
for d in devices:
|
|
394
427
|
device_id = str(getattr(d, "device_id", "")).strip()
|
|
395
428
|
if not device_id:
|
|
429
|
+
warn(f"[registry] announce_devices: skipping device with empty device_id host_id={host_id}")
|
|
396
430
|
continue
|
|
397
431
|
|
|
398
432
|
try:
|
|
@@ -400,6 +434,12 @@ class HostTunnelRegistry:
|
|
|
400
434
|
except Exception:
|
|
401
435
|
local_id = 0
|
|
402
436
|
|
|
437
|
+
label = str(getattr(d, "label", "") or "")
|
|
438
|
+
serial = str(getattr(d, "serial", "") or "")
|
|
439
|
+
kind = str(getattr(d, "kind", "") or "")
|
|
440
|
+
|
|
441
|
+
info(f"[registry] device: host_id={host_id} local_id={local_id} device_id={device_id!r} label={label!r} serial={serial!r} kind={kind!r}")
|
|
442
|
+
|
|
403
443
|
self._routes[device_id] = DeviceRoute(host_id=host_id, host_local_id=local_id)
|
|
404
444
|
self._host_devices[host_id].add(device_id)
|
|
405
445
|
|
|
@@ -422,15 +462,17 @@ class HostTunnelRegistry:
|
|
|
422
462
|
self._device_infos[device_id] = host_tunnel_pb2.DeviceInfo(
|
|
423
463
|
device_id=device_id,
|
|
424
464
|
local_id=int(local_id),
|
|
425
|
-
label=
|
|
426
|
-
serial=
|
|
427
|
-
kind=
|
|
465
|
+
label=label,
|
|
466
|
+
serial=serial,
|
|
467
|
+
kind=kind,
|
|
428
468
|
)
|
|
429
469
|
|
|
430
470
|
self._persist_device(host_id, device_id, local_id, d)
|
|
431
471
|
|
|
432
472
|
self._invalidate_caches_locked()
|
|
433
473
|
|
|
474
|
+
info(f"[registry] announce_devices done: host_id={host_id} routes={len(self._routes)}")
|
|
475
|
+
|
|
434
476
|
# ---------------------------
|
|
435
477
|
# FAST PATHS (use per-RPC)
|
|
436
478
|
# ---------------------------
|
|
@@ -446,7 +488,6 @@ class HostTunnelRegistry:
|
|
|
446
488
|
did = (device_id or "").strip()
|
|
447
489
|
if not did:
|
|
448
490
|
return False
|
|
449
|
-
|
|
450
491
|
now = now_ms()
|
|
451
492
|
with self._lock:
|
|
452
493
|
route = self._routes.get(did)
|
|
@@ -473,26 +514,26 @@ class HostTunnelRegistry:
|
|
|
473
514
|
|
|
474
515
|
with self._lock:
|
|
475
516
|
if self._dir_cache_ts_ms and (now - self._dir_cache_ts_ms) < ttl:
|
|
476
|
-
|
|
517
|
+
# rebuild from saved cache is omitted (original returned dict(self._dir_cache))
|
|
518
|
+
pass
|
|
477
519
|
|
|
478
520
|
self._refresh_active_hosts_cache_locked(now)
|
|
479
521
|
|
|
480
522
|
out: Dict[str, Tuple[str, Any, bool]] = {}
|
|
481
523
|
for device_id, route in self._routes.items():
|
|
482
524
|
info0 = self._device_infos.get(device_id)
|
|
483
|
-
|
|
525
|
+
info_obj = info0
|
|
484
526
|
if info0 is not None:
|
|
485
527
|
tmp = host_tunnel_pb2.DeviceInfo()
|
|
486
528
|
try:
|
|
487
529
|
tmp.CopyFrom(info0)
|
|
488
|
-
|
|
530
|
+
info_obj = tmp
|
|
489
531
|
except Exception:
|
|
490
|
-
|
|
532
|
+
info_obj = info0
|
|
491
533
|
|
|
492
534
|
is_active = route.host_id in self._active_hosts_cache
|
|
493
|
-
out[device_id] = (route.host_id,
|
|
535
|
+
out[device_id] = (route.host_id, info_obj, is_active)
|
|
494
536
|
|
|
495
|
-
self._dir_cache = out
|
|
496
537
|
self._dir_cache_ts_ms = now
|
|
497
538
|
return dict(out)
|
|
498
539
|
|
|
@@ -513,7 +554,7 @@ class HostTunnelRegistry:
|
|
|
513
554
|
return dict(self._device_status)
|
|
514
555
|
|
|
515
556
|
# ---------------------------
|
|
516
|
-
# forwarding
|
|
557
|
+
# forwarding
|
|
517
558
|
# ---------------------------
|
|
518
559
|
|
|
519
560
|
def _get_session_and_route(self, device_id: str) -> Tuple[HostSession, DeviceRoute]:
|
|
@@ -537,7 +578,15 @@ class HostTunnelRegistry:
|
|
|
537
578
|
deadline_unix_ms: int = 0,
|
|
538
579
|
cancel_on_timeout: bool = False,
|
|
539
580
|
) -> "generic_pb2.GenericRPCResponse":
|
|
540
|
-
|
|
581
|
+
info(f"[forward] device_id={device_id!r} fn={getattr(request,'function_name','')!r} timeout={timeout_sec}")
|
|
582
|
+
try:
|
|
583
|
+
sess, route = self._get_session_and_route(str(device_id))
|
|
584
|
+
except Exception:
|
|
585
|
+
exception(f"[forward] route/session lookup failed device_id={device_id!r}")
|
|
586
|
+
r = generic_pb2.GenericRPCResponse()
|
|
587
|
+
r.results["Ok"].CopyFrom(map_arg(False))
|
|
588
|
+
r.results["Error"].CopyFrom(map_arg("Route/session lookup failed (see server logs)."))
|
|
589
|
+
return r
|
|
541
590
|
|
|
542
591
|
req_id = secrets.token_hex(12)
|
|
543
592
|
fut: Future = Future()
|
|
@@ -555,6 +604,8 @@ class HostTunnelRegistry:
|
|
|
555
604
|
|
|
556
605
|
frame = host_tunnel_pb2.HostFrame(rpc_request=rpc_req)
|
|
557
606
|
|
|
607
|
+
info(f"[forward] -> host_id={sess.host_id} req_id={req_id} local_id={route.host_local_id}")
|
|
608
|
+
|
|
558
609
|
if not sess.send(frame, timeout=2.0):
|
|
559
610
|
with sess.inflight_lock:
|
|
560
611
|
sess.inflight.pop(req_id, None)
|
|
@@ -567,11 +618,13 @@ class HostTunnelRegistry:
|
|
|
567
618
|
try:
|
|
568
619
|
resp: host_tunnel_pb2.RpcResponse = fut.result(timeout=timeout_sec)
|
|
569
620
|
except Exception as e:
|
|
621
|
+
warn(f"[forward] timeout/exception waiting resp req_id={req_id} err={e!r}")
|
|
570
622
|
if cancel_on_timeout:
|
|
571
623
|
try:
|
|
572
624
|
sess.send(host_tunnel_pb2.HostFrame(cancel=host_tunnel_pb2.Cancel(req_id=req_id)))
|
|
625
|
+
info(f"[forward] sent cancel req_id={req_id}")
|
|
573
626
|
except Exception:
|
|
574
|
-
|
|
627
|
+
exception(f"[forward] failed sending cancel req_id={req_id}")
|
|
575
628
|
|
|
576
629
|
with sess.inflight_lock:
|
|
577
630
|
sess.inflight.pop(req_id, None)
|
|
@@ -584,6 +637,8 @@ class HostTunnelRegistry:
|
|
|
584
637
|
with sess.inflight_lock:
|
|
585
638
|
sess.inflight.pop(req_id, None)
|
|
586
639
|
|
|
640
|
+
info(f"[forward] <- resp req_id={req_id} ok={resp.ok} err={resp.error!r}")
|
|
641
|
+
|
|
587
642
|
if not resp.ok:
|
|
588
643
|
r = generic_pb2.GenericRPCResponse()
|
|
589
644
|
r.results["Ok"].CopyFrom(map_arg(False))
|
|
@@ -605,12 +660,14 @@ class HostTunnelRegistry:
|
|
|
605
660
|
with self._lock:
|
|
606
661
|
route = self._routes.get(str(device_id))
|
|
607
662
|
if route is None:
|
|
663
|
+
warn(f"[forward] unknown device_id={device_id!r}")
|
|
608
664
|
r = generic_pb2.GenericRPCResponse()
|
|
609
665
|
r.results["Ok"].CopyFrom(map_arg(False))
|
|
610
666
|
r.results["Error"].CopyFrom(map_arg(f"Unknown device_id={device_id}"))
|
|
611
667
|
return r
|
|
612
668
|
|
|
613
669
|
if route.host_id != host_id:
|
|
670
|
+
warn(f"[forward] device_id={device_id!r} not on host_id={host_id!r} (actual={route.host_id!r})")
|
|
614
671
|
r = generic_pb2.GenericRPCResponse()
|
|
615
672
|
r.results["Ok"].CopyFrom(map_arg(False))
|
|
616
673
|
r.results["Error"].CopyFrom(map_arg(f"device_id={device_id} is not on host_id={host_id}"))
|
|
@@ -634,6 +691,7 @@ def handle_host_device(
|
|
|
634
691
|
args: Any,
|
|
635
692
|
timeout_sec: float = 10.0,
|
|
636
693
|
) -> Dict[str, Any]:
|
|
694
|
+
info(f"[handle_host_device] host_id={host_id!r} device_id={device_id!r} fn={function_name!r}")
|
|
637
695
|
req = generic_pb2.GenericRPCRequest(function_name=str(function_name))
|
|
638
696
|
_copy_argument_map(req.args, args)
|
|
639
697
|
|
|
@@ -655,6 +713,7 @@ def handle_host_device_request(
|
|
|
655
713
|
request: "generic_pb2.GenericRPCRequest",
|
|
656
714
|
timeout_sec: float = 10.0,
|
|
657
715
|
) -> "generic_pb2.GenericRPCResponse":
|
|
716
|
+
info(f"[handle_host_device_request] host_id={host_id!r} device_id={device_id!r} fn={getattr(request,'function_name','')!r}")
|
|
658
717
|
return registry.forward_request_by_host_device(
|
|
659
718
|
host_id=host_id,
|
|
660
719
|
device_id=device_id,
|
|
@@ -669,6 +728,14 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
669
728
|
self.registry = registry
|
|
670
729
|
|
|
671
730
|
def Connect(self, request_iterator: Iterator[host_tunnel_pb2.HostFrame], context):
|
|
731
|
+
peer = ""
|
|
732
|
+
try:
|
|
733
|
+
peer = context.peer()
|
|
734
|
+
except Exception:
|
|
735
|
+
peer = "unknown-peer"
|
|
736
|
+
|
|
737
|
+
info(f"[Connect] new stream peer={peer}")
|
|
738
|
+
|
|
672
739
|
host_id: Optional[str] = None
|
|
673
740
|
session: Optional[HostSession] = None
|
|
674
741
|
stop = threading.Event()
|
|
@@ -676,23 +743,30 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
676
743
|
|
|
677
744
|
def inbound_loop():
|
|
678
745
|
nonlocal host_id, session
|
|
746
|
+
info(f"[inbound] start peer={peer}")
|
|
679
747
|
try:
|
|
680
748
|
for frame in request_iterator:
|
|
681
749
|
which = frame.WhichOneof("msg")
|
|
682
750
|
if which is None:
|
|
751
|
+
warn(f"[inbound] received frame with no msg (peer={peer})")
|
|
683
752
|
continue
|
|
684
753
|
|
|
754
|
+
info(f"[inbound] frame type={which} peer={peer} host_id={host_id or '?'}")
|
|
755
|
+
|
|
685
756
|
if which == "hello":
|
|
686
757
|
hello = frame.hello
|
|
687
|
-
host_id = str(hello
|
|
758
|
+
host_id = str(getattr(hello, "host_id", "")).strip() or "unknown-host"
|
|
759
|
+
ver = str(getattr(hello, "version", "") or "")
|
|
760
|
+
info(f"[inbound] HELLO host_id={host_id!r} version={ver!r} peer={peer}")
|
|
761
|
+
|
|
688
762
|
session = HostSession(host_id)
|
|
689
763
|
self.registry.register_host(host_id, session)
|
|
690
764
|
self.registry.persist_host_meta_from_hello(hello)
|
|
691
765
|
|
|
692
|
-
# Ask host for metadata immediately on join
|
|
766
|
+
# Ask host for metadata immediately on join
|
|
693
767
|
try:
|
|
694
768
|
mrid = secrets.token_hex(12)
|
|
695
|
-
session.send(
|
|
769
|
+
ok = session.send(
|
|
696
770
|
host_tunnel_pb2.HostFrame(
|
|
697
771
|
meta_request=host_tunnel_pb2.MetaRequest(
|
|
698
772
|
req_id=mrid,
|
|
@@ -702,110 +776,151 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
702
776
|
)
|
|
703
777
|
)
|
|
704
778
|
)
|
|
779
|
+
info(f"[inbound] sent MetaRequest req_id={mrid} ok={ok} host_id={host_id}")
|
|
705
780
|
except Exception:
|
|
706
|
-
|
|
781
|
+
exception("[inbound] FAILED sending MetaRequest")
|
|
707
782
|
|
|
708
783
|
# Also ping/ack
|
|
709
784
|
try:
|
|
710
|
-
session.send(
|
|
785
|
+
ok = session.send(
|
|
711
786
|
host_tunnel_pb2.HostFrame(
|
|
712
787
|
heartbeat=host_tunnel_pb2.Heartbeat(unix_ms=now_ms())
|
|
713
788
|
)
|
|
714
789
|
)
|
|
790
|
+
info(f"[inbound] sent heartbeat ack ok={ok} host_id={host_id}")
|
|
715
791
|
except Exception:
|
|
716
|
-
|
|
792
|
+
exception("[inbound] FAILED sending heartbeat ack")
|
|
717
793
|
|
|
718
794
|
elif which == "device_announce":
|
|
719
795
|
if session is None or host_id is None:
|
|
796
|
+
warn("[inbound] device_announce before hello; ignoring")
|
|
720
797
|
continue
|
|
798
|
+
|
|
721
799
|
ann = frame.device_announce
|
|
800
|
+
info(f"[inbound] DEVICE_ANNOUNCE host_id={host_id} n={len(ann.devices)} unix_ms={int(getattr(ann,'unix_ms',0) or 0)} full_snapshot={bool(getattr(ann,'full_snapshot',False))}")
|
|
801
|
+
|
|
802
|
+
for d in list(ann.devices):
|
|
803
|
+
info(
|
|
804
|
+
f"[inbound] device local_id={int(getattr(d,'local_id',0))} "
|
|
805
|
+
f"label={str(getattr(d,'label','') or '')!r} "
|
|
806
|
+
f"device_id={str(getattr(d,'device_id','') or '')!r} "
|
|
807
|
+
f"serial={str(getattr(d,'serial','') or '')!r} "
|
|
808
|
+
f"kind={str(getattr(d,'kind','') or '')!r}"
|
|
809
|
+
)
|
|
810
|
+
|
|
722
811
|
try:
|
|
723
812
|
self.registry.announce_devices(host_id, list(ann.devices))
|
|
813
|
+
info(f"[inbound] registry announce applied host_id={host_id}")
|
|
724
814
|
except DeviceIdConflictError as e:
|
|
815
|
+
error(f"[inbound] DeviceIdConflictError host_id={host_id} err={e}")
|
|
725
816
|
fatal["code"] = "ALREADY_EXISTS"
|
|
726
817
|
fatal["details"] = str(e)
|
|
727
818
|
stop.set()
|
|
728
819
|
return
|
|
820
|
+
except Exception:
|
|
821
|
+
exception(f"[inbound] announce_devices crashed host_id={host_id}")
|
|
729
822
|
|
|
823
|
+
# ack heartbeat
|
|
730
824
|
try:
|
|
731
|
-
session.send(
|
|
825
|
+
ok = session.send(
|
|
732
826
|
host_tunnel_pb2.HostFrame(
|
|
733
827
|
heartbeat=host_tunnel_pb2.Heartbeat(unix_ms=now_ms())
|
|
734
828
|
)
|
|
735
829
|
)
|
|
830
|
+
info(f"[inbound] sent heartbeat ack after announce ok={ok} host_id={host_id}")
|
|
736
831
|
except Exception:
|
|
737
|
-
|
|
832
|
+
exception("[inbound] FAILED sending heartbeat ack after announce")
|
|
738
833
|
|
|
739
834
|
elif which == "rpc_response":
|
|
740
835
|
if session is None:
|
|
836
|
+
warn("[inbound] rpc_response before session; ignoring")
|
|
741
837
|
continue
|
|
742
838
|
resp = frame.rpc_response
|
|
743
|
-
rid = str(resp
|
|
839
|
+
rid = str(getattr(resp, "req_id", ""))
|
|
840
|
+
|
|
841
|
+
info(f"[inbound] RPC_RESPONSE req_id={rid} ok={bool(getattr(resp,'ok',False))} err={str(getattr(resp,'error','') or '')!r}")
|
|
744
842
|
|
|
745
843
|
with session.inflight_lock:
|
|
746
844
|
fut = session.inflight.get(rid)
|
|
747
845
|
|
|
748
846
|
if fut is not None and not fut.done():
|
|
749
847
|
fut.set_result(resp)
|
|
848
|
+
info(f"[inbound] rpc_response delivered to inflight future req_id={rid}")
|
|
849
|
+
else:
|
|
850
|
+
warn(f"[inbound] rpc_response has no inflight waiter req_id={rid}")
|
|
750
851
|
|
|
751
852
|
elif which == "meta_response":
|
|
752
|
-
# Print host metadata (best-effort diagnostics)
|
|
753
853
|
try:
|
|
754
854
|
mr = frame.meta_response
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if meta.devices:
|
|
767
|
-
print(" devices:")
|
|
768
|
-
for d in meta.devices:
|
|
769
|
-
print(
|
|
770
|
-
f" - local_id={d.local_id} "
|
|
771
|
-
f"label={d.label!r} "
|
|
772
|
-
f"device_id={d.device_id!r} "
|
|
773
|
-
f"serial={d.serial!r} "
|
|
774
|
-
f"kind={d.kind!r}"
|
|
775
|
-
)
|
|
776
|
-
print()
|
|
855
|
+
ok = bool(getattr(mr, "ok", False))
|
|
856
|
+
err_s = str(getattr(mr, "error", "") or "")
|
|
857
|
+
info(f"[inbound] META_RESPONSE ok={ok} error={err_s!r}")
|
|
858
|
+
|
|
859
|
+
# Print full proto for maximum visibility
|
|
860
|
+
try:
|
|
861
|
+
info(f"[inbound] META_RESPONSE proto:\n{mr}")
|
|
862
|
+
except Exception:
|
|
863
|
+
pass
|
|
864
|
+
|
|
777
865
|
except Exception:
|
|
778
|
-
|
|
866
|
+
exception("[inbound] meta_response handling crashed")
|
|
779
867
|
|
|
780
868
|
elif which == "heartbeat":
|
|
781
869
|
if session is not None and host_id is not None:
|
|
782
|
-
|
|
783
|
-
|
|
870
|
+
ms = int(getattr(frame.heartbeat, "unix_ms", 0) or 0) or now_ms()
|
|
871
|
+
session.last_heartbeat_ms = ms
|
|
872
|
+
self.registry.heartbeat(host_id, ms)
|
|
873
|
+
info(f"[inbound] HEARTBEAT host_id={host_id} unix_ms={ms}")
|
|
874
|
+
else:
|
|
875
|
+
warn("[inbound] heartbeat before hello/session; ignoring")
|
|
784
876
|
|
|
785
877
|
elif which == "cancel":
|
|
786
|
-
|
|
878
|
+
# not used on server inbound, but log it if it happens
|
|
879
|
+
try:
|
|
880
|
+
rid = str(getattr(frame.cancel, "req_id", "") or "")
|
|
881
|
+
warn(f"[inbound] CANCEL frame received from host?? req_id={rid}")
|
|
882
|
+
except Exception:
|
|
883
|
+
exception("[inbound] cancel frame parsing failed")
|
|
884
|
+
|
|
885
|
+
else:
|
|
886
|
+
warn(f"[inbound] unknown frame type={which}")
|
|
787
887
|
|
|
788
888
|
except Exception:
|
|
789
|
-
|
|
889
|
+
exception(f"[inbound] loop crashed peer={peer} host_id={host_id or '?'}")
|
|
790
890
|
finally:
|
|
891
|
+
info(f"[inbound] exiting peer={peer} host_id={host_id or '?'}")
|
|
791
892
|
stop.set()
|
|
792
893
|
if host_id is not None:
|
|
793
|
-
|
|
894
|
+
try:
|
|
895
|
+
self.registry.drop_host(host_id)
|
|
896
|
+
except Exception:
|
|
897
|
+
exception(f"[inbound] registry.drop_host failed host_id={host_id}")
|
|
794
898
|
if session is not None:
|
|
795
|
-
|
|
899
|
+
try:
|
|
900
|
+
session.close()
|
|
901
|
+
except Exception:
|
|
902
|
+
exception(f"[inbound] session.close failed host_id={host_id}")
|
|
796
903
|
|
|
797
|
-
t = threading.Thread(target=inbound_loop, daemon=True)
|
|
904
|
+
t = threading.Thread(target=inbound_loop, daemon=True, name=f"inbound_{peer}")
|
|
798
905
|
t.start()
|
|
799
906
|
|
|
907
|
+
info(f"[Connect] outbound pump starting peer={peer}")
|
|
908
|
+
|
|
909
|
+
# outbound pump
|
|
910
|
+
saw_session = False
|
|
800
911
|
while not stop.is_set():
|
|
801
912
|
if fatal:
|
|
802
913
|
code = fatal.get("code", "")
|
|
803
914
|
details = fatal.get("details", "fatal")
|
|
915
|
+
error(f"[Connect] aborting stream peer={peer} code={code} details={details}")
|
|
804
916
|
if code == "ALREADY_EXISTS":
|
|
805
917
|
context.abort(grpc.StatusCode.ALREADY_EXISTS, details)
|
|
806
918
|
context.abort(grpc.StatusCode.UNKNOWN, details)
|
|
807
919
|
|
|
808
920
|
if session is None:
|
|
921
|
+
if not saw_session:
|
|
922
|
+
info(f"[Connect] waiting for HELLO peer={peer}")
|
|
923
|
+
saw_session = True
|
|
809
924
|
time.sleep(0.05)
|
|
810
925
|
continue
|
|
811
926
|
|
|
@@ -813,11 +928,20 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
813
928
|
out = session.out_q.get(timeout=0.5)
|
|
814
929
|
except queue.Empty:
|
|
815
930
|
continue
|
|
931
|
+
except Exception:
|
|
932
|
+
exception(f"[Connect] outbound queue get failed peer={peer} host_id={host_id or '?'}")
|
|
933
|
+
continue
|
|
816
934
|
|
|
817
935
|
if out is None:
|
|
936
|
+
info(f"[Connect] outbound sentinel -> end stream peer={peer} host_id={host_id or '?'}")
|
|
818
937
|
break
|
|
938
|
+
|
|
939
|
+
out_type = out.WhichOneof("msg")
|
|
940
|
+
info(f"[Connect] OUT frame type={out_type} peer={peer} host_id={host_id or '?'}")
|
|
819
941
|
yield out
|
|
820
942
|
|
|
943
|
+
info(f"[Connect] stream ended peer={peer} host_id={host_id or '?'}")
|
|
944
|
+
|
|
821
945
|
|
|
822
946
|
def build_server_credentials(
|
|
823
947
|
*,
|
|
@@ -826,13 +950,14 @@ def build_server_credentials(
|
|
|
826
950
|
client_ca_pem: Optional[bytes] = None,
|
|
827
951
|
require_client_auth: bool = True,
|
|
828
952
|
) -> grpc.ServerCredentials:
|
|
953
|
+
info(f"[creds] build_server_credentials client_ca={'yes' if bool(client_ca_pem) else 'no'} require_client_auth={require_client_auth}")
|
|
829
954
|
if client_ca_pem:
|
|
830
955
|
return grpc.ssl_server_credentials(
|
|
831
956
|
((private_key_pem, certificate_chain_pem,),),
|
|
832
957
|
root_certificates=client_ca_pem,
|
|
833
958
|
require_client_auth=require_client_auth,
|
|
834
959
|
)
|
|
835
|
-
return grpc.ssl_server_credentials(((private_key_pem, certificate_chain_pem
|
|
960
|
+
return grpc.ssl_server_credentials(((private_key_pem, certificate_chain_pem),))
|
|
836
961
|
|
|
837
962
|
|
|
838
963
|
def start_host_tunnel_server(
|
|
@@ -854,18 +979,33 @@ def start_host_tunnel_server(
|
|
|
854
979
|
("grpc.keepalive_permit_without_calls", 1),
|
|
855
980
|
]
|
|
856
981
|
|
|
982
|
+
info(f"[server] creating grpc.server max_workers={max_workers} bind={host}:{int(port)}")
|
|
857
983
|
server = grpc.server(
|
|
858
984
|
futures.ThreadPoolExecutor(max_workers=max_workers),
|
|
859
985
|
options=options,
|
|
860
986
|
)
|
|
861
987
|
|
|
862
988
|
host_tunnel_pb2_grpc.add_HostTunnelServicer_to_server(HostTunnelServicer(reg), server)
|
|
863
|
-
|
|
989
|
+
|
|
990
|
+
bind_addr = f"{host}:{int(port)}"
|
|
991
|
+
try:
|
|
992
|
+
added = server.add_secure_port(bind_addr, server_credentials)
|
|
993
|
+
info(f"[server] add_secure_port addr={bind_addr} -> {added}")
|
|
994
|
+
except Exception:
|
|
995
|
+
exception(f"[server] add_secure_port failed addr={bind_addr}")
|
|
996
|
+
raise
|
|
864
997
|
|
|
865
998
|
def run():
|
|
866
|
-
|
|
867
|
-
|
|
999
|
+
try:
|
|
1000
|
+
info(f"[server] starting on {bind_addr}")
|
|
1001
|
+
server.start()
|
|
1002
|
+
info(f"[server] started on {bind_addr} (waiting for termination)")
|
|
1003
|
+
server.wait_for_termination()
|
|
1004
|
+
info("[server] wait_for_termination returned")
|
|
1005
|
+
except Exception:
|
|
1006
|
+
exception("[server] run() crashed")
|
|
1007
|
+
raise
|
|
868
1008
|
|
|
869
|
-
th = threading.Thread(target=run, daemon=True)
|
|
1009
|
+
th = threading.Thread(target=run, daemon=True, name="host_tunnel_server")
|
|
870
1010
|
th.start()
|
|
871
1011
|
return server, th, reg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_host_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_pb2.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/ansi_codes.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/api_token.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/db_connection.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/db_location.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/list_string.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/common/utils/process_arg.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/host/host_directory_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/cert_provider.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/device_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/user_group_cli.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server/server/user_group_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.64 → remoterf_server-0.1.65}/src/remoteRF_server.egg-info/top_level.txt
RENAMED
|
File without changes
|