diff --git a/.github/linters/urunc-dict.txt b/.github/linters/urunc-dict.txt index 538649e0..f4a3b003 100644 --- a/.github/linters/urunc-dict.txt +++ b/.github/linters/urunc-dict.txt @@ -405,4 +405,9 @@ Logr onsi ESRCH Prafful -praffq \ No newline at end of file +praffq +mountfrom +vtbd +acpi +mmio +microvm diff --git a/README.md b/README.md index 3715bb88..6bbf9956 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ supported VM/Sandbox monitors and unikernels: | MirageOS | QEMU, Solo5-hvt, Solo5-spt | x86,aarch64 | Block/Devmapper | | Mewz | QEMU | x86 | In-memory | | Linux | QEMU, Firecracker | x86 | Initrd, Block/Devmapper, 9pfs, Virtiofs | +| FreeBSD | Firecracker | x86 | Block/Devmapper | We plan to add support for more unikernel frameworks and other platforms too. Feel free to [contact](#Contact) us for a specific unikernel framework or similar diff --git a/docs/hypervisor-support.md b/docs/hypervisor-support.md index df349d72..29aecc65 100644 --- a/docs/hypervisor-support.md +++ b/docs/hypervisor-support.md @@ -133,6 +133,7 @@ Supported unikernel frameworks with `urunc`: - [Unikraft](../unikernel-support#unikraft) - [Linux](../unikernel-support#linux) +- [FreeBSD](../unikernel-support#freebsd) An example unikernel: diff --git a/docs/index.md b/docs/index.md index c9fc2e0a..2d3945ae 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,6 +60,7 @@ Sandbox monitors, along with the unikernels that can run on top of them. | [MirageOS](./unikernel-support#mirage)| [Qemu](./hypervisor-support#qemu), [Solo5-hvt](./hypervisor-support#solo5-hvt), [Solo5-spt](./hypervisor-support#solo5-spt) | x86, aarch64 | Block/Devmapper | | [Mewz](./unikernel-support#mewz)| [Qemu](./hypervisor-support#qemu) | x86 | In-memory | | [Linux](./unikernel-support#linux)| [Qemu](./hypervisor-support#qemu), [Firecracker](./hypervisor-support#aws-firecracker) | x86, aarch64 | Initrd, Block/Devmapper, 9pfs, Virtiofs | +| [FreeBSD](./unikernel-support#freebsd)| [Firecracker](./hypervisor-support#aws-firecracker) | x86 | Block/Devmapper | diff --git a/docs/unikernel-support.md b/docs/unikernel-support.md index f0a8bd8d..92ff6a9b 100644 --- a/docs/unikernel-support.md +++ b/docs/unikernel-support.md @@ -342,6 +342,62 @@ An example of a Redis alpine image transformed to a block file on top of sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/redis-firecracker-linux-block:latest ``` +## FreeBSD + +[FreeBSD](https://www.freebsd.org/) is a popular BSD operating system which +powers modern servers, storage systems, network appliances and embedded +platforms. [FreeBSD](https://www.freebsd.org/) is well known for its +performance, advanced networking, and strong focus on correctness and security. +As a result, many applications and services, especially those requiring +stability and fine-grained system control, are built to run on +[FreeBSD](https://www.freebsd.org/). Of course, +[FreeBSD](https://www.freebsd.org/) is not a unikernel framework. However, +thanks to its its modular architecture, combined with features like custom +kernel configuration, allows us to build highly specialized and minimal system +images tailored for specific workloads. Therefore, it fits well in the single +application model of `urunc`. + +Furtermore, the introduction of [FreeBSD](https://www.freebsd.org/) in the [OCI +spec](https://github.com/opencontainers/runtime-spec) as a target platform +resulted to the creation and distribution of FreeBSD based OCI images. +[FreeBSD](https://www.freebsd.org/) has wide support for different +Therfore, these images can be easily executed on top of `urunc` with a focus on +single application containers. + +### VMMs and other sandbox monitors + +While [FreeBSD](https://www.freebsd.org/) has a wide support for various +hypervisors, it assumes a full VM and not a microVM> However, on 2022 support +for [Firecracker](https://github.com/firecracker-microvm/firecracker) has been +added. On the other hand, the Qemu microVM does not seem to work properly with +[FreeBSD](https://www.freebsd.org/). + +### FreeBSD and `urunc` + +Focusing on the single-application notion of using the +[FreeBSD](https://www.freebsd.org/) kernel, `urunc` provides support for +[Firecracker](https://github.com/firecracker-microvm/firecracker). For network, +`urunc` will make use of virtio-net through MMIO. In the case of storage, +`urunc` can use only virtio-block, since the support for initrd is not there +for [FreeBSD](https://www.freebsd.org/) and shared filesystems are not +supported in +[Firecracker](https://github.com/firecracker-microvm/firecracker). + +As a result, the [FreeBSD](https://www.freebsd.org/) based images for `urunc` +need to either contain a block-based image for rootfs, or use the devmapper +snapshotter with "ext2" as a filesystem, since +[FreeBSD](https://www.freebsd.org/) does not support other Linux-based +filesystems. For more information on setting up devmapper, please take a look +on our [installation guide](../installation#setup-thinpool-devmapper). + +An example of a Caddy HTTP server on top of [FreeBSD](https://www.freebsd.org/) +and [Firecracker](https://github.com/firecracker-microvm/firecracker). with +'urunc' and devmapper as a snapshotter: + +```bash +sudo nerdctl run --rm -ti --snapshotter devmapper --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/caddy-firecracker-freebsd-raw:latest +``` + ## Future unikernels and frameworks: In the near future, we plan to add support for the following frameworks: diff --git a/pkg/unikontainers/unikernels/freebsd.go b/pkg/unikontainers/unikernels/freebsd.go new file mode 100644 index 00000000..82f80174 --- /dev/null +++ b/pkg/unikontainers/unikernels/freebsd.go @@ -0,0 +1,242 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unikernels + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" + + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +const ( + FreeBSDUnikernel string = "freebsd" + netStartMarker string = "UNS" // Net configuration start marker + netEndMarker string = "UNE" // Net configuration end marker + paddingMarker string = "PAD" // Padding bytes +) + +type FreeBSD struct { + Command []string + Monitor string + Env []string + BlockImgAsRootfs bool + Net FreeBSDNet + Block []types.BlockDevParams + ProcConfig types.ProcessConfig +} + +type FreeBSDNet struct { + Address string + Gateway string + Mask string +} + +func (f *FreeBSD) CommandString() (string, error) { + if f.BlockImgAsRootfs { + return "vfs.root.mountfrom=ext2fs:/dev/vtbd0", nil + } + return "vfs.root.mountfrom=/dev/vtbd0", nil +} + +func (f *FreeBSD) SupportsBlock() bool { + return true +} + +func (f *FreeBSD) SupportsFS(fsType string) bool { + switch fsType { + case "ext2": + return true + default: + return false + } +} + +func (f *FreeBSD) MonitorNetCli(_ string, _ string) string { + // TODO: Commenting out, since FreeBSd does not work properly over Qemu microVM + // switch f.Monitor { + // TODO: Commenting out, since it is not working properly + // case "qemu": + // netOption := " -netdev tap,id=net0,script=no,downscript=no,ifname=" + ifName + // netOption += " -device virtio-net-device,netdev=net0,mac=" + mac + // return netOption + // default: + // return "" + // } + return "" +} + +func (f *FreeBSD) MonitorBlockCli() []types.MonitorBlockArgs { + if len(f.Block) == 0 { + return nil + } + blkArgs := make([]types.MonitorBlockArgs, 0, len(f.Block)) + switch f.Monitor { + // TODO: Commenting out, since it is not working properly + // case "qemu": + // for _, aBlock := range f.Block { + // bcli1 := fmt.Sprintf(" -device virtio-blk-device,serial=%s,drive=%s", aBlock.ID, aBlock.ID) + // bcli2 := fmt.Sprintf(" -drive format=raw,if=none,id=%s,file=%s", aBlock.ID, aBlock.Source) + // blkArgs = append(blkArgs, types.MonitorBlockArgs{ + // ExactArgs: bcli1 + bcli2, + // }) + // } + case "firecracker": + for _, aBlock := range f.Block { + blkArgs = append(blkArgs, types.MonitorBlockArgs{ + ID: "FC" + aBlock.ID, + Path: aBlock.Source, + }) + } + blkArgs = append(blkArgs, types.MonitorBlockArgs{ + ID: "FC_URUNIT_CONFIG", + Path: urunitConfPath, + }) + default: + return nil + } + + return blkArgs +} + +func (f *FreeBSD) MonitorCli() types.MonitorCliArgs { + // TODO: Commenting out, since FreeBSd does not work properly over Qemu microVM + // switch f.Monitor { + // case "qemu": + // monArgs := " -M microvm,rtc=on,acpi=off,pic=off,accel=kvm -global virtio-mmio.force-legacy=false -no-reboot -display none -nodefaults -serial stdio" + // return types.MonitorCliArgs{ + // OtherArgs: monArgs, + // } + // default: + // return types.MonitorCliArgs{} + // } + return types.MonitorCliArgs{} +} + +func (f *FreeBSD) Init(data types.UnikernelParams) error { + // if Mask is empty, there is no network support + if data.Net.Mask != "" { + f.Net.Address = data.Net.IP + f.Net.Gateway = data.Net.Gateway + f.Net.Mask = data.Net.Mask + } + f.Block = data.Block + f.Env = data.EnvVars + f.Command = data.CmdLine + f.Monitor = data.Monitor + f.ProcConfig = data.ProcConf + f.BlockImgAsRootfs = false + if data.Rootfs.MountedPath != "" { + f.BlockImgAsRootfs = true + } + + err := f.setupUrunitConfig(data.Rootfs) + if err != nil { + return err + } + + return nil +} + +// setupUrunitConfig creates the urunit configuration file with environment variables. +func (f *FreeBSD) setupUrunitConfig(rfs types.RootfsParams) error { + urunitConfig := f.buildUrunitConfig() + + urunitConfigFile := filepath.Join(rfs.MonRootfs, urunitConfPath) + err := createFile(urunitConfigFile, urunitConfig) + if err != nil { + return fmt.Errorf("failed to setup urunit config: %w", err) + } + + return nil +} + +// buildEnvConfig creates the environment configuration content for urunit. +func (f *FreeBSD) buildUrunitConfig() string { + // Format: UES\n\n\n...\nUEE\n + var sb strings.Builder + sb.WriteString(envStartMarker) + sb.WriteString("\n") + if len(f.Env) > 0 { + sb.WriteString(strings.Join(f.Env, "\n")) + sb.WriteString("\n") + } + sb.WriteString(envEndMarker) + sb.WriteString("\n") + sb.WriteString(lpcStartMarker) + sb.WriteString("\n") + sb.WriteString("UID:") + sb.WriteString(strconv.FormatUint(uint64(f.ProcConfig.UID), 10)) + sb.WriteString("\n") + sb.WriteString("GID:") + sb.WriteString(strconv.FormatUint(uint64(f.ProcConfig.GID), 10)) + sb.WriteString("\n") + sb.WriteString("WD:") + sb.WriteString(f.ProcConfig.WorkDir) + sb.WriteString("\n") + sb.WriteString("ARC:") + sb.WriteString(strconv.FormatUint(uint64(len(f.Command)), 10)) + sb.WriteString("\n") + for _, c := range f.Command { + sb.WriteString("ARV:") + sb.WriteString(c) + sb.WriteString("\n") + } + sb.WriteString(lpcEndMarker) + sb.WriteString("\n") + sb.WriteString(blkStartMarker) + sb.WriteString("\n") + for _, b := range f.Block { + if b.ID == "rootfs" { + continue + } + sb.WriteString("ID:") + if f.Monitor == "firecracker" { + sb.WriteString("FC") + } + sb.WriteString(b.ID) + sb.WriteString("\n") + sb.WriteString("MP:") + sb.WriteString(b.MountPoint) + sb.WriteString("\n") + } + sb.WriteString(blkEndMarker) + sb.WriteString("\n") + sb.WriteString(netStartMarker) + sb.WriteString("\n") + sb.WriteString("IP:") + sb.WriteString(f.Net.Address) + sb.WriteString("\n") + sb.WriteString("GW:") + sb.WriteString(f.Net.Gateway) + sb.WriteString("\n") + sb.WriteString("MSK:") + sb.WriteString(f.Net.Mask) + sb.WriteString("\n") + sb.WriteString(netEndMarker) + sb.WriteString("\n") + for i := 0; i < 128; i++ { + sb.WriteString(paddingMarker) + sb.WriteString("\n") + } + return sb.String() +} + +func newFreeBSD() *FreeBSD { + freebsdStruct := new(FreeBSD) + return freebsdStruct +} diff --git a/pkg/unikontainers/unikernels/unikernel.go b/pkg/unikontainers/unikernels/unikernel.go index 38326874..700211a5 100644 --- a/pkg/unikontainers/unikernels/unikernel.go +++ b/pkg/unikontainers/unikernels/unikernel.go @@ -39,6 +39,9 @@ func New(unikernelType string) (types.Unikernel, error) { case LinuxUnikernel: unikernel := newLinux() return unikernel, nil + case FreeBSDUnikernel: + unikernel := newFreeBSD() + return unikernel, nil default: return nil, ErrNotSupportedUnikernel }