QEMU host

QEMU host
This commit is contained in:
Kroese 2023-04-26 21:26:04 +02:00 committed by GitHub
commit 9792ae6615
12 changed files with 41 additions and 302 deletions

View File

@ -8,7 +8,3 @@ updates:
directory: /
schedule:
interval: weekly
- package-ecosystem: gomod
directory: /serial
schedule:
interval: daily

View File

@ -11,4 +11,4 @@ jobs:
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
SHELLCHECK_OPTS: -x -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064
SHELLCHECK_OPTS: -x -e SC2001 -e SC2002 -e SC2223 -e SC2034 -e SC2064

View File

@ -5,6 +5,7 @@ on:
- master
paths:
- readme.md
- README.md
- .github/workflows/hub.yml
jobs:

View File

@ -4,11 +4,15 @@ on:
- master
paths:
- '**/*.sh'
- '.github/workflows/test.yml'
- '.github/workflows/check.yml'
pull_request:
paths:
- '**/*.sh'
- '.github/workflows/test.yml'
- '.github/workflows/check.yml'
name: "Test"
permissions: {}

View File

@ -1,10 +1,10 @@
FROM golang AS builder
COPY serial/ /src/serial/
WORKDIR /src/serial
FROM golang as builder
WORKDIR /
RUN git clone https://github.com/qemu-tools/qemu-host.git
WORKDIR /qemu-host/src
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /src/serial/main .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /qemu-host/src/main .
FROM debian:bookworm-slim
@ -34,12 +34,12 @@ RUN apt-get update && apt-get -y upgrade && \
COPY run/*.sh /run/
COPY agent/*.sh /agent/
COPY --from=builder /src/serial/main /run/serial.bin
COPY --from=builder /qemu-host/src/main /run/host.bin
RUN ["chmod", "+x", "/run/run.sh"]
RUN ["chmod", "+x", "/run/check.sh"]
RUN ["chmod", "+x", "/run/server.sh"]
RUN ["chmod", "+x", "/run/serial.bin"]
RUN ["chmod", "+x", "/run/host.bin"]
VOLUME /storage
@ -62,8 +62,8 @@ ENV VERSION=$VERSION_ARG
LABEL org.opencontainers.image.created=${DATE_ARG}
LABEL org.opencontainers.image.revision=${BUILD_ARG}
LABEL org.opencontainers.image.version=${VERSION_ARG}
LABEL org.opencontainers.image.url=https://hub.docker.com/r/kroese/virtual-dsm/
LABEL org.opencontainers.image.source=https://github.com/kroese/virtual-dsm/
LABEL org.opencontainers.image.url=https://hub.docker.com/r/kroese/virtual-dsm/
HEALTHCHECK --interval=30s --retries=1 CMD /run/check.sh

View File

@ -182,4 +182,4 @@ Based on an [article](https://jxcn.org/2022/04/vdsm-first-try/) by JXCN.
## Disclaimer
Only run this container on Synology hardware, any other use is not permitted and might not be legal according to their terms.
Only run this container on Synology hardware, any other use is not permitted and might not be legal.

View File

@ -81,5 +81,5 @@ set +m
if (( KERNEL > 4 )); then
pidwait -F "${_QEMU_PID}" & wait $!
else
tail --pid "$(cat ${_QEMU_PID})" --follow /dev/null & wait $!
tail --pid "$(cat "${_QEMU_PID}")" --follow /dev/null & wait $!
fi

View File

@ -4,12 +4,11 @@ set -eu
# Docker environment variables
: ${HOST_CPU:=''}
: ${HOST_BUILD:='42962'}
: ${HOST_VERSION:='2.6.1-12139'}
: ${HOST_TIMESTAMP:='1679863686'}
: ${HOST_SERIAL:='0000000000000'}
: ${GUEST_SERIAL:='0000000000000'}
: ${GUEST_UUID:='ba13a19a-c0c1-4fef-9346-915ed3b98341'}
: ${HOST_BUILD:=''}
: ${HOST_SERIAL:=''}
: ${GUEST_SERIAL:=''}
: ${HOST_VERSION:=''}
: ${HOST_TIMESTAMP:=''}
if [ -z "$HOST_CPU" ]; then
HOST_CPU=$(lscpu | sed -nr '/Model name/ s/.*:\s*(.*) @ .*/\1/p' | sed ':a;s/ / /;ta' | sed s/"(R)"//g | sed 's/[^[:alnum:] ]\+/ /g' | sed 's/ */ /g')
@ -21,14 +20,24 @@ else
HOST_CPU="QEMU, Virtual CPU, X86_64"
fi
./run/serial.bin -cpu="${CPU_CORES}" \
-cpu_arch="${HOST_CPU}" \
-hostsn="${HOST_SERIAL}" \
-guestsn="${GUEST_SERIAL}" \
-vmmts="${HOST_TIMESTAMP}" \
-vmmversion="${HOST_VERSION}" \
-buildnumber="${HOST_BUILD}" \
-guestuuid="${GUEST_UUID}" > /dev/null 2>&1 &
HOST_ARGS=()
HOST_ARGS+=("-cpu_arch=${HOST_CPU}")
[ -n "$CPU_CORES" ] && HOST_ARGS+=("-cpu=${CPU_CORES}")
[ -n "$HOST_BUILD" ] && HOST_ARGS+=("-build=${HOST_BUILD}")
[ -n "$HOST_SERIAL" ] && HOST_ARGS+=("-hostsn=${HOST_SERIAL}")
[ -n "$HOST_TIMESTAMP" ] && HOST_ARGS+=("-ts=${HOST_TIMESTAMP}")
[ -n "$GUEST_SERIAL" ] && HOST_ARGS+=("-guestsn=${GUEST_SERIAL}")
[ -n "$HOST_VERSION" ] && HOST_ARGS+=("-version=${HOST_VERSION}")
if [ "$DEBUG" = "Y" ]; then
echo -n "./run/host.bin "
echo "${HOST_ARGS[*]}" && echo
fi
./run/host.bin "${HOST_ARGS[@]}" > /dev/null 2>&1 &
# Configure serial ports
SERIAL_OPTS="\
-serial mon:stdio \

View File

@ -1 +0,0 @@
package main

View File

@ -1,5 +0,0 @@
module vdsm-serial
go 1.20
require github.com/gorilla/mux v1.8.0

View File

@ -1,2 +0,0 @@
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=

View File

@ -1,263 +0,0 @@
package main
import (
"fmt"
"log"
"net"
"flag"
"bytes"
"strconv"
"net/http"
"math/rand"
"encoding/binary"
"github.com/gorilla/mux"
)
type REQ struct {
RandID int64
GuestUUID [16]byte
GuestID int64
IsReq int32
IsResp int32
NeedResponse int32
ReqLength int32
RespLength int32
CommandID int32
SubCommand int32
Reserve int32
}
var GuestCPUs = flag.Int("cpu", 1, "Num of Guest cpu")
var Cluster_UUID = "3bdea92b-68f4-4fe9-aa4b-d645c3c63864"
var HostDSMfixNumber = flag.Int("fixNumber", 0, "Fix Number of Host")
var VMMTimestamp = flag.Int("vmmts", 1679863686, "VMM Timestamp")
var VMMVersion = flag.String("vmmversion", "2.6.1-12139", "VMM version")
var HostSN = flag.String("hostsn", "0000000000000", "Host SN, 13 bytes")
var GuestSN = flag.String("guestsn", "0000000000000", "Guest SN, 13 bytes")
var HostDSMBuildNumber = flag.Int("buildnumber", 42962, "Build Number of Host")
var GuestCPU_ARCH = flag.String("cpu_arch", "QEMU, Virtual CPU, X86_64", "CPU arch")
var GuestUUID = flag.String("guestuuid", "ba13a19a-c0c1-4fef-9346-915ed3b98341", "Guest UUID")
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.Fatalln("Error listening", err.Error())
return
}
log.Println("Start listen on " + *ListenAddr)
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalln("Error on accept", err.Error())
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)
if err != nil {
log.Println("Error on read", err.Error())
return
}
if len != 4096 {
log.Printf("Read %d Bytes, not 4096\n", len)
// something wrong, close and wait for reconnect
conn.Close()
return
}
go process_req(buf, conn)
//log.Printf("Read %d Bytes\n%#v\n", len, string(buf[:len]))
}
}
var commandsName = map[int]string{
3: "Guest Power info",
4: "Host DSM version",
5: "Guest SN",
7: "Guest CPU info",
9: "Host DSM version",
8: "VMM version",
10: "Get Guest Info",
11: "Guest UUID",
12: "Cluster UUID",
13: "Host SN",
16: "Update Deadline",
17: "Guest Timestamp",
}
func process_req(buf []byte, conn net.Conn) {
var req REQ
var data string
err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, &req)
if err != nil {
log.Printf("Error on decode %s\n", err)
return
}
if req.IsReq == 1 {
data = string(buf[64 : 64+req.ReqLength])
} else if req.IsResp == 1 {
data = string(buf[64 : 64+req.RespLength])
}
// log.Printf("%#v\n", req)
log.Printf("Command: %s from Guest:%d \n", commandsName[int(req.CommandID)], req.GuestID)
if data != "" {
log.Printf("Info: %s\n", data)
}
// Hard code of command
switch req.CommandID {
case 3:
// Guest start/reboot
case 4:
// Host DSM version
data = fmt.Sprintf(`{"buildnumber":%d,"smallfixnumber":%d}`, *HostDSMBuildNumber, *HostDSMfixNumber)
case 5:
// Guest SN
data = *GuestSN
case 7:
// CPU info
// {"cpuinfo":"QEMU, Virtual CPU, X86_64, 1" "vcpu_num":1}
data = fmt.Sprintf(`{"cpuinfo":"%s","vcpu_num":%d}`,
*GuestCPU_ARCH+", "+strconv.Itoa(*GuestCPUs), *GuestCPUs)
case 8:
data = fmt.Sprintf(`{"id":"Virtualization","name":"Virtual Machine Manager","timestamp":%d,"version":"%s"}`,
*VMMTimestamp, *VMMVersion)
case 9:
// Version Info
case 10:
// Guest Info
case 11:
// Guest UUID
data = *GuestUUID
case 12:
// cluster UUID
data = Cluster_UUID
case 13:
// Host SN
data = *HostSN
case 16:
// Update Dead line time, always 0x7fffffffffffffff
data = "9223372036854775807"
case 17:
// TimeStamp
default:
log.Printf("No handler for this command %d\n", req.CommandID)
return
}
// if it's a req and need response
if req.IsReq == 1 && req.NeedResponse == 1 {
buf = make([]byte, 0, 4096)
writer := bytes.NewBuffer(buf)
req.IsResp = 1
req.IsReq = 0
req.ReqLength = 0
req.RespLength = int32(len([]byte(data)) + 1)
log.Printf("Response data: %s\n", data)
// write to buf
binary.Write(writer, binary.LittleEndian, &req)
writer.Write([]byte(data))
res := writer.Bytes()
// full fill 4096
buf = make([]byte, 4096, 4096)
copy(buf, res)
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
}