Add support for Luckfox PicoKVM

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2025-08-07 14:26:01 +08:00
parent 3e7d8fb0f5
commit 8fbd6bcf0d
114 changed files with 4676 additions and 3270 deletions

279
native_vpn.go Normal file
View File

@@ -0,0 +1,279 @@
package kvm
import (
"encoding/json"
"errors"
"fmt"
"net"
"os"
"os/exec"
"sync"
"time"
)
var (
vpnCmd *exec.Cmd
vpnCmdLock = &sync.Mutex{}
)
var vpnSocketConn net.Conn
var vpnOngoingRequests = make(map[int32]chan *CtrlResponse)
var vpnLock = &sync.Mutex{}
func CallVpnCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
vpnLock.Lock()
defer vpnLock.Unlock()
ctrlAction := CtrlAction{
Action: action,
Seq: seq,
Params: params,
}
responseChan := make(chan *CtrlResponse)
vpnOngoingRequests[seq] = responseChan
seq++
jsonData, err := json.Marshal(ctrlAction)
if err != nil {
delete(vpnOngoingRequests, ctrlAction.Seq)
return nil, fmt.Errorf("error marshaling ctrl action: %w", err)
}
scopedLogger := vpnLogger.With().
Str("action", ctrlAction.Action).
Interface("params", ctrlAction.Params).Logger()
scopedLogger.Debug().Msg("sending vpn ctrl action")
err = WriteVpnCtrlMessage(jsonData)
if err != nil {
delete(vpnOngoingRequests, ctrlAction.Seq)
return nil, ErrorfL(&scopedLogger, "error writing vpn ctrl message", err)
}
select {
case response := <-responseChan:
delete(vpnOngoingRequests, seq)
if response.Error != "" {
return nil, ErrorfL(
&scopedLogger,
"error vpn response: %s",
errors.New(response.Error),
)
}
return response, nil
case <-time.After(10 * time.Second):
close(responseChan)
delete(vpnOngoingRequests, seq)
return nil, ErrorfL(&scopedLogger, "timeout waiting for response", nil)
}
}
func WriteVpnCtrlMessage(message []byte) error {
if vpnSocketConn == nil {
return fmt.Errorf("vpn socket not conn ected")
}
_, err := vpnSocketConn.Write(message)
return err
}
var vpnCtrlSocketListener net.Listener
var vpnCtrlClientConnected = make(chan struct{})
func waitVpnCtrlClientConnected() {
<-vpnCtrlClientConnected
}
func StartVpnSocketServer(socketPath string, handleClient func(net.Conn), isCtrl bool) net.Listener {
scopedLogger := vpnLogger.With().
Str("socket_path", socketPath).
Logger()
// Remove the socket file if it already exists
if _, err := os.Stat(socketPath); err == nil {
if err := os.Remove(socketPath); err != nil {
scopedLogger.Warn().Err(err).Msg("failed to remove existing socket file")
os.Exit(1)
}
}
listener, err := net.Listen("unixpacket", socketPath)
if err != nil {
scopedLogger.Warn().Err(err).Msg("failed to start server")
os.Exit(1)
}
scopedLogger.Info().Msg("server listening")
go func() {
for {
conn, err := listener.Accept()
if err != nil {
scopedLogger.Warn().Err(err).Msg("failed to accept socket")
continue
}
if isCtrl {
// check if the channel is closed
select {
case <-vpnCtrlClientConnected:
scopedLogger.Debug().Msg("vpn ctrl client reconnected")
default:
close(vpnCtrlClientConnected)
scopedLogger.Debug().Msg("first vpn ctrl socket client connected")
}
}
//conn.Write([]byte("[handleVpnCtrlClient]vpn sock test"))
go handleClient(conn)
}
}()
return listener
}
func StartVpnCtrlSocketServer() {
vpnCtrlSocketListener = StartVpnSocketServer("/var/run/kvm_vpn.sock", handleVpnCtrlClient, true)
vpnLogger.Debug().Msg("vpn ctrl sock started")
}
func handleVpnCtrlClient(conn net.Conn) {
defer conn.Close()
scopedLogger := vpnLogger.With().
Str("addr", conn.RemoteAddr().String()).
Str("type", "vpn_ctrl").
Logger()
scopedLogger.Info().Msg("vpn socket client connected")
if vpnSocketConn != nil {
scopedLogger.Debug().Msg("closing existing vpn socket connection")
vpnSocketConn.Close()
}
vpnSocketConn = conn
readBuf := make([]byte, 4096)
for {
n, err := conn.Read(readBuf)
if err != nil {
scopedLogger.Warn().Err(err).Msg("error reading from vpn sock")
break
}
readMsg := string(readBuf[:n])
vpnResp := CtrlResponse{}
err = json.Unmarshal([]byte(readMsg), &vpnResp)
if err != nil {
scopedLogger.Warn().Err(err).Str("data", readMsg).Msg("error parsing vpn sock msg")
continue
}
scopedLogger.Trace().Interface("data", vpnResp).Msg("vpn sock msg")
if vpnResp.Seq != 0 {
responseChan, ok := vpnOngoingRequests[vpnResp.Seq]
if ok {
responseChan <- &vpnResp
}
}
switch vpnResp.Event {
case "vpn_display_update":
HandleVpnDisplayUpdateMessage(vpnResp)
}
}
scopedLogger.Debug().Msg("vpn sock disconnected")
}
func startVpnBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
vpnCmdLock.Lock()
defer vpnCmdLock.Unlock()
cmd, err := startVpnBinary(binaryPath)
if err != nil {
return nil, err
}
vpnCmd = cmd
return cmd, nil
}
func restartVpnBinary(binaryPath string) error {
time.Sleep(10 * time.Second)
// restart the binary
vpnLogger.Info().Msg("restarting vpn_video binary")
cmd, err := startVpnBinary(binaryPath)
if err != nil {
vpnLogger.Warn().Err(err).Msg("failed to restart binary")
}
vpnCmd = cmd
return err
}
func superviseVpnBinary(binaryPath string) error {
vpnCmdLock.Lock()
defer vpnCmdLock.Unlock()
if vpnCmd == nil || vpnCmd.Process == nil {
return restartVpnBinary(binaryPath)
}
err := vpnCmd.Wait()
if err == nil {
vpnLogger.Info().Err(err).Msg("kvm_vpn binary exited with no error")
} else if exiterr, ok := err.(*exec.ExitError); ok {
vpnLogger.Warn().Int("exit_code", exiterr.ExitCode()).Msg("kvm_vpn binary exited with error")
} else {
vpnLogger.Warn().Err(err).Msg("kvm_vpn binary exited with unknown error")
}
return restartVpnBinary(binaryPath)
}
func ExtractAndRunVpnBin() error {
binaryPath := "/userdata/picokvm/bin/kvm_vpn"
// Make the binary executable
if err := os.Chmod(binaryPath, 0755); err != nil {
return fmt.Errorf("failed to make binary executable: %w", err)
}
// Run the binary in the background
cmd, err := startVpnBinaryWithLock(binaryPath)
if err != nil {
return fmt.Errorf("failed to start binary: %w", err)
}
// check if the binary is still running every 10 seconds
go func() {
for {
select {
case <-appCtx.Done():
vpnLogger.Info().Msg("stopping vpn binary supervisor")
return
default:
err := superviseVpnBinary(binaryPath)
if err != nil {
vpnLogger.Warn().Err(err).Msg("failed to supervise vpn binary")
time.Sleep(1 * time.Second) // Add a short delay to prevent rapid successive calls
}
}
}
}()
go func() {
<-appCtx.Done()
vpnLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
err := cmd.Process.Kill()
if err != nil {
vpnLogger.Warn().Err(err).Msg("failed to kill process")
return
}
}()
vpnLogger.Info().Int("pid", cmd.Process.Pid).Msg("kvm_vpn binary started")
return nil
}