Zpět na domů

Go TUN UDP relay: YAML konfigurace a kód

Článek popisuje implementaci UDP-relay na Go s podporou TUN rozhraní prostřednictvím YAML konfigurace. Detailně je popsán protokol s MD5 hašem a timestamp, uvedeny jsou benchmarky iperf3 na 925 Mbit/s. Vhodný pro middle/senior vývojářů síťového softwaru.

Vytváříme UDP-relay na Go s TUN rozhraními
Advertisement 728x90

Go a TUN/TAP: UDP-relay s konfigurací ve formátu YAML

Vývojáři v Go mohou vytvořit síťový relay pro práci s TUN rozhraními a UDP tunely. Architektura využívá konfiguraci ve formátu YAML k definici řetězce ingress-egress. Každý relay zpracovává dvousměrný tok: od TUN k UDP a zpět. To umožňuje vytvářet složité trasy bez pevného přidružení k kódu.

Základem je implementace io.ReadWriteCloser pro všechny uzly. Logika je jednoduchá: čtení paketů z ingress, předání na egress, zpracování v opačném směru.

Struktura YAML-konfigurace

Konfigurace definuje relays jako pole párů ingress/egress. Parametry zahrnují typ rozhraní, IP adresaci, pravidla NAT a ověření přístupu.

Google AdInline article slot

Příklad TUN-to-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"

Pro UDP-relay se přidají klient a server:

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"

Vznikne tak řetězec tun10 → UDP-klient → UDP-server → tun11. Doprava zůstává na localhostu.

Google AdInline article slot

Protokol UDP-tunelu

K IP-paketu se přidá hlavička: 4 bajty timestamp (uint32 BigEndian) + 16 bajtů MD5-haš. Haš se počítá z hesla + timestamp + prvních 64 bajtů paketu.

Při přijetí se ověří:

  • Velikost paketu ≥ HeaderSize (20 bajtů)
  • Timestamp je v rámci 10 sekund od aktuálního času
  • Správnost haše

Kód rozbalení:

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
}

Balení je symetrické: calcHash + binary.BigEndian + odeslání.

Testování výkonu

Místní testy s iperf3 přes tun10-tun11:

Server:

iperf3 -s -B 10.0.1.2

Klient:

iperf3 -c 10.0.1.2 -B 10.0.0.2

Výsledky (10 sekund):

| Interval | Transfer | Bitrate |

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

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

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

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

| Celkem | 1,08 GB | 925 Mbit/s (odesílatel) |

~922 Mbit/s na přijímači. CPU: relay ~250% na M1, iperf3 ~10–30%. Optimalizace je možná prostřednictvím batching, zero-copy nebo SIMD-hashování.

Klíčové metriky pro analýzu:

  • Propustnost: průměrně 925 Mbit/s
  • Nároky na CPU: vysoká zátěž jednoho vlákna
  • Zpoždění: okno timestampu 10 s (dostatečné pro místní testy)
  • Spolehlivost: haš + čas chrání proti replay/injection

Co je důležité

  • Konfigurace ve formátu YAML umožňuje kombinovat TUN/UDP bez překompilace
  • Protokol je minimalistický: 20 bajtů nadbytečných dat, MD5 + timestamp
  • Výkon ~900+ Mbit/s lokálně, ale omezený výkonem CPU
  • Rozšiřitelnost prostřednictvím io.ReadWriteCloser: snadné přidání WireGuard, QUIC
  • Pravidla NAT v konfiguraci zjednodušují komunikaci mezi uzly

— Editorial Team

Advertisement 728x90

Číst dál