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