Commit b99719a1 authored by Kroese's avatar Kroese
Browse files

Implemented API for guest communication

This allows to send the shutdown command from the host to the guest
parent 589ec849
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -2,7 +2,9 @@ FROM golang:1.20 AS builder

COPY serial/ /src/serial/
WORKDIR /src/serial

RUN go get -d -v golang.org/x/net/html
RUN go get -d -v github.com/gorilla/mux
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main .

FROM debian:bookworm-20230320-slim
+23 −14
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ set -eu
# Configure QEMU for graceful shutdown

QEMU_MONPORT=7100
QEMU_POWERDOWN_TIMEOUT=30
QEMU_POWERDOWN_TIMEOUT=50
_QEMU_PID=/run/qemu.pid
_QEMU_SHUTDOWN_COUNTER=/run/qemu.counter

@@ -22,24 +22,33 @@ _graceful_shutdown(){
  local QEMU_POWERDOWN_TIMEOUT="${QEMU_POWERDOWN_TIMEOUT:-120}"

  set +e
  echo "Received $1 signal.."
  echo "Received $1 signal, shutting down..."
  echo 0 > "${_QEMU_SHUTDOWN_COUNTER}"

  # Don't send the powerdown signal because vDSM ignores ACPI signals
  # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null

  # Send shutdown command to guest agent tools instead via serial port
  RESPONSE=$(curl -s -m 2 -S http://127.0.0.1:2210/write?command=6 2>&1)

  if [[ ! "${RESPONSE}" =~ "\"success\"" ]] ; then

    echo "Could not send shutdown command to guest, error: $RESPONSE"

    FILE="${IMG}/agent.ver"
    [ ! -f "$FILE" ] && echo "1" > "$FILE"
    AGENT_VERSION=$(cat "${FILE}")

  # Don't send the powerdown signal because Synology ignores it
  # echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null

    if ((AGENT_VERSION < 2)); then
      echo "Please update the agent to allow gracefull shutdowns..."
      pkill -f qemu-system-x86_64
    else
     # Send a NMI interrupt which will be detected by the agent
      # Send a NMI interrupt which will be detected by the kernel
      echo 'nmi' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null
    fi

  fi

  while [ "$(cat ${_QEMU_SHUTDOWN_COUNTER})" -lt "${QEMU_POWERDOWN_TIMEOUT}" ]; do

    # Increase the counter
@@ -54,7 +63,7 @@ _graceful_shutdown(){
    fi
  done

  echo "Killing VM.."
  echo "Quitting..."
  echo 'quit' | nc -q 1 -w 1 localhost "${QEMU_MONPORT}">/dev/null || true

  return
+88 −0
Original line number Diff line number Diff line
@@ -8,6 +8,9 @@ import (
	"log"
	"net"
	"strconv"
	"net/http"
	"math/rand"
	"github.com/gorilla/mux"
)

type REQ struct {
@@ -35,16 +38,27 @@ var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version")
var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp")
var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864"

var ApiPort = flag.String("api", ":2210", "API port")
var ListenAddr = flag.String("addr", "0.0.0.0:12345", "Listen address")

var LastConnection net.Conn

func main() {

	flag.Parse()

	r := mux.NewRouter()
	r.HandleFunc("/", home)
	r.HandleFunc("/write", write)
	go http.ListenAndServe(*ApiPort, r)

	listener, err := net.Listen("tcp", *ListenAddr)

	if err != nil {
		log.Println("Error listening", err.Error())
		return
	}

	log.Println("Start listen on " + *ListenAddr)

	for {
@@ -54,11 +68,15 @@ func main() {
			return
		}
		log.Printf("New connection from %s\n", conn.RemoteAddr().String())

		go incoming_conn(conn)
	}
}

func incoming_conn(conn net.Conn) {

	LastConnection = conn

	for {
		buf := make([]byte, 4096)
		len, err := conn.Read(buf)
@@ -173,3 +191,73 @@ func process_req(buf []byte, conn net.Conn) {
		conn.Write(buf)
	}
}

func home(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte(`{"status": "error", "data": null, "message": "No command specified"}`))
}


func write(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/json")

	var err error
	var commandID int

	query := r.URL.Query()
	commandID, err = strconv.Atoi(query.Get("command"))

	if (err != nil || commandID < 1) {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{"status": "error", "data": null, "message": "Invalid command ID"}`))
		return
	}

	if (send_command((int32)(commandID), 1) == false) {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(`{"status": "error", "data": null, "message": "Failed to send command"}`))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status": "success", "data": null, "message": null}`))
	return
}

func send_command(CommandID int32, SubCommand int32) bool {

	var req REQ

	req.CommandID = CommandID
	req.SubCommand = SubCommand

	req.IsReq = 1
	req.IsResp = 0
	req.ReqLength = 0
	req.RespLength = 0
	req.NeedResponse = 0
	req.GuestID = 10000000
	req.RandID = rand.Int63()

	var buf = make([]byte, 0, 4096)
	var writer = bytes.NewBuffer(buf)

	// write to buf
	binary.Write(writer, binary.LittleEndian, &req)
	res := writer.Bytes()

	// full fill 4096
	buf = make([]byte, 4096, 4096)
	copy(buf, res)

	//log.Printf("Writing command %d\n", CommandID)

	if (LastConnection == nil) { return false }

	LastConnection.Write(buf)
	return true

}