arblant
firefox-vpn-client / core / internal / service / tunallow_test.go
tunallow_test.go98 lines · 3.0 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
package service

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/armanlife/firefox-vpn-client/core/internal/config"
	"github.com/armanlife/firefox-vpn-client/core/internal/routing"
)

// The phone's VPN refuses at once what Mozilla would refuse, unless the
// routing sends it directly.
func TestTunnelAllowsMozillaPortsAndDirectDestinations(t *testing.T) {
	d := newTestDaemon(t)
	for port, want := range map[string]bool{"443": true, "80": true, "8443": true, "5222": false, "22": false} {
		if got := d.TunnelAllows("chat.example", port); got != want {
			t.Errorf("port %s: %t", port, got)
		}
	}
	// Git over SSH to GitHub goes to its SSH server on port 443.
	if !d.TunnelAllows("github.com", "22") || !d.TunnelAllows("GitHub.com", "22") {
		t.Error("GitHub's SSH is refused")
	}
	writeTestGeoIP(t, d, map[string][]string{"private": {"10.0.0.0/8"}})
	d.applyRouting(config.Routing{Enabled: true, Direct: []string{"domain:bank.test"}})
	if d.currentRouter() == nil {
		t.Fatal("the routing did not load")
	}
	if !d.TunnelAllows("www.bank.test", "8081") {
		t.Fatal("a direct destination may use any port")
	}
	if d.TunnelAllows("chat.example", "5222") {
		t.Fatal("a proxied destination keeps Mozilla's ports")
	}
}

// The region's app list is read only while routing is on.
func TestDirectAppsFollowTheRouting(t *testing.T) {
	d := newTestDaemon(t)
	if err := writeTestFile(d.paths.RoutingDir, routing.AppListFile("ir"), "cab.snapp.passenger\ncom.digikala\n"); err != nil {
		t.Fatal(err)
	}
	d.mu.Lock()
	d.cfg.Routing.Enabled, d.cfg.Routing.Region = true, "ir"
	d.mu.Unlock()
	if got := d.DirectApps(); len(got) != 2 {
		t.Fatalf("apps %v", got)
	}
	d.mu.Lock()
	d.cfg.Routing.Enabled = false
	d.mu.Unlock()
	if got := d.DirectApps(); got != nil {
		t.Fatalf("routing off, apps %v", got)
	}
}

// A network change voids the last test and, connected, reconnects.
func TestNetworkChangeReconnects(t *testing.T) {
	d, f := runLazyDaemon(t)
	view, _ := d.StatusView()
	if err := lazyEcho(t, view.Proxy.Listen, "before"); err != nil {
		t.Fatal(err)
	}
	before := f.connects.Load()
	d.NetworkChanged(true)
	view, _ = d.StatusView()
	if view.NetworkChangedAt.IsZero() || time.Since(view.NetworkChangedAt) > time.Minute {
		t.Fatalf("network change time %v", view.NetworkChangedAt)
	}
	if err := lazyEcho(t, view.Proxy.Listen, "after"); err != nil {
		t.Fatalf("after the change: %v", err)
	}
	if f.passes.Load() != 1 {
		t.Fatalf("passes %d; a reconnect keeps the pass", f.passes.Load())
	}
	if f.connects.Load() <= before {
		t.Fatal("no connection after the change")
	}
	if view.NoNetwork {
		t.Fatal("a network is connected")
	}

	// No network: the status says so at once.
	t.Cleanup(func() { d.NetworkChanged(true) })
	d.NetworkChanged(false)
	if view, _ = d.StatusView(); !view.NoNetwork {
		t.Fatal("no_network not reported")
	}
}

func writeTestFile(dir, name, content string) error {
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600)
}