Volver al inicio

Relay Go TUN UDP: Configuración YAML y código

El artículo describe la implementación de relay UDP en Go con soporte para interfaces TUN vía configuración YAML. Se detalla el protocolo con hash MD5 y timestamp, se proporcionan benchmarks iperf3 a 925 Mbit/s. Adecuado para desarrolladores de software de red middle/senior.

Creando relay UDP en Go con interfaces TUN
Advertisement 728x90

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.

Google AdInline article slot

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.

Google AdInline article slot

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:

Google AdInline article slot
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

Advertisement 728x90

Leer después