From fb6b1ae25ec147a844071200650da41dcdfb9605 Mon Sep 17 00:00:00 2001 From: AlexSSD7 Date: Fri, 1 Sep 2023 14:41:19 +0100 Subject: [PATCH] Add non-Windows nettap stubs --- cmd/utils.go | 7 +- nettap/errors.go | 3 +- nettap/impl.go | 39 +++++++++++ nettap/impl_windows.go | 149 +++++++++++++++++++++++++++++++++++++++++ nettap/nettap.go | 140 -------------------------------------- 5 files changed, 196 insertions(+), 142 deletions(-) create mode 100644 nettap/impl.go create mode 100644 nettap/impl_windows.go diff --git a/cmd/utils.go b/cmd/utils.go index f63d41b..4aca1c1 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -87,7 +87,12 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag return 1 } - tapNameToUse := nettap.NewRandomTapName() + tapNameToUse, err := nettap.NewRandomTapName() + if err != nil { + slog.Error("Failed to generate new network tap name", "error", err.Error()) + return 1 + } + knownAllocs, err := store.ListNetTapAllocations() if err != nil { slog.Error("Failed to list net tap allocations", "error", err.Error()) diff --git a/nettap/errors.go b/nettap/errors.go index c1dfc93..fa1a6f8 100644 --- a/nettap/errors.go +++ b/nettap/errors.go @@ -3,5 +3,6 @@ package nettap import "errors" var ( - ErrTapNotFound = errors.New("tap not found") + ErrTapNotFound = errors.New("tap not found") + ErrTapManagerUnimplemented = errors.New("tap manager is implemented on windows only") ) diff --git a/nettap/impl.go b/nettap/impl.go new file mode 100644 index 0000000..a047873 --- /dev/null +++ b/nettap/impl.go @@ -0,0 +1,39 @@ +//go:build !windows + +package nettap + +import ( + "log/slog" +) + +func Available() bool { + return false +} + +type TapManager struct { + logger *slog.Logger +} + +func NewTapManager(logger *slog.Logger) (*TapManager, error) { + return nil, ErrTapManagerUnimplemented +} + +func NewRandomTapName() (string, error) { + return "", ErrTapManagerUnimplemented +} + +func (tm *TapManager) CreateNewTap(tapName string) error { + return ErrTapManagerUnimplemented +} + +func ValidateTapName(s string) error { + return ErrTapManagerUnimplemented +} + +func (tm *TapManager) DeleteTap(name string) error { + return ErrTapManagerUnimplemented +} + +func (tm *TapManager) ConfigureNet(tapName string, hostCIDR string) error { + return ErrTapManagerUnimplemented +} diff --git a/nettap/impl_windows.go b/nettap/impl_windows.go new file mode 100644 index 0000000..4cf2a33 --- /dev/null +++ b/nettap/impl_windows.go @@ -0,0 +1,149 @@ +//go:build windows + +package nettap + +import ( + "bytes" + "fmt" + "net" + "os" + "os/exec" + "regexp" + "strings" + "time" + + "github.com/AlexSSD7/linsk/utils" + "github.com/alessio/shellescape" + "github.com/google/uuid" + "github.com/pkg/errors" + "golang.org/x/exp/slog" +) + +func Available() bool { + return true +} + +type TapManager struct { + logger *slog.Logger + + tapctlPath string +} + +func NewTapManager(logger *slog.Logger) (*TapManager, error) { + tapctlPath := `C:\Program Files\OpenVPN\bin\tapctl.exe` + _, err := os.Stat(tapctlPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + logger.Warn("Required OpenVPN tap networking Windows drivers do not appear to be installed. The easiest way to get them is to install OpenVPN: https://openvpn.net/community-downloads/") + } + return nil, errors.Wrapf(err, "stat tapctl path '%v'", tapctlPath) + } + + return &TapManager{ + logger: logger, + + tapctlPath: tapctlPath, + }, nil +} + +// We need some sort of format to avoid conflicting with other Windows interfaces. +var tapNameRegexp = regexp.MustCompile(`^LinskTap-\d+$`) + +func NewRandomTapName() (string, error) { + return fmt.Sprintf("LinskTap-%v", time.Now().UnixNano()) +} + +func (tm *TapManager) CreateNewTap(tapName string) error { + err := ValidateTapName(tapName) + if err != nil { + return errors.Wrap(err, "validate tap name") + } + + out, err := exec.Command(tm.tapctlPath, "create", "--name", tapName).CombinedOutput() + if err != nil { + return errors.Wrapf(err, "exec tapctl create cmd (out '%v')", utils.ClearUnprintableChars(string(out), false)) + } + + tm.logger.Info("Created network tap", "name", tapName) + + return nil +} + +func ValidateTapName(s string) error { + if !tapNameRegexp.MatchString(s) { + return fmt.Errorf("invalid tap name '%v'", s) + } + + return nil +} + +func (tm *TapManager) DeleteTap(name string) error { + stderr := bytes.NewBuffer(nil) + cmd := exec.Command(tm.tapctlPath, "list") + cmd.Stderr = stderr + tapList, err := cmd.Output() + if err != nil { + return errors.Wrapf(err, "exec tapctl list cmd (out '%v')", utils.ClearUnprintableChars(stderr.String(), false)) + } + + for _, line := range strings.Split(string(tapList), "\n") { + if line == "" { + continue + } + + line = strings.ReplaceAll(line, "\t", " ") + line = utils.ClearUnprintableChars(line, false) + + split := strings.Split(line, " ") + if want, have := 2, len(split); want > have { + return fmt.Errorf("bad tap list item split length: want %v > have %v (line '%v')", want, have, line) + } + + if name != split[1] { + continue + } + + lineTapUUIDStr := strings.TrimPrefix(split[0], "{") + lineTapUUIDStr = strings.TrimSuffix(lineTapUUIDStr, "}") + lineTapUUID, err := uuid.Parse(lineTapUUIDStr) + if err != nil { + return errors.Wrapf(err, "parse found line tap uuid (value '%v', line '%v')", lineTapUUIDStr, line) + } + + deleteOut, err := exec.Command(tm.tapctlPath, "delete", "{"+lineTapUUID.String()+"}").CombinedOutput() + if err != nil { + return errors.Wrapf(err, "exec tapctl delete (out '%v')", utils.ClearUnprintableChars(string(deleteOut), false)) + } + + tm.logger.Info("Deleted network tap", "name", name) + + return nil + } + + return ErrTapNotFound +} + +func (tm *TapManager) ConfigureNet(tapName string, hostCIDR string) error { + err := ValidateTapName(tapName) + if err != nil { + return errors.Wrap(err, "validate tap name") + } + + ip, _, err := net.ParseCIDR(hostCIDR) + if err != nil { + return errors.Wrap(err, "parse cidr") + } + + if !utils.IsIPv6IP(ip) { + return fmt.Errorf("ipv6 is accepted only (have '%v')", ip) + } + + out, err := exec.Command("netsh", "interface", "ipv6", "set", "address", shellescape.Quote(tapName), shellescape.Quote(hostCIDR)).CombinedOutput() + if err != nil { + return errors.Wrapf(err, "exec netsh cmd (out '%v')", utils.ClearUnprintableChars(string(out), false)) + } + + tm.logger.Info("Configured network tap", "name", tapName, "cidr", hostCIDR) + + return nil +} diff --git a/nettap/nettap.go b/nettap/nettap.go index 7d7b698..1bc2b8f 100644 --- a/nettap/nettap.go +++ b/nettap/nettap.go @@ -1,153 +1,13 @@ package nettap import ( - "bytes" "crypto/rand" "fmt" "net" - "os" - "os/exec" - "regexp" - "strings" - "time" - "log/slog" - - "github.com/AlexSSD7/linsk/utils" - "github.com/alessio/shellescape" - "github.com/google/uuid" "github.com/pkg/errors" ) -func Available() bool { - return true -} - -type TapManager struct { - logger *slog.Logger - - tapctlPath string -} - -func NewTapManager(logger *slog.Logger) (*TapManager, error) { - tapctlPath := `C:\Program Files\OpenVPN\bin\tapctl.exe` - _, err := os.Stat(tapctlPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - logger.Warn("Required OpenVPN tap networking Windows drivers do not appear to be installed. The easiest way to get them is to install OpenVPN: https://openvpn.net/community-downloads/") - } - return nil, errors.Wrapf(err, "stat tapctl path '%v'", tapctlPath) - } - - return &TapManager{ - logger: logger, - - tapctlPath: tapctlPath, - }, nil -} - -// We need some sort of format to avoid conflicting with other Windows interfaces. -var tapNameRegexp = regexp.MustCompile(`^LinskTap-\d+$`) - -func NewRandomTapName() string { - return fmt.Sprintf("LinskTap-%v", time.Now().UnixNano()) -} - -func (tm *TapManager) CreateNewTap(tapName string) error { - err := ValidateTapName(tapName) - if err != nil { - return errors.Wrap(err, "validate tap name") - } - - out, err := exec.Command(tm.tapctlPath, "create", "--name", tapName).CombinedOutput() - if err != nil { - return errors.Wrapf(err, "exec tapctl create cmd (out '%v')", utils.ClearUnprintableChars(string(out), false)) - } - - tm.logger.Info("Created network tap", "name", tapName) - - return nil -} - -func ValidateTapName(s string) error { - if !tapNameRegexp.MatchString(s) { - return fmt.Errorf("invalid tap name '%v'", s) - } - - return nil -} - -func (tm *TapManager) DeleteTap(name string) error { - stderr := bytes.NewBuffer(nil) - cmd := exec.Command(tm.tapctlPath, "list") - cmd.Stderr = stderr - tapList, err := cmd.Output() - if err != nil { - return errors.Wrapf(err, "exec tapctl list cmd (out '%v')", utils.ClearUnprintableChars(stderr.String(), false)) - } - - for _, line := range strings.Split(string(tapList), "\n") { - if line == "" { - continue - } - - line = strings.ReplaceAll(line, "\t", " ") - line = utils.ClearUnprintableChars(line, false) - - split := strings.Split(line, " ") - if want, have := 2, len(split); want > have { - return fmt.Errorf("bad tap list item split length: want %v > have %v (line '%v')", want, have, line) - } - - if name != split[1] { - continue - } - - lineTapUUIDStr := strings.TrimPrefix(split[0], "{") - lineTapUUIDStr = strings.TrimSuffix(lineTapUUIDStr, "}") - lineTapUUID, err := uuid.Parse(lineTapUUIDStr) - if err != nil { - return errors.Wrapf(err, "parse found line tap uuid (value '%v', line '%v')", lineTapUUIDStr, line) - } - - deleteOut, err := exec.Command(tm.tapctlPath, "delete", "{"+lineTapUUID.String()+"}").CombinedOutput() - if err != nil { - return errors.Wrapf(err, "exec tapctl delete (out '%v')", utils.ClearUnprintableChars(string(deleteOut), false)) - } - - tm.logger.Info("Deleted network tap", "name", name) - - return nil - } - - return ErrTapNotFound -} - -func (tm *TapManager) ConfigureNet(tapName string, hostCIDR string) error { - err := ValidateTapName(tapName) - if err != nil { - return errors.Wrap(err, "validate tap name") - } - - ip, _, err := net.ParseCIDR(hostCIDR) - if err != nil { - return errors.Wrap(err, "parse cidr") - } - - if !utils.IsIPv6IP(ip) { - return fmt.Errorf("ipv6 is accepted only (have '%v')", ip) - } - - out, err := exec.Command("netsh", "interface", "ipv6", "set", "address", shellescape.Quote(tapName), shellescape.Quote(hostCIDR)).CombinedOutput() - if err != nil { - return errors.Wrapf(err, "exec netsh cmd (out '%v')", utils.ClearUnprintableChars(string(out), false)) - } - - tm.logger.Info("Configured network tap", "name", tapName, "cidr", hostCIDR) - - return nil -} - type TapNet struct { HostIP net.IP GuestIP net.IP