From 8f8449dfc29394d2b021697e0fbf1472ce61133e Mon Sep 17 00:00:00 2001 From: HARSHVARANDANI Date: Sat, 18 Jul 2026 21:48:39 +0530 Subject: [PATCH 1/3] fix(network): populate MTU in static network configuration Ensure the MTU is retrieved from the container interface and populated in the Interface struct, preventing it from defaulting to 0. Signed-off-by: HARSHVARANDANI --- pkg/network/network_static.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/network/network_static.go b/pkg/network/network_static.go index fa46608cc..0c2767a2a 100644 --- a/pkg/network/network_static.go +++ b/pkg/network/network_static.go @@ -112,6 +112,7 @@ func (n StaticNetwork) NetworkSetup(uid uint32, gid uint32) (*UnikernelNetworkIn Mask: "255.255.255.0", Interface: redirectLink.Attrs().Name, // or tap0_urunc? MAC: redirectLink.Attrs().HardwareAddr.String(), + MTU: redirectLink.Attrs().MTU, }, }, nil } From d0615a05d6a9ca9e1e8a5b66e38879b90e5d93dc Mon Sep 17 00:00:00 2001 From: HARSHVARANDANI Date: Sat, 18 Jul 2026 21:49:16 +0530 Subject: [PATCH 2/3] fix(hypervisors/firecracker): propagate MTU to Firecracker config Add MTU field to FirecrackerNet config struct and serialize it to the Firecracker config JSON to enable VIRTIO_NET_F_MTU negotiation with the guest. Signed-off-by: HARSHVARANDANI --- pkg/unikontainers/hypervisors/firecracker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/unikontainers/hypervisors/firecracker.go b/pkg/unikontainers/hypervisors/firecracker.go index 9588a45bf..fb352a0b0 100644 --- a/pkg/unikontainers/hypervisors/firecracker.go +++ b/pkg/unikontainers/hypervisors/firecracker.go @@ -60,6 +60,7 @@ type FirecrackerNet struct { IfaceID string `json:"iface_id"` GuestMAC string `json:"guest_mac,omitempty"` HostIF string `json:"host_dev_name"` + MTU int `json:"mtu,omitempty"` } type FirecrackerVSockDev struct { @@ -148,6 +149,7 @@ func (fc *Firecracker) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel IfaceID: "net1", GuestMAC: args.Net.MAC, HostIF: args.Net.TapDev, + MTU: args.Net.MTU, } FCNet = append(FCNet, AnIF) } From 2623e40d9e5df68a578a87f43cd39bfd5d475d9d Mon Sep 17 00:00:00 2001 From: HARSHVARANDANI Date: Sat, 18 Jul 2026 21:49:37 +0530 Subject: [PATCH 3/3] test(hypervisors/firecracker): add unit tests for Firecracker command building Add test cases covering MTU parsing, JSON serialization, and baseline configuration generation for Firecracker. Signed-off-by: HARSHVARANDANI --- .../hypervisors/firecracker_test.go | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 pkg/unikontainers/hypervisors/firecracker_test.go diff --git a/pkg/unikontainers/hypervisors/firecracker_test.go b/pkg/unikontainers/hypervisors/firecracker_test.go new file mode 100644 index 000000000..09d2c05ee --- /dev/null +++ b/pkg/unikontainers/hypervisors/firecracker_test.go @@ -0,0 +1,262 @@ +// 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 hypervisors + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +const ( + testFCBinary = "/usr/bin/firecracker" + testFCKernelPath = "/rootfs/unikernel.bin" + testFCCommand = "init=/bin/sh" +) + +func TestFirecrackerBuildExecCmd(t *testing.T) { + + tests := []struct { + name string + args types.ExecArgs + unikernel types.Unikernel + checkJSON func(t *testing.T, config FirecrackerConfig) + mustContain []string // substrings in the joined command + wantNoSeccomp bool // expect --no-seccomp in the command + }{ + { + name: "baseline defaults with no network", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + assert.Equal(t, testFCKernelPath, config.Source.ImagePath) + assert.Equal(t, testFCCommand, config.Source.BootArgs) + assert.Equal(t, uint64(DefaultMemory), config.Machine.MemSizeMiB) + assert.Empty(t, config.NetIfs, "no network interfaces expected") + }, + }, + { + name: "network interface includes MTU", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + Net: types.NetDevParams{ + TapDev: "tap0_urunc", + MAC: "52:54:00:12:34:56", + MTU: 9000, + }, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + require.Len(t, config.NetIfs, 1) + netIf := config.NetIfs[0] + assert.Equal(t, "net1", netIf.IfaceID) + assert.Equal(t, "52:54:00:12:34:56", netIf.GuestMAC) + assert.Equal(t, "tap0_urunc", netIf.HostIF) + assert.Equal(t, 9000, netIf.MTU, "MTU must be propagated to FC config") + }, + }, + { + name: "network interface with default MTU 1500", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + Net: types.NetDevParams{ + TapDev: "tap0_urunc", + MAC: "aa:bb:cc:dd:ee:ff", + MTU: 1500, + }, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + require.Len(t, config.NetIfs, 1) + assert.Equal(t, 1500, config.NetIfs[0].MTU) + }, + }, + { + name: "MTU zero is omitted from JSON", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + Net: types.NetDevParams{ + TapDev: "tap0_urunc", + MAC: "aa:bb:cc:dd:ee:ff", + MTU: 0, + }, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + require.Len(t, config.NetIfs, 1) + assert.Equal(t, 0, config.NetIfs[0].MTU) + }, + }, + { + name: "seccomp disabled emits --no-seccomp", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: false, + }, + unikernel: &fakeUnikernel{}, + wantNoSeccomp: true, + }, + { + name: "custom memory size", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + MemSizeB: 512 * 1024 * 1024, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + assert.Equal(t, uint64(512), config.Machine.MemSizeMiB) + }, + }, + { + name: "VCPUs are set", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + VCPUs: 4, + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + assert.Equal(t, uint(4), config.Machine.VcpuCount) + }, + }, + { + name: "initrd path is set in boot source", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + InitrdPath: "/rootfs/initrd.img", + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + assert.Equal(t, "/rootfs/initrd.img", config.Source.InitrdPath) + }, + }, + { + name: "vsock device is configured", + args: types.ExecArgs{ + UnikernelPath: testFCKernelPath, + Command: testFCCommand, + Seccomp: true, + VAccelType: "vsock", + VSockDevID: 42, + VSockDevPath: "/run/vsock", + }, + unikernel: &fakeUnikernel{}, + checkJSON: func(t *testing.T, config FirecrackerConfig) { + assert.Equal(t, 42, config.VSock.GuestCID) + assert.Equal(t, "/run/vsock/vaccel.sock", config.VSock.UDSPath) + assert.Equal(t, "root", config.VSock.VSockID) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Subtests cannot be parallel: they all write to /tmp/fc.json. + os.Remove(filepath.Join("/tmp/", FCJsonFilename)) // clean slate + + fc := &Firecracker{binary: FirecrackerBinary, binaryPath: testFCBinary} + out, err := fc.BuildExecCmd(tt.args, tt.unikernel) + require.NoError(t, err) + require.NotEmpty(t, out) + + // The first element must be the binary path. + assert.Equal(t, testFCBinary, out[0], "binary path must be the first element") + + // Check --no-api and --config-file are always present. + joined := "" + for _, arg := range out { + joined += arg + " " + } + assert.Contains(t, joined, "--no-api") + assert.Contains(t, joined, "--config-file") + + if tt.wantNoSeccomp { + assert.Contains(t, joined, "--no-seccomp") + } else { + assert.NotContains(t, joined, "--no-seccomp") + } + + for _, want := range tt.mustContain { + assert.Contains(t, joined, want, "expected %q to be present", want) + } + + // Read back the generated JSON config and validate it. + if tt.checkJSON != nil { + jsonFile := filepath.Join("/tmp/", FCJsonFilename) + data, err := os.ReadFile(jsonFile) + require.NoError(t, err, "fc.json must be readable") + + var config FirecrackerConfig + err = json.Unmarshal(data, &config) + require.NoError(t, err, "fc.json must be valid JSON") + + tt.checkJSON(t, config) + } + }) + } +} + +// TestFirecrackerNetMTUJSON verifies the JSON serialization of FirecrackerNet +// includes the "mtu" field when it is non-zero and omits it when zero. +func TestFirecrackerNetMTUJSON(t *testing.T) { + t.Parallel() + + t.Run("non-zero MTU is serialized", func(t *testing.T) { + t.Parallel() + net := FirecrackerNet{ + IfaceID: "net1", + GuestMAC: "52:54:00:12:34:56", + HostIF: "tap0_urunc", + MTU: 9000, + } + data, err := json.Marshal(net) + require.NoError(t, err) + assert.Contains(t, string(data), `"mtu":9000`) + }) + + t.Run("zero MTU is omitted from JSON", func(t *testing.T) { + t.Parallel() + net := FirecrackerNet{ + IfaceID: "net1", + GuestMAC: "52:54:00:12:34:56", + HostIF: "tap0_urunc", + MTU: 0, + } + data, err := json.Marshal(net) + require.NoError(t, err) + assert.NotContains(t, string(data), `"mtu"`, "zero MTU should be omitted via omitempty") + }) +}