-
Notifications
You must be signed in to change notification settings - Fork 26
feat(mirage): add dynamic device naming via manifest and annotations #363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
|
@@ -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) | ||
| return netOption | ||
| default: | ||
| return "" | ||
|
|
@@ -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 | ||
| } | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
|
|
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
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.