Monorepo for Tangled
tangled.org
1package microvm
2
3import (
4 "bytes"
5 "context"
6 _ "embed"
7 "fmt"
8 "log/slog"
9 "net"
10 "os"
11 "os/exec"
12 "text/template"
13)
14
15// https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
16// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
17// https://datatracker.ietf.org/doc/rfc6890/
18var blockedNamespaceRoutes = []string{
19 "0.0.0.0/8", // unspecified / "this network" addresses
20 "10.0.0.0/8", // private network
21 "100.64.0.0/10", // shared carrier-grade nat space
22 "127.0.0.0/8", // loopback
23 "169.254.0.0/16", // link-local / autoconfiguration
24 "172.16.0.0/12", // private network
25 "192.0.0.0/24", // ietf protocol assignments
26 "192.0.2.0/24", // documentation / examples
27 "192.88.99.0/24", // deprecated 6to4 relay anycast
28 "192.168.0.0/16", // private network
29 "198.18.0.0/15", // benchmarking / testing
30 "198.51.100.0/24", // documentation / examples
31 "203.0.113.0/24", // documentation / examples
32 "224.0.0.0/4", // multicast
33 "240.0.0.0/4", // reserved / future use, includes limited broadcast
34 "::/128", // unspecified address
35 "::1/128", // loopback
36 "::ffff:0:0/96", // ipv4-mapped addresses
37 "64:ff9b::/96", // ipv4/ipv6 translation prefix
38 "100::/64", // discard-only prefix
39 "2001::/23", // ietf protocol assignments
40 "2001:db8::/32", // documentation / examples
41 "2002::/16", // deprecated 6to4 addressing
42 "fc00::/7", // unique local addresses
43 "fe80::/10", // link-local unicast
44 "ff00::/8", // multicast
45}
46
47var blockedNamespaceNets = func() []*net.IPNet {
48 nets := make([]*net.IPNet, 0, len(blockedNamespaceRoutes))
49 for _, route := range blockedNamespaceRoutes {
50 _, ipnet, err := net.ParseCIDR(route)
51 if err != nil {
52 panic(fmt.Sprintf("parse blocked route %q: %v", route, err))
53 }
54 nets = append(nets, ipnet)
55 }
56 return nets
57}()
58
59//go:embed netns_wrapper.sh.tmpl
60var netnsWrapperTemplate string
61
62type netnsWrapperData struct {
63 TapName string
64 BlockedRoutes []string
65}
66
67func writeNetnsWrapper(path string, dev bool) error {
68 tmpl, err := template.New("netns-wrapper").Parse(netnsWrapperTemplate)
69 if err != nil {
70 return fmt.Errorf("parse qemu network namespace wrapper template: %w", err)
71 }
72
73 var script bytes.Buffer
74
75 var routes []string
76 if !dev {
77 routes = blockedNamespaceRoutes
78 }
79
80 err = tmpl.Execute(&script, netnsWrapperData{
81 TapName: netnsTapName,
82 BlockedRoutes: routes,
83 })
84 if err != nil {
85 return fmt.Errorf("render qemu network namespace wrapper template: %w", err)
86 }
87
88 if err := os.WriteFile(path, script.Bytes(), 0o700); err != nil {
89 return fmt.Errorf("write qemu network namespace wrapper: %w", err)
90 }
91
92 return nil
93}
94
95type slirpNamespace struct {
96 spec ImageSpec
97 pidFile string
98 dev bool
99}
100
101func (n *slirpNamespace) Start(ctx context.Context, logFile *os.File, logger *slog.Logger) (*exec.Cmd, *os.File, error) {
102 pid, err := waitForPIDFile(ctx, n.pidFile)
103 if err != nil {
104 return nil, nil, err
105 }
106
107 exitR, exitW, err := os.Pipe()
108 if err != nil {
109 return nil, nil, fmt.Errorf("create slirp4netns exit pipe: %w", err)
110 }
111 defer exitR.Close() // always close our read end; child gets it via ExtraFiles dup
112
113 var ok bool
114 defer func() {
115 if !ok {
116 _ = exitW.Close()
117 }
118 }()
119
120 slirpPath, err := exec.LookPath("slirp4netns")
121 if err != nil {
122 return nil, nil, fmt.Errorf("slirp4netns command not found in PATH: %w", err)
123 }
124
125 args := []string{
126 "--configure",
127 "--mtu=" + netnsMTU,
128 }
129 if !n.dev {
130 args = append(args, "--disable-host-loopback")
131 }
132 args = append(args,
133 "--enable-sandbox",
134 "--enable-seccomp",
135 "--exit-fd=3",
136 "--cidr="+outerSlirpCIDR,
137 pid,
138 netnsTapName,
139 )
140
141 cmd := exec.CommandContext(ctx, slirpPath, args...)
142 cmd.ExtraFiles = []*os.File{exitR}
143 cmd.Stdout = logFile
144 cmd.Stderr = logFile
145 if err := cmd.Start(); err != nil {
146 return nil, nil, fmt.Errorf("start slirp4netns: %w", err)
147 }
148 logger.Info("started slirp4netns network namespace", "pid", pid, "cidr", outerSlirpCIDR, "tap", netnsTapName)
149
150 ok = true
151 return cmd, exitW, nil
152}