Retour à l'accueil

Relais Go TUN UDP : config YAML et code

L'article décrit l'implémentation d'un relais UDP en Go avec support des interfaces TUN via configuration YAML. Le protocole avec hachage MD5 et horodatage est détaillé, des benchmarks iperf3 à 925 Mbit/s sont fournis. Convient aux développeurs de logiciels réseau middle/senior.

Création d'un relais UDP en Go avec interfaces TUN
Advertisement 728x90

Go et TUN/TAP : Créer un relais UDP avec configuration YAML

Les développeurs Go peuvent créer des relais réseau fonctionnant avec les interfaces TUN et les tunnels UDP. L'architecture repose sur une configuration YAML pour définir des chaînes d'entrée et de sortie. Chaque relais gère le trafic bidirectionnel — entre TUN et UDP, puis inversement — permettant des routages complexes sans codage dur de la logique.

Au cœur de l'implémentation, on utilise io.ReadWriteCloser pour tous les nœuds. La logique est simple : lire les paquets à l'entrée, les acheminer vers la sortie, puis traiter le flux inverse.

Structure de configuration YAML

La configuration définit les relais sous forme de tableaux de paires entrée/sortie. Les paramètres incluent le type d'interface, l'adressage IP, les règles NAT et l'authentification.

Google AdInline article slot

Exemple : TUN vers 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"

Pour un relais UDP, ajoutez les points de terminaison client et serveur :

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"

Cela crée une chaîne : tun10 → client UDP → serveur UDP → tun11. Le trafic reste local.

Google AdInline article slot

Protocole de tunnel UDP

Un en-tête est ajouté à chaque paquet IP : 4 octets pour l'horodatage (uint32 BigEndian) + 16 octets pour le hachage MD5. Ce dernier est calculé à partir du mot de passe + horodatage + les 64 premiers octets du paquet.

À la réception, le système vérifie :

  • La taille du paquet ≥ TailleEnTête (20 octets)
  • L'horodatage dans une fenêtre de 10 secondes par rapport à l'heure actuelle
  • Le hachage correspond

Code de dépaquetage :

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
}

Le paquetage est symétrique : calcHash + binary.BigEndian + envoi.

Tests de performance

Tests locaux avec iperf3 sur tun10-tun11 :

Serveur :

iperf3 -s -B 10.0.1.2

Client :

iperf3 -c 10.0.1.2 -B 10.0.0.2

Résultats (10 secondes) :

| Intervalle | Transfert | Débit |

|----------|----------|---------|

| 0-1s | 113 MB | 941 Mbit/s |

| 1-2s | 109 MB | 921 Mbit/s |

| ... | ... | ... |

| Total | 1.08 GB | 925 Mbit/s (expéditeur) |

~922 Mbit/s au récepteur. CPU : relais ~250 % sur M1, iperf3 ~10–30 %. Des optimisations sont possibles via regroupement, copie zéro ou hachage SIMD.

Métriques clés pour l’analyse :

  • Débit : 925 Mbit/s en moyenne
  • Charge CPU : usage élevé sur un seul thread
  • Latence : fenêtre d’horodatage de 10 secondes (suffisante pour les tests locaux)
  • Fiabilité : hachage + horodatage empêchent les attaques par relecture et injection

Points clés

  • La configuration YAML permet de mélanger TUN/UDP sans recompilation
  • Le protocole est minimal : surcharge de 20 octets, MD5 + horodatage
  • Performance ~900+ Mbit/s localement, mais limitée par le CPU
  • Extensible via io.ReadWriteCloser : ajout facile de WireGuard, QUIC
  • Les règles NAT dans la config simplifient les configurations pair à pair

— Editorial Team

Advertisement 728x90

Lire ensuite