package vpn import ( "strings" "testing" ) func tunnel(addrs []string, peers ...string) Tunnel { t := Tunnel{Name: "wg0", PublicKey: "SERVERKEY=", Addrs: addrs} for _, p := range peers { t.Peers = append(t.Peers, Peer{PublicKey: p + "KEY=", AllowedIPs: []string{p}}) } return t } func TestNextAddress(t *testing.T) { // .1 is the interface itself and .2 and .3 are taken, so the next free one is .4 got, err := NextAddress(tunnel([]string{"172.21.46.1/24"}, "172.21.46.3/32", "172.21.46.2/32")) if err != nil || got != "172.21.46.4" { t.Errorf("NextAddress = %q, %v; want 172.21.46.4", got, err) } // a gap is filled before the end is reached got, _ = NextAddress(tunnel([]string{"172.21.46.1/24"}, "172.21.46.2/32", "172.21.46.4/32")) if got != "172.21.46.3" { t.Errorf("NextAddress = %q, want the gap at .3", got) } // an empty tunnel starts after the server got, _ = NextAddress(tunnel([]string{"10.9.0.1/24"})) if got != "10.9.0.2" { t.Errorf("NextAddress = %q, want 10.9.0.2", got) } // a full one says so rather than handing out a duplicate full := tunnel([]string{"10.9.0.1/30"}, "10.9.0.2/32") if _, err := NextAddress(full); err != ErrNoRoom { t.Errorf("err = %v, want ErrNoRoom on a full range", err) } // IPv6-only, or nothing at all, is refused rather than guessed if _, err := NextAddress(tunnel([]string{"fc00::1/64"})); err == nil { t.Error("want an error when there is no IPv4 range") } } func TestSlug(t *testing.T) { for in, want := range map[string]string{ "ip12": "ip12", "Mitchell's iPad": "mitchells_ipad", "my-phone": "my_phone", " spaced out ": "spaced__out", "!!!": "peer", strings.Repeat("a", 40): strings.Repeat("a", 24), } { if got := slug(in); got != want { t.Errorf("slug(%q) = %q, want %q", in, got, want) } } } func TestProfile(t *testing.T) { tun := tunnel([]string{"172.21.46.1/24"}) got := profile("PRIV=", "PSK=", tun, "172.21.46.4", "home.example.com:51820", "1.1.1.1, 1.0.0.1", false) for _, want := range []string{ "PrivateKey = PRIV=", "Address = 172.21.46.4/24", // the interface's own prefix, not a bare /32 "DNS = 1.1.1.1, 1.0.0.1", "PublicKey = SERVERKEY=", "PresharedKey = PSK=", "Endpoint = home.example.com:51820", "AllowedIPs = 0.0.0.0/0", } { if !strings.Contains(got, want) { t.Errorf("profile is missing %q:\n%s", want, got) } } // no DNS line at all when none was asked for, rather than an empty one if strings.Contains(profile("P=", "", tun, "172.21.46.4", "h:1", "", false), "DNS") { t.Error("an empty DNS should leave the line out") } // split routes the private ranges instead of everything split := profile("P=", "", tun, "172.21.46.4", "h:1", "", true) if strings.Contains(split, "0.0.0.0/0") { t.Errorf("a split tunnel must not route everything:\n%s", split) } if !strings.Contains(split, "172.21.46.0/24") { t.Errorf("a split tunnel should include the tunnel's own range:\n%s", split) } } func TestCheckEndpoint(t *testing.T) { for _, ok := range []string{"bigham.dev:51820", "1.2.3.4:51820", "[2001:db8::1]:51820"} { if _, err := checkEndpoint(ok); err != nil { t.Errorf("checkEndpoint(%q) = %v, want it accepted", ok, err) } } for _, bad := range []string{"", "bigham.dev", ":51820", "bigham.dev:0", "bigham.dev:notaport", "bigham.dev:99999"} { if _, err := checkEndpoint(bad); err == nil { t.Errorf("checkEndpoint(%q) was accepted, want refused", bad) } } } func TestHostOf(t *testing.T) { for in, want := range map[string]string{"172.21.46.4": "172.21.46.4", "172.21.46.4/32": "172.21.46.4", " 10.0.0.9/24 ": "10.0.0.9"} { got, err := hostOf(in) if err != nil || got != want { t.Errorf("hostOf(%q) = %q, %v; want %q", in, got, err, want) } } if _, err := hostOf("not-an-address"); err == nil { t.Error("hostOf accepted nonsense") } } // The name is what becomes a configuration section, so it is the input worth being strict // about; everything refused here is refused before anything is written. func TestNameRe(t *testing.T) { for _, ok := range []string{"ip12", "My Phone", "a", "work-laptop.2"} { if !NameRe.MatchString(ok) { t.Errorf("%q should be a valid name", ok) } } for _, bad := range []string{"", " leading", "semi;colon", "quote'd", "back`tick", "new\nline", "$(whoami)", strings.Repeat("a", 32)} { if NameRe.MatchString(bad) { t.Errorf("%q should be refused as a name", bad) } } }