mirror of
https://git.zx2c4.com/wireguard-go
synced 2024-11-15 01:05:15 +01:00
[WIP] device: run testns.sh from a Go test harness
objectives: - to make it easy to run these tests during go development (TODO: running sudo go test is weird, is there some alternative? does docker provide a namespace hole for normal users if it's installed?) - to make it easy to run some part of the script, e.g. just run the sticky sockets test without waiting on the prior iperf tests not clear yet if the shell-inside-go is worth it Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
parent
da9d300cf8
commit
c9159a5b86
292
device/ns_test.go
Normal file
292
device/ns_test.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2020 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test the following topology:
|
||||||
|
//
|
||||||
|
// ┌─────────────────────┐ ┌──────────────────────────────────┐ ┌─────────────────────┐
|
||||||
|
// │ $netns1 namespace │ │ $netns0 namespace │ │ $netns2 namespace │
|
||||||
|
// │ │ │ │ │ │
|
||||||
|
// │┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐│
|
||||||
|
// ││ $wg1 │───────────┼───┼────────────│ lo │────────────┼───┼───────────│ $wg2 ││
|
||||||
|
// │├────────┴──────────┐│ │ ┌───────┴────────┴────────┐ │ │┌──────────┴────────┤│
|
||||||
|
// ││192.168.241.1/24 ││ │ │(ns1) (ns2) │ │ ││192.168.241.2/24 ││
|
||||||
|
// ││fd00::1/24 ││ │ │127.0.0.1:1 127.0.0.1:2│ │ ││fd00::2/24 ││
|
||||||
|
// │└───────────────────┘│ │ │[::]:1 [::]:2 │ │ │└───────────────────┘│
|
||||||
|
// └─────────────────────┘ │ └─────────────────────────┘ │ └─────────────────────┘
|
||||||
|
// └──────────────────────────────────┘
|
||||||
|
//
|
||||||
|
// Note: $netns0 is the endpoint for the wg1 interfaces in $netns1 and $netns2.
|
||||||
|
// See https://www.wireguard.com/netns/ for further details.
|
||||||
|
func TestNS(t *testing.T) {
|
||||||
|
checkRootOnLinux(t)
|
||||||
|
|
||||||
|
mustsh := func(t *testing.T, cmd string, arg, stdin string) string {
|
||||||
|
t.Helper()
|
||||||
|
sh := exec.Command(cmd, arg)
|
||||||
|
if stdin != "" {
|
||||||
|
sh.Stdin = strings.NewReader(stdin)
|
||||||
|
}
|
||||||
|
out, err := sh.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s %s: %v", cmd, arg, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
netns := func(num int) string {
|
||||||
|
return fmt.Sprintf("wg-test-%d-%d", os.Getpid(), num)
|
||||||
|
}
|
||||||
|
wg := func(num int) string {
|
||||||
|
return fmt.Sprintf("wg%d%d", 1, num)
|
||||||
|
}
|
||||||
|
key1 := mustsh(t, "wg", "genkey", "")
|
||||||
|
key2 := mustsh(t, "wg", "genkey", "")
|
||||||
|
script := scriptScope{
|
||||||
|
vars: map[string]string{
|
||||||
|
"netns0": netns(0),
|
||||||
|
"netns1": netns(1),
|
||||||
|
"netns2": netns(2),
|
||||||
|
"wg1": wg(1),
|
||||||
|
"wg2": wg(2),
|
||||||
|
"key1": key1,
|
||||||
|
"key2": key2,
|
||||||
|
"pub1": mustsh(t, "wg", "pubkey", key1),
|
||||||
|
"pub2": mustsh(t, "wg", "pubkey", key2),
|
||||||
|
"psk": mustsh(t, "wg", "genpsk", ""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: orig_message_cost
|
||||||
|
|
||||||
|
script.run(t, "setup namespace", `
|
||||||
|
ip netns del $netns0 2>/dev/null || true
|
||||||
|
ip netns del $netns1 2>/dev/null || true
|
||||||
|
ip netns del $netns2 2>/dev/null || true
|
||||||
|
ip netns add $netns0
|
||||||
|
ip netns add $netns1
|
||||||
|
ip netns add $netns2
|
||||||
|
ip0 link set up dev lo
|
||||||
|
`)
|
||||||
|
|
||||||
|
wg1cmd := startWG(t, netns(0), "$wg1", wg(1))
|
||||||
|
defer wg1cmd.Process.Kill()
|
||||||
|
script.run(t, "setup $wg1", `ip0 link set $wg1 netns $netns1`)
|
||||||
|
|
||||||
|
wg2cmd := startWG(t, netns(0), "$wg2", wg(2))
|
||||||
|
defer wg2cmd.Process.Kill()
|
||||||
|
script.run(t, "setup $wg2", `ip0 link set $wg2 netns $netns2`)
|
||||||
|
|
||||||
|
script.run(t, "configure", `
|
||||||
|
ip1 addr add 192.168.241.1/24 dev $wg1
|
||||||
|
ip1 addr add fd00::1/24 dev $wg1
|
||||||
|
|
||||||
|
ip2 addr add 192.168.241.2/24 dev $wg2
|
||||||
|
ip2 addr add fd00::2/24 dev $wg2
|
||||||
|
|
||||||
|
n0 wg set $wg1 \
|
||||||
|
private-key <(echo "$key1") \
|
||||||
|
listen-port 10000 \
|
||||||
|
peer "$pub2" \
|
||||||
|
preshared-key <(echo "$psk") \
|
||||||
|
allowed-ips 192.168.241.2/32,fd00::2/128
|
||||||
|
n0 wg set $wg2 \
|
||||||
|
private-key <(echo "$key2") \
|
||||||
|
listen-port 20000 \
|
||||||
|
peer "$pub1" \
|
||||||
|
preshared-key <(echo "$psk") \
|
||||||
|
allowed-ips 192.168.241.1/32,fd00::1/128
|
||||||
|
|
||||||
|
ip1 link set up dev $wg1
|
||||||
|
ip2 link set up dev $wg2
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test using IPv4 as outer transport
|
||||||
|
#n0 wg set $wg1 peer "$pub2" endpoint 127.0.0.1:20000
|
||||||
|
n0 wg set $wg2 peer "$pub1" endpoint 127.0.0.1:10000
|
||||||
|
|
||||||
|
n0 wg showconf $wg1
|
||||||
|
n0 wg showconf $wg2
|
||||||
|
`)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// TODO: counter test
|
||||||
|
|
||||||
|
script.run(t, "ping test", `
|
||||||
|
# Ping over IPv4
|
||||||
|
n2 ping -c 10 -f -W 1 192.168.241.1
|
||||||
|
n1 ping -c 10 -f -W 1 192.168.241.2
|
||||||
|
|
||||||
|
# Ping over IPv6
|
||||||
|
n2 ping6 -c 10 -f -W 1 fd00::1
|
||||||
|
n1 ping6 -c 10 -f -W 1 fd00::2
|
||||||
|
`)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// TestSticky tests sticky sockets work.
|
||||||
|
//
|
||||||
|
// We start with this topology:
|
||||||
|
//
|
||||||
|
// ┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
|
||||||
|
// │ $netns1 namespace │ │ $netns2 namespace │
|
||||||
|
// │ │ │ │
|
||||||
|
// │ ┌──────┐ ┌─────┐ │ │ ┌─────┐ ┌──────┐ │
|
||||||
|
// │ │ $wg1 |────────────│veth1│───────────┼────┼──│veth2│────────────│ $wg2 │ │
|
||||||
|
// │ ├──────┴─────────┐ ├─────┴──────────┐│ │ ├─────┴──────────┐ ├──────┴─────────┐ │
|
||||||
|
// │ │192.168.241.1/24│ │10.0.0.1/24 ││ │ │10.0.0.2/24 │ │192.168.241.2/24│ │
|
||||||
|
// │ │fd00::1/24 │ │fd00:aa::1/96 ││ │ │fd00:aa::2/96 │ │fd00::2/24 │ │
|
||||||
|
// │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └────────────────┘ │
|
||||||
|
// └────────────────────────────────────────┘ └────────────────────────────────────────┘
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// startWG starts wireguard-go by forking the test process and executing
|
||||||
|
// TestChild with a magic env variable. We do this instead of starting
|
||||||
|
// wireguard directly in the process because we want to run under a
|
||||||
|
// linux namespace.
|
||||||
|
//
|
||||||
|
// We cannot use setns(2) for this, because the syscall is per-thread.
|
||||||
|
// The initial tun binding works, but then device.NewDevice creates
|
||||||
|
// goroutines which run on other OS threads where setns(2) wasn't applied.
|
||||||
|
func startWG(t *testing.T, netns, dispname, name string) *exec.Cmd {
|
||||||
|
t.Helper()
|
||||||
|
cmd := exec.Command("ip", "netns", "exec", netns, os.Args[0], "-test.run=TestChild$")
|
||||||
|
cmd.Env = append([]string{
|
||||||
|
"TEST_WG_CHILD=" + name,
|
||||||
|
}, os.Environ()...)
|
||||||
|
cmd.Stdout = logWriter{logf: func(format string, args ...interface{}) {
|
||||||
|
t.Logf(dispname+": "+format, args...)
|
||||||
|
}}
|
||||||
|
cmd.Stderr = cmd.Stdout
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second) // eww TODO
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChild is a fake test. See startWG for details.
|
||||||
|
func TestChild(t *testing.T) {
|
||||||
|
name := os.Getenv("TEST_WG_CHILD")
|
||||||
|
if name == "" {
|
||||||
|
t.Skip("TestChild is a fake test used to start the tun $TEST_WG_CHILD, skipping")
|
||||||
|
}
|
||||||
|
log.Printf("starting wireguard-go on tun %s", name)
|
||||||
|
|
||||||
|
tun, err := tun.CreateTUN(name, device.DefaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
d := device.NewDevice(tun, device.NewLogger(device.LogLevelInfo, "")) // TODO: LogLevelDebug option
|
||||||
|
|
||||||
|
fileUAPI, err := ipc.UAPIOpen(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
uapi, err := ipc.UAPIListen(name, fileUAPI)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
conn, err := uapi.Accept()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go d.IpcHandle(conn)
|
||||||
|
}
|
||||||
|
// TODO: listen for a user signal to know shutdown is clean
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type scriptScope struct {
|
||||||
|
vars map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s scriptScope) run(t *testing.T, name, script string) {
|
||||||
|
const header = `#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export WG_HIDE_KEYS=never
|
||||||
|
|
||||||
|
n0() { ip netns exec $netns0 "$@"; }
|
||||||
|
n1() { ip netns exec $netns1 "$@"; }
|
||||||
|
n2() { ip netns exec $netns2 "$@"; }
|
||||||
|
ip0() { ip -n $netns0 "$@"; }
|
||||||
|
ip1() { ip -n $netns1 "$@"; }
|
||||||
|
ip2() { ip -n $netns2 "$@"; }
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.WriteString(header)
|
||||||
|
var varNames []string
|
||||||
|
for name := range s.vars {
|
||||||
|
varNames = append(varNames, name)
|
||||||
|
}
|
||||||
|
sort.Strings(varNames)
|
||||||
|
for _, name := range varNames {
|
||||||
|
fmt.Fprintf(buf, "%s=%q\n", name, s.vars[name])
|
||||||
|
}
|
||||||
|
buf.WriteString("set -x\n")
|
||||||
|
buf.WriteString(script)
|
||||||
|
|
||||||
|
sh := exec.Command("/bin/bash")
|
||||||
|
sh.Stdin = buf
|
||||||
|
sh.Stdout = logWriter{logf: t.Logf}
|
||||||
|
sh.Stderr = logWriter{logf: t.Logf}
|
||||||
|
if err := sh.Run(); err != nil {
|
||||||
|
t.Fatalf("%s failed: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRootOnLinux(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
t.Skip("SKIPPING test, requires GOOS=linux")
|
||||||
|
}
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
t.Skip("SKIPPING test, requires root")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logger(name string, logf func(format string, args ...interface{})) *device.Logger {
|
||||||
|
w := logWriter{logf: logf}
|
||||||
|
return &device.Logger{
|
||||||
|
Debug: log.New(ioutil.Discard, "DEBUG("+name+"): ", 0),
|
||||||
|
Info: log.New(w, "INFO("+name+"): ", 0),
|
||||||
|
Error: log.New(w, "ERROR("+name+"): ", 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logWriter struct {
|
||||||
|
logf func(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw logWriter) Write(b []byte) (int, error) {
|
||||||
|
lw.logf("%s", b)
|
||||||
|
return len(b), nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user