From b437caaf57acd40afe3add745b7f7eb609d98a53 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Sun, 30 Mar 2025 01:38:01 -0400 Subject: [PATCH] working on multiple nics --- go.mod | 2 + go.sum | 5 ++ machine.yaml | 19 +++++ main.go | 36 ++++++++- pkg/utils/utils.go | 23 ++++++ pkg/v1/image/{cmd_create.go => cmd.go} | 53 ++++++++++++++ pkg/v1/image/cmd_load_image.go | 37 ---------- pkg/v1/image/cmd_save_image.go | 31 -------- pkg/v1/image/{utils.go => helpers.go} | 2 +- pkg/v1/system/machine.go | 26 ++++--- pkg/v1/types/accel/accel.go | 39 +++++++++- .../{utils/utils.go => helpers/helpers.go} | 14 ++-- pkg/v1/types/keyboard/keyboard.go | 35 +++++++++ pkg/v1/types/nic/nic.go | 73 +++++++++++++++---- 14 files changed, 286 insertions(+), 109 deletions(-) create mode 100644 machine.yaml create mode 100644 pkg/utils/utils.go rename pkg/v1/image/{cmd_create.go => cmd.go} (62%) delete mode 100644 pkg/v1/image/cmd_load_image.go delete mode 100644 pkg/v1/image/cmd_save_image.go rename pkg/v1/image/{utils.go => helpers.go} (95%) rename pkg/v1/types/{utils/utils.go => helpers/helpers.go} (91%) create mode 100644 pkg/v1/types/keyboard/keyboard.go diff --git a/go.mod b/go.mod index facf79d..eee9cfa 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -25,4 +26,5 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/sys v0.20.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index d4e5863..ba618cc 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/abdfnx/gosh v0.4.0 h1:cBvmHw8yV3oXiAcORpi7Oip+MT6/5HBMOrvpn0cZNYM= github.com/abdfnx/gosh v0.4.0/go.mod h1:MUTJRMc7FpBb/bFnZ2U0hj48zj8284J1cS3AUiIRZ/o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -8,6 +10,7 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -81,3 +84,5 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/machine.yaml b/machine.yaml new file mode 100644 index 0000000..d257fff --- /dev/null +++ b/machine.yaml @@ -0,0 +1,19 @@ +Arch: x86_64 +accel: + accel: "whpx\r" +boot: + menu: false + order: + - c +hda: ./imgs/UbuntuTest.img +m: + size: 4096 +nic: + options: + tap: + ifname: qemu-tap + type: tap +nographic: flag-on +numa: {} +smp: + cpus: 4 diff --git a/main.go b/main.go index 1a0ea82..06f6803 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,8 @@ import ( "github.com/rayaman/go-qemu/pkg/v1/types/smp" "github.com/shirou/gopsutil/v3/process" "golang.org/x/crypto/ssh" + + "sigs.k8s.io/yaml" ) type Credentials struct { @@ -78,7 +80,6 @@ func StartMachine(machine system.Machine) *Controller { var stdout bytes.Buffer var stderr bytes.Buffer cmd := exec.Command(fmt.Sprintf("qemu-system-%v", machine.Arch), machine.Expand()...) - fmt.Println(cmd.String()) cmd.Stdout = &stderr cmd.Stderr = &stdout ctrl.cmd = cmd @@ -137,17 +138,44 @@ func main() { Size: 4096, }, Accel: accel.Accel{ - Accelerator: accel.WHPX, + Accelerator: accel.GetAccelerator(arch.X86_64), }, Nic: nic.NIC{ Type: nic.TAP, - Options: &nic.TapOptions{ - IFName: "qemu-tap", + Options: nic.Options{ + Tap: &nic.TapOptions{ + IFName: "qemu-tap", + }, }, }, NoGraphic: types.Set, HardDiskA: `./imgs/UbuntuTest.img`, } + + data, err := yaml.Marshal(machine) + if err != nil { + panic(err) + } + // Write data to a file + err = os.WriteFile("machine.yaml", data, 0644) + if err != nil { + panic(err) + } + + data, err = os.ReadFile("machine.yaml") + if err != nil { + panic(err) + } + mac := system.Machine{} + err = yaml.Unmarshal(data, &mac) + if err != nil { + panic(err) + } + fmt.Println(machine.Expand()) + // fmt.Println(mac.Expand()) + + fmt.Println(accel.GetAccelerator(arch.X86_64)) + os.Exit(0) ctrl := StartMachine(machine) fmt.Println("Press enter to stop machine") diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..7c2a045 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,23 @@ +package utils + +import "net" + +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} + +// converts bool to on/off format +var SW = map[bool]string{ + true: "on", + false: "off", +} diff --git a/pkg/v1/image/cmd_create.go b/pkg/v1/image/cmd.go similarity index 62% rename from pkg/v1/image/cmd_create.go rename to pkg/v1/image/cmd.go index 2f6c93d..1933661 100644 --- a/pkg/v1/image/cmd_create.go +++ b/pkg/v1/image/cmd.go @@ -1,6 +1,7 @@ package image import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -8,6 +9,7 @@ import ( "strings" "github.com/abdfnx/gosh" + "github.com/rayaman/go-qemu/pkg/v1/types" "github.com/rayaman/go-qemu/pkg/v1/types/disk" ) @@ -59,3 +61,54 @@ func Create(i Image, size disk.Size, opts ...Options) error { return nil } + +// Loads a saved image structure into memory +func LoadImage(path string) (Image, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var h = &holder{} + + err = json.Unmarshal(data, h) + if err != nil { + return nil, err + } + + data, err = json.Marshal(h.Image) + if err != nil { + return nil, err + } + + t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface() + err = json.Unmarshal(data, t) + if err != nil { + return nil, err + } + + return t, nil +} + +// Saves an image structure to disk +func SaveImage(path string, img Image) error { + + data, err := json.MarshalIndent(holder{ + Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""), + Image: img, + }, "", "\t") + + if err != nil { + return err + } + + file, err := os.Create(path) + + if err != nil { + return err + } + + _, err = file.Write(data) + + return err +} diff --git a/pkg/v1/image/cmd_load_image.go b/pkg/v1/image/cmd_load_image.go deleted file mode 100644 index cc71e5e..0000000 --- a/pkg/v1/image/cmd_load_image.go +++ /dev/null @@ -1,37 +0,0 @@ -package image - -import ( - "encoding/json" - "os" - "reflect" - - "github.com/rayaman/go-qemu/pkg/v1/types" -) - -// Loads a saved image structure into memory -func LoadImage(path string) (Image, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - var h = &holder{} - - err = json.Unmarshal(data, h) - if err != nil { - return nil, err - } - - data, err = json.Marshal(h.Image) - if err != nil { - return nil, err - } - - t := reflect.New(reflect.TypeOf(types.GetTypes()[h.Format])).Interface() - err = json.Unmarshal(data, t) - if err != nil { - return nil, err - } - - return t, nil -} diff --git a/pkg/v1/image/cmd_save_image.go b/pkg/v1/image/cmd_save_image.go deleted file mode 100644 index 91519f4..0000000 --- a/pkg/v1/image/cmd_save_image.go +++ /dev/null @@ -1,31 +0,0 @@ -package image - -import ( - "encoding/json" - "os" - "reflect" - "strings" -) - -// Saves an image structure to disk -func SaveImage(path string, img Image) error { - - data, err := json.MarshalIndent(holder{ - Format: strings.ReplaceAll(strings.ToLower(reflect.TypeOf(img).String()), "*formats.", ""), - Image: img, - }, "", "\t") - - if err != nil { - return err - } - - file, err := os.Create(path) - - if err != nil { - return err - } - - _, err = file.Write(data) - - return err -} diff --git a/pkg/v1/image/utils.go b/pkg/v1/image/helpers.go similarity index 95% rename from pkg/v1/image/utils.go rename to pkg/v1/image/helpers.go index 43f7d3a..1729ec2 100644 --- a/pkg/v1/image/utils.go +++ b/pkg/v1/image/helpers.go @@ -5,7 +5,7 @@ import ( "reflect" "strings" - "github.com/rayaman/go-qemu/pkg/v1/types/utils" + "github.com/rayaman/go-qemu/pkg/utils" ) func getOptions(m map[string]string) string { diff --git a/pkg/v1/system/machine.go b/pkg/v1/system/machine.go index cac6592..553f8fd 100644 --- a/pkg/v1/system/machine.go +++ b/pkg/v1/system/machine.go @@ -11,11 +11,12 @@ import ( "github.com/rayaman/go-qemu/pkg/v1/types/arch" "github.com/rayaman/go-qemu/pkg/v1/types/boot" "github.com/rayaman/go-qemu/pkg/v1/types/chip" + "github.com/rayaman/go-qemu/pkg/v1/types/helpers" + "github.com/rayaman/go-qemu/pkg/v1/types/keyboard" "github.com/rayaman/go-qemu/pkg/v1/types/memory" "github.com/rayaman/go-qemu/pkg/v1/types/nic" "github.com/rayaman/go-qemu/pkg/v1/types/numa" "github.com/rayaman/go-qemu/pkg/v1/types/smp" - "github.com/rayaman/go-qemu/pkg/v1/types/utils" ) /* @@ -39,14 +40,15 @@ type Machine struct { // Amount of memory in MB Memory memory.Memory `json:"m,omitempty"` // Number of CPU cores - Cores smp.SMP `json:"smp,omitempty"` - Cpu chip.CHIP `json:"cpu,omitempty"` - Accel accel.Accel `json:"accel,omitempty"` - Boot boot.Boot `json:"boot,omitempty"` - Numa numa.Numa `json:"numa,omitempty" omit:"true"` - MemoryPath string `json:"memory-path,omitempty"` - MemoryPrealloc types.Flag `json:"memory-prealloc,omitempty"` - Nic nic.NIC `json:"nic,omitempty"` + Cores smp.SMP `json:"smp,omitempty"` + Cpu chip.CHIP `json:"cpu,omitempty"` + Accel accel.Accel `json:"accel,omitempty"` + Boot boot.Boot `json:"boot,omitempty"` + Numa numa.Numa `json:"numa,omitempty" omit:"true"` // omit tells the Expand function to leave out the tag name + MemoryPath string `json:"memory-path,omitempty"` + MemoryPrealloc types.Flag `json:"memory-prealloc,omitempty"` + Nic nic.NIC `json:"nic,omitempty"` + KeyboardLayout keyboard.Keyboard `json:"k,omitempty"` // Graphics NoGraphic types.Flag `json:"nographic,omitempty"` @@ -70,9 +72,9 @@ func (m *Machine) Expand() []string { if tag != "" { if field.Kind() == reflect.Struct || field.Kind() == reflect.Interface && !field.IsZero() { if omit != "" { - exp = append(exp, utils.Expand(field.Value())...) + exp = append(exp, helpers.Expand(field.Value())...) } else { - exp = append(exp, utils.Expand(field.Value(), tag)...) + exp = append(exp, helpers.Expand(field.Value(), tag)...) } } else { if !field.IsZero() { @@ -85,6 +87,6 @@ func (m *Machine) Expand() []string { } } } - exp = utils.Remove(exp, "") + exp = helpers.Remove(exp, "") return exp } diff --git a/pkg/v1/types/accel/accel.go b/pkg/v1/types/accel/accel.go index b8013dc..9f08ff7 100644 --- a/pkg/v1/types/accel/accel.go +++ b/pkg/v1/types/accel/accel.go @@ -1,5 +1,14 @@ package accel +import ( + "bytes" + "fmt" + "os/exec" + "strings" + + "github.com/rayaman/go-qemu/pkg/v1/types/arch" +) + type ( VMExit string Accelerator string @@ -12,7 +21,6 @@ const ( XEN Accelerator = "xen" HVF Accelerator = "hvf" NVMM Accelerator = "nvmm" - // Windows WHPX Accelerator = "whpx" TCG Accelerator = "tcg" @@ -55,3 +63,32 @@ type Accel struct { // KVM device path, default /dev/kvm Device string `json:"device,omitempty"` } + +// GetAccelerator returns an accelerator for the given architecture, if it cannot be found, TCG will be returned +func GetAccelerator(a arch.System) Accelerator { + cmd := exec.Command(fmt.Sprintf("qemu-system-%v", a), "-accel", "help") + + var stdout bytes.Buffer + var stdin bytes.Buffer + cmd.Stdout = &stdout + cmd.Stdin = &stdin + + err := cmd.Run() + + if err != nil { + return TCG + } + + data := string(stdout.Bytes()) + + list := strings.Split(data, "\n") + + for i := 1; i < len(list); i++ { + if strings.Contains(list[i], "tcg") { + continue + } + return Accelerator(list[i]) + } + + return TCG +} diff --git a/pkg/v1/types/utils/utils.go b/pkg/v1/types/helpers/helpers.go similarity index 91% rename from pkg/v1/types/utils/utils.go rename to pkg/v1/types/helpers/helpers.go index 6f5119c..3bd18f5 100644 --- a/pkg/v1/types/utils/utils.go +++ b/pkg/v1/types/helpers/helpers.go @@ -1,4 +1,4 @@ -package utils +package helpers import ( "fmt" @@ -6,17 +6,12 @@ import ( "strings" "github.com/fatih/structs" + "github.com/rayaman/go-qemu/pkg/utils" "github.com/rayaman/go-qemu/pkg/v1/types/boot" "github.com/rayaman/go-qemu/pkg/v1/types/nic" "github.com/rayaman/go-qemu/pkg/v1/types/numa" ) -// converts bool to on/off format -var SW = map[bool]string{ - true: "on", - false: "off", -} - func Remove(s []string, r string) []string { for i, v := range s { if v == r { @@ -33,13 +28,14 @@ func Expand(obj any, tag ...string) []string { for _, field := range fields { opt := strings.ReplaceAll(field.Tag("json"), ",omitempty", "") opt = strings.ReplaceAll(opt, "omitempty", "") + opt = strings.ReplaceAll(opt, ",inline", "") omit := field.Tag("omit") if !field.IsZero() || field.Kind() == reflect.Bool || strings.Contains(opt, "id") && field.Kind() != reflect.String { switch value := field.Value().(type) { case bool: - opts = append(opts, fmt.Sprintf("%v=%v", opt, SW[value])) + opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[value])) case *bool: - opts = append(opts, fmt.Sprintf("%v=%v", opt, SW[*value])) + opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[*value])) case []string: opts = append(opts, fmt.Sprintf("%v=%v", opt, strings.Join(value, ""))) case numa.CPUS: diff --git a/pkg/v1/types/keyboard/keyboard.go b/pkg/v1/types/keyboard/keyboard.go new file mode 100644 index 0000000..8714629 --- /dev/null +++ b/pkg/v1/types/keyboard/keyboard.go @@ -0,0 +1,35 @@ +package keyboard + +type ( + Keyboard string +) + +var ( + En_US Keyboard = "en-us" + Fr Keyboard = "fr" + Da Keyboard = "da" + De Keyboard = "de" + It Keyboard = "it" + Nl Keyboard = "nl" + No Keyboard = "no" + Pt Keyboard = "pt" + Es Keyboard = "es" + Fr_ch Keyboard = "fr-ch" + De_ch Keyboard = "de-ch" + En_gb Keyboard = "en-gb" + Ko Keyboard = "ko" + Tw Keyboard = "tw" + Ja Keyboard = "ja" + Fr_ca Keyboard = "fr-ca" + Hu Keyboard = "hu" + Pl Keyboard = "pl" + Cz Keyboard = "cz" + Ru Keyboard = "ru" + Lv Keyboard = "lv" + Tr Keyboard = "tr" + Gr Keyboard = "gr" + Ar Keyboard = "ar" + Lt Keyboard = "lt" + Nl_be Keyboard = "nl-be" + Be Keyboard = "be" +) diff --git a/pkg/v1/types/nic/nic.go b/pkg/v1/types/nic/nic.go index 8e5920f..d159b70 100644 --- a/pkg/v1/types/nic/nic.go +++ b/pkg/v1/types/nic/nic.go @@ -1,7 +1,12 @@ package nic import ( + "fmt" + "reflect" "strings" + + "github.com/fatih/structs" + "github.com/rayaman/go-qemu/pkg/utils" ) type ( @@ -23,28 +28,21 @@ type TapOptions struct { } func (t *TapOptions) ExpandOptions() []string { - opts := []string{} - if t.ID != "" { - opts = append(opts, "id="+t.ID) - } - if t.IFName != "" { - opts = append(opts, "ifname="+t.IFName) - } - return []string{"tap," + strings.Join(opts, ",")} + return []string{"tap," + strings.Join(expand(t), ",")} } type BridgeOptions struct { } func (b *BridgeOptions) ExpandOptions() []string { - return []string{} + return []string{"bridge," + strings.Join(expand(b), ",")} } type UserOptions struct { } func (u *UserOptions) ExpandOptions() []string { - return []string{} + return []string{"user," + strings.Join(expand(u), ",")} } type SocketOptions struct { @@ -52,17 +50,36 @@ type SocketOptions struct { FD string `json:"fd,omitempty"` MCast string `json:"mcast,omitempty"` UDP string `json:"udp,omitempty"` - LocalAddress string `json:"localaddr,omitempty"` Listen string `json:"listen,omitempty"` + LocalAddress string `json:"localaddr,omitempty"` Connect string `json:"connect,omitempty"` } func (s *SocketOptions) ExpandOptions() []string { - return []string{} + return []string{"socket," + strings.Join(expand(s), ",")} } -type Options interface { - ExpandOptions() []string +type Options struct { + Tap *TapOptions `json:"tap,omitempty"` + Bridge *BridgeOptions `json:"bridge,omitempty"` + User *UserOptions `json:"user,omitempty"` + Socket *SocketOptions `json:"socket,omitempty"` +} + +func (o *Options) ExpandOptions() []string { + if o.Tap != nil { + return o.Tap.ExpandOptions() + } + if o.Bridge != nil { + return o.Bridge.ExpandOptions() + } + if o.User != nil { + return o.User.ExpandOptions() + } + if o.Socket != nil { + return o.Socket.ExpandOptions() + } + return []string{} } type NIC struct { @@ -70,3 +87,31 @@ type NIC struct { MacAddress string `json:"mac,omitempty"` Options Options `json:"options,omitempty"` } + +func expand(obj any, tag ...string) []string { + opts := []string{} + fields := structs.Fields(obj) + for _, field := range fields { + opt := strings.ReplaceAll(field.Tag("json"), ",omitempty", "") + opt = strings.ReplaceAll(opt, "omitempty", "") + opt = strings.ReplaceAll(opt, ",inline", "") + omit := field.Tag("omit") + if !field.IsZero() || field.Kind() == reflect.Bool || strings.Contains(opt, "id") && field.Kind() != reflect.String { + switch value := field.Value().(type) { + case *bool: + opts = append(opts, fmt.Sprintf("%v=%v", opt, utils.SW[*value])) + default: + if omit == "" { + opts = append(opts, fmt.Sprintf("%v=%v", opt, value)) + } else if omit == "tag" { + opts = append(opts, fmt.Sprintf("%v", value)) + } + } + } + } + if len(tag) > 0 { + return []string{"-" + tag[0], strings.Join(opts, ",")} + } else { + return []string{strings.Join(opts, ",")} + } +}