Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions pkg/unikontainers/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,17 @@ type ProcessConfig struct {

// UnikernelParams holds the data required to build the unikernels commandline
type UnikernelParams struct {
CmdLine []string // The cmdline provided by the image
EnvVars []string // The environment variables provided by the image
Monitor string // The monitor where guest will execute
Version string // The version of the unikernel
InitrdPath string // The path to the initrd of the unikernel
Net NetDevParams
Block []BlockDevParams
Rootfs RootfsParams // Information about rootfs
ProcConf ProcessConfig // Information for the process execution inside the guest
CmdLine []string // The cmdline provided by the image
EnvVars []string // The environment variables provided by the image
Monitor string // The monitor where guest will execute
Version string // The version of the unikernel
InitrdPath string // The path to the initrd of the unikernel
Net NetDevParams
Block []BlockDevParams
Rootfs RootfsParams // Information about rootfs
ProcConf ProcessConfig // Information for the process execution inside the guest
UnikernelPath string
Annotations map[string]string
}

// ExecArgs holds the data required by Execve to start the VMM
Expand Down
130 changes: 117 additions & 13 deletions pkg/unikontainers/unikernels/mirage.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,37 @@
package unikernels

import (
"debug/elf"
"encoding/json"
"fmt"
"strings"

"github.com/urunc-dev/urunc/pkg/unikontainers/types"
)

const MirageUnikernel string = "mirage"
const (
MirageUnikernel string = "mirage"
AnnotationNetMap = "urunc.dev/mirage-net-map"
AnnotationBlockMap = "urunc.dev/mirage-block-map"
)

type Mirage struct {
Command string
Monitor string
Net MirageNet
Block []MirageBlock
Command string
Monitor string
Net MirageNet
Block []MirageBlock
BinaryPath string
Manifest *Solo5Manifest
Annotations map[string]string
}

type Solo5Manifest struct {
Devices []Solo5Device `json:"devices"`
}

type Solo5Device struct {
Name string `json:"name"`
Type string `json:"type"`
}

type MirageNet struct {
Expand Down Expand Up @@ -57,8 +75,10 @@ func (m *Mirage) SupportsFS(_ string) bool {
func (m *Mirage) MonitorNetCli(ifName string, mac string) string {
switch m.Monitor {
case "hvt", "spt":
netOption := "--net:service=" + ifName
netOption += " --net-mac:service=" + mac
mirageID := m.getMirageDeviceName(ifName, "NET_BASIC", "service")

netOption := fmt.Sprintf("--net:%s=%s", mirageID, ifName)
netOption += fmt.Sprintf(" --net-mac:%s=%s", mirageID, mac)
Comment on lines +78 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the security model is, but you may consider enforcing the constraint that device names are alphanumerical (and at most a fixed length which I don't remember). Otherwise you can inject arbitrary strings here.

return netOption
default:
return ""
Expand All @@ -81,12 +101,22 @@ func (m *Mirage) MonitorBlockCli() []types.MonitorBlockArgs {
// use a single block device. We also need to find some use cases
// where multiple block devices are configured in MirageOS and check
// how MirageOS handles/configures them.
return []types.MonitorBlockArgs{
{
ID: "storage",
Path: m.Block[0].HostPath,
},
var blockArgs []types.MonitorBlockArgs

for i, blk := range m.Block {
defaultName := "storage"
if i > 0 {
defaultName = fmt.Sprintf("storage%d", i)
}

mirageID := m.getMirageDeviceName(blk.ID, "BLOCK_BASIC", defaultName)

blockArgs = append(blockArgs, types.MonitorBlockArgs{
ID: mirageID,
Path: blk.HostPath,
})
}
return blockArgs
default:
return nil
}
Expand All @@ -96,8 +126,82 @@ func (m *Mirage) MonitorCli() types.MonitorCliArgs {
return types.MonitorCliArgs{}
}

// getMirageDeviceName determines the correct Solo5 interface name.
func (m *Mirage) getMirageDeviceName(hostDev string, devType string, defaultName string) string {
// 1. Check Annotations
var mapKey string
if devType == "NET_BASIC" {
mapKey = AnnotationNetMap
} else {
mapKey = AnnotationBlockMap
}

if val, ok := m.Annotations[mapKey]; ok {
var mapping map[string]string
if err := json.Unmarshal([]byte(val), &mapping); err == nil {
if mirageName, found := mapping[hostDev]; found {
return mirageName
}
}
}

// 2. Check Manifest (Auto-detect if only one device exists)
if m.Manifest != nil {
var validDevs []string
for _, dev := range m.Manifest.Devices {
if dev.Type == devType {
validDevs = append(validDevs, dev.Name)
}
}
if len(validDevs) == 1 {
return validDevs[0]
}
}

// 3. Fallback
return defaultName
}

// parseSolo5Manifest extracts the .note.solo5.manifest section from the ELF
func getSolo5Manifest(path string) (*Solo5Manifest, error) {
f, err := elf.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

section := f.Section(".note.solo5.manifest")
if section == nil {
return nil, fmt.Errorf("no solo5 manifest section found")
}

data, err := section.Data()
if err != nil {
return nil, err
}

var manifest Solo5Manifest
// Attempt to find the start of the JSON object '{'
jsonStart := strings.Index(string(data), "{")
if jsonStart == -1 {
return nil, fmt.Errorf("invalid manifest format")
}

if err := json.Unmarshal(data[jsonStart:], &manifest); err != nil {
return nil, err
}

return &manifest, nil
Comment on lines +183 to +194
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The note doesn't contain json. See https://github.com/Solo5/solo5/blob/dabc69fd89b8119449ec4088c54b458d4ccc851b/include/mft_abi.h for the format, or https://git.robur.coop/robur/ocaml-solo5-elftool/src/branch/main/lib/solo5_elftool.ml#L73-L146 for code that parses the format.

If you want json you can use solo5-elftool query-manifest $executable assuming solo5-elftool is in $PATH. This adds an external dependency though.

}

func (m *Mirage) Init(data types.UnikernelParams) error {
// if Mask is empty, there is no network support

m.BinaryPath = data.UnikernelPath
m.Annotations = data.Annotations
if manifest, err := getSolo5Manifest(m.BinaryPath); err == nil {
m.Manifest = manifest
}

if data.Net.Mask != "" {
m.Net.Address = "--ipv4=" + data.Net.IP + "/24"
m.Net.Gateway = "--ipv4-gateway=" + data.Net.Gateway
Expand Down
12 changes: 7 additions & 5 deletions pkg/unikontainers/unikontainers.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,13 @@
// UnikernelParams
// populate unikernel params
unikernelParams := types.UnikernelParams{
CmdLine: u.Spec.Process.Args,
EnvVars: u.Spec.Process.Env,
Monitor: vmmType,
Version: unikernelVersion,
ProcConf: procAttrs,
CmdLine: u.Spec.Process.Args,
EnvVars: u.Spec.Process.Env,
Monitor: vmmType,
Version: unikernelVersion,
ProcConf: procAttrs,
UnikernelPath: u.Unikernel,

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Lint code / lint (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)) (typecheck)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Lint code / lint (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel) (typecheck)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Lint code / lint (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)) (typecheck)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Lint code / lint (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel) (typecheck)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Lint code / lint (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)) (typecheck)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Unit tests / unit-test (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Unit tests / unit-test (arm64, ubuntu-22.04-arm)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Build / build (amd64, ubuntu-22.04)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)

Check failure on line 291 in pkg/unikontainers/unikontainers.go

View workflow job for this annotation

GitHub Actions / ci-on-push / Build / build (arm64, ubuntu-22.04-arm)

u.Unikernel undefined (type *Unikontainer has no field or method Unikernel)
Annotations: u.Spec.Annotations,
}
if len(unikernelParams.CmdLine) == 0 {
unikernelParams.CmdLine = strings.Fields(u.State.Annotations[annotCmdLine])
Expand Down
Loading