Add non-Windows nettap stubs

This commit is contained in:
AlexSSD7 2023-09-01 14:41:19 +01:00
commit fb6b1ae25e
5 changed files with 196 additions and 142 deletions

View file

@ -87,7 +87,12 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
return 1 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() 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())

View file

@ -4,4 +4,5 @@ import "errors"
var ( var (
ErrTapNotFound = errors.New("tap not found") ErrTapNotFound = errors.New("tap not found")
ErrTapManagerUnimplemented = errors.New("tap manager is implemented on windows only")
) )

39
nettap/impl.go Normal file
View file

@ -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
}

149
nettap/impl_windows.go Normal file
View file

@ -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
}

View file

@ -1,153 +1,13 @@
package nettap package nettap
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"net" "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" "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 { type TapNet struct {
HostIP net.IP HostIP net.IP
GuestIP net.IP GuestIP net.IP