remote.DocumentaciónInicio

Descripción técnica

Este documento describe varios mecanismos centrales del código actual. Las tablas completas de campos de mensajes, formatos de tramas y secuencias de negociación están en Protocolo de comunicación.

El operador y el dispositivo se conectan hacia exchange

Los dispositivos domésticos y de sucursales suelen estar detrás de NAT, por lo que exchange no puede iniciar conexiones hacia ellos. Ambos extremos inician un WebSocket hacia exchange:

  • El dispositivo conecta con wss://<主机>/ws/device, o ws:// para depuración sin cifrar, y se registra enviando su token en la primera trama.
  • El operador conecta con wss://<主机>/ws/operator y utiliza su token durante la negociación.

exchange solo se encarga de autenticación, encaminamiento y trazabilidad. --listen o REMOTE_LISTEN determina la dirección de escucha; el proxy inverso que lo precede termina TLS.

Dos espacios de identificadores de sesión

El operador y el dispositivo mantienen sus propios identificadores sin ocupar los del otro extremo. El operador puede incluir su identificador al enviar la solicitud, sin esperar a que el servidor le asigne uno antes de enviar más tramas. Los identificadores del dispositivo se asignan de forma independiente en su conexión actual, incrementándose desde 1. Aunque dos operadores usen el identificador local 1, obtendrán identificadores distintos en el dispositivo.

El 0 no se asigna a ninguna sesión: queda reservado para mensajes de conexión. Si el identificador del operador es 0 o ya está ocupado en esa conexión de operador, el registro falla directamente y no se incorpora a la tabla de rutas.

La tabla de traducción está en crates/exchange/src/hub/session_table.rs. hub.rs asocia la traducción con cada conexión: en el dispositivo, «ID del dispositivo → ID del operador»; en el operador, «ID del operador → (conexión del dispositivo, ID del dispositivo)».

La retransmisión solo modifica el identificador de sesión

Los bytes PTY y stdout/stderr de los comandos viajan en tramas binarias. La cabecera tiene 9 bytes: 1 para el tipo de flujo, 4 para session_id y 4 para la longitud de la carga, seguidos por la carga sin procesar. Se define en crates/proto/src/frame.rs.

Al retransmitir estas tramas, exchange solo sustituye session_id:

  • Del operador al dispositivo: crates/exchange/src/ws/operator.rs utiliza DataFrame::with_session_id para sustituirlo por el ID del dispositivo y coloca los bytes codificados en su cola de envío.
  • Del dispositivo al operador: handle_data, en crates/exchange/src/ws/device.rs, sustituye el ID del dispositivo por el del propio operador. El comentario del código se refiere precisamente a esta modificación.

La carga pasa sin cambios. El encaminamiento no decide según las pulsaciones PTY ni el contenido de la salida; si no encuentra la sesión, descarta la trama. Los mensajes de control son tramas de texto JSON. Al abrir y cerrar sesiones, también se traducen los identificadores al espacio del otro extremo, conservando los campos funcionales.

La reconexión del dispositivo sustituye la conexión anterior

Cuando un dispositivo vuelve a conectarse, la nueva conexión se incorpora a la tabla y sustituye la anterior, que se devuelve al llamador para cerrarla. Rechazar la nueva conexión no resuelve las «conexiones duplicadas»: una conexión anterior semiabierta puede seguir en la tabla y el dispositivo tendría que esperar hasta que expirase.

Cada conexión de dispositivo tiene un epoch, un UUID generado en Hub::register_device. La limpieza pasa por Hub::unregister_device, que solo retira el dispositivo y lo marca como desconectado si el epoch de la tabla coincide con el de esa limpieza. Si la limpieza de la conexión anterior llega después del registro de la nueva, los epoch no coinciden, la función devuelve una lista vacía y la nueva conexión permanece en la tabla.

Colas de envío limitadas: al llenarse se cierra la conexión

Cada WebSocket tiene una cola con capacidad de 512 elementos, implementada por channel en crates/exchange/src/hub/outbound.rs. Como el encaminamiento mantiene el bloqueo de la tabla de conexiones, solo utiliza Sender::try_send para entregar mensajes, sin esperar al otro extremo dentro del bloqueo.

Si la cola está llena, try_send devuelve SendError::Lagging y activa la señal de cierre. El control Close no entra en la cola: activa esa misma señal, de modo que se puede cerrar incluso una conexión con la cola bloqueada. Los bucles de aceptación de dispositivos y operadores esperan sender.closed() en tokio::select!, en crates/exchange/src/ws/device.rs y crates/exchange/src/ws/operator.rs. Al recibir la señal, termina la retransmisión y se da de baja el dispositivo según su epoch o se limpian las sesiones del operador. Si el otro extremo ya ha descartado el receptor, se devuelve SendError::Closed, diferenciándolo de un consumidor que no puede seguir el ritmo.

Tokens con prefijo y secreto: solo se almacena argon2 del secreto

El token en claro tiene la forma <prefix>.<secret>. split_token, en crates/exchange/src/auth.rs, lo divide por el primer .; un prefijo o secreto vacío se considera un error de formato.

Al emitirlo, el prefijo es la representación hexadecimal de 8 bytes aleatorios y el secreto es base64url de 32 bytes aleatorios. La base de datos solo guarda dos columnas:

  • token_prefix: en claro, con índice único; permite una búsqueda durante la validación.
  • token_hash: cadena PHC argon2id del secreto, generada por hash_secret.

Dispositivos y cuentas de operador usan este formato. Para validar, se recupera el hash mediante el prefijo y verify_secret comprueba el secreto. El token en claro solo se devuelve al emitirlo y no puede reconstruirse desde la base de datos.

Los parámetros de argon2 son 8 MiB de memoria, 2 iteraciones y paralelismo 1, inferiores a los predeterminados de la biblioteca. El secreto es un valor aleatorio de 256 bits: un hash lento no aporta protección práctica adicional frente a fuerza bruta, pero sí aumenta la latencia de negociación cuando muchos dispositivos reconectan simultáneamente. Estos parámetros solo son adecuados para este tipo de tokens aleatorios.

Documentación relacionada