Net tap cleanup & proper error exit handling

This commit is contained in:
AlexSSD7 2023-08-31 20:10:26 +01:00
commit 3a56fb9db8
3 changed files with 51 additions and 42 deletions

View file

@ -11,7 +11,7 @@ var buildCmd = &cobra.Command{
Use: "build", Use: "build",
Short: "Build (set up) a VM image for local use. This needs to be run after the initial installation.", Short: "Build (set up) a VM image for local use. This needs to be run after the initial installation.",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
store := createStore() store := createStoreOrExit()
err := store.BuildVMImageWithInterruptHandler(vmDebugFlag, buildOverwriteFlag) err := store.BuildVMImageWithInterruptHandler(vmDebugFlag, buildOverwriteFlag)
if err != nil { if err != nil {

View file

@ -16,7 +16,7 @@ var cleanCmd = &cobra.Command{
Use: "clean", Use: "clean",
Short: "Remove the Linsk data directory.", Short: "Remove the Linsk data directory.",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
store := createStore() store := createStoreOrExit()
if nettap.Available() { if nettap.Available() {
tm, err := nettap.NewTapManager(slog.With("caller", "nettap-manager")) tm, err := nettap.NewTapManager(slog.With("caller", "nettap-manager"))

View file

@ -52,7 +52,7 @@ func doUSBRootCheck() {
} }
} }
func createStore() *storage.Storage { func createStoreOrExit() *storage.Storage {
store, err := storage.NewStorage(slog.With("caller", "storage"), dataDirFlag) store, err := storage.NewStorage(slog.With("caller", "storage"), dataDirFlag)
if err != nil { if err != nil {
slog.Error("Failed to create Linsk data storage", "error", err.Error(), "data-dir", dataDirFlag) slog.Error("Failed to create Linsk data storage", "error", err.Error(), "data-dir", dataDirFlag)
@ -63,29 +63,36 @@ func createStore() *storage.Storage {
} }
func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManager, *share.NetTapRuntimeContext) int, forwardPortsRules []vm.PortForwardingRule, unrestrictedNetworking bool, withNetTap bool) int { func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManager, *share.NetTapRuntimeContext) int, forwardPortsRules []vm.PortForwardingRule, unrestrictedNetworking bool, withNetTap bool) int {
store := createStore() store := createStoreOrExit()
vmImagePath, err := store.CheckVMImageExists() vmImagePath, err := store.CheckVMImageExists()
if err != nil { if err != nil {
slog.Error("Failed to check whether VM image exists", "error", err.Error()) slog.Error("Failed to check whether VM image exists", "error", err.Error())
os.Exit(1) return 1
} }
if vmImagePath == "" { if vmImagePath == "" {
slog.Error("VM image does not exist. You need to build it first before attempting to start Linsk. Please run `linsk build` first.") slog.Error("VM image does not exist. You need to build it first before attempting to start Linsk. Please run `linsk build` first.")
os.Exit(1) return 1
} }
biosPath, err := store.CheckDownloadVMBIOS() biosPath, err := store.CheckDownloadVMBIOS()
if err != nil { if err != nil {
slog.Error("Failed to check/download VM BIOS", "error", err.Error()) slog.Error("Failed to check/download VM BIOS", "error", err.Error())
os.Exit(1) return 1
} }
var passthroughConfig vm.PassthroughConfig var passthroughConfig vm.PassthroughConfig
if passthroughArg != "" { if passthroughArg != "" {
passthroughConfig = getDevicePassthroughConfig(passthroughArg) passthroughConfigPtr, err := getDevicePassthroughConfig(passthroughArg)
if err != nil {
slog.Error("Failed to get device passthrough config", "error", err.Error())
return 1
}
passthroughConfig = *passthroughConfigPtr
doUSBRootCheck() doUSBRootCheck()
} }
@ -96,14 +103,14 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
tapManager, err := nettap.NewTapManager(slog.With("caller", "nettap-manager")) tapManager, err := nettap.NewTapManager(slog.With("caller", "nettap-manager"))
if err != nil { if err != nil {
slog.Error("Failed to create new network tap manager", "error", err.Error()) slog.Error("Failed to create new network tap manager", "error", err.Error())
os.Exit(1) return 1
} }
tapNameToUse := nettap.NewRandomTapName() tapNameToUse := nettap.NewRandomTapName()
knownAllocs, err := store.ListNetTapAllocations() knownAllocs, err := store.ListNetTapAllocations()
if err != nil { if err != nil {
slog.Error("Failed to list net tap allocations", "error", err.Error()) slog.Error("Failed to list net tap allocations", "error", err.Error())
os.Exit(1) return 1
} }
removedTaps, err := tapManager.PruneTaps(knownAllocs) removedTaps, err := tapManager.PruneTaps(knownAllocs)
@ -122,36 +129,49 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
err = store.SaveNetTapAllocation(tapNameToUse, os.Getpid()) err = store.SaveNetTapAllocation(tapNameToUse, os.Getpid())
if err != nil { if err != nil {
slog.Error("Failed to save net tap allocation", "error", err.Error()) slog.Error("Failed to save net tap allocation", "error", err.Error())
os.Exit(1) return 1
} }
tapManager, err = nettap.NewTapManager(slog.Default()) tapManager, err = nettap.NewTapManager(slog.Default())
if err != nil { if err != nil {
slog.Error("Failed to create net tap manager", "error", err.Error()) slog.Error("Failed to create net tap manager", "error", err.Error())
os.Exit(1) return 1
} }
err = tapManager.CreateNewTap(tapNameToUse) err = tapManager.CreateNewTap(tapNameToUse)
if err != nil { if err != nil {
releaseErr := store.ReleaseNetTapAllocation(tapNameToUse) releaseErr := store.ReleaseNetTapAllocation(tapNameToUse)
if releaseErr != nil { if releaseErr != nil {
slog.Error("Failed to release net tap allocation", "error", releaseErr.Error(), "tapname", tapNameToUse) slog.Error("Failed to release net tap allocation", "error", releaseErr.Error(), "tap-name", tapNameToUse)
// Non-critical error.
} }
slog.Error("Failed to create new tap", "error", err.Error()) slog.Error("Failed to create new tap", "error", err.Error())
os.Exit(1) return 1
} }
defer func() {
err := tapManager.DeleteTap(tapNameToUse)
if err != nil {
slog.Error("Failed to clean up net tap", "error", err.Error(), "tap-name", tapNameToUse)
} else {
err = store.ReleaseNetTapAllocation(tapNameToUse)
if err != nil {
slog.Error("Failed to release net tap allocation", "error", err.Error(), "tap-name", tapNameToUse)
}
}
}()
tapNet, err := nettap.GenerateNet() tapNet, err := nettap.GenerateNet()
if err != nil { if err != nil {
slog.Error("Failed to generate tap net plan", "error", err.Error()) slog.Error("Failed to generate tap net plan", "error", err.Error())
os.Exit(1) return 1
} }
err = tapManager.ConfigureNet(tapNameToUse, tapNet.HostCIDR) err = tapManager.ConfigureNet(tapNameToUse, tapNet.HostCIDR)
if err != nil { if err != nil {
slog.Error("Failed to configure tap net", "error", err.Error()) slog.Error("Failed to configure tap net", "error", err.Error())
os.Exit(1) return 1
} }
tapRuntimeCtx = &share.NetTapRuntimeContext{ tapRuntimeCtx = &share.NetTapRuntimeContext{
@ -163,8 +183,6 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
tapsConfig = []vm.TapConfig{{ tapsConfig = []vm.TapConfig{{
Name: tapNameToUse, Name: tapNameToUse,
}} }}
// TODO: Clean the tap up before exiting.
} }
vmCfg := vm.VMConfig{ vmCfg := vm.VMConfig{
@ -191,7 +209,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
vi, err := vm.NewVM(slog.Default().With("caller", "vm"), vmCfg) vi, err := vm.NewVM(slog.Default().With("caller", "vm"), vmCfg)
if err != nil { if err != nil {
slog.Error("Failed to create vm instance", "error", err.Error()) slog.Error("Failed to create vm instance", "error", err.Error())
os.Exit(1) return 1
} }
runErrCh := make(chan error, 1) runErrCh := make(chan error, 1)
@ -247,12 +265,12 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
} }
slog.Error("Failed to start the VM", "error", err.Error()) slog.Error("Failed to start the VM", "error", err.Error())
os.Exit(1) return 1
case <-vi.SSHUpNotifyChan(): case <-vi.SSHUpNotifyChan():
err := fm.Init() err := fm.Init()
if err != nil { if err != nil {
slog.Error("Failed to initialize File Manager", "error", err.Error()) slog.Error("Failed to initialize File Manager", "error", err.Error())
os.Exit(1) return 1
} }
startupFailed := false startupFailed := false
@ -276,7 +294,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
err = vi.Cancel() err = vi.Cancel()
if err != nil { if err != nil {
slog.Error("Failed to cancel VM context", "error", err.Error()) slog.Error("Failed to cancel VM context", "error", err.Error())
os.Exit(1) return 1
} }
wg.Wait() wg.Wait()
@ -285,7 +303,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
case err := <-runErrCh: case err := <-runErrCh:
if err != nil { if err != nil {
slog.Error("Failed to run the VM", "error", err.Error()) slog.Error("Failed to run the VM", "error", err.Error())
os.Exit(1) return 1
} }
default: default:
} }
@ -295,61 +313,52 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
} }
} }
func getDevicePassthroughConfig(val string) vm.PassthroughConfig { func getDevicePassthroughConfig(val string) (*vm.PassthroughConfig, error) {
valSplit := strings.Split(val, ":") valSplit := strings.Split(val, ":")
if want, have := 2, len(valSplit); want != have { if want, have := 2, len(valSplit); want != have {
slog.Error("Bad device passthrough syntax", "error", fmt.Errorf("wrong items split by ':' count: want %v, have %v", want, have)) return nil, fmt.Errorf("bad device passthrough syntax: wrong items split by ':' count: want %v, have %v", want, have)
os.Exit(1)
} }
switch valSplit[0] { switch valSplit[0] {
case "usb": case "usb":
usbValsSplit := strings.Split(valSplit[1], ",") usbValsSplit := strings.Split(valSplit[1], ",")
if want, have := 2, len(usbValsSplit); want != have { if want, have := 2, len(usbValsSplit); want != have {
slog.Error("Bad USB device passthrough syntax", "error", fmt.Errorf("wrong args split by ',' count: want %v, have %v", want, have)) return nil, fmt.Errorf("bad usb device passthrough syntax: wrong args split by ',' count: want %v, have %v", want, have)
os.Exit(1)
} }
vendorID, err := strconv.ParseUint(usbValsSplit[0], 16, 32) vendorID, err := strconv.ParseUint(usbValsSplit[0], 16, 32)
if err != nil { if err != nil {
slog.Error("Bad USB vendor ID", "value", usbValsSplit[0]) return nil, fmt.Errorf("bad usb vendor id '%v'", usbValsSplit[0])
os.Exit(1)
} }
productID, err := strconv.ParseUint(usbValsSplit[1], 16, 32) productID, err := strconv.ParseUint(usbValsSplit[1], 16, 32)
if err != nil { if err != nil {
slog.Error("Bad USB product ID", "value", usbValsSplit[1]) return nil, fmt.Errorf("bad usb product id '%v'", usbValsSplit[1])
os.Exit(1)
} }
return vm.PassthroughConfig{ return &vm.PassthroughConfig{
USB: []vm.USBDevicePassthroughConfig{{ USB: []vm.USBDevicePassthroughConfig{{
VendorID: uint16(vendorID), VendorID: uint16(vendorID),
ProductID: uint16(productID), ProductID: uint16(productID),
}}, }},
} }, nil
case "dev": case "dev":
devPath := filepath.Clean(valSplit[1]) devPath := filepath.Clean(valSplit[1])
// TODO: This is for Linux only. Should support Windows as well. // TODO: This is for Linux only. Should support Windows as well.
// stat, err := os.Stat(devPath) // stat, err := os.Stat(devPath)
// if err != nil { // if err != nil {
// slog.Error("Failed to stat the device path", "error", err.Error(), "path", devPath) // slog.Error("Failed to stat the device path", "error", err.Error(), "path", devPath)
// os.Exit(1)
// } // }
// isDev := stat.Mode()&os.ModeDevice != 0 // isDev := stat.Mode()&os.ModeDevice != 0
// if !isDev { // if !isDev {
// slog.Error("Provided path is not a path to a valid block device", "path", devPath, "file-mode", stat.Mode()) // slog.Error("Provided path is not a path to a valid block device", "path", devPath, "file-mode", stat.Mode())
// os.Exit(1)
// } // }
return vm.PassthroughConfig{Block: []vm.BlockDevicePassthroughConfig{{ return &vm.PassthroughConfig{Block: []vm.BlockDevicePassthroughConfig{{
Path: devPath, Path: devPath,
}}} }}}, nil
default: default:
slog.Error("Unknown device passthrough type", "value", valSplit[0]) return nil, fmt.Errorf("unknown device passthrough type '%v'", val)
os.Exit(1)
// This unreachable code is required to compile.
return vm.PassthroughConfig{}
} }
} }