model, tunnel, wg, authtoken, netguard, docker and dotenv, split out of the original single repository so Hammerhead, Blacktip and Bamboo can live in their own repos and import them. README extended; LICENSE unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
package wg
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func freeUDP(t *testing.T) int {
|
|
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pc.Close()
|
|
return pc.LocalAddr().(*net.UDPAddr).Port
|
|
}
|
|
|
|
func TestTunnelCarriesTCPAndUDP(t *testing.T) {
|
|
ka, _ := GenerateKey()
|
|
kb, _ := GenerateKey()
|
|
pa, _ := PublicKey(ka)
|
|
pb, _ := PublicKey(kb)
|
|
portA := freeUDP(t)
|
|
|
|
a, err := New(ka, netip.MustParseAddr("100.89.0.1"), portA) // the "edge": has a fixed port
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer a.Close()
|
|
b, err := New(kb, netip.MustParseAddr("100.89.0.2"), 0) // the "site": behind NAT, dials out
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer b.Close()
|
|
if err := a.SetPeers([]Peer{{PublicKey: pb, AllowedIP: "100.89.0.2"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := b.SetPeers([]Peer{{PublicKey: pa, AllowedIP: "100.89.0.1", Endpoint: net.JoinHostPort("127.0.0.1", strconv.Itoa(portA))}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// TCP: site listens inside the tunnel, edge dials it
|
|
ln, err := b.ListenTCP(9111)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func() { defer c.Close(); io.Copy(c, c) }()
|
|
}
|
|
}()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
c, err := a.DialContext(ctx, "tcp", "100.89.0.2:9111")
|
|
if err != nil {
|
|
t.Fatalf("dial through tunnel: %v", err)
|
|
}
|
|
defer c.Close()
|
|
c.Write([]byte("hello"))
|
|
buf := make([]byte, 5)
|
|
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
if _, err := io.ReadFull(c, buf); err != nil || string(buf) != "hello" {
|
|
t.Fatalf("echo: %q %v", buf, err)
|
|
}
|
|
|
|
// UDP both ways
|
|
pc, err := b.ListenUDP(9112)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pc.Close()
|
|
go func() {
|
|
p := make([]byte, 2048)
|
|
for {
|
|
n, from, err := pc.ReadFrom(p)
|
|
if err != nil {
|
|
return
|
|
}
|
|
pc.WriteTo(append([]byte("re:"), p[:n]...), from)
|
|
}
|
|
}()
|
|
u, err := a.DialContext(ctx, "udp", "100.89.0.2:9112")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer u.Close()
|
|
u.Write([]byte("ping"))
|
|
u.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
rb := make([]byte, 64)
|
|
n, err := u.Read(rb)
|
|
if err != nil || string(rb[:n]) != "re:ping" {
|
|
t.Fatalf("udp: %q %v", rb[:n], err)
|
|
}
|
|
|
|
// the site shows a recent handshake with the edge
|
|
if hs := b.Handshakes(); time.Since(hs[pa]) > time.Minute {
|
|
t.Errorf("no handshake recorded: %v", hs)
|
|
}
|
|
|
|
// a second peer can be added and removed without disturbing the first
|
|
kc, _ := GenerateKey()
|
|
pcKey, _ := PublicKey(kc)
|
|
if err := a.SetPeers([]Peer{{PublicKey: pb, AllowedIP: "100.89.0.2"}, {PublicKey: pcKey, AllowedIP: "100.89.0.3"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Write([]byte("again"))
|
|
buf2 := make([]byte, 5)
|
|
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
if _, err := io.ReadFull(c, buf2); err != nil || string(buf2) != "again" {
|
|
t.Fatalf("existing connection broke when a peer was added: %q %v", buf2, err)
|
|
}
|
|
if err := a.SetPeers([]Peer{{PublicKey: pb, AllowedIP: "100.89.0.2"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|