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  }