Skip to content
Draft
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
7 changes: 6 additions & 1 deletion .github/linters/urunc-dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -405,4 +405,9 @@ Logr
onsi
ESRCH
Prafful
praffq
praffq
mountfrom
vtbd
acpi
mmio
microvm
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/hypervisor-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Supported unikernel frameworks with `urunc`:

- [Unikraft](../unikernel-support#unikraft)
- [Linux](../unikernel-support#linux)
- [FreeBSD](../unikernel-support#freebsd)

An example unikernel:

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

<!-- ## urunc and the CNCF -->

Expand Down
56 changes: 56 additions & 0 deletions docs/unikernel-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
242 changes: 242 additions & 0 deletions pkg/unikontainers/unikernels/freebsd.go
Original file line number Diff line number Diff line change
@@ -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<env1>\n<env2>\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
}
3 changes: 3 additions & 0 deletions pkg/unikontainers/unikernels/unikernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading