Clean segregated file share backends
This commit is contained in:
parent
cbce4f1dfc
commit
40aa08c86c
6 changed files with 320 additions and 0 deletions
19
share/backend.go
Normal file
19
share/backend.go
Normal 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
57
share/cfg.go
Normal 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
67
share/ftp.go
Normal 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
72
share/ports.go
Normal 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
82
share/smb.go
Normal 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
23
share/types.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue