Skip to content

Commit 1208aaa

Browse files
committed
feat(freebsd): Initial support for freebsd guests
Add initial support for FreeBSD guests. In the current status, urunc can boot FreeBSD based workloads which use a block based rootfs. This can be done either through a block image inside the container's image or through devmapper and the use of ext2 as the filesystem for the block-based snapshot. Since FreeBSD does not accept arbitrary kernel boot parameters, nor accepts network configuration through kernel boot parameters, the current implementation attaches an extra text file through virtio block. This text file contains the urunit configuration which has been updates to also include the network configuration and the application, along with its arguments. Furthermore, the current approach is limited to Firecracker, since the Qemu microVM support does not seem to work properly for FreeBSD. Signed-off-by: Charalampos Mainas <cmainas@nubificus.co.uk>
1 parent f64191a commit 1208aaa

3 files changed

Lines changed: 251 additions & 1 deletion

File tree

.github/linters/urunc-dict.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,4 +405,9 @@ Logr
405405
onsi
406406
ESRCH
407407
Prafful
408-
praffq
408+
praffq
409+
mountfrom
410+
vtbd
411+
acpi
412+
mmio
413+
microvm
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright (c) 2023-2026, Nubificus LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package unikernels
16+
17+
import (
18+
"fmt"
19+
"path/filepath"
20+
"strconv"
21+
"strings"
22+
23+
"github.com/urunc-dev/urunc/pkg/unikontainers/types"
24+
)
25+
26+
const (
27+
FreeBSDUnikernel string = "freebsd"
28+
netStartMarker string = "UNS" // Net configuration start marker
29+
netEndMarker string = "UNE" // Net configuration end marker
30+
paddingMarker string = "PAD" // Padding bytes
31+
)
32+
33+
type FreeBSD struct {
34+
Command []string
35+
Monitor string
36+
Env []string
37+
BlockImgAsRootfs bool
38+
Net FreeBSDNet
39+
Block []types.BlockDevParams
40+
ProcConfig types.ProcessConfig
41+
}
42+
43+
type FreeBSDNet struct {
44+
Address string
45+
Gateway string
46+
Mask string
47+
}
48+
49+
func (f *FreeBSD) CommandString() (string, error) {
50+
if f.BlockImgAsRootfs {
51+
return "vfs.root.mountfrom=ext2fs:/dev/vtbd0", nil
52+
}
53+
return "vfs.root.mountfrom=/dev/vtbd0", nil
54+
}
55+
56+
func (f *FreeBSD) SupportsBlock() bool {
57+
return true
58+
}
59+
60+
func (f *FreeBSD) SupportsFS(fsType string) bool {
61+
switch fsType {
62+
case "ext2":
63+
return true
64+
default:
65+
return false
66+
}
67+
}
68+
69+
func (f *FreeBSD) MonitorNetCli(_ string, _ string) string {
70+
// TODO: Commenting out, since FreeBSd does not work properly over Qemu microVM
71+
// switch f.Monitor {
72+
// TODO: Commenting out, since it is not working properly
73+
// case "qemu":
74+
// netOption := " -netdev tap,id=net0,script=no,downscript=no,ifname=" + ifName
75+
// netOption += " -device virtio-net-device,netdev=net0,mac=" + mac
76+
// return netOption
77+
// default:
78+
// return ""
79+
// }
80+
return ""
81+
}
82+
83+
func (f *FreeBSD) MonitorBlockCli() []types.MonitorBlockArgs {
84+
if len(f.Block) == 0 {
85+
return nil
86+
}
87+
blkArgs := make([]types.MonitorBlockArgs, 0, len(f.Block))
88+
switch f.Monitor {
89+
// TODO: Commenting out, since it is not working properly
90+
// case "qemu":
91+
// for _, aBlock := range f.Block {
92+
// bcli1 := fmt.Sprintf(" -device virtio-blk-device,serial=%s,drive=%s", aBlock.ID, aBlock.ID)
93+
// bcli2 := fmt.Sprintf(" -drive format=raw,if=none,id=%s,file=%s", aBlock.ID, aBlock.Source)
94+
// blkArgs = append(blkArgs, types.MonitorBlockArgs{
95+
// ExactArgs: bcli1 + bcli2,
96+
// })
97+
// }
98+
case "firecracker":
99+
for _, aBlock := range f.Block {
100+
blkArgs = append(blkArgs, types.MonitorBlockArgs{
101+
ID: "FC" + aBlock.ID,
102+
Path: aBlock.Source,
103+
})
104+
}
105+
blkArgs = append(blkArgs, types.MonitorBlockArgs{
106+
ID: "FC_URUNIT_CONFIG",
107+
Path: urunitConfPath,
108+
})
109+
default:
110+
return nil
111+
}
112+
113+
return blkArgs
114+
}
115+
116+
func (f *FreeBSD) MonitorCli() types.MonitorCliArgs {
117+
// TODO: Commenting out, since FreeBSd does not work properly over Qemu microVM
118+
// switch f.Monitor {
119+
// case "qemu":
120+
// monArgs := " -M microvm,rtc=on,acpi=off,pic=off,accel=kvm -global virtio-mmio.force-legacy=false -no-reboot -display none -nodefaults -serial stdio"
121+
// return types.MonitorCliArgs{
122+
// OtherArgs: monArgs,
123+
// }
124+
// default:
125+
// return types.MonitorCliArgs{}
126+
// }
127+
return types.MonitorCliArgs{}
128+
}
129+
130+
func (f *FreeBSD) Init(data types.UnikernelParams) error {
131+
// if Mask is empty, there is no network support
132+
if data.Net.Mask != "" {
133+
f.Net.Address = data.Net.IP
134+
f.Net.Gateway = data.Net.Gateway
135+
f.Net.Mask = data.Net.Mask
136+
}
137+
f.Block = data.Block
138+
f.Env = data.EnvVars
139+
f.Command = data.CmdLine
140+
f.Monitor = data.Monitor
141+
f.ProcConfig = data.ProcConf
142+
f.BlockImgAsRootfs = false
143+
if data.Rootfs.MountedPath != "" {
144+
f.BlockImgAsRootfs = true
145+
}
146+
147+
err := f.setupUrunitConfig(data.Rootfs)
148+
if err != nil {
149+
return err
150+
}
151+
152+
return nil
153+
}
154+
155+
// setupUrunitConfig creates the urunit configuration file with environment variables.
156+
func (f *FreeBSD) setupUrunitConfig(rfs types.RootfsParams) error {
157+
urunitConfig := f.buildUrunitConfig()
158+
159+
urunitConfigFile := filepath.Join(rfs.MonRootfs, urunitConfPath)
160+
err := createFile(urunitConfigFile, urunitConfig)
161+
if err != nil {
162+
return fmt.Errorf("failed to setup urunit config: %w", err)
163+
}
164+
165+
return nil
166+
}
167+
168+
// buildEnvConfig creates the environment configuration content for urunit.
169+
func (f *FreeBSD) buildUrunitConfig() string {
170+
// Format: UES\n<env1>\n<env2>\n...\nUEE\n
171+
var sb strings.Builder
172+
sb.WriteString(envStartMarker)
173+
sb.WriteString("\n")
174+
if len(f.Env) > 0 {
175+
sb.WriteString(strings.Join(f.Env, "\n"))
176+
sb.WriteString("\n")
177+
}
178+
sb.WriteString(envEndMarker)
179+
sb.WriteString("\n")
180+
sb.WriteString(lpcStartMarker)
181+
sb.WriteString("\n")
182+
sb.WriteString("UID:")
183+
sb.WriteString(strconv.FormatUint(uint64(f.ProcConfig.UID), 10))
184+
sb.WriteString("\n")
185+
sb.WriteString("GID:")
186+
sb.WriteString(strconv.FormatUint(uint64(f.ProcConfig.GID), 10))
187+
sb.WriteString("\n")
188+
sb.WriteString("WD:")
189+
sb.WriteString(f.ProcConfig.WorkDir)
190+
sb.WriteString("\n")
191+
sb.WriteString("ARC:")
192+
sb.WriteString(strconv.FormatUint(uint64(len(f.Command)), 10))
193+
sb.WriteString("\n")
194+
for _, c := range f.Command {
195+
sb.WriteString("ARV:")
196+
sb.WriteString(c)
197+
sb.WriteString("\n")
198+
}
199+
sb.WriteString(lpcEndMarker)
200+
sb.WriteString("\n")
201+
sb.WriteString(blkStartMarker)
202+
sb.WriteString("\n")
203+
for _, b := range f.Block {
204+
if b.ID == "rootfs" {
205+
continue
206+
}
207+
sb.WriteString("ID:")
208+
if f.Monitor == "firecracker" {
209+
sb.WriteString("FC")
210+
}
211+
sb.WriteString(b.ID)
212+
sb.WriteString("\n")
213+
sb.WriteString("MP:")
214+
sb.WriteString(b.MountPoint)
215+
sb.WriteString("\n")
216+
}
217+
sb.WriteString(blkEndMarker)
218+
sb.WriteString("\n")
219+
sb.WriteString(netStartMarker)
220+
sb.WriteString("\n")
221+
sb.WriteString("IP:")
222+
sb.WriteString(f.Net.Address)
223+
sb.WriteString("\n")
224+
sb.WriteString("GW:")
225+
sb.WriteString(f.Net.Gateway)
226+
sb.WriteString("\n")
227+
sb.WriteString("MSK:")
228+
sb.WriteString(f.Net.Mask)
229+
sb.WriteString("\n")
230+
sb.WriteString(netEndMarker)
231+
sb.WriteString("\n")
232+
for i := 0; i < 128; i++ {
233+
sb.WriteString(paddingMarker)
234+
sb.WriteString("\n")
235+
}
236+
return sb.String()
237+
}
238+
239+
func newFreeBSD() *FreeBSD {
240+
freebsdStruct := new(FreeBSD)
241+
return freebsdStruct
242+
}

pkg/unikontainers/unikernels/unikernel.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ func New(unikernelType string) (types.Unikernel, error) {
3939
case LinuxUnikernel:
4040
unikernel := newLinux()
4141
return unikernel, nil
42+
case FreeBSDUnikernel:
43+
unikernel := newFreeBSD()
44+
return unikernel, nil
4245
default:
4346
return nil, ErrNotSupportedUnikernel
4447
}

0 commit comments

Comments
 (0)