Sans-IO: el secreto de Rust efectivo para servicios de red
(firezone.dev)- En Firezone usan Rust para construir acceso remoto seguro y escalable en teléfonos Android, computadoras MacOS o servidores Linux
- Usan una biblioteca de conexión llamada
connlibpara gestionar conexiones de red y túneles WireGuard - Después de varias iteraciones, llegaron a un diseño llamado sans-IO que ofrece pruebas rápidas y exhaustivas, personalización profunda y alta confiabilidad
connlib está escrito en Rust y sigue un diseño sans-IO
- Gracias a la velocidad y seguridad de memoria de Rust, es adecuado para construir servicios de red
- Usa el runtime
tokio, WebSockets contungstenite, la implementación de WireGuardboringtuny cifrado de tráfico API conrustls, entre otros - El diseño sans-IO implementa protocolos como máquinas de estado puras, en lugar de enviar y recibir bytes por sockets en múltiples lugares
El modelo asíncrono de Rust y la discusión sobre el "coloreado de funciones"
- Las funciones asíncronas solo pueden llamarse desde otras funciones asíncronas
- Si una función está en lo profundo de una cadena asíncrona, todas las funciones que la llaman también deben volverse asíncronas
- Esto puede ser un problema para quienes quieren escribir código indiferente a si sus dependencias son asíncronas o no
Introducción a sans-IO
- La idea central de sans-IO es similar al principio de inversión de dependencias del mundo OOP
- La política (qué hacer) no debería depender de los detalles de implementación (cómo hacerlo)
- En lugar de transmitir datos usando la estructura
Transmit, se emite unTransmit
Aplicación de la inversión de dependencias
- En lugar de transmitir datos usando la estructura
Transmit, se emite unTransmit - El bucle de eventos implementa los efectos secundarios y llama realmente a
UdpSocket::send
Máquina de estado
- El diagrama de máquina de estado para una solicitud de binding STUN tiene dos estados:
SentyReceived - La máquina de estado se implementa definiendo la estructura
StunBindingy sus funciones relacionadas
Bucle de eventos
- El bucle de eventos impulsa la máquina de estado y procesa datos usando
poll_transmityhandle_input
Abstracción del tiempo
- Los requisitos basados en tiempo se manejan usando las APIs
poll_timeoutyhandle_timeout
Premisa de sans-IO
- El diseño sans-IO deja en manos de la aplicación la decisión sobre si las dependencias son asíncronas o no
- El diseño sans-IO es fácil de componer, ofrece una API flexible, facilita las pruebas y encaja bien con las capacidades de Rust
Composición sencilla
- La API de
StunBindingpuede aplicarse a la mayoría de los protocolos de red - La biblioteca
snownetde Firezone combina ICE y WireGuard para ofrecer un túnel IP "mágico" que funciona sin importar la configuración de red
API flexible
- Escribir directamente el bucle de eventos permite ajustar el código y facilita su mantenimiento
Pruebas rápidas
- El código sans-IO no tiene efectos secundarios, por lo que es muy fácil de probar
- En Firezone implementan una máquina de estado de referencia y realizan pruebas comparándola con el estado real de
connlib
Casos límite y fallos de IO
- El diseño sans-IO separa la implementación del protocolo de los efectos secundarios reales de IO, lo que facilita manejar casos límite y errores
Rust + sans-IO: ¿una combinación perfecta?
- Rust modela explícitamente la propiedad y la mutabilidad, por lo que encaja muy bien con el diseño sans-IO
- El diseño sans-IO usa libremente
&mutpara expresar cambios de estado y, a diferencia de Rustasync, solo utiliza APIs síncronas
Desventajas
- Escribir el bucle de eventos manualmente puede introducir bugs sutiles
- Los flujos de trabajo secuenciales pueden requerir más código
- El diseño sans-IO todavía no está muy extendido en la comunidad de Rust
Cierre
- El código sans-IO al principio puede parecer poco familiar, pero una vez que uno se acostumbra resulta muy disfrutable
- Rust ofrece herramientas excelentes para modelar máquinas de estado
- El diseño sans-IO fuerza a tratar el manejo de errores como parte del procesamiento de entradas, y eso se siente como la forma correcta de escribir código de networking
Opinión de GN⁺
- El diseño sans-IO encaja bien con el modelo de propiedad de Rust, por lo que es muy adecuado para implementar protocolos de red
- Escribir el bucle de eventos directamente mejora la flexibilidad del código y facilita su mantenimiento
- La facilidad para hacer pruebas ayuda mucho a escribir código confiable
- Sin embargo, como todavía no está muy extendido en la comunidad de Rust, puede haber escasez de bibliotecas relacionadas
- Al adoptar una tecnología nueva, conviene considerar la curva de aprendizaje y el soporte de la comunidad
1 comentarios
Comentarios de Hacker News
Antes de la introducción de la sintaxis async/await en Rust, se implementaban máquinas de estado manualmente
Al escribir una biblioteca VT100, se dio cuenta de un problema con el patrón de encapsulación de Rust
Comparado con un diseño que transmite datos usando canales
En el ecosistema de Haskell existe la idea de separar la lógica de la ejecución
tokio::select!Las funciones async de Rust se compilan como máquinas de estado
Si se expone el estado, una función async podría volverse "pura"
Firezone es una herramienta sorprendente
Sería bueno que el compilador pudiera convertir automáticamente código async a sans-IO
Después de leer el artículo y los comentarios, parece que se reinventó el estilo de arquitectura hexagonal o de puertos/adaptadores
Hay curiosidad por saber si el tráfico real pasa por el gateway, o si solo se usa para el establecimiento de la conexión