banger/internal/vsockping/vsockping.go
Thales Maciel 08ef706e3f
Add vsock-backed SSH session reminders
Remind users when a VM is still running after 	hanger vm ssh exits instead of silently dropping them back to the host shell.\n\nAttach a Firecracker vsock device to each VM, persist the host vsock path/CID,\nadd a new guest-side banger-vsock-pingd responder to the runtime bundle and both\nimage-build paths, and expose a vm.ping RPC that the CLI and TUI call after SSH\nreturns. Doctor and start/build preflight now validate the helper plus\n/dev/vhost-vsock so the feature fails early and clearly.\n\nValidated with go mod tidy, bash -n customize.sh, git diff --check, make build,\nand GOCACHE=/tmp/banger-gocache go test ./... outside the sandbox because the\ndaemon tests need real Unix/UDP sockets. Rebuild the image/rootfs used for new\nVMs so the guest ping service is present.
2026-03-18 20:14:51 -03:00

105 lines
2.4 KiB
Go

package vsockping
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"net"
"strings"
"time"
sdkvsock "github.com/firecracker-microvm/firecracker-go-sdk/vsock"
)
const (
Port uint32 = 42070
RequestLine = "PING\n"
ResponseLine = "PONG\n"
GuestBinaryName = "banger-vsock-pingd"
GuestInstallPath = "/usr/local/bin/" + GuestBinaryName
ServiceName = "banger-vsock-pingd.service"
serviceUnit = `[Unit]
Description=Banger vsock ping responder
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/banger-vsock-pingd
Restart=on-failure
RestartSec=1
[Install]
WantedBy=multi-user.target
`
modulesLoadConfig = "vsock\nvmw_vsock_virtio_transport\n"
)
func Ping(ctx context.Context, logger *slog.Logger, socketPath string) error {
conn, err := sdkvsock.DialContext(
ctx,
socketPath,
Port,
sdkvsock.WithRetryTimeout(3*time.Second),
sdkvsock.WithRetryInterval(100*time.Millisecond),
)
if err != nil {
return err
}
defer conn.Close()
if deadline, ok := ctx.Deadline(); ok {
_ = conn.SetDeadline(deadline)
} else {
_ = conn.SetDeadline(time.Now().Add(3 * time.Second))
}
if _, err := io.WriteString(conn, RequestLine); err != nil {
return err
}
line, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
return err
}
if strings.TrimSpace(line) != strings.TrimSpace(ResponseLine) {
return fmt.Errorf("unexpected vsock ping response %q", strings.TrimSpace(line))
}
if logger != nil {
logger.Debug("vsock ping ok", "vsock_path", socketPath, "vsock_port", Port)
}
return nil
}
func ServiceUnit() string {
return serviceUnit
}
func ModulesLoadConfig() string {
return modulesLoadConfig
}
func ReminderMessage(name string) string {
return fmt.Sprintf("session ended; %s is still running (stop it with 'banger vm stop %s')", name, name)
}
func WarningMessage(name string, err error) string {
if err == nil {
return ""
}
return fmt.Sprintf("warning: failed to check whether %s is still running: %v", name, err)
}
func ServeConn(conn net.Conn) error {
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
line, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
return err
}
if strings.TrimSpace(line) != strings.TrimSpace(RequestLine) {
return fmt.Errorf("unexpected request %q", strings.TrimSpace(line))
}
_, err = io.WriteString(conn, ResponseLine)
return err
}