Zurück zur Startseite

Go TUN UDP-Relay: YAML-Konfig und Code

Der Artikel beschreibt die Implementierung eines UDP-Relays in Go mit Unterstützung für TUN-Schnittstellen über YAML-Konfiguration. Das Protokoll mit MD5-Hash und Timestamp wird detailliert, iperf3-Benchmarks bei 925 Mbit/s werden bereitgestellt. Geeignet für mittlere/senior Netzwerk-Softwareentwickler.

UDP-Relay in Go mit TUN-Schnittstellen erstellen
Advertisement 728x90

Go und TUN/TAP: UDP-Relais mit YAML-Konfiguration erstellen

Go-Entwickler können Netzwerk-Relais erstellen, die mit TUN-Schnittstellen und UDP-Tunneln arbeiten. Die Architektur nutzt eine YAML-Konfiguration, um Eingangs- und Ausgangsketten zu definieren. Jeder Relay verarbeitet bidirektionale Datenströme – zwischen TUN und UDP und zurück – und ermöglicht komplexe Routing-Logik, ohne dass diese im Code fest verankert sein muss.

Im Kern verwendet die Implementierung io.ReadWriteCloser für alle Knoten. Die Logik ist einfach: Pakete vom Eingang lesen, an den Ausgang weiterleiten und den Rückfluss verarbeiten.

YAML-Konfigurationsstruktur

Die Konfiguration definiert Relays als Array von Eingangs-/Ausgangspaaren. Parameter umfassen Schnittstellentyp, IP-Adressierung, NAT-Regeln und Authentifizierung.

Google AdInline article slot

Beispiel: TUN-zu-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"

Für einen UDP-Relay fügen Sie Client- und Serverendpunkte hinzu:

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"

Dies erzeugt eine Kette: tun10 → UDP-Client → UDP-Server → tun11. Der Datenverkehr bleibt lokal.

Google AdInline article slot

UDP-Tunnel-Protokoll

Jedes IP-Paket erhält einen Header: 4 Bytes Zeitstempel (uint32 BigEndian) + 16 Bytes MD5-Hash. Der Hash wird aus Passwort + Zeitstempel + ersten 64 Bytes des Pakets berechnet.

Bei der Aufnahme prüft das System:

  • Paketgröße ≥ HeaderSize (20 Bytes)
  • Zeitstempel liegt innerhalb von 10 Sekunden der aktuellen Uhrzeit
  • Hash stimmt überein

Entpackungscode:

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
}

Das Verpacken ist symmetrisch: calcHash + binary.BigEndian + Senden.

Leistungstest

Lokale Tests mit iperf3 über tun10-tun11:

Server:

iperf3 -s -B 10.0.1.2

Client:

iperf3 -c 10.0.1.2 -B 10.0.0.2

Ergebnisse (10 Sekunden):

| Intervall | Übertragung | Bitrate |

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

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

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

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

| Gesamt | 1,08 GB | 925 Mbit/s (Sender) |

~922 Mbit/s am Empfänger. CPU: Relay ~250% auf M1, iperf3 ~10–30%. Optimierungen sind möglich durch Batching, Zero-Copy oder SIMD-Hashing.

Wichtige Metriken für die Analyse:

  • Durchsatz: Durchschnittlich 925 Mbit/s
  • CPU-Auslastung: Hoher Einzelthread-Verbrauch
  • Latenz: 10-Sekunden-Zeitfenster (ausreichend für lokale Tests)
  • Zuverlässigkeit: Hash + Zeitstempel schützen vor Replay- und Injection-Angriffen

Wichtige Erkenntnisse

  • YAML-Konfiguration ermöglicht das Mischen von TUN/UDP ohne Neukompilierung
  • Protokoll ist minimal: 20-Byte-Overhead, MD5 + Zeitstempel
  • Leistung ~900+ Mbit/s lokal, aber CPU-begrenzt
  • Erweiterbar via io.ReadWriteCloser: einfach WireGuard, QUIC hinzufügen
  • NAT-Regeln in der Konfiguration vereinfachen Peer-to-Peer-Setups

— Editorial Team

Advertisement 728x90

Weiterlesen