working on multiple nics

This commit is contained in:
Ryan Ward 2025-03-30 01:38:01 -04:00
parent c0cabf5c57
commit b437caaf57
14 changed files with 286 additions and 109 deletions

2
go.mod
View File

@ -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
)

5
go.sum
View File

@ -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=

19
machine.yaml Normal file
View File

@ -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

36
main.go
View File

@ -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")

23
pkg/utils/utils.go Normal file
View File

@ -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",
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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:

View File

@ -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"
)

View File

@ -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, ",")}
}
}