1// SPDX-License-Identifier: MIT
2
3package netsetup
4
5import (
6 "context"
7 "errors"
8 "net/netip"
9 "os"
10 "path/filepath"
11 "strings"
12 "testing"
13
14 "bnema.dev/ermite-runner/internal/cmdrun"
15 "bnema.dev/ermite-runner/internal/netpolicy"
16)
17
18func guest() netpolicy.Guest {
19 return netpolicy.Guest{Iface: "ertap0", Gateway: netip.MustParseAddr("127.0.0.1"), Addr: netip.MustParseAddr("10.200.0.2")}
20}
21
22func TestAttachDetach(t *testing.T) {
23 f := &cmdrun.Fake{}
24 n := New(f, 0, "")
25 t.Cleanup(n.Close)
26 ctx := context.Background()
27 if err := n.Attach(ctx, guest()); err != nil {
28 t.Skip("cannot bind loopback:", err)
29 }
30 if _, ok := n.DNS.Addr(guest().Gateway); !ok {
31 t.Fatal("DNS forwarder not listening on the gateway")
32 }
33 if err := n.Detach(ctx, "ertap0", guest().Gateway); err != nil {
34 t.Fatal(err)
35 }
36 if _, ok := n.DNS.Addr(guest().Gateway); ok {
37 t.Fatal("DNS forwarder still listening after Detach")
38 }
39 if len(f.Calls) != 2 || f.Calls[1].Stdin != netpolicy.Ruleset(nil) {
40 t.Fatalf("calls: %v", f.Lines())
41 }
42 if err := n.Detach(ctx, "ertap0", guest().Gateway); err != nil {
43 t.Fatalf("Detach must be idempotent: %v", err)
44 }
45}
46
47func TestAttachRollsBackPolicyWhenDNSFails(t *testing.T) {
48 f := &cmdrun.Fake{}
49 n := New(f, 0, "")
50 t.Cleanup(n.Close)
51 ctx := context.Background()
52 if err := n.Attach(ctx, guest()); err != nil {
53 t.Skip(err)
54 }
55 before := len(f.Calls)
56 if err := n.Attach(ctx, guest()); err == nil { // already listening
57 t.Fatal("second Attach on the same gateway must fail")
58 }
59 if len(f.Calls) != before+2 {
60 t.Fatalf("expected add then rollback remove, got %v", f.Lines())
61 }
62}
63
64func TestAttachPolicyError(t *testing.T) {
65 boom := errors.New("nft failed")
66 n := New(&cmdrun.Fake{Handle: func(cmdrun.Call) ([]byte, error) { return nil, boom }}, 0, "")
67 if err := n.Attach(context.Background(), guest()); !errors.Is(err, boom) {
68 t.Fatalf("got %v", err)
69 }
70 if _, ok := n.DNS.Addr(guest().Gateway); ok {
71 t.Fatal("DNS must not start when the policy failed")
72 }
73}
74
75func TestEnableForwarding(t *testing.T) {
76 n := New(&cmdrun.Fake{}, 0, "")
77 n.ForwardFile = filepath.Join(t.TempDir(), "ip_forward")
78 _ = os.WriteFile(n.ForwardFile, []byte("0\n"), 0o644)
79 if err := n.EnableForwarding(); err != nil {
80 t.Fatal(err)
81 }
82 if b, _ := os.ReadFile(n.ForwardFile); strings.TrimSpace(string(b)) != "1" {
83 t.Fatalf("not enabled: %q", b)
84 }
85 n.ForwardFile = filepath.Join(t.TempDir(), "missing", "ip_forward")
86 if err := n.EnableForwarding(); err == nil || !strings.Contains(err.Error(), "ip_forward") {
87 t.Fatalf("want a clear error, got %v", err)
88 }
89}