Clean segregated file share backends

This commit is contained in:
AlexSSD7 2023-08-31 16:19:03 +01:00
commit 40aa08c86c
6 changed files with 320 additions and 0 deletions

19
share/backend.go Normal file
View file

@ -0,0 +1,19 @@
package share
import "context"
type NewBackendFunc func(uc *UserConfiguration) (Backend, *VMShareOptions, error)
type Backend interface {
Apply(ctx context.Context, sharePWD string, vc *VMShareContext) (string, error)
}
var backends = map[string]NewBackendFunc{
"ftp": NewFTPBackend,
"smb": NewSMBBackend,
}
// Will return nil if no backend is found.
func GetBackend(id string) NewBackendFunc {
return backends[id]
}

57
share/cfg.go Normal file
View file

@ -0,0 +1,57 @@
package share
import (
"fmt"
"net"
"log/slog"
)
var defaultListenIP = net.ParseIP("127.0.0.1")
func GetDefaultListenIPStr() string {
return defaultListenIP.String()
}
type UserConfiguration struct {
listenIP net.IP
ftpExtIP net.IP
smbExtMode bool
}
type RawUserConfiguration struct {
ListenIP string
// Backend-specific
FTPExtIP string
SMBExtMode bool
}
func (rc RawUserConfiguration) Process(backend string, warnLogger *slog.Logger) (*UserConfiguration, error) {
listenIP := net.ParseIP(rc.ListenIP)
if listenIP == nil {
return nil, fmt.Errorf("invalid listen ip '%v'", rc.ListenIP)
}
ftpExtIP := net.ParseIP(rc.FTPExtIP)
if ftpExtIP == nil {
return nil, fmt.Errorf("invalid ftp ext ip '%v'", rc.FTPExtIP)
}
if backend == "ftp" {
if !listenIP.Equal(defaultListenIP) && ftpExtIP.Equal(defaultListenIP) {
slog.Warn("No external FTP IP address via --ftp-extip was configured. This is a requirement in almost all scenarios if you want to connect remotely.")
}
} else {
if !ftpExtIP.Equal(defaultListenIP) {
slog.Warn("FTP external IP address specification is ineffective with non-FTP backends", "selected", backend)
}
}
return &UserConfiguration{
listenIP: listenIP,
ftpExtIP: ftpExtIP,
smbExtMode: rc.SMBExtMode,
}, nil
}

67
share/ftp.go Normal file
View file

@ -0,0 +1,67 @@
package share
import (
"context"
"fmt"
"net"
"log/slog"
"github.com/AlexSSD7/linsk/vm"
"github.com/pkg/errors"
)
type FTPBackend struct {
sharePort uint16
passivePortCount uint16
extIP net.IP
}
func NewFTPBackend(uc *UserConfiguration) (Backend, *VMShareOptions, error) {
// TODO: Make this changeable?
passivePortCount := uint16(9)
sharePort, err := getNetworkSharePort(9)
if err != nil {
return nil, nil, errors.Wrap(err, "get network share port")
}
ports := []vm.PortForwardingRule{{
HostIP: uc.listenIP,
HostPort: sharePort,
VMPort: 21,
}}
for i := uint16(0); i < passivePortCount; i++ {
p := sharePort + 1 + i
ports = append(ports, vm.PortForwardingRule{
HostIP: uc.listenIP,
HostPort: p,
VMPort: p,
})
}
return &FTPBackend{
sharePort: sharePort,
passivePortCount: passivePortCount,
extIP: uc.ftpExtIP,
}, &VMShareOptions{
Ports: ports,
EnableTap: false,
}, nil
}
func (b *FTPBackend) Apply(ctx context.Context, sharePWD string, vc *VMShareContext) (string, error) {
if vc.NetTapCtx != nil {
return "", fmt.Errorf("net taps are unsupported in ftp")
}
err := vc.FileManager.StartFTP(sharePWD, b.sharePort+1, b.passivePortCount, b.extIP)
if err != nil {
return "", errors.Wrap(err, "start ftp server")
}
slog.Info("Started the network share successfully", "type", "ftp")
return "ftp://" + b.extIP.String() + ":" + fmt.Sprint(b.sharePort), nil
}

72
share/ports.go Normal file
View file

@ -0,0 +1,72 @@
package share
import (
"fmt"
"net"
"os"
"syscall"
"github.com/pkg/errors"
)
func getNetworkSharePort(subsequent uint16) (uint16, error) {
return getClosestAvailPortWithSubsequent(9000, subsequent)
}
func getClosestAvailPortWithSubsequent(port uint16, subsequent uint16) (uint16, error) {
// We use 10 as port range
for i := port; i < 65535; i += subsequent {
ok, err := checkPortAvailable(i, subsequent)
if err != nil {
return 0, errors.Wrapf(err, "check port available (%v)", i)
}
if ok {
return i, nil
}
}
return 0, fmt.Errorf("no available port (with %v subsequent ones) found", subsequent)
}
func checkPortAvailable(port uint16, subsequent uint16) (bool, error) {
if port+subsequent < port {
return false, fmt.Errorf("subsequent ports exceed allowed port range")
}
if subsequent == 0 {
ln, err := net.Listen("tcp", ":"+fmt.Sprint(port))
if err != nil {
if opErr, ok := err.(*net.OpError); ok {
if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
if sysErr.Err == syscall.EADDRINUSE {
// The port is in use.
return false, nil
}
}
}
return false, errors.Wrapf(err, "net listen (port %v)", port)
}
err = ln.Close()
if err != nil {
return false, errors.Wrap(err, "close ephemeral listener")
}
return true, nil
}
for i := uint16(0); i < subsequent; i++ {
ok, err := checkPortAvailable(port+i, 0)
if err != nil {
return false, errors.Wrapf(err, "check subsequent port available (base: %v, seq: %v)", port, i)
}
if !ok {
return false, nil
}
}
return true, nil
}

82
share/smb.go Normal file
View file

@ -0,0 +1,82 @@
package share
import (
"context"
"fmt"
"net"
"runtime"
"strings"
"log/slog"
"github.com/AlexSSD7/linsk/vm"
"github.com/pkg/errors"
)
const smbPort = 445
// TODO: Test SMB backend on macOS.
type SMBBackend struct {
listenIP net.IP
sharePort *uint16
}
func NewSMBBackend(uc *UserConfiguration) (Backend, *VMShareOptions, error) {
var ports []vm.PortForwardingRule
var sharePortPtr *uint16
if !uc.smbExtMode {
sharePort, err := getNetworkSharePort(0)
if err != nil {
return nil, nil, errors.Wrap(err, "get network share port")
}
sharePortPtr = &sharePort
ports = append(ports, vm.PortForwardingRule{
HostIP: uc.listenIP,
HostPort: sharePort,
VMPort: smbPort,
})
}
return &SMBBackend{
listenIP: uc.listenIP,
sharePort: sharePortPtr,
}, &VMShareOptions{
Ports: ports,
EnableTap: uc.smbExtMode,
}, nil
}
func (b *SMBBackend) Apply(ctx context.Context, sharePWD string, vc *VMShareContext) (string, error) {
if b.sharePort != nil && vc.NetTapCtx != nil {
return "", fmt.Errorf("conflict: configured to use a forwarded port but a net tap configuration was detected")
}
if b.sharePort == nil && vc.NetTapCtx == nil {
return "", fmt.Errorf("no net tap configuration found")
}
err := vc.FileManager.StartSMB(sharePWD)
if err != nil {
return "", errors.Wrap(err, "start smb server")
}
slog.Info("Started the network share successfully", "type", "smb", "ext", vc.NetTapCtx != nil)
var shareURL string
if b.sharePort != nil {
shareURL = "smb://" + net.JoinHostPort(b.listenIP.String(), fmt.Sprint(*b.sharePort))
} else if vc.NetTapCtx != nil {
if runtime.GOOS == "windows" {
shareURL = `\\` + strings.ReplaceAll(vc.NetTapCtx.Net.GuestIP.String(), ":", "-") + ".ipv6-literal.net" + `\linsk`
} else {
shareURL = "smb://" + net.JoinHostPort(vc.NetTapCtx.Net.GuestIP.String(), fmt.Sprint(smbPort))
}
} else {
return "", fmt.Errorf("no port forwarding and net tap configured")
}
return shareURL, nil
}

23
share/types.go Normal file
View file

@ -0,0 +1,23 @@
package share
import (
"github.com/AlexSSD7/linsk/nettap"
"github.com/AlexSSD7/linsk/vm"
)
type NetTapRuntimeContext struct {
Manager *nettap.TapManager
Name string
Net nettap.TapNet
}
type VMShareOptions struct {
Ports []vm.PortForwardingRule
EnableTap bool
}
type VMShareContext struct {
Instance *vm.VM
FileManager *vm.FileManager
NetTapCtx *NetTapRuntimeContext
}