Go y TUN/TAP: Creando un retransmisor UDP con configuración YAML
Los desarrolladores de Go pueden crear retransmisores de red que funcionan con interfaces TUN y túneles UDP. La arquitectura utiliza una configuración YAML para definir cadenas de entrada-salida. Cada retransmisor maneja tráfico bidireccional—entre TUN y UDP, y viceversa—permitiendo rutas complejas sin codificar lógica directamente.
En esencia, la implementación usa io.ReadWriteCloser en todos los nodos. La lógica es sencilla: leer paquetes desde la entrada, reenviarlos a la salida, y procesar el flujo inverso.
Estructura de configuración YAML
La configuración define retransmisores como una matriz de pares entrada-salida. Los parámetros incluyen tipo de interfaz, direccionamiento IP, reglas NAT y autenticación.
Ejemplo: TUN a TUN
relays:
- ingress:
type: tun
name: tun10
cidr: "10.0.0.2/24"
peer: "10.0.0.1"
egress:
type: tun
name: tun11
cidr: "10.0.1.2/24"
peer: "10.0.1.1"
nat:
forward:
src: "10.0.1.1"
backward:
dst: "10.0.0.2"
Para un retransmisor UDP, añade puntos finales cliente y servidor:
relays:
- ingress:
type: tun
name: tun10
cidr: "10.0.0.2/24"
peer: "10.0.0.1"
egress:
type: udp
dial: "localhost:4000"
password: "pass"
- ingress:
type: udp
listen: "localhost:4000"
password: "pass"
egress:
type: tun
name: tun11
cidr: "10.0.1.2/24"
peer: "10.0.1.1"
nat:
forward:
src: "10.0.1.1"
backward:
dst: "10.0.0.2"
Esto crea una cadena: tun10 → cliente UDP → servidor UDP → tun11. El tráfico permanece local.
Protocolo de túnel UDP
Se añade un encabezado a cada paquete IP: 4 bytes de marca de tiempo (uint32 BigEndian) + 16 bytes de hash MD5. El hash se calcula a partir de contraseña + marca de tiempo + los primeros 64 bytes del paquete.
Al recibir, el sistema verifica:
- Tamaño del paquete ≥ HeaderSize (20 bytes)
- Marca de tiempo dentro de los 10 segundos actuales
- Hash coincide
Código de desempaquetado:
func (i *Ingress) Read(b []byte) (int, error) {
n, raddr, err := i.conn.ReadFrom(b)
if err != nil {
return 0, err
}
data, err := unpack(b[:n:n], i.pass)
if err != nil {
return 0, err
}
i.raddr = raddr
copy(b, data)
return len(data), nil
}
func unpack(packet []byte, pass string) ([]byte, error) {
if len(packet) < HeaderSize {
return nil, ErrSmallPacket
}
rtimestamp := binary.BigEndian.Uint32(packet[0:4])
rhash := [HashSize]byte(packet[4 : 4+HashSize])
payload := packet[HeaderSize:]
timestamp := uint32(time.Now().Unix())
if timestamp-rtimestamp > MaxTimeDiff && rtimestamp-timestamp > MaxTimeDiff {
return nil, ErrStalePacket
}
hash, err := calcHash(pass, payload, rtimestamp)
if err != nil {
return nil, fmt.Errorf("calc hash: %w", err)
}
if rhash != hash {
return nil, ErrWrongPass
}
return payload, nil
}
El empaquetado es simétrico: calcHash + binary.BigEndian + envío.
Pruebas de rendimiento
Pruebas locales usando iperf3 entre tun10 y tun11:
Servidor:
iperf3 -s -B 10.0.1.2
Cliente:
iperf3 -c 10.0.1.2 -B 10.0.0.2
Resultados (10 segundos):
| Intervalo | Transferencia | Tasa de bits |
|----------|----------|---------|
| 0-1s | 113 MB | 941 Mbit/s |
| 1-2s | 109 MB | 921 Mbit/s |
| ... | ... | ... |
| Total | 1.08 GB | 925 Mbit/s (emisor) |
~922 Mbit/s en receptor. CPU: retransmisor ~250% en M1, iperf3 ~10–30%. Posibles optimizaciones mediante agrupamiento, cero copia o hashing SIMD.
Métricas clave para análisis:
- Rendimiento: 925 Mbit/s promedio
- Carga de CPU: Uso elevado en hilo único
- Latencia: ventana de marca de tiempo de 10 segundos (suficiente para pruebas locales)
- Fiabilidad: hash + marca de tiempo previenen ataques de reproducción e inyección
Conclusiones principales
- La configuración YAML permite mezclar TUN/UDP sin recompilar
- El protocolo es mínimo: sobrecarga de 20 bytes, MD5 + marca de tiempo
- Rendimiento ~900+ Mbit/s localmente, pero limitado por CPU
- Extensible mediante
io.ReadWriteCloser: fácil agregar WireGuard, QUIC - Reglas NAT en la configuración simplifican configuraciones punto a punto
— Editorial Team
Aún no hay comentarios.