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