github.com/containers/podman/v4@v4.9.4/pkg/machine/wsl/usermodenet.go (about) 1 //go:build windows 2 // +build windows 3 4 package wsl 5 6 import ( 7 "errors" 8 "fmt" 9 "os" 10 "os/exec" 11 "path/filepath" 12 13 "github.com/containers/podman/v4/pkg/machine" 14 "github.com/containers/podman/v4/pkg/machine/wsl/wutil" 15 "github.com/containers/podman/v4/pkg/specgen" 16 "github.com/sirupsen/logrus" 17 ) 18 19 const startUserModeNet = ` 20 set -e 21 STATE=/mnt/wsl/podman-usermodenet 22 mkdir -p $STATE 23 cp -f /mnt/wsl/resolv.conf $STATE/resolv.orig 24 ip route show default > $STATE/route.dat 25 ROUTE=$(<$STATE/route.dat) 26 if [[ $ROUTE =~ .*192\.168\.127\.1.* ]]; then 27 exit 2 28 fi 29 if [[ ! $ROUTE =~ default\ via ]]; then 30 exit 3 31 fi 32 nohup /usr/local/bin/vm -iface podman-usermode -stop-if-exist ignore -url "stdio:$GVPROXY?listen-stdio=accept" > /var/log/vm.log 2> /var/log/vm.err < /dev/null & 33 echo $! > $STATE/vm.pid 34 sleep 1 35 ps -eo args | grep -q -m1 ^/usr/local/bin/vm || exit 42 36 ` 37 38 const stopUserModeNet = ` 39 STATE=/mnt/wsl/podman-usermodenet 40 if [[ ! -f "$STATE/vm.pid" || ! -f "$STATE/route.dat" ]]; then 41 exit 2 42 fi 43 cp -f $STATE/resolv.orig /mnt/wsl/resolv.conf 44 GPID=$(<$STATE/vm.pid) 45 kill $GPID > /dev/null 46 while kill -0 $GPID > /dev/null 2>&1; do 47 sleep 1 48 done 49 ip route del default > /dev/null 2>&1 50 ROUTE=$(<$STATE/route.dat) 51 if [[ ! $ROUTE =~ default\ via ]]; then 52 exit 3 53 fi 54 ip route add $ROUTE 55 rm -rf /mnt/wsl/podman-usermodenet 56 ` 57 58 func verifyWSLUserModeCompat() error { 59 if wutil.IsWSLStoreVersionInstalled() { 60 return nil 61 } 62 63 prefix := "" 64 if !winVersionAtLeast(10, 0, 19043) { 65 prefix = "upgrade to 22H2, " 66 } 67 68 return fmt.Errorf("user-mode networking requires a newer version of WSL: "+ 69 "%sapply all outstanding windows updates, and then run `wsl --update`", 70 prefix) 71 } 72 73 func (v *MachineVM) startUserModeNetworking() error { 74 if !v.UserModeNetworking { 75 return nil 76 } 77 78 exe, err := machine.FindExecutablePeer(gvProxy) 79 if err != nil { 80 return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy) 81 } 82 83 flock, err := v.obtainUserModeNetLock() 84 if err != nil { 85 return err 86 } 87 defer flock.unlock() 88 89 running, err := isWSLRunning(userModeDist) 90 if err != nil { 91 return err 92 } 93 running = running && isGvProxyVMRunning() 94 95 // Start or reuse 96 if !running { 97 if err := v.launchUserModeNetDist(exe); err != nil { 98 return err 99 } 100 } 101 102 if err := createUserModeResolvConf(toDist(v.Name)); err != nil { 103 return err 104 } 105 106 // Register in-use 107 err = v.addUserModeNetEntry() 108 if err != nil { 109 return err 110 } 111 112 return nil 113 } 114 115 func (v *MachineVM) stopUserModeNetworking(dist string) error { 116 if !v.UserModeNetworking { 117 return nil 118 } 119 120 flock, err := v.obtainUserModeNetLock() 121 if err != nil { 122 return err 123 } 124 defer flock.unlock() 125 126 err = v.removeUserModeNetEntry() 127 if err != nil { 128 return err 129 } 130 131 count, err := v.cleanupAndCountNetEntries() 132 if err != nil { 133 return err 134 } 135 136 // Leave running if still in-use 137 if count > 0 { 138 return nil 139 } 140 141 fmt.Println("Stopping user-mode networking...") 142 143 err = wslPipe(stopUserModeNet, userModeDist, "bash") 144 if err != nil { 145 if exitErr, ok := err.(*exec.ExitError); ok { 146 switch exitErr.ExitCode() { 147 case 2: 148 err = fmt.Errorf("startup state was missing") 149 case 3: 150 err = fmt.Errorf("route state is missing a default route") 151 } 152 } 153 logrus.Warnf("problem tearing down user-mode networking cleanly, forcing: %s", err.Error()) 154 } 155 156 return terminateDist(userModeDist) 157 } 158 159 func isGvProxyVMRunning() bool { 160 return wslInvoke(userModeDist, "bash", "-c", "ps -eo args | grep -q -m1 ^/usr/local/bin/vm || exit 42") == nil 161 } 162 163 func (v *MachineVM) launchUserModeNetDist(exeFile string) error { 164 fmt.Println("Starting user-mode networking...") 165 166 exe, err := specgen.ConvertWinMountPath(exeFile) 167 if err != nil { 168 return err 169 } 170 171 cmdStr := fmt.Sprintf("GVPROXY=%q\n%s", exe, startUserModeNet) 172 if err := wslPipe(cmdStr, userModeDist, "bash"); err != nil { 173 _ = terminateDist(userModeDist) 174 175 if exitErr, ok := err.(*exec.ExitError); ok { 176 switch exitErr.ExitCode() { 177 case 2: 178 return fmt.Errorf("another user-mode network is running, only one can be used at a time: shut down all machines and run wsl --shutdown if this is unexpected") 179 case 3: 180 err = fmt.Errorf("route state is missing a default route: shutdown all machines and run wsl --shutdown to recover") 181 } 182 } 183 184 return fmt.Errorf("error setting up user-mode networking: %w", err) 185 } 186 187 return nil 188 } 189 190 func installUserModeDist(dist string, imagePath string) error { 191 if err := verifyWSLUserModeCompat(); err != nil { 192 return err 193 } 194 195 exists, err := isWSLExist(userModeDist) 196 if err != nil { 197 return err 198 } 199 200 if !exists { 201 if err := wslInvoke(dist, "test", "-f", "/usr/local/bin/vm"); err != nil { 202 return fmt.Errorf("existing machine is too old, can't install user-mode networking dist until machine is reinstalled (using podman machine rm, then podman machine init)") 203 } 204 205 const prompt = "Installing user-mode networking distribution..." 206 if _, err := provisionWSLDist(userModeDist, imagePath, prompt); err != nil { 207 return err 208 } 209 210 _ = terminateDist(userModeDist) 211 } 212 213 return nil 214 } 215 216 func createUserModeResolvConf(dist string) error { 217 err := wslPipe(resolvConfUserNet, dist, "bash", "-c", "(rm -f /etc/resolv.conf; cat > /etc/resolv.conf)") 218 if err != nil { 219 return fmt.Errorf("could not create resolv.conf: %w", err) 220 } 221 return err 222 } 223 224 func (v *MachineVM) getUserModeNetDir() (string, error) { 225 vmDataDir, err := machine.GetDataDir(vmtype) 226 if err != nil { 227 return "", err 228 } 229 230 dir := filepath.Join(vmDataDir, userModeDist) 231 if err := os.MkdirAll(dir, 0755); err != nil { 232 return "", fmt.Errorf("could not create %s directory: %w", userModeDist, err) 233 } 234 235 return dir, nil 236 } 237 238 func (v *MachineVM) getUserModeNetEntriesDir() (string, error) { 239 netDir, err := v.getUserModeNetDir() 240 if err != nil { 241 return "", err 242 } 243 244 dir := filepath.Join(netDir, "entries") 245 if err := os.MkdirAll(dir, 0755); err != nil { 246 return "", fmt.Errorf("could not create %s/entries directory: %w", userModeDist, err) 247 } 248 249 return dir, nil 250 } 251 252 func (v *MachineVM) addUserModeNetEntry() error { 253 entriesDir, err := v.getUserModeNetEntriesDir() 254 if err != nil { 255 return err 256 } 257 258 path := filepath.Join(entriesDir, toDist(v.Name)) 259 file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 260 if err != nil { 261 return fmt.Errorf("could not add user-mode networking registration: %w", err) 262 } 263 file.Close() 264 return nil 265 } 266 267 func (v *MachineVM) removeUserModeNetEntry() error { 268 entriesDir, err := v.getUserModeNetEntriesDir() 269 if err != nil { 270 return err 271 } 272 273 path := filepath.Join(entriesDir, toDist(v.Name)) 274 return os.Remove(path) 275 } 276 277 func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) { 278 entriesDir, err := v.getUserModeNetEntriesDir() 279 if err != nil { 280 return 0, err 281 } 282 283 allDists, err := getAllWSLDistros(true) 284 if err != nil { 285 return 0, err 286 } 287 288 var count uint = 0 289 files, err := os.ReadDir(entriesDir) 290 if err != nil { 291 return 0, err 292 } 293 294 for _, file := range files { 295 _, running := allDists[file.Name()] 296 if !running { 297 _ = os.Remove(filepath.Join(entriesDir, file.Name())) 298 continue 299 } 300 count++ 301 } 302 303 return count, nil 304 } 305 306 func (v *MachineVM) obtainUserModeNetLock() (*fileLock, error) { 307 dir, err := v.getUserModeNetDir() 308 309 if err != nil { 310 return nil, err 311 } 312 313 var flock *fileLock 314 lockPath := filepath.Join(dir, "podman-usermodenet.lck") 315 if flock, err = lockFile(lockPath); err != nil { 316 return nil, fmt.Errorf("could not lock user-mode networking lock file: %w", err) 317 } 318 319 return flock, nil 320 } 321 322 func changeDistUserModeNetworking(dist string, user string, image string, enable bool) error { 323 // Only install if user-mode is being enabled and there was an image path passed 324 if enable { 325 if len(image) <= 0 { 326 return errors.New("existing machine configuration is corrupt, no image is defined") 327 } 328 if err := installUserModeDist(dist, image); err != nil { 329 return err 330 } 331 } 332 333 if err := writeWslConf(dist, user); err != nil { 334 return err 335 } 336 337 if enable { 338 return appendDisableAutoResolve(dist) 339 } 340 341 return nil 342 } 343 344 func appendDisableAutoResolve(dist string) error { 345 if err := wslPipe(wslConfUserNet, dist, "sh", "-c", "cat >> /etc/wsl.conf"); err != nil { 346 return fmt.Errorf("could not append resolv config to wsl.conf: %w", err) 347 } 348 349 return nil 350 }