main 33a243c

netsetup.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3// Package netsetup is the host-side network plumbing shared by both sandbox
4// drivers: the nftables policy (package netpolicy), the DNS forwarder (package
5// dnsfwd) and IPv4 forwarding. Each driver creates its own interface (a TAP or
6// a Docker bridge) and then Attaches it here.
7package netsetup
8
9import (
10	"context"
11	"errors"
12	"fmt"
13	"net/netip"
14	"os"
15	"strings"
16
17	"bnema.dev/ermite-runner/internal/cmdrun"
18	"bnema.dev/ermite-runner/internal/dnsfwd"
19	"bnema.dev/ermite-runner/internal/netpolicy"
20	"bnema.dev/ermite-runner/internal/tcprelay"
21)
22
23// Plumbing is the host network surface a driver needs. Net implements it; tests
24// of the drivers use a fake because binding gateway addresses and loading nft
25// rules needs privileges.
26type Plumbing interface {
27	EnableForwarding() error
28	Attach(ctx context.Context, g netpolicy.Guest) error
29	Detach(ctx context.Context, iface string, gateway netip.Addr) error
30	Reset(ctx context.Context) error
31	Close()
32}
33
34var _ Plumbing = (*Net)(nil)
35
36// Net is the host network state of one driver.
37type Net struct {
38	Policy *netpolicy.Manager
39	DNS    *dnsfwd.Forwarder
40	// ForwardFile is the ip_forward sysctl; overridden in tests.
41	ForwardFile string
42	// Relay, when not nil, forwards the guest forge port of every gateway to
43	// the forge (ERMITE_RUNNER_GUEST_FORGE_TARGET): the forge then does not
44	// need to listen in the runner's network namespace.
45	Relay *tcprelay.Relay
46}
47
48// New builds a Net: nft runs through run, DNS listens on dnsPort and forwards
49// to the resolvers of resolvConf ("" = /etc/resolv.conf).
50func New(run cmdrun.Runner, dnsPort int, resolvConf string) *Net {
51	r := dnsfwd.NewResolvers(resolvConf)
52	return &Net{
53		Policy:      netpolicy.NewManager(run, "nft"),
54		DNS:         dnsfwd.New(r.Addrs, dnsPort),
55		ForwardFile: "/proc/sys/net/ipv4/ip_forward",
56	}
57}
58
59// EnableForwarding turns on IPv4 forwarding (NAT needs it). It is a no-op when
60// already on, and an error that names the fix when it cannot be turned on. In
61// a container /proc/sys is read-only: the runtime must start the container's
62// network namespace with net.ipv4.ip_forward=1 (rootless Podman does).
63func (n *Net) EnableForwarding() error {
64	b, err := os.ReadFile(n.ForwardFile)
65	if err == nil && strings.TrimSpace(string(b)) == "1" {
66		return nil
67	}
68	if err := os.WriteFile(n.ForwardFile, []byte("1\n"), 0o644); err != nil {
69		return fmt.Errorf("enable IPv4 forwarding (%s): %w; set net.ipv4.ip_forward=1", n.ForwardFile, err)
70	}
71	return nil
72}
73
74// Attach installs the firewall policy of g and starts the DNS forwarder on its
75// gateway address, which must already be configured on the host interface.
76// On error nothing stays installed.
77func (n *Net) Attach(ctx context.Context, g netpolicy.Guest) error {
78	if err := n.Policy.Add(ctx, g); err != nil {
79		return err
80	}
81	if err := n.DNS.Listen(ctx, g.Gateway); err != nil {
82		return errors.Join(err, n.Policy.Remove(context.WithoutCancel(ctx), g.Iface))
83	}
84	if n.Relay != nil && g.ForgePort != 0 {
85		if err := n.Relay.Listen(ctx, g.Gateway, g.ForgePort); err != nil {
86			n.DNS.Close(g.Gateway)
87			return errors.Join(err, n.Policy.Remove(context.WithoutCancel(ctx), g.Iface))
88		}
89	}
90	return nil
91}
92
93// Detach undoes Attach. It is idempotent and keeps going after an error.
94func (n *Net) Detach(ctx context.Context, iface string, gateway netip.Addr) error {
95	if n.Relay != nil {
96		n.Relay.Close(gateway)
97	}
98	n.DNS.Close(gateway)
99	return n.Policy.Remove(ctx, iface)
100}
101
102// Reset removes the runner's nftables table: used at startup to reap a previous
103// run. Only the table is touched, never other tables.
104func (n *Net) Reset(ctx context.Context) error { return n.Policy.Reset(ctx) }
105
106// Close stops every DNS listener.
107func (n *Net) Close() {
108	if n.Relay != nil {
109		n.Relay.CloseAll()
110	}
111	n.DNS.CloseAll()
112}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close