├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── docs ├── icon.svg └── index.html ├── ff-api ├── build.gradle.kts └── src │ └── main │ ├── go │ ├── ffrt │ ├── fgConstants.go │ ├── fgDevice.go │ ├── fgInit.go │ ├── fgMonitor.go │ ├── fgMount.go │ ├── fgMount_test.go │ ├── fgResolve.go │ ├── fgResolve_test.go │ ├── go.mod │ ├── go.sum │ └── main.go │ └── java │ └── io │ └── vacco │ └── ff │ ├── FgMain.java │ ├── api │ ├── FgApi.java │ ├── FgApiHdl.java │ ├── FgRoute.java │ └── FgUiHdl.java │ ├── firecracker │ ├── Balloon.java │ ├── BalloonStats.java │ ├── BalloonStatsUpdate.java │ ├── BalloonUpdate.java │ ├── BootSource.java │ ├── CpuConfig.java │ ├── CpuTemplate.java │ ├── Drive.java │ ├── EntropyDevice.java │ ├── Error.java │ ├── FirecrackerVersion.java │ ├── FullVmConfiguration.java │ ├── InstanceActionInfo.java │ ├── InstanceInfo.java │ ├── Logger.java │ ├── MachineConfiguration.java │ ├── MemoryBackend.java │ ├── Metrics.java │ ├── MmdsConfig.java │ ├── MmdsContentsObject.java │ ├── NetworkInterface.java │ ├── PartialDrive.java │ ├── PartialNetworkInterface.java │ ├── RateLimiter.java │ ├── SnapshotCreateParams.java │ ├── SnapshotLoadParams.java │ ├── TokenBucket.java │ ├── Vm.java │ ├── Vsock.java │ ├── cpuconfig │ │ ├── Cpuid_modifiers.java │ │ ├── Msr_modifiers.java │ │ └── Reg_modifiers.java │ ├── drive │ │ ├── Cache_type.java │ │ └── Io_engine.java │ ├── instanceactioninfo │ │ └── Action_type.java │ ├── instanceinfo │ │ └── State.java │ ├── logger │ │ └── Level.java │ ├── machineconfiguration │ │ └── Huge_pages.java │ ├── memorybackend │ │ └── Backend_type.java │ ├── mmdsconfig │ │ └── Version.java │ ├── snapshotcreateparams │ │ └── Snapshot_type.java │ └── vm │ │ └── State.java │ ├── initramfs │ ├── FgConstants.java │ ├── FgCpio.java │ ├── FgDockerIo.java │ ├── FgTarEntry.java │ └── FgTarIo.java │ ├── net │ ├── FgDhcpDiscover.java │ ├── FgDhcpFrames.java │ ├── FgDhcpRequests.java │ ├── FgEthFrame.java │ ├── FgJni.java │ └── FgNetIo.java │ ├── schema │ ├── FcApiResponse.java │ ├── FgConfig.java │ ├── FgEnvVar.java │ ├── FgImage.java │ ├── FgIpConfig.java │ ├── FgNetConfig.java │ ├── FgRequest.java │ ├── FgVm.java │ ├── FgVmCreate.java │ ├── FgVmFiles.java │ ├── FgVmList.java │ ├── FgVmLogs.java │ ├── FgVmResourceList.java │ ├── FgVmStart.java │ ├── FgVmStatus.java │ ├── FgVmStop.java │ └── FgVmTag.java │ ├── service │ ├── FgContext.java │ ├── FgFirecracker.java │ ├── FgLogging.java │ ├── FgOptions.java │ ├── FgValid.java │ ├── FgVmSvc.java │ ├── FgVmSvcBuild.java │ ├── FgVmSvcControl.java │ ├── FgVmSvcDhcp.java │ └── FgVmSvcStatus.java │ └── util │ └── FgIo.java ├── ff-app ├── build.gradle.kts └── src │ └── main │ └── resources │ ├── reflect-config.json │ └── resource-config.json ├── ff-jni ├── .vscode │ ├── c_cpp_properties.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Makefile ├── build.gradle.kts ├── out │ └── fg_jni.so └── src │ ├── fg │ ├── fg_proc.c │ ├── fg_proc.h │ ├── fg_raw.c │ ├── fg_raw.h │ ├── fg_tap.c │ ├── fg_tap.h │ ├── fg_unix.c │ ├── fg_unix.h │ ├── fg_vsock.c │ └── fg_vsock.h │ ├── jni │ └── fg_jni.c │ └── test │ └── fg_tap_test.c ├── ff-test ├── README.md ├── build.gradle.kts ├── etc │ ├── cpio │ │ ├── tree-ref.cpio │ │ └── tree │ │ │ ├── etc │ │ │ └── localtime │ │ │ ├── lib │ │ │ └── libnss_dns.txt │ │ │ └── lib64 │ ├── kernel-build │ │ ├── README.md │ │ └── microvm-kernel-ci-x86_64-6.1.config │ └── vsock-proxy │ │ ├── README.md │ │ ├── unix-proxy.c │ │ └── vsock-proxy.c └── src │ └── test │ ├── java │ └── io │ │ └── vacco │ │ └── ff │ │ ├── FgContextTest.java │ │ ├── FgCpioTest.java │ │ ├── FgDockerIoTest.java │ │ ├── FgNetSocketTest.java │ │ ├── FgNetTapTest.java │ │ ├── FgTarIoTest.java │ │ └── FgTest.java │ └── resources │ ├── blob.tar │ ├── disk.img │ ├── kernel │ ├── extract-vmlinux │ └── vmlinux-6.1.98 │ └── proc │ └── 1982 │ └── environ ├── ff-ui ├── @ff │ ├── components │ │ ├── FgIcons.tsx │ │ ├── FgLock.tsx │ │ ├── FgLogViewer.tsx │ │ ├── FgMenuLeft.tsx │ │ ├── FgMenuTop.tsx │ │ ├── FgNoData.tsx │ │ ├── FgToast.tsx │ │ ├── FgVersion.tsx │ │ └── index.ts │ ├── index.tsx │ ├── routes │ │ ├── FgVmEdit.tsx │ │ ├── FgVmList.tsx │ │ └── FgVmLogs.tsx │ ├── rpc.ts │ ├── store.ts │ ├── ui.ts │ └── util.ts ├── build.gradle.kts ├── package-lock.json ├── package.json ├── res │ ├── favicon.svg │ ├── main.scss │ └── ui-lock.css ├── rollup.config.js ├── src │ └── main │ │ └── resources │ │ └── ui │ │ └── version └── tsconfig.json ├── gradle.properties ├── native-compile.sh └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [vaccovecrana] 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Native Build 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Display release tag 11 | run: echo ${{ github.event.release.tag_name }} 12 | - name: Pull Docker Image 13 | run: docker pull gradle:jdk21-graal-jammy 14 | - name: Gradle Build 15 | run: | 16 | docker run --rm \ 17 | -e GITHUB_REF=${{ github.ref }} \ 18 | -e GITHUB_SHA=${{ github.sha }} \ 19 | -e PLUGIN_ORGCONFIG=http://56db25d3f6c39937b48e-6eaf716421c53330be45fa9d36560381.r85.cf2.rackcdn.com/org-config/vacco.json \ 20 | -v ${{ github.workspace }}:/workspace \ 21 | -w /workspace \ 22 | gradle:jdk21-graal-jammy \ 23 | gradle clean build nativeCompile 24 | - name: Upload binaries 25 | uses: svenstaro/upload-release-action@v2 26 | with: 27 | repo_token: ${{ secrets.GITHUB_TOKEN }} 28 | tag: ${{ github.ref }} 29 | file: ff-app/build/native/nativeCompile/flc-* 30 | file_glob: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .attach* 3 | 4 | build/ 5 | bin/ 6 | linux-6* 7 | linux-5* 8 | bzImage 9 | 10 | hs_err_* 11 | 12 | /fg_jni.so 13 | ff-test/fg_jni.so 14 | /**/out/fg_*.o 15 | /**/out/fg_test 16 | 17 | .idea/ 18 | .gradle/ 19 | gradle/ 20 | gradlew* 21 | 22 | node_modules/ 23 | 24 | vm-test/ 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Vaccove Crana, LLC. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frag-falcon 2 | 3 | Firecracker VM management. Run Docker images as micro VMs. 4 | 5 | ## Quick start 6 | 7 | Grab the latest release [here](https://github.com/vaccovecrana/frag-falcon/releases). 8 | 9 | To run `flc`, you need: 10 | 11 | - A `glibc` based Linux distribution with virtualization support. Support for `musl` is being [considered](https://github.com/vaccovecrana/frag-falcon/issues/9). 12 | - The `tun` and `kvm` (intel or amd) kernel modules loaded. 13 | - A Linux bridge VMs can attach to. The bridge needs to be attached to a router that can provide DHCP addresses. 14 | - A Linux kernel. You can grab [this one](https://github.com/vaccovecrana/frag-falcon/raw/main/ff-test/src/test/resources/kernel/vmlinux-6.1.98) we use for testing, or [compile your own](https://github.com/firecracker-microvm/firecracker/tree/main/resources/guest_configs). 15 | - The latest [firecracker](https://github.com/firecracker-microvm/firecracker/releases) release. 16 | 17 | Make a directory to store kernels and virtual machines, and place at least one kernel in the `kernels` directory. 18 | 19 | ``` 20 | localhost:~/flc# tree 21 | . 22 | ├── kernels 23 | └── virtual-machines 24 | 3 directories, 0 files 25 | ``` 26 | 27 | > Note: if you plan to run `flc` as a non-root user, you'll need to `setcap` on the `flc` binary to grant network management capabilities. See [here](https://github.com/vaccovecrana/frag-falcon/blob/main/ff-test/README.md) for details. 28 | 29 | Start `flc`: 30 | 31 | ``` 32 | flc \ 33 | --api-host=0.0.0.0 \ 34 | --vm-dir=./virtual-machines \ 35 | --krn-dir=./kernels \ 36 | --fc-path=/usr/local/bin/firecracker 37 | ``` 38 | 39 | Open a browser and go to `http://:7070` 40 | 41 | Use the integrated UI to create a test VM using your target Linux kernel and network bridge. 42 | 43 | Screenshot 2024-08-19 at 10 37 48 PM 44 | 45 | You can also create a VM with an API call too: 46 | 47 | ``` 48 | curl -i -X POST \ 49 | -H "Content-Type:application/json" \ 50 | -d \ 51 | '{ 52 | "vm": { 53 | "tag": { 54 | "id": "new", 55 | "label": "test-vm-01", 56 | "description": "Test VM 01" 57 | }, 58 | "image": { "source": "docker.io/hashicorp/http-echo:latest" }, 59 | "config": { 60 | "bootsource": { "kernel_image_path": "/root/flc/kernels/vmlinux-6.1.98" }, 61 | "machineconfig": { "vcpu_count": 1, "mem_size_mib": 512 } 62 | } 63 | }, 64 | "network": { "dhcp": true, "brIf": "br0" }, 65 | "rebuildInitRamFs": false 66 | }' \ 67 | 'http://:7070/api/v1/vm' 68 | ``` 69 | 70 | You will then have a list of VMs that you can start, stop, and inspect logs on. 71 | 72 | Screenshot 2024-08-19 at 10 56 55 PM 73 | 74 | The test VM I am running is using the `hashicorp/http-echo:latest` image. So I can `curl` it's IP address, just like any other machine in my internal network: 75 | 76 | ``` 77 | % curl http://172.16.4.107:5678 78 | hello-world 79 | ``` 80 | 81 | ## Building/Development 82 | 83 | Requires Gradle 8 or later. 84 | 85 | Besides the usual `gradle clean build`, create a file with the following content at `~/.gsOrgConfig.json`: 86 | 87 | ``` 88 | { 89 | "orgId": "vacco-oss", 90 | "orgConfigUrl": "https://vacco-oss.s3.us-east-2.amazonaws.com/vacco-oss.json" 91 | } 92 | ``` 93 | 94 | > Note: there's still a lot of tests with local paths I need to document/refactor. 95 | 96 | ## Resources/credits 97 | 98 | - [TinyUntar](https://github.com/dsoprea/TinyUntar) 99 | - [Firecracker API](https://github.com/firecracker-microvm/firecracker/blob/main/src/firecracker/swagger/firecracker.yaml) 100 | - [Solar Bold Icons](https://www.svgrepo.com/collection/solar-bold-icons/1) 101 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { id("io.vacco.oss.gitflow") version "1.0.1" apply(false) } 2 | 3 | subprojects { 4 | apply(plugin = "io.vacco.oss.gitflow") 5 | 6 | group = "io.vacco.ff" 7 | version = "0.5.10" 8 | 9 | configure { 10 | addClasspathHell() 11 | } 12 | 13 | configure { 14 | resourceExclusions.add("module-info.class") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Frag Falcon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 |
22 | Frag Falcon 23 |

Frag Falcon

24 |

A Firecracker micro VM management tool. Run Docker images as micro VMs.

25 |
26 | 27 | Quick Start 28 |   29 | Docs 30 | 31 |
32 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ff-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.vacco.ronove") version "1.2.6" 3 | } 4 | 5 | configure { 6 | sharedLibrary(false, false) 7 | } 8 | 9 | configure { 10 | controllerClasses = arrayOf("io.vacco.ff.api.FgApiHdl") 11 | outFile.set(file("../ff-ui/@ff/rpc.ts")) 12 | } 13 | 14 | val api by configurations 15 | 16 | dependencies { 17 | api("io.vacco.shax:shax:2.0.6.0.1.0") 18 | api("com.google.code.gson:gson:2.11.0") 19 | api("am.ik.yavi:yavi:0.14.1") 20 | api("io.vacco.ronove:rv-kit-murmux:1.2.6_2.2.5") 21 | api(project(":ff-jni")) 22 | api(project(":ff-ui")) 23 | } 24 | 25 | val copyFfRt = tasks.register("copyFfRt") { 26 | from("./src/main/go/ffrt") 27 | from("./src/main/c/out/fgnet.so") 28 | into("./build/resources/main/io/vacco/ff") 29 | } 30 | 31 | tasks.processResources { 32 | dependsOn(copyFfRt) 33 | } 34 | -------------------------------------------------------------------------------- /ff-api/src/main/go/ffrt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaccovecrana/frag-falcon/abcd3955daaeb5adb30f25191ed03e4bdef01b09/ff-api/src/main/go/ffrt -------------------------------------------------------------------------------- /ff-api/src/main/go/fgConstants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Show debug output if this variable is defined (regardless of value). 4 | const FF_DEBUG = "FF_DEBUG" 5 | 6 | // Entry point. e.g. FF_ENTRYPOINT="["nats-server"]" 7 | const FF_ENTRYPOINT = "FF_ENTRYPOINT" 8 | 9 | // Command arguments. e.g. FF_CMD="["-arg0", "--arg1"]" 10 | const FF_CMD = "FF_CMD" 11 | 12 | // Working directory. 13 | const FF_WORKINGDIR = "FF_WORKINGDIR" 14 | 15 | // Numeric placeholders for drive mounts (JSON data) 16 | // FF_MOUNT_0=dev/vda:/media/data:true:false 17 | // ::: 18 | const FF_MOUNT = "FF_MOUNT" 19 | 20 | // Numeric placeholders for nameservers 21 | // e.g. FF_NS_0=1.1.1.1 22 | const FF_NS = "FF_NS" 23 | 24 | // Max number of file descriptors 25 | // Typically used by databases or storage solutions 26 | const FF_MAXFD = "FF_MAXFD" 27 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgDevice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "strconv" 10 | "syscall" 11 | 12 | "github.com/pilebones/go-udev/crawler" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | func initDevNull() error { 17 | devPath := "/dev/null" 18 | major := uint32(1) 19 | minor := uint32(3) 20 | devNum := unix.Mkdev(major, minor) 21 | mode := uint32(syscall.S_IFCHR | 0666) 22 | if stat, err := os.Stat(devPath); err == nil { 23 | if stat.Mode() != os.FileMode(mode) { 24 | log.Printf("%s exists but has incorrect permissions: %v, fixing permissions...", devPath, stat.Mode()) 25 | if err := os.Chmod(devPath, os.FileMode(mode)); err != nil { 26 | return fmt.Errorf("failed to chmod %s: %w", devPath, err) 27 | } 28 | } else { 29 | log.Println(devPath, "already exists with correct permissions") 30 | } 31 | return nil 32 | } 33 | if err := os.MkdirAll(filepath.Dir(devPath), 0755); err != nil { 34 | return err 35 | } 36 | if err := syscall.Mknod(devPath, mode, int(devNum)); err != nil { 37 | return err 38 | } 39 | if err := os.Chmod(devPath, os.FileMode(mode)); err != nil { 40 | return fmt.Errorf("failed to chmod %s after creation: %w", devPath, err) 41 | } 42 | return nil 43 | } 44 | 45 | func initDevMem() error { 46 | major := uint32(1) 47 | minor := uint32(1) 48 | devNum := unix.Mkdev(major, minor) 49 | mode := uint32(syscall.S_IFCHR | 0640) 50 | if _, err := os.Stat("/dev/mem"); os.IsNotExist(err) { 51 | if err := unix.Mknod("/dev/mem", mode, int(devNum)); err != nil { 52 | return fmt.Errorf("failed to create /dev/mem: %v", err) 53 | } 54 | if err := os.Chown("/dev/mem", 0, unix.Getegid()); err != nil { 55 | return fmt.Errorf("failed to set owner/group for /dev/mem: %v", err) 56 | } 57 | } 58 | _, err := os.OpenFile("/dev/mem", os.O_RDWR, 0640) 59 | if err != nil { 60 | return fmt.Errorf("failed to open /dev/mem: %v", err) 61 | } 62 | return nil 63 | } 64 | 65 | func initDevPts() error { 66 | devPtsPath := "/dev/pts" 67 | if _, err := os.Stat(devPtsPath); os.IsNotExist(err) { 68 | if err := os.MkdirAll(devPtsPath, 0755); err != nil { 69 | return fmt.Errorf("failed to create %s: %w", devPtsPath, err) 70 | } 71 | } 72 | if err := unix.Mount("devpts", devPtsPath, "devpts", 0, ""); err != nil { 73 | if err == unix.EBUSY { 74 | log.Printf("%s is already mounted", devPtsPath) 75 | return nil 76 | } 77 | return fmt.Errorf("failed to mount devpts on %s: %w", devPtsPath, err) 78 | } 79 | log.Println("Mounted devpts on", devPtsPath) 80 | return nil 81 | } 82 | 83 | func initMaxFileDescriptors() error { 84 | maxFdStr := os.Getenv(FF_MAXFD) 85 | if maxFdStr == "" { 86 | return nil 87 | } 88 | maxFd, err := strconv.Atoi(maxFdStr) 89 | if err != nil { 90 | return fmt.Errorf("invalid file descriptor limit value: %v", err) 91 | } 92 | var rLimit syscall.Rlimit 93 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { 94 | return fmt.Errorf("failed to get current file descriptor limit: %v", err) 95 | } 96 | rLimit.Cur = uint64(maxFd) 97 | if rLimit.Max < uint64(maxFd) { 98 | rLimit.Max = uint64(maxFd) 99 | } 100 | if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil { 101 | return fmt.Errorf("failed to set file descriptor limit: %v", err) 102 | } 103 | fmt.Printf("Set maximum file descriptors to %d\n", maxFd) 104 | return nil 105 | } 106 | 107 | func exists(path string) bool { 108 | if _, err := os.Stat(path); err == nil { 109 | return true 110 | } 111 | return false 112 | } 113 | 114 | func isType(maj int, min int, devType string) bool { 115 | path := fmt.Sprintf("/sys/dev/%s/%d:%d", devType, maj, min) 116 | exists := exists(path) 117 | return exists 118 | } 119 | 120 | func createDevice(device crawler.Device) error { 121 | if devName, ok := device.Env["DEVNAME"]; ok { 122 | if major, ok := device.Env["MAJOR"]; ok { 123 | if minor, ok := device.Env["MINOR"]; ok { 124 | if iMaj, err := strconv.Atoi(major); err == nil { 125 | if iMin, err := strconv.Atoi(minor); err == nil { 126 | if fInfo, err := os.Stat(device.KObj); err == nil { 127 | 128 | dst := fmt.Sprintf("/dev/%s", devName) 129 | devNum := unix.Mkdev(uint32(iMaj), uint32(iMin)) 130 | mode := fInfo.Mode() 131 | perm := os.FileMode.Perm(mode) 132 | 133 | switch { 134 | case (isType(iMaj, iMin, "char")): 135 | perm |= syscall.S_IFCHR 136 | case (isType(iMaj, iMin, "block")): 137 | perm |= syscall.S_IFBLK 138 | default: 139 | return fmt.Errorf("%s is not a device", device.KObj) 140 | } 141 | 142 | oldMask := syscall.Umask(int(0)) 143 | if err := os.MkdirAll(path.Dir(dst), 0755); err != nil { 144 | return err 145 | } else if isDebugEnabled() { 146 | log.Println("Create device at", device.KObj, "on", dst, "mode", perm, "dev", devNum, "with env", device.Env) 147 | } 148 | if err := syscall.Mknod(dst, uint32(perm), int(devNum)); err != nil { 149 | return err 150 | } 151 | syscall.Umask(oldMask) 152 | } else { 153 | return err 154 | } 155 | } else { 156 | return err 157 | } 158 | } else { 159 | return err 160 | } 161 | } 162 | } 163 | } // else { log.Println("Skipping device", device.KObj) } 164 | 165 | return nil 166 | 167 | } 168 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgInit.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/lorenzosaino/go-sysctl" 8 | "github.com/pilebones/go-udev/crawler" 9 | ) 10 | 11 | func initKernelParams() error { 12 | if exists("/etc/sysctl.conf") { 13 | log.Println("Loading kernel parameters...") 14 | if err := sysctl.LoadConfigAndApply(); err == nil { 15 | if isDebugEnabled() { 16 | v, _ := sysctl.GetAll() 17 | log.Println("Current kernel parameters", v) 18 | } 19 | } else { 20 | log.Println("Failed to load kernel parameters", err) 21 | return err 22 | } 23 | } 24 | return nil 25 | } 26 | 27 | func initDev() { 28 | if err := initDevMem(); err != nil { 29 | log.Println("Failed to create /dev/mem device", err) 30 | } 31 | if err := initDevNull(); err != nil { 32 | log.Println("Failed to create /dev/null device", err) 33 | } 34 | if err := initDevPts(); err != nil { 35 | log.Println("Failed to create /dev/pts", err) 36 | } 37 | if err := initMaxFileDescriptors(); err != nil { 38 | fmt.Println(err) 39 | return 40 | } 41 | log.Println("Enumerating existing devices...") 42 | queue := make(chan crawler.Device) 43 | errors := make(chan error) 44 | crawler.ExistingDevices(queue, errors, nil) 45 | for { 46 | select { 47 | case device, more := <-queue: 48 | if !more { 49 | log.Printf("Finished enumerating devices\n") 50 | return 51 | } 52 | if err := createDevice(device); err != nil { 53 | log.Println("Failed to create device", device.KObj, "-", err) 54 | } 55 | case err := <-errors: 56 | log.Println("ERROR:", err) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgMonitor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | func shutdown(cmd *exec.Cmd, mounts []SpMount, terminate bool) { 15 | if terminate { 16 | log.Println("Terminating process", cmd) 17 | cmd.Process.Signal(syscall.SIGINT) 18 | time.Sleep(3 * time.Second) // TODO parameterize shutdown standby time. 19 | } 20 | for _, mount := range mounts { 21 | log.Println("Unmounting", mount) 22 | if err := syscall.Unmount(mount.Path, 0); err != nil { 23 | log.Println("Unable to unmount storage", mount) 24 | } 25 | } 26 | syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF) 27 | } 28 | 29 | func getArray(envVar string) ([]string, error) { 30 | envVal := os.Getenv(envVar) 31 | if envVal == "" { 32 | return []string{}, nil 33 | } 34 | var envArr []string 35 | err := json.Unmarshal([]byte(envVal), &envArr) 36 | if err != nil { 37 | return nil, fmt.Errorf("error parsing [%s]: %v", envVar, err) 38 | } 39 | return envArr, nil 40 | } 41 | 42 | func monitor() { 43 | signals := make(chan os.Signal, 1) 44 | signal.Notify(signals, syscall.Signal(38)) // SIGRTMIN+4 45 | 46 | if mounts, err := initMounts(os.Environ()); err != nil { 47 | log.Println("Unable to initialize mount points", err) 48 | } else if entryPoint, err := getArray(FF_ENTRYPOINT); err != nil { 49 | log.Println("Unable to load entry point", err) 50 | } else if cmdArgs, err := getArray(FF_CMD); err != nil { 51 | log.Println("Unable to load command", err) 52 | } else if len(entryPoint) == 0 && len(cmdArgs) == 0 { 53 | log.Println("No command to execute: both entry point and command arguments are missing") 54 | } else { 55 | command := append(entryPoint, cmdArgs...) 56 | log.Println("Executing", command) 57 | cmd := exec.Command(command[0]) 58 | if len(command) > 1 { 59 | cmd = exec.Command(command[0], command[1:]...) 60 | } 61 | cmd.Stdout = os.Stdout 62 | cmd.Stderr = os.Stderr 63 | cmd.Stdin = os.Stdin 64 | cmd.Env = os.Environ() 65 | workingDir := os.Getenv(FF_WORKINGDIR) 66 | if workingDir != "" { 67 | cmd.Dir = workingDir 68 | } 69 | if err := cmd.Start(); err != nil { 70 | log.Println("Process", entryPoint, "failed to start", err) 71 | } else { 72 | go func() { 73 | <-signals 74 | shutdown(cmd, mounts, true) 75 | }() 76 | if err := cmd.Wait(); err != nil { 77 | log.Println("Process", entryPoint, "finished abnormally", err) 78 | } else { 79 | log.Println("Process", entryPoint, "finished normally") 80 | } 81 | } 82 | shutdown(nil, mounts, false) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgMount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "syscall" 10 | ) 11 | 12 | var ext4MountOptions = strings.Join([]string{ 13 | "journal_checksum", 14 | "journal_ioprio=0", 15 | "barrier=1", 16 | "data=ordered", 17 | "errors=remount-ro", 18 | }, ",") 19 | 20 | func MountExt4(mount SpMount) error { 21 | var flags uintptr 22 | if mount.ReadOnly { 23 | flags |= syscall.MS_RDONLY 24 | } 25 | if mount.Sync { 26 | flags |= syscall.MS_SYNCHRONOUS | syscall.MS_DIRSYNC 27 | } 28 | err := syscall.Mount(mount.Device, mount.Path, "ext4", flags, ext4MountOptions) 29 | return os.NewSyscallError("mount", err) 30 | } 31 | 32 | type SpMount struct { 33 | Device string 34 | Path string 35 | Sync bool 36 | ReadOnly bool 37 | } 38 | 39 | func parseBool(value string) (bool, error) { 40 | if value == "true" { 41 | return true, nil 42 | } else if value == "false" { 43 | return false, nil 44 | } 45 | return false, errors.New("invalid boolean value") 46 | } 47 | 48 | func parseMount(arg string) (*SpMount, error) { 49 | parts := strings.Split(arg, ":") 50 | if len(parts) != 4 { 51 | return nil, fmt.Errorf("invalid mount format: %s", parts) 52 | } 53 | device := parts[0] 54 | path := parts[1] 55 | sync, err := parseBool(parts[2]) 56 | if err != nil { 57 | return nil, fmt.Errorf("invalid sync value: %s", parts[2]) 58 | } 59 | readOnly, err := parseBool(parts[3]) 60 | if err != nil { 61 | return nil, fmt.Errorf("invalid read-only value: %s", parts[3]) 62 | } 63 | return &SpMount{ 64 | Device: device, 65 | Path: path, 66 | Sync: sync, 67 | ReadOnly: readOnly, 68 | }, nil 69 | } 70 | 71 | func initMounts(envKv []string) ([]SpMount, error) { 72 | mounts := make([]SpMount, 0) 73 | for _, e := range envKv { 74 | pair := strings.SplitN(e, "=", 2) 75 | if strings.HasPrefix(pair[0], FF_MOUNT) { 76 | log.Println("Mounting", pair[1]) 77 | mount, err := parseMount(pair[1]) 78 | if err != nil { 79 | return nil, err 80 | } 81 | if err := os.MkdirAll(mount.Path, os.ModePerm); err != nil { 82 | return nil, err 83 | } else if err := MountExt4(*mount); err != nil { 84 | return nil, err 85 | } 86 | mounts = append(mounts, *mount) 87 | } 88 | } 89 | return mounts, nil 90 | } 91 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgMount_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestParseMount(t *testing.T) { 9 | tests := []struct { 10 | input string 11 | expectedDevice string 12 | expectedPath string 13 | expectedSync bool 14 | expectedRO bool 15 | expectError bool 16 | }{ 17 | { 18 | input: "/dev/vda:/media/data:true:false", 19 | expectedDevice: "/dev/vda", 20 | expectedPath: "/media/data", 21 | expectedSync: true, 22 | expectedRO: false, 23 | expectError: false, 24 | }, 25 | { 26 | input: "/dev/vdb:/media/backup:true:true", 27 | expectedDevice: "/dev/vdb", 28 | expectedPath: "/media/backup", 29 | expectedSync: true, 30 | expectedRO: true, 31 | expectError: false, 32 | }, 33 | { 34 | input: "/dev/vdc:/media/invalid:true:maybe", 35 | expectError: true, 36 | }, 37 | { 38 | input: "/dev/vdd:/media/missing_part", 39 | expectError: true, 40 | }, 41 | { 42 | input: "", 43 | expectError: true, 44 | }, 45 | } 46 | 47 | for _, test := range tests { 48 | t.Run(test.input, func(t *testing.T) { 49 | mount, err := parseMount(test.input) 50 | 51 | if test.expectError { 52 | if err == nil { 53 | t.Errorf("expected error, got nil") 54 | } 55 | return 56 | } 57 | 58 | if err != nil { 59 | t.Errorf("unexpected error: %v", err) 60 | return 61 | } 62 | 63 | if mount.Device != test.expectedDevice { 64 | t.Errorf("expected Device %s, got %s", test.expectedDevice, mount.Device) 65 | } 66 | 67 | if mount.Path != test.expectedPath { 68 | t.Errorf("expected Path %s, got %s", test.expectedPath, mount.Path) 69 | } 70 | 71 | if mount.Sync != test.expectedSync { 72 | t.Errorf("expected Sync %v, got %v", test.expectedSync, mount.Sync) 73 | } 74 | 75 | if mount.ReadOnly != test.expectedRO { 76 | t.Errorf("expected ReadOnly %v, got %v", test.expectedRO, mount.ReadOnly) 77 | } 78 | }) 79 | } 80 | 81 | log.Println("SpMount tests - Done.") 82 | } 83 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgResolve.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func initResolv(envKv []string, path string) error { 11 | nameServers := make([]string, 0) 12 | for _, e := range envKv { 13 | pair := strings.SplitN(e, "=", 2) 14 | if strings.HasPrefix(pair[0], FF_NS) { 15 | log.Println("Adding nameserver", pair[1]) 16 | nameServers = append(nameServers, pair[1]) 17 | } 18 | } 19 | file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) 20 | if err != nil { 21 | return err 22 | } 23 | writer := bufio.NewWriter(file) 24 | for _, str := range nameServers { 25 | _, _ = writer.WriteString("nameserver " + str + "\n") 26 | } 27 | writer.Flush() 28 | file.Close() 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /ff-api/src/main/go/fgResolve_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestResolv(t *testing.T) { 9 | env := make([]string, 0) 10 | env = append(env, "FF_NS_0=1.1.1.1") 11 | env = append(env, "FF_NS_1=8.8.8.8") 12 | if err := initResolv(env, "/tmp/gopher-ns.txt"); err == nil { 13 | log.Println("Nameserver data written.") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ff-api/src/main/go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vaccovecrana/frag-falcon/ffrt 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/lorenzosaino/go-sysctl v0.3.1 7 | github.com/pilebones/go-udev v0.9.0 8 | golang.org/x/sys v0.22.0 9 | ) 10 | 11 | require ( 12 | github.com/BurntSushi/toml v1.4.0 // indirect 13 | golang.org/x/exp/typeparams v0.0.0-20240716175740-e3f259677ff7 // indirect 14 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect 15 | golang.org/x/mod v0.19.0 // indirect 16 | golang.org/x/sync v0.7.0 // indirect 17 | golang.org/x/tools v0.23.0 // indirect 18 | honnef.co/go/tools v0.4.7 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /ff-api/src/main/go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 2 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 5 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 6 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | github.com/lorenzosaino/go-sysctl v0.3.1 h1:3phX80tdITw2fJjZlwbXQnDWs4S30beNcMbw0cn0HtY= 9 | github.com/lorenzosaino/go-sysctl v0.3.1/go.mod h1:5grcsBRpspKknNS1qzt1eIeRDLrhpKZAtz8Fcuvs1Rc= 10 | github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q= 11 | github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI= 12 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 13 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 14 | golang.org/x/exp/typeparams v0.0.0-20240716175740-e3f259677ff7 h1:WYloiet/cq8nk77Tw4jAHFi4WPD4FwpjEZtuLpHaJww= 15 | golang.org/x/exp/typeparams v0.0.0-20240716175740-e3f259677ff7/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 16 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 17 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 18 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 19 | golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= 20 | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 21 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 22 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 25 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 26 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 27 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 29 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 32 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= 33 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 34 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 35 | honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= 36 | honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= 37 | -------------------------------------------------------------------------------- /ff-api/src/main/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | const banner = ` 11 | -------------------------- 12 | ________ ___ ______ 13 | / __/ __/___/ _ \/_ __/ 14 | / _// _//___/ , _/ / / 15 | /_/ /_/ /_/|_| /_/ 16 | --------------------------` 17 | 18 | func mountFs(source, target, fstype string, flags uintptr) error { 19 | if err := os.MkdirAll(target, 0755); err != nil { 20 | return fmt.Errorf("failed to create mount target %s: %v", target, err) 21 | } 22 | if err := syscall.Mount(source, target, fstype, flags, ""); err != nil { 23 | return fmt.Errorf("failed to mount %s to %s: %v", source, target, err) 24 | } 25 | log.Printf("Mounted %s to %s", source, target) 26 | return nil 27 | } 28 | 29 | func isDebugEnabled() bool { 30 | _, debugDev := os.LookupEnv(FF_DEBUG) 31 | return debugDev 32 | } 33 | 34 | func main() { 35 | log.Println(banner) 36 | flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RELATIME) 37 | if err := mountFs("proc", "/proc", "proc", flags); err != nil { 38 | log.Fatalf("Failed to mount /proc: %v", err) 39 | } else if err := mountFs("sysfs", "/sys", "sysfs", flags); err != nil { 40 | log.Fatalf("Failed to mount /sys: %v", err) 41 | } else { 42 | initResolv(os.Environ(), "/etc/resolv.conf") 43 | initKernelParams() 44 | initDev() 45 | monitor() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/FgMain.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import io.vacco.ff.service.FgOptions; 4 | import io.vacco.ff.service.FgContext; 5 | import java.util.Arrays; 6 | 7 | public class FgMain { 8 | 9 | public static void main(String[] args) { 10 | if (args == null || args.length == 0 || Arrays.asList(args).contains("--help")) { 11 | System.out.println(FgOptions.usage()); 12 | return; 13 | } 14 | FgOptions.setFrom(args); 15 | var ctx = new FgContext(); 16 | try { // TODO add UNIX SIGTERM handler 17 | ctx.init(); 18 | } catch (Exception e) { 19 | System.out.printf("Application error - %s %s%n", 20 | e.getClass().getSimpleName(), e.getMessage() 21 | ); 22 | ctx.close(); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/api/FgApi.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.api; 2 | 3 | import com.google.gson.Gson; 4 | import io.vacco.ff.service.FgVmSvc; 5 | import io.vacco.ff.util.FgIo; 6 | import io.vacco.murmux.Murmux; 7 | import io.vacco.murmux.http.MxStatus; 8 | import io.vacco.murmux.middleware.MxRouter; 9 | import io.vacco.ronove.murmux.RvMxAdapter; 10 | import org.slf4j.*; 11 | import java.io.*; 12 | import java.util.concurrent.ThreadFactory; 13 | 14 | import static java.lang.String.format; 15 | import static java.lang.Integer.toHexString; 16 | import static io.vacco.ff.service.FgOptions.*; 17 | import static java.util.concurrent.Executors.newCachedThreadPool; 18 | import static io.vacco.ff.service.FgLogging.onError; 19 | import static io.vacco.ff.api.FgRoute.*; 20 | 21 | public class FgApi implements Closeable { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(FgApi.class); 24 | 25 | private final Murmux mx; 26 | private final FgVmSvc vmSvc; 27 | 28 | public FgApi(Gson g) { 29 | var tf = (ThreadFactory) r -> new Thread(r, format("ff-api-%s", toHexString(r.hashCode()))); 30 | this.mx = new Murmux(host, newCachedThreadPool(tf)); 31 | this.vmSvc = new FgVmSvc(); 32 | var apiHdl = new FgApiHdl(this.vmSvc); 33 | var uiHdl = new FgUiHdl(); 34 | var rpc = new RvMxAdapter<>(apiHdl, (xc, e) -> { 35 | onError(log, "api - request handling error - {}", e, xc.getPath()); 36 | xc.withStatus(MxStatus._500); 37 | xc.commit(); 38 | }, g::fromJson, g::toJson).build(); 39 | mx.rootHandler(new MxRouter().prefix(apiRoot, rpc).noMatch(uiHdl)); 40 | } 41 | 42 | public FgApi open() { 43 | mx.listen(port); 44 | vmSvc.start(); 45 | log.info("ui - ready at http://{}:{}", host, port); 46 | return this; 47 | } 48 | 49 | @Override public void close() { 50 | mx.stop(); 51 | FgIo.close(vmSvc); 52 | log.info("ui - stopped"); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/api/FgApiHdl.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.api; 2 | 3 | import io.vacco.ff.schema.*; 4 | import io.vacco.ff.service.FgVmSvc; 5 | import io.vacco.ronove.RvResponse; 6 | import jakarta.ws.rs.*; 7 | import jakarta.ws.rs.core.Response; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | import static io.vacco.ff.api.FgRoute.*; 11 | 12 | public class FgApiHdl { 13 | 14 | private final FgVmSvc vmSvc; 15 | 16 | public FgApiHdl(FgVmSvc vmSvc) { 17 | this.vmSvc = requireNonNull(vmSvc); 18 | } 19 | 20 | @GET @Path(apiV1Vm) 21 | public RvResponse apiV1VmGet() { 22 | var r = new RvResponse().withBody(vmSvc.vmList()); 23 | return r.body.errors == null || r.body.errors.isEmpty() 24 | ? r.withStatus(Response.Status.OK) 25 | : r.withStatus(Response.Status.BAD_REQUEST); 26 | } 27 | 28 | @GET @Path(apiV1VmId) 29 | public RvResponse apiV1VmIdGet(@PathParam(VmId) String vmId) { 30 | var r = new RvResponse().withBody(vmSvc.vmGet(vmId)); 31 | var noErrors = r.body.errors == null || r.body.errors.isEmpty(); 32 | var noWarnings = r.body.warnings == null || r.body.warnings.isEmpty(); 33 | return noErrors && noWarnings 34 | ? r.withStatus(Response.Status.OK) 35 | : r.withStatus(Response.Status.BAD_REQUEST); 36 | } 37 | 38 | @POST @Path(apiV1Vm) 39 | public RvResponse apiV1VmPost(@BeanParam FgVmCreate req) { 40 | var r = new RvResponse().withBody(vmSvc.vmBuild(req)); 41 | return req.errors == null || req.errors.isEmpty() 42 | ? r.withStatus(Response.Status.OK) 43 | : r.withStatus(Response.Status.BAD_REQUEST); 44 | } 45 | 46 | @POST @Path(apiV1VmStart) 47 | public RvResponse apiV1VmStartPost(@BeanParam FgVmStart req) { 48 | var r = new RvResponse().withBody(vmSvc.vmStart(req)); 49 | return req.errors == null || req.errors.isEmpty() 50 | ? r.withStatus(Response.Status.OK) 51 | : r.withStatus(Response.Status.BAD_REQUEST); 52 | } 53 | 54 | @POST @Path(apiV1VmStop) 55 | public RvResponse apiV1VmStopPost(@BeanParam FgVmStop req) { 56 | var r = new RvResponse().withBody(vmSvc.vmStop(req)); 57 | return req.errors == null || req.errors.isEmpty() 58 | ? r.withStatus(Response.Status.OK) 59 | : r.withStatus(Response.Status.BAD_REQUEST); 60 | } 61 | 62 | @POST @Path(apiV1VmLogs) 63 | public RvResponse apiV1VmLogsPost(@BeanParam FgVmLogs req) { 64 | var r = new RvResponse().withBody(vmSvc.vmLogs(req)); 65 | return req.errors == null || req.errors.isEmpty() 66 | ? r.withStatus(Response.Status.OK) 67 | : r.withStatus(Response.Status.BAD_REQUEST); 68 | } 69 | 70 | @DELETE @Path(apiV1VmLogs) 71 | public RvResponse apiV1VmLogsDelete(@QueryParam(VmId) String vmId) { 72 | var r = new RvResponse().withBody(vmSvc.vmLogsDelete(vmId)); 73 | return r.body.errors == null || r.body.errors.isEmpty() 74 | ? r.withStatus(Response.Status.OK) 75 | : r.withStatus(Response.Status.BAD_REQUEST); 76 | } 77 | 78 | @GET @Path(apiV1Br) 79 | public RvResponse apiV1BrGet() { 80 | var r = new RvResponse().withBody(vmSvc.brIfList()); 81 | return r.body.errors == null || r.body.errors.isEmpty() 82 | ? r.withStatus(Response.Status.OK) 83 | : r.withStatus(Response.Status.BAD_REQUEST); 84 | } 85 | 86 | @GET @Path(apiV1Krn) 87 | public RvResponse apiV1KrnGet() { 88 | var r = new RvResponse().withBody(vmSvc.krnList()); 89 | return r.body.errors == null || r.body.errors.isEmpty() 90 | ? r.withStatus(Response.Status.OK) 91 | : r.withStatus(Response.Status.BAD_REQUEST); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/api/FgRoute.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.api; 2 | 3 | public class FgRoute { 4 | 5 | public static final String VmId = "vmId"; 6 | public static final String VmIdNew = "new"; 7 | 8 | public static final String apiRoot = "/api"; 9 | public static final String 10 | apiV1Vm = "/api/v1/vm", 11 | apiV1VmId = "/api/v1/vm/{vmId}", 12 | apiV1VmStart = "/api/v1/vm/start", 13 | apiV1VmStop = "/api/v1/vm/stop", 14 | apiV1VmLogs = "/api/v1/vm/logs", 15 | apiV1Br = "/api/v1/br", 16 | apiV1Krn = "/api/v1/krn"; 17 | 18 | public static final String 19 | fgUi = "/@ff", 20 | indexCss = "/index.css", indexJs = "/index.js", indexJsMap = "/index.js.map", 21 | favicon = "/favicon.svg", version = "/version"; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/api/FgUiHdl.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.api; 2 | 3 | import io.vacco.murmux.http.*; 4 | import io.vacco.murmux.middleware.MxStatic; 5 | import java.io.File; 6 | import java.nio.file.*; 7 | 8 | import static java.lang.String.format; 9 | import static io.vacco.ff.api.FgRoute.*; 10 | import static io.vacco.ff.util.FgIo.hostName; 11 | 12 | public class FgUiHdl extends MxStatic { 13 | 14 | private static final File pkgJson = new File("../ff-ui/package.json"); 15 | private static final Origin contentOrigin = pkgJson.exists() ? Origin.FileSystem : Origin.Classpath; 16 | private static final Path contentRoot = pkgJson.exists() 17 | ? Paths.get("../ff-ui/build/resources/main/ui") 18 | : Paths.get("/ui"); 19 | 20 | private static final String indexHtml = String.join("\n", "", 21 | "", 22 | "", 23 | "", 24 | " ", 25 | " ", 26 | " ", 27 | " ", 28 | " ", 29 | format(" %s", hostName()), 30 | "", 31 | "", 32 | "
", 33 | " ", 34 | " ", 35 | "", 36 | "" 37 | ); 38 | 39 | public FgUiHdl() { 40 | super(contentOrigin, contentRoot); 41 | this.withNoTypeResolver((p, o) -> p.endsWith(".map") ? MxMime.json.type : MxMime.bin.type); 42 | } 43 | 44 | @Override public void handle(MxExchange xc) { 45 | var p = xc.getPath(); 46 | if (p.startsWith(fgUi)) { 47 | super.handle(xc); 48 | return; 49 | } 50 | switch (p) { 51 | case indexCss: 52 | case indexJs: 53 | case indexJsMap: 54 | case favicon: 55 | case version: 56 | super.handle(xc); 57 | break; 58 | default: 59 | xc.commitHtml(indexHtml); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Balloon.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Boolean; 4 | import java.lang.Long; 5 | 6 | /** 7 | * Balloon device descriptor. 8 | */ 9 | public class Balloon { 10 | public Long amount_mib; 11 | 12 | public Boolean deflate_on_oom; 13 | 14 | public Long stats_polling_interval_s; 15 | 16 | /** 17 | * Target balloon size in MiB. 18 | */ 19 | public Balloon amount_mib(Long amount_mib) { 20 | this.amount_mib = amount_mib; 21 | return this; 22 | } 23 | 24 | /** 25 | * Whether the balloon should deflate when the guest has memory pressure. 26 | */ 27 | public Balloon deflate_on_oom(Boolean deflate_on_oom) { 28 | this.deflate_on_oom = deflate_on_oom; 29 | return this; 30 | } 31 | 32 | /** 33 | * Interval in seconds between refreshing statistics. A non-zero value will enable the statistics. Defaults to 0. 34 | */ 35 | public Balloon stats_polling_interval_s(Long stats_polling_interval_s) { 36 | this.stats_polling_interval_s = stats_polling_interval_s; 37 | return this; 38 | } 39 | 40 | public static Balloon balloon() { 41 | return new Balloon(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/BalloonStats.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Long; 4 | 5 | /** 6 | * Describes the balloon device statistics. 7 | */ 8 | public class BalloonStats { 9 | public Long actual_mib; 10 | 11 | public Long actual_pages; 12 | 13 | public Long available_memory; 14 | 15 | public Long disk_caches; 16 | 17 | public Long free_memory; 18 | 19 | public Long hugetlb_allocations; 20 | 21 | public Long hugetlb_failures; 22 | 23 | public Long major_faults; 24 | 25 | public Long minor_faults; 26 | 27 | public Long swap_in; 28 | 29 | public Long swap_out; 30 | 31 | public Long target_mib; 32 | 33 | public Long target_pages; 34 | 35 | public Long total_memory; 36 | 37 | /** 38 | * Actual amount of memory (in MiB) the device is holding. 39 | */ 40 | public BalloonStats actual_mib(Long actual_mib) { 41 | this.actual_mib = actual_mib; 42 | return this; 43 | } 44 | 45 | /** 46 | * Actual number of pages the device is holding. 47 | */ 48 | public BalloonStats actual_pages(Long actual_pages) { 49 | this.actual_pages = actual_pages; 50 | return this; 51 | } 52 | 53 | /** 54 | * An estimate of how much memory is available (in bytes) for starting new applications, without pushing the system to swap. 55 | */ 56 | public BalloonStats available_memory(Long available_memory) { 57 | this.available_memory = available_memory; 58 | return this; 59 | } 60 | 61 | /** 62 | * The amount of memory, in bytes, that can be quickly reclaimed without additional I/O. Typically these pages are used for caching files from disk. 63 | */ 64 | public BalloonStats disk_caches(Long disk_caches) { 65 | this.disk_caches = disk_caches; 66 | return this; 67 | } 68 | 69 | /** 70 | * The amount of memory not being used for any purpose (in bytes). 71 | */ 72 | public BalloonStats free_memory(Long free_memory) { 73 | this.free_memory = free_memory; 74 | return this; 75 | } 76 | 77 | /** 78 | * The number of successful hugetlb page allocations in the guest. 79 | */ 80 | public BalloonStats hugetlb_allocations(Long hugetlb_allocations) { 81 | this.hugetlb_allocations = hugetlb_allocations; 82 | return this; 83 | } 84 | 85 | /** 86 | * The number of failed hugetlb page allocations in the guest. 87 | */ 88 | public BalloonStats hugetlb_failures(Long hugetlb_failures) { 89 | this.hugetlb_failures = hugetlb_failures; 90 | return this; 91 | } 92 | 93 | /** 94 | * The number of major page faults that have occurred. 95 | */ 96 | public BalloonStats major_faults(Long major_faults) { 97 | this.major_faults = major_faults; 98 | return this; 99 | } 100 | 101 | /** 102 | * The number of minor page faults that have occurred. 103 | */ 104 | public BalloonStats minor_faults(Long minor_faults) { 105 | this.minor_faults = minor_faults; 106 | return this; 107 | } 108 | 109 | /** 110 | * The amount of memory that has been swapped in (in bytes). 111 | */ 112 | public BalloonStats swap_in(Long swap_in) { 113 | this.swap_in = swap_in; 114 | return this; 115 | } 116 | 117 | /** 118 | * The amount of memory that has been swapped out to disk (in bytes). 119 | */ 120 | public BalloonStats swap_out(Long swap_out) { 121 | this.swap_out = swap_out; 122 | return this; 123 | } 124 | 125 | /** 126 | * Target amount of memory (in MiB) the device aims to hold. 127 | */ 128 | public BalloonStats target_mib(Long target_mib) { 129 | this.target_mib = target_mib; 130 | return this; 131 | } 132 | 133 | /** 134 | * Target number of pages the device aims to hold. 135 | */ 136 | public BalloonStats target_pages(Long target_pages) { 137 | this.target_pages = target_pages; 138 | return this; 139 | } 140 | 141 | /** 142 | * The total amount of memory available (in bytes). 143 | */ 144 | public BalloonStats total_memory(Long total_memory) { 145 | this.total_memory = total_memory; 146 | return this; 147 | } 148 | 149 | public static BalloonStats balloonStats() { 150 | return new BalloonStats(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/BalloonStatsUpdate.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Long; 4 | 5 | /** 6 | * Update the statistics polling interval, with the first statistics update scheduled immediately. Statistics cannot be turned on/off after boot. 7 | */ 8 | public class BalloonStatsUpdate { 9 | public Long stats_polling_interval_s; 10 | 11 | /** 12 | * Interval in seconds between refreshing statistics. 13 | */ 14 | public BalloonStatsUpdate stats_polling_interval_s(Long stats_polling_interval_s) { 15 | this.stats_polling_interval_s = stats_polling_interval_s; 16 | return this; 17 | } 18 | 19 | public static BalloonStatsUpdate balloonStatsUpdate() { 20 | return new BalloonStatsUpdate(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/BalloonUpdate.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Long; 4 | 5 | /** 6 | * Balloon device descriptor. 7 | */ 8 | public class BalloonUpdate { 9 | public Long amount_mib; 10 | 11 | /** 12 | * Target balloon size in MiB. 13 | */ 14 | public BalloonUpdate amount_mib(Long amount_mib) { 15 | this.amount_mib = amount_mib; 16 | return this; 17 | } 18 | 19 | public static BalloonUpdate balloonUpdate() { 20 | return new BalloonUpdate(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/BootSource.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | /** 6 | * Boot source descriptor. 7 | */ 8 | public class BootSource { 9 | public String boot_args; 10 | 11 | public String initrd_path; 12 | 13 | public String kernel_image_path; 14 | 15 | /** 16 | * Kernel boot arguments 17 | */ 18 | public BootSource boot_args(String boot_args) { 19 | this.boot_args = boot_args; 20 | return this; 21 | } 22 | 23 | /** 24 | * Host level path to the initrd image used to boot the guest 25 | */ 26 | public BootSource initrd_path(String initrd_path) { 27 | this.initrd_path = initrd_path; 28 | return this; 29 | } 30 | 31 | /** 32 | * Host level path to the kernel image used to boot the guest 33 | */ 34 | public BootSource kernel_image_path(String kernel_image_path) { 35 | this.kernel_image_path = kernel_image_path; 36 | return this; 37 | } 38 | 39 | public static BootSource bootSource() { 40 | return new BootSource(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/CpuConfig.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.cpuconfig.Cpuid_modifiers; 4 | import io.vacco.ff.firecracker.cpuconfig.Msr_modifiers; 5 | import io.vacco.ff.firecracker.cpuconfig.Reg_modifiers; 6 | 7 | /** 8 | * The CPU configuration template defines a set of bit maps as modifiers of flags accessed by register to be disabled/enabled for the microvm. 9 | */ 10 | public class CpuConfig { 11 | public Cpuid_modifiers cpuid_modifiers; 12 | 13 | public Msr_modifiers msr_modifiers; 14 | 15 | public Reg_modifiers reg_modifiers; 16 | 17 | /** 18 | * A collection of CPUIDs to be modified. (x86_64) 19 | */ 20 | public CpuConfig cpuid_modifiers(Cpuid_modifiers cpuid_modifiers) { 21 | this.cpuid_modifiers = cpuid_modifiers; 22 | return this; 23 | } 24 | 25 | /** 26 | * A collection of model specific registers to be modified. (x86_64) 27 | */ 28 | public CpuConfig msr_modifiers(Msr_modifiers msr_modifiers) { 29 | this.msr_modifiers = msr_modifiers; 30 | return this; 31 | } 32 | 33 | /** 34 | * A collection of registers to be modified. (aarch64) 35 | */ 36 | public CpuConfig reg_modifiers(Reg_modifiers reg_modifiers) { 37 | this.reg_modifiers = reg_modifiers; 38 | return this; 39 | } 40 | 41 | public static CpuConfig cpuConfig() { 42 | return new CpuConfig(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/CpuTemplate.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | /** 4 | * The CPU Template defines a set of flags to be disabled from the microvm so that the features exposed to the guest are the same as in the selected instance type. This parameter has been deprecated and it will be removed in future Firecracker release. 5 | */ 6 | public enum CpuTemplate { 7 | C3, 8 | 9 | T2, 10 | 11 | T2S, 12 | 13 | T2CL, 14 | 15 | T2A, 16 | 17 | V1N1, 18 | 19 | None 20 | } 21 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Drive.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.drive.Cache_type; 4 | import io.vacco.ff.firecracker.drive.Io_engine; 5 | import java.lang.Boolean; 6 | import java.lang.String; 7 | 8 | public class Drive { 9 | public Cache_type cache_type; 10 | 11 | public String drive_id; 12 | 13 | public Io_engine io_engine; 14 | 15 | public Boolean is_read_only; 16 | 17 | public Boolean is_root_device; 18 | 19 | public String partuuid; 20 | 21 | public String path_on_host; 22 | 23 | public RateLimiter rate_limiter; 24 | 25 | public String socket; 26 | 27 | /** 28 | * Represents the caching strategy for the block device. 29 | */ 30 | public Drive cache_type(Cache_type cache_type) { 31 | this.cache_type = cache_type; 32 | return this; 33 | } 34 | 35 | public Drive drive_id(String drive_id) { 36 | this.drive_id = drive_id; 37 | return this; 38 | } 39 | 40 | /** 41 | * Type of the IO engine used by the device. "Async" is supported on host kernels newer than 5.10.51. This field is optional for virtio-block config and should be omitted for vhost-user-block configuration. 42 | */ 43 | public Drive io_engine(Io_engine io_engine) { 44 | this.io_engine = io_engine; 45 | return this; 46 | } 47 | 48 | /** 49 | * Is block read only. This field is required for virtio-block config and should be omitted for vhost-user-block configuration. 50 | */ 51 | public Drive is_read_only(Boolean is_read_only) { 52 | this.is_read_only = is_read_only; 53 | return this; 54 | } 55 | 56 | public Drive is_root_device(Boolean is_root_device) { 57 | this.is_root_device = is_root_device; 58 | return this; 59 | } 60 | 61 | /** 62 | * Represents the unique id of the boot partition of this device. It is optional and it will be taken into account only if the is_root_device field is true. 63 | */ 64 | public Drive partuuid(String partuuid) { 65 | this.partuuid = partuuid; 66 | return this; 67 | } 68 | 69 | /** 70 | * Host level path for the guest drive. This field is required for virtio-block config and should be omitted for vhost-user-block configuration. 71 | */ 72 | public Drive path_on_host(String path_on_host) { 73 | this.path_on_host = path_on_host; 74 | return this; 75 | } 76 | 77 | public Drive rate_limiter(RateLimiter rate_limiter) { 78 | this.rate_limiter = rate_limiter; 79 | return this; 80 | } 81 | 82 | /** 83 | * Path to the socket of vhost-user-block backend. This field is required for vhost-user-block config should be omitted for virtio-block configuration. 84 | */ 85 | public Drive socket(String socket) { 86 | this.socket = socket; 87 | return this; 88 | } 89 | 90 | public static Drive drive() { 91 | return new Drive(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/EntropyDevice.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | /** 4 | * Defines an entropy device. 5 | */ 6 | public class EntropyDevice { 7 | public RateLimiter rate_limiter; 8 | 9 | public EntropyDevice rate_limiter(RateLimiter rate_limiter) { 10 | this.rate_limiter = rate_limiter; 11 | return this; 12 | } 13 | 14 | public static EntropyDevice entropyDevice() { 15 | return new EntropyDevice(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Error.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | public class Error { 6 | public String fault_message; 7 | 8 | /** 9 | * A description of the error condition 10 | */ 11 | public Error fault_message(String fault_message) { 12 | this.fault_message = fault_message; 13 | return this; 14 | } 15 | 16 | public static Error error() { 17 | return new Error(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/FirecrackerVersion.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | /** 6 | * Describes the Firecracker version. 7 | */ 8 | public class FirecrackerVersion { 9 | public String firecracker_version; 10 | 11 | /** 12 | * Firecracker build version. 13 | */ 14 | public FirecrackerVersion firecracker_version(String firecracker_version) { 15 | this.firecracker_version = firecracker_version; 16 | return this; 17 | } 18 | 19 | public static FirecrackerVersion firecrackerVersion() { 20 | return new FirecrackerVersion(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/FullVmConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import java.util.List; 5 | 6 | public class FullVmConfiguration { 7 | public Balloon balloon; 8 | 9 | @SerializedName("boot-source") 10 | public BootSource bootsource; 11 | 12 | public List drives; 13 | 14 | public Logger logger; 15 | 16 | @SerializedName("machine-config") 17 | public MachineConfiguration machineconfig; 18 | 19 | public Metrics metrics; 20 | 21 | @SerializedName("mmds-config") 22 | public MmdsConfig mmdsconfig; 23 | 24 | @SerializedName("network-interfaces") 25 | public List networkinterfaces; 26 | 27 | public Vsock vsock; 28 | 29 | public FullVmConfiguration balloon(Balloon balloon) { 30 | this.balloon = balloon; 31 | return this; 32 | } 33 | 34 | public FullVmConfiguration bootsource(BootSource bootsource) { 35 | this.bootsource = bootsource; 36 | return this; 37 | } 38 | 39 | /** 40 | * Configurations for all block devices. 41 | */ 42 | public FullVmConfiguration drives(List drives) { 43 | this.drives = drives; 44 | return this; 45 | } 46 | 47 | public FullVmConfiguration logger(Logger logger) { 48 | this.logger = logger; 49 | return this; 50 | } 51 | 52 | public FullVmConfiguration machineconfig(MachineConfiguration machineconfig) { 53 | this.machineconfig = machineconfig; 54 | return this; 55 | } 56 | 57 | public FullVmConfiguration metrics(Metrics metrics) { 58 | this.metrics = metrics; 59 | return this; 60 | } 61 | 62 | public FullVmConfiguration mmdsconfig(MmdsConfig mmdsconfig) { 63 | this.mmdsconfig = mmdsconfig; 64 | return this; 65 | } 66 | 67 | /** 68 | * Configurations for all net devices. 69 | */ 70 | public FullVmConfiguration networkinterfaces(List networkinterfaces) { 71 | this.networkinterfaces = networkinterfaces; 72 | return this; 73 | } 74 | 75 | public FullVmConfiguration vsock(Vsock vsock) { 76 | this.vsock = vsock; 77 | return this; 78 | } 79 | 80 | public static FullVmConfiguration fullVmConfiguration() { 81 | return new FullVmConfiguration(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/InstanceActionInfo.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.instanceactioninfo.Action_type; 4 | 5 | /** 6 | * Variant wrapper containing the real action. 7 | */ 8 | public class InstanceActionInfo { 9 | public Action_type action_type; 10 | 11 | /** 12 | * Enumeration indicating what type of action is contained in the payload 13 | */ 14 | public InstanceActionInfo action_type(Action_type action_type) { 15 | this.action_type = action_type; 16 | return this; 17 | } 18 | 19 | public static InstanceActionInfo instanceActionInfo() { 20 | return new InstanceActionInfo(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/InstanceInfo.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.instanceinfo.State; 4 | import java.lang.String; 5 | 6 | /** 7 | * Describes MicroVM instance information. 8 | */ 9 | public class InstanceInfo { 10 | public String app_name; 11 | 12 | public String id; 13 | 14 | public State state; 15 | 16 | public String vmm_version; 17 | 18 | /** 19 | * Application name. 20 | */ 21 | public InstanceInfo app_name(String app_name) { 22 | this.app_name = app_name; 23 | return this; 24 | } 25 | 26 | /** 27 | * MicroVM / instance ID. 28 | */ 29 | public InstanceInfo id(String id) { 30 | this.id = id; 31 | return this; 32 | } 33 | 34 | /** 35 | * The current detailed state (Not started, Running, Paused) of the Firecracker instance. This value is read-only for the control-plane. 36 | */ 37 | public InstanceInfo state(State state) { 38 | this.state = state; 39 | return this; 40 | } 41 | 42 | /** 43 | * MicroVM hypervisor build version. 44 | */ 45 | public InstanceInfo vmm_version(String vmm_version) { 46 | this.vmm_version = vmm_version; 47 | return this; 48 | } 49 | 50 | public static InstanceInfo instanceInfo() { 51 | return new InstanceInfo(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Logger.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.logger.Level; 4 | import java.lang.Boolean; 5 | import java.lang.String; 6 | 7 | /** 8 | * Describes the configuration option for the logging capability. 9 | */ 10 | public class Logger { 11 | public Level level; 12 | 13 | public String log_path; 14 | 15 | public String module; 16 | 17 | public Boolean show_level; 18 | 19 | public Boolean show_log_origin; 20 | 21 | /** 22 | * Set the level. The possible values are case-insensitive. 23 | */ 24 | public Logger level(Level level) { 25 | this.level = level; 26 | return this; 27 | } 28 | 29 | /** 30 | * Path to the named pipe or file for the human readable log output. 31 | */ 32 | public Logger log_path(String log_path) { 33 | this.log_path = log_path; 34 | return this; 35 | } 36 | 37 | /** 38 | * The module path to filter log messages by. 39 | */ 40 | public Logger module(String module) { 41 | this.module = module; 42 | return this; 43 | } 44 | 45 | /** 46 | * Whether or not to output the level in the logs. 47 | */ 48 | public Logger show_level(Boolean show_level) { 49 | this.show_level = show_level; 50 | return this; 51 | } 52 | 53 | /** 54 | * Whether or not to include the file path and line number of the log's origin. 55 | */ 56 | public Logger show_log_origin(Boolean show_log_origin) { 57 | this.show_log_origin = show_log_origin; 58 | return this; 59 | } 60 | 61 | public static Logger logger() { 62 | return new Logger(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/MachineConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.machineconfiguration.Huge_pages; 4 | import java.lang.Boolean; 5 | import java.lang.Long; 6 | 7 | /** 8 | * Describes the number of vCPUs, memory size, SMT capabilities, huge page configuration and the CPU template. 9 | */ 10 | public class MachineConfiguration { 11 | public CpuTemplate cpu_template; 12 | 13 | public Huge_pages huge_pages; 14 | 15 | public Long mem_size_mib; 16 | 17 | public Boolean smt; 18 | 19 | public Boolean track_dirty_pages; 20 | 21 | public Long vcpu_count; 22 | 23 | public MachineConfiguration cpu_template(CpuTemplate cpu_template) { 24 | this.cpu_template = cpu_template; 25 | return this; 26 | } 27 | 28 | /** 29 | * Which huge pages configuration (if any) should be used to back guest memory. 30 | */ 31 | public MachineConfiguration huge_pages(Huge_pages huge_pages) { 32 | this.huge_pages = huge_pages; 33 | return this; 34 | } 35 | 36 | /** 37 | * Memory size of VM 38 | */ 39 | public MachineConfiguration mem_size_mib(Long mem_size_mib) { 40 | this.mem_size_mib = mem_size_mib; 41 | return this; 42 | } 43 | 44 | /** 45 | * Flag for enabling/disabling simultaneous multithreading. Can be enabled only on x86. 46 | */ 47 | public MachineConfiguration smt(Boolean smt) { 48 | this.smt = smt; 49 | return this; 50 | } 51 | 52 | /** 53 | * Enable dirty page tracking. If this is enabled, then incremental guest memory snapshots can be created. These belong to diff snapshots, which contain, besides the microVM state, only the memory dirtied since a previous snapshot. Full snapshots each contain a full copy of the guest memory. 54 | */ 55 | public MachineConfiguration track_dirty_pages(Boolean track_dirty_pages) { 56 | this.track_dirty_pages = track_dirty_pages; 57 | return this; 58 | } 59 | 60 | /** 61 | * Number of vCPUs (either 1 or an even number) 62 | */ 63 | public MachineConfiguration vcpu_count(Long vcpu_count) { 64 | this.vcpu_count = vcpu_count; 65 | return this; 66 | } 67 | 68 | public static MachineConfiguration machineConfiguration() { 69 | return new MachineConfiguration(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/MemoryBackend.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.memorybackend.Backend_type; 4 | import java.lang.String; 5 | 6 | public class MemoryBackend { 7 | public String backend_path; 8 | 9 | public Backend_type backend_type; 10 | 11 | /** 12 | * Based on 'backend_type' it is either 1) Path to the file that contains the guest memory to be loaded 2) Path to the UDS where a process is listening for a UFFD initialization control payload and open file descriptor that it can use to serve this process's guest memory page faults 13 | */ 14 | public MemoryBackend backend_path(String backend_path) { 15 | this.backend_path = backend_path; 16 | return this; 17 | } 18 | 19 | public MemoryBackend backend_type(Backend_type backend_type) { 20 | this.backend_type = backend_type; 21 | return this; 22 | } 23 | 24 | public static MemoryBackend memoryBackend() { 25 | return new MemoryBackend(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Metrics.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | /** 6 | * Describes the configuration option for the metrics capability. 7 | */ 8 | public class Metrics { 9 | public String metrics_path; 10 | 11 | /** 12 | * Path to the named pipe or file where the JSON-formatted metrics are flushed. 13 | */ 14 | public Metrics metrics_path(String metrics_path) { 15 | this.metrics_path = metrics_path; 16 | return this; 17 | } 18 | 19 | public static Metrics metrics() { 20 | return new Metrics(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/MmdsConfig.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.mmdsconfig.Version; 4 | import java.lang.String; 5 | import java.util.List; 6 | 7 | /** 8 | * Defines the MMDS configuration. 9 | */ 10 | public class MmdsConfig { 11 | public String ipv4_address; 12 | 13 | public List network_interfaces; 14 | 15 | public Version version; 16 | 17 | /** 18 | * A valid IPv4 link-local address. 19 | */ 20 | public MmdsConfig ipv4_address(String ipv4_address) { 21 | this.ipv4_address = ipv4_address; 22 | return this; 23 | } 24 | 25 | /** 26 | * List of the network interface IDs capable of forwarding packets to the MMDS. Network interface IDs mentioned must be valid at the time of this request. The net device model will reply to HTTP GET requests sent to the MMDS address via the interfaces mentioned. In this case, both ARP requests and TCP segments heading to `ipv4_address` are intercepted by the device model, and do not reach the associated TAP device. 27 | */ 28 | public MmdsConfig network_interfaces(List network_interfaces) { 29 | this.network_interfaces = network_interfaces; 30 | return this; 31 | } 32 | 33 | /** 34 | * Enumeration indicating the MMDS version to be configured. 35 | */ 36 | public MmdsConfig version(Version version) { 37 | this.version = version; 38 | return this; 39 | } 40 | 41 | public static MmdsConfig mmdsConfig() { 42 | return new MmdsConfig(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/MmdsContentsObject.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Object; 4 | import java.lang.String; 5 | import java.lang.SuppressWarnings; 6 | import java.util.LinkedHashMap; 7 | 8 | @SuppressWarnings("serial") 9 | public class MmdsContentsObject extends LinkedHashMap { 10 | public MmdsContentsObject kv(String key, Object value) { 11 | put(key, value); 12 | return this; 13 | } 14 | 15 | public static MmdsContentsObject mmdsContentsObject() { 16 | return new MmdsContentsObject(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/NetworkInterface.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | /** 6 | * Defines a network interface. 7 | */ 8 | public class NetworkInterface { 9 | public String guest_mac; 10 | 11 | public String host_dev_name; 12 | 13 | public String iface_id; 14 | 15 | public RateLimiter rx_rate_limiter; 16 | 17 | public RateLimiter tx_rate_limiter; 18 | 19 | public NetworkInterface guest_mac(String guest_mac) { 20 | this.guest_mac = guest_mac; 21 | return this; 22 | } 23 | 24 | /** 25 | * Host level path for the guest network interface 26 | */ 27 | public NetworkInterface host_dev_name(String host_dev_name) { 28 | this.host_dev_name = host_dev_name; 29 | return this; 30 | } 31 | 32 | public NetworkInterface iface_id(String iface_id) { 33 | this.iface_id = iface_id; 34 | return this; 35 | } 36 | 37 | public NetworkInterface rx_rate_limiter(RateLimiter rx_rate_limiter) { 38 | this.rx_rate_limiter = rx_rate_limiter; 39 | return this; 40 | } 41 | 42 | public NetworkInterface tx_rate_limiter(RateLimiter tx_rate_limiter) { 43 | this.tx_rate_limiter = tx_rate_limiter; 44 | return this; 45 | } 46 | 47 | public static NetworkInterface networkInterface() { 48 | return new NetworkInterface(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/PartialDrive.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | public class PartialDrive { 6 | public String drive_id; 7 | 8 | public String path_on_host; 9 | 10 | public RateLimiter rate_limiter; 11 | 12 | public PartialDrive drive_id(String drive_id) { 13 | this.drive_id = drive_id; 14 | return this; 15 | } 16 | 17 | /** 18 | * Host level path for the guest drive. This field is optional for virtio-block config and should be omitted for vhost-user-block configuration. 19 | */ 20 | public PartialDrive path_on_host(String path_on_host) { 21 | this.path_on_host = path_on_host; 22 | return this; 23 | } 24 | 25 | public PartialDrive rate_limiter(RateLimiter rate_limiter) { 26 | this.rate_limiter = rate_limiter; 27 | return this; 28 | } 29 | 30 | public static PartialDrive partialDrive() { 31 | return new PartialDrive(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/PartialNetworkInterface.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.String; 4 | 5 | /** 6 | * Defines a partial network interface structure, used to update the rate limiters for that interface, after microvm start. 7 | */ 8 | public class PartialNetworkInterface { 9 | public String iface_id; 10 | 11 | public RateLimiter rx_rate_limiter; 12 | 13 | public RateLimiter tx_rate_limiter; 14 | 15 | public PartialNetworkInterface iface_id(String iface_id) { 16 | this.iface_id = iface_id; 17 | return this; 18 | } 19 | 20 | public PartialNetworkInterface rx_rate_limiter(RateLimiter rx_rate_limiter) { 21 | this.rx_rate_limiter = rx_rate_limiter; 22 | return this; 23 | } 24 | 25 | public PartialNetworkInterface tx_rate_limiter(RateLimiter tx_rate_limiter) { 26 | this.tx_rate_limiter = tx_rate_limiter; 27 | return this; 28 | } 29 | 30 | public static PartialNetworkInterface partialNetworkInterface() { 31 | return new PartialNetworkInterface(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/RateLimiter.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | /** 4 | * Defines an IO rate limiter with independent bytes/s and ops/s limits. Limits are defined by configuring each of the _bandwidth_ and _ops_ token buckets. This field is optional for virtio-block config and should be omitted for vhost-user-block configuration. 5 | */ 6 | public class RateLimiter { 7 | public TokenBucket bandwidth; 8 | 9 | public TokenBucket ops; 10 | 11 | public RateLimiter bandwidth(TokenBucket bandwidth) { 12 | this.bandwidth = bandwidth; 13 | return this; 14 | } 15 | 16 | public RateLimiter ops(TokenBucket ops) { 17 | this.ops = ops; 18 | return this; 19 | } 20 | 21 | public static RateLimiter rateLimiter() { 22 | return new RateLimiter(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/SnapshotCreateParams.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.snapshotcreateparams.Snapshot_type; 4 | import java.lang.String; 5 | 6 | public class SnapshotCreateParams { 7 | public String mem_file_path; 8 | 9 | public String snapshot_path; 10 | 11 | public Snapshot_type snapshot_type; 12 | 13 | /** 14 | * Path to the file that will contain the guest memory. 15 | */ 16 | public SnapshotCreateParams mem_file_path(String mem_file_path) { 17 | this.mem_file_path = mem_file_path; 18 | return this; 19 | } 20 | 21 | /** 22 | * Path to the file that will contain the microVM state. 23 | */ 24 | public SnapshotCreateParams snapshot_path(String snapshot_path) { 25 | this.snapshot_path = snapshot_path; 26 | return this; 27 | } 28 | 29 | /** 30 | * Type of snapshot to create. It is optional and by default, a full snapshot is created. 31 | */ 32 | public SnapshotCreateParams snapshot_type(Snapshot_type snapshot_type) { 33 | this.snapshot_type = snapshot_type; 34 | return this; 35 | } 36 | 37 | public static SnapshotCreateParams snapshotCreateParams() { 38 | return new SnapshotCreateParams(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/SnapshotLoadParams.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Boolean; 4 | import java.lang.String; 5 | 6 | /** 7 | * Defines the configuration used for handling snapshot resume. Exactly one of the two `mem_*` fields must be present in the body of the request. 8 | */ 9 | public class SnapshotLoadParams { 10 | public Boolean enable_diff_snapshots; 11 | 12 | public MemoryBackend mem_backend; 13 | 14 | public String mem_file_path; 15 | 16 | public Boolean resume_vm; 17 | 18 | public String snapshot_path; 19 | 20 | /** 21 | * Enable support for incremental (diff) snapshots by tracking dirty guest pages. 22 | */ 23 | public SnapshotLoadParams enable_diff_snapshots(Boolean enable_diff_snapshots) { 24 | this.enable_diff_snapshots = enable_diff_snapshots; 25 | return this; 26 | } 27 | 28 | public SnapshotLoadParams mem_backend(MemoryBackend mem_backend) { 29 | this.mem_backend = mem_backend; 30 | return this; 31 | } 32 | 33 | /** 34 | * Path to the file that contains the guest memory to be loaded. It is only allowed if `mem_backend` is not present. This parameter has been deprecated and it will be removed in future Firecracker release. 35 | */ 36 | public SnapshotLoadParams mem_file_path(String mem_file_path) { 37 | this.mem_file_path = mem_file_path; 38 | return this; 39 | } 40 | 41 | /** 42 | * When set to true, the vm is also resumed if the snapshot load is successful. 43 | */ 44 | public SnapshotLoadParams resume_vm(Boolean resume_vm) { 45 | this.resume_vm = resume_vm; 46 | return this; 47 | } 48 | 49 | /** 50 | * Path to the file that contains the microVM state to be loaded. 51 | */ 52 | public SnapshotLoadParams snapshot_path(String snapshot_path) { 53 | this.snapshot_path = snapshot_path; 54 | return this; 55 | } 56 | 57 | public static SnapshotLoadParams snapshotLoadParams() { 58 | return new SnapshotLoadParams(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/TokenBucket.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Long; 4 | 5 | /** 6 | * Defines a token bucket with a maximum capacity (size), an initial burst size (one_time_burst) and an interval for refilling purposes (refill_time). The refill-rate is derived from size and refill_time, and it is the constant rate at which the tokens replenish. The refill process only starts happening after the initial burst budget is consumed. Consumption from the token bucket is unbounded in speed which allows for bursts bound in size by the amount of tokens available. Once the token bucket is empty, consumption speed is bound by the refill_rate. 7 | */ 8 | public class TokenBucket { 9 | public Long one_time_burst; 10 | 11 | public Long refill_time; 12 | 13 | public Long size; 14 | 15 | /** 16 | * The initial size of a token bucket. 17 | */ 18 | public TokenBucket one_time_burst(Long one_time_burst) { 19 | this.one_time_burst = one_time_burst; 20 | return this; 21 | } 22 | 23 | /** 24 | * The amount of milliseconds it takes for the bucket to refill. 25 | */ 26 | public TokenBucket refill_time(Long refill_time) { 27 | this.refill_time = refill_time; 28 | return this; 29 | } 30 | 31 | /** 32 | * The total number of tokens this bucket can hold. 33 | */ 34 | public TokenBucket size(Long size) { 35 | this.size = size; 36 | return this; 37 | } 38 | 39 | public static TokenBucket tokenBucket() { 40 | return new TokenBucket(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Vm.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import io.vacco.ff.firecracker.vm.State; 4 | 5 | /** 6 | * Defines the microVM running state. It is especially useful in the snapshotting context. 7 | */ 8 | public class Vm { 9 | public State state; 10 | 11 | public Vm state(State state) { 12 | this.state = state; 13 | return this; 14 | } 15 | 16 | public static Vm vm() { 17 | return new Vm(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/Vsock.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker; 2 | 3 | import java.lang.Long; 4 | import java.lang.String; 5 | 6 | /** 7 | * Defines a vsock device, backed by a set of Unix Domain Sockets, on the host side. For host-initiated connections, Firecracker will be listening on the Unix socket identified by the path `uds_path`. Firecracker will create this socket, bind and listen on it. Host-initiated connections will be performed by connection to this socket and issuing a connection forwarding request to the desired guest-side vsock port (i.e. `CONNECT 52\n`, to connect to port 52). For guest-initiated connections, Firecracker will expect host software to be bound and listening on Unix sockets at `uds_path_<PORT>`. E.g. "/path/to/host_vsock.sock_52" for port number 52. 8 | */ 9 | public class Vsock { 10 | public Long guest_cid; 11 | 12 | public String uds_path; 13 | 14 | public String vsock_id; 15 | 16 | /** 17 | * Guest Vsock CID 18 | */ 19 | public Vsock guest_cid(Long guest_cid) { 20 | this.guest_cid = guest_cid; 21 | return this; 22 | } 23 | 24 | /** 25 | * Path to UNIX domain socket, used to proxy vsock connections. 26 | */ 27 | public Vsock uds_path(String uds_path) { 28 | this.uds_path = uds_path; 29 | return this; 30 | } 31 | 32 | /** 33 | * This parameter has been deprecated and it will be removed in future Firecracker release. 34 | */ 35 | public Vsock vsock_id(String vsock_id) { 36 | this.vsock_id = vsock_id; 37 | return this; 38 | } 39 | 40 | public static Vsock vsock() { 41 | return new Vsock(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/cpuconfig/Cpuid_modifiers.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.cpuconfig; 2 | 3 | import java.lang.Object; 4 | import java.lang.String; 5 | import java.lang.SuppressWarnings; 6 | import java.util.LinkedHashMap; 7 | 8 | @SuppressWarnings("serial") 9 | public class Cpuid_modifiers extends LinkedHashMap { 10 | public Cpuid_modifiers kv(String key, Object value) { 11 | put(key, value); 12 | return this; 13 | } 14 | 15 | public static Cpuid_modifiers cpuid_modifiers() { 16 | return new Cpuid_modifiers(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/cpuconfig/Msr_modifiers.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.cpuconfig; 2 | 3 | import java.lang.Object; 4 | import java.lang.String; 5 | import java.lang.SuppressWarnings; 6 | import java.util.LinkedHashMap; 7 | 8 | @SuppressWarnings("serial") 9 | public class Msr_modifiers extends LinkedHashMap { 10 | public Msr_modifiers kv(String key, Object value) { 11 | put(key, value); 12 | return this; 13 | } 14 | 15 | public static Msr_modifiers msr_modifiers() { 16 | return new Msr_modifiers(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/cpuconfig/Reg_modifiers.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.cpuconfig; 2 | 3 | import java.lang.Object; 4 | import java.lang.String; 5 | import java.lang.SuppressWarnings; 6 | import java.util.LinkedHashMap; 7 | 8 | @SuppressWarnings("serial") 9 | public class Reg_modifiers extends LinkedHashMap { 10 | public Reg_modifiers kv(String key, Object value) { 11 | put(key, value); 12 | return this; 13 | } 14 | 15 | public static Reg_modifiers reg_modifiers() { 16 | return new Reg_modifiers(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/drive/Cache_type.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.drive; 2 | 3 | /** 4 | * Represents the caching strategy for the block device. 5 | */ 6 | public enum Cache_type { 7 | Unsafe, 8 | 9 | Writeback 10 | } 11 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/drive/Io_engine.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.drive; 2 | 3 | /** 4 | * Type of the IO engine used by the device. "Async" is supported on host kernels newer than 5.10.51. This field is optional for virtio-block config and should be omitted for vhost-user-block configuration. 5 | */ 6 | public enum Io_engine { 7 | Sync, 8 | 9 | Async 10 | } 11 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/instanceactioninfo/Action_type.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.instanceactioninfo; 2 | 3 | /** 4 | * Enumeration indicating what type of action is contained in the payload 5 | */ 6 | public enum Action_type { 7 | FlushMetrics, 8 | 9 | InstanceStart, 10 | 11 | SendCtrlAltDel 12 | } 13 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/instanceinfo/State.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.instanceinfo; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * The current detailed state (Not started, Running, Paused) of the Firecracker instance. This value is read-only for the control-plane. 7 | */ 8 | public enum State { 9 | @SerializedName("Not started") 10 | Val000, 11 | 12 | Running, 13 | 14 | Paused 15 | } 16 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/logger/Level.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.logger; 2 | 3 | /** 4 | * Set the level. The possible values are case-insensitive. 5 | */ 6 | public enum Level { 7 | Error, 8 | 9 | Warning, 10 | 11 | Info, 12 | 13 | Debug, 14 | 15 | Trace, 16 | 17 | Off 18 | } 19 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/machineconfiguration/Huge_pages.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.machineconfiguration; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Which huge pages configuration (if any) should be used to back guest memory. 7 | */ 8 | public enum Huge_pages { 9 | None, 10 | @SerializedName("2M") 11 | Val001 12 | } 13 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/memorybackend/Backend_type.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.memorybackend; 2 | 3 | public enum Backend_type { 4 | File, 5 | 6 | Uffd 7 | } 8 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/mmdsconfig/Version.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.mmdsconfig; 2 | 3 | /** 4 | * Enumeration indicating the MMDS version to be configured. 5 | */ 6 | public enum Version { 7 | V1, 8 | 9 | V2 10 | } 11 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/snapshotcreateparams/Snapshot_type.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.snapshotcreateparams; 2 | 3 | /** 4 | * Type of snapshot to create. It is optional and by default, a full snapshot is created. 5 | */ 6 | public enum Snapshot_type { 7 | Full, 8 | 9 | Diff 10 | } 11 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/firecracker/vm/State.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.firecracker.vm; 2 | 3 | public enum State { 4 | Paused, 5 | 6 | Resumed 7 | } 8 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/initramfs/FgConstants.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.initramfs; 2 | 3 | public class FgConstants { 4 | 5 | public static final String fInitRamFs = "initramfs.cpio"; 6 | 7 | public static final String 8 | FF_ENTRYPOINT = "FF_ENTRYPOINT", 9 | FF_CMD = "FF_CMD", 10 | FF_WORKINGDIR = "FF_WORKINGDIR"; 11 | 12 | public static final String 13 | dockerOs = "linux", dockerArch = "amd64"; // TODO add support if demand grows. 14 | 15 | public static final String 16 | pBlobs = "blobs", 17 | pExtract = "extract", 18 | pUnzipped = "unzipped"; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/initramfs/FgTarEntry.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.initramfs; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.attribute.PosixFilePermission; 5 | import java.util.EnumSet; 6 | import java.util.Objects; 7 | import java.util.Set; 8 | 9 | /** 10 | * Blind dogma is why creativity and innovation die. 11 | */ 12 | public class FgTarEntry implements Comparable { 13 | 14 | public static final String[] rwx = {"r", "w", "x"}; 15 | 16 | public String name; 17 | public final boolean isDirectory; 18 | public final boolean isSymlink; 19 | public final boolean isHardlink; 20 | public final String linkName; 21 | public final boolean isExecutable; 22 | 23 | public int mode; 24 | public Path fsPath; 25 | 26 | public final Set permissions; 27 | public final int size; 28 | 29 | public FgTarEntry(byte[] header) { 30 | this.name = new String(header, 0, 100).trim(); 31 | this.isDirectory = header[156] == '5'; 32 | this.isSymlink = header[156] == '2'; 33 | this.isHardlink = header[156] == '1'; 34 | this.linkName = new String(header, 157, 100).trim(); 35 | this.permissions = parsePermissions(header); 36 | this.isExecutable = (header[100] & 0b001001001) != 0; 37 | this.size = parseOctal(header, 124, 12); 38 | } 39 | 40 | public boolean isFile() { 41 | return !isDirectory && !isSymlink && !isHardlink; 42 | } 43 | 44 | public FgTarEntry withFsPath(Path fsPath) { 45 | this.fsPath = Objects.requireNonNull(fsPath); 46 | return this; 47 | } 48 | 49 | private Set parsePermissions(byte[] header) { 50 | Set perms = EnumSet.noneOf(PosixFilePermission.class); 51 | this.mode = parseOctal(header, 100, 7); 52 | if ((mode & 0400) != 0) perms.add(PosixFilePermission.OWNER_READ); 53 | if ((mode & 0200) != 0) perms.add(PosixFilePermission.OWNER_WRITE); 54 | if ((mode & 0100) != 0) perms.add(PosixFilePermission.OWNER_EXECUTE); 55 | if ((mode & 0040) != 0) perms.add(PosixFilePermission.GROUP_READ); 56 | if ((mode & 0020) != 0) perms.add(PosixFilePermission.GROUP_WRITE); 57 | if ((mode & 0010) != 0) perms.add(PosixFilePermission.GROUP_EXECUTE); 58 | if ((mode & 0004) != 0) perms.add(PosixFilePermission.OTHERS_READ); 59 | if ((mode & 0002) != 0) perms.add(PosixFilePermission.OTHERS_WRITE); 60 | if ((mode & 0001) != 0) perms.add(PosixFilePermission.OTHERS_EXECUTE); 61 | return perms; 62 | } 63 | 64 | @Override public int compareTo(FgTarEntry o) { 65 | return this.name.compareTo(o.name); 66 | } 67 | 68 | private int parseOctal(byte[] header, int offset, int length) { 69 | int value = 0; 70 | for (int i = offset; i < offset + length; i++) { 71 | if (header[i] == 0) break; 72 | if (header[i] >= '0' && header[i] <= '7') { 73 | value = (value << 3) + (header[i] - '0'); 74 | } 75 | } 76 | return value; 77 | } 78 | 79 | public static String modeToPosixString(int mode) { 80 | var perms = new char[9]; 81 | for (int i = 0; i < 3; i++) { 82 | int shift = (2 - i) * 3; 83 | perms[i * 3] = ((mode & (4 << shift)) != 0) ? 'r' : '-'; 84 | perms[i * 3 + 1] = ((mode & (2 << shift)) != 0) ? 'w' : '-'; 85 | perms[i * 3 + 2] = ((mode & (1 << shift)) != 0) ? 'x' : '-'; 86 | } 87 | return new String(perms); 88 | } 89 | 90 | @Override public String toString() { 91 | return String.format( 92 | "[%s%s%s%s%s, %s, %08d] %s%s", 93 | isDirectory ? "d" : "", 94 | isSymlink ? "l" : "", 95 | isHardlink ? "h" : "", 96 | isFile() ? "f" : "", 97 | isExecutable ? "x" : "", 98 | modeToPosixString(mode), 99 | size, 100 | name, 101 | linkName != null && !linkName.isEmpty() 102 | ? String.format(" <-- %s", linkName) 103 | : "" 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/initramfs/FgTarIo.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.initramfs; 2 | 3 | import java.io.*; 4 | import java.nio.ByteBuffer; 5 | import java.nio.file.*; 6 | import java.util.*; 7 | import java.util.function.BiConsumer; 8 | 9 | import static java.nio.file.Files.*; 10 | 11 | public class FgTarIo { 12 | 13 | private static final int TAR_BLOCK_SIZE = 512; 14 | 15 | private static void afterEntry(FgTarEntry entry, BufferedInputStream bis) { 16 | try { 17 | long skipBytes = TAR_BLOCK_SIZE - (entry.size % TAR_BLOCK_SIZE); 18 | if (skipBytes < TAR_BLOCK_SIZE) { 19 | bis.skip(skipBytes); 20 | } 21 | } catch (IOException e) { 22 | throw new IllegalStateException(e); 23 | } 24 | } 25 | 26 | private static void copy(FgTarEntry entry, BufferedInputStream bis, byte[] buffer, 27 | File outputFile, byte[] tempBuffer) throws IOException { 28 | int fileSize = entry.size; 29 | var fileBb = ByteBuffer.allocateDirect(fileSize); 30 | while (fileSize > 0) { 31 | int len = bis.read(buffer, 0, Math.min(TAR_BLOCK_SIZE, fileSize)); 32 | if (len == -1) { 33 | break; 34 | } 35 | fileBb.put(buffer, 0, len); 36 | fileSize -= len; 37 | } 38 | fileBb.flip(); 39 | try (var bos = new BufferedOutputStream(new FileOutputStream(outputFile))) { 40 | Arrays.fill(tempBuffer, (byte) 0); 41 | while (fileBb.hasRemaining()) { 42 | int len = Math.min(tempBuffer.length, fileBb.remaining()); 43 | fileBb.get(tempBuffer, 0, len); 44 | bos.write(tempBuffer, 0, len); 45 | } 46 | } 47 | } 48 | 49 | public static boolean hasCommonPathPrefix(String entryName1, String entryName2) { 50 | var pathComponents1 = entryName1.split("/"); 51 | var pathComponents2 = entryName2.split("/"); 52 | int minLength = Math.min(pathComponents1.length, pathComponents2.length); 53 | for (int i = 0; i < minLength; i++) { 54 | if (!pathComponents1[i].equals(pathComponents2[i])) { 55 | return i > 0; // Return true if we matched at least one component 56 | } 57 | } 58 | return minLength > 0; 59 | } 60 | 61 | public static List extract(File tar, File outDir, BiConsumer onError) { 62 | var entriesWithPermissions = new ArrayList(); 63 | var lastDirectory = ""; 64 | byte[] tempBuffer = new byte[8192]; // 8 KB buffer for bulk writes 65 | 66 | try (var fis = new FileInputStream(tar); 67 | var bis = new BufferedInputStream(fis)) { 68 | var buffer = new byte[TAR_BLOCK_SIZE]; 69 | while (true) { 70 | FgTarEntry entry = null; 71 | try { 72 | int bytesRead = bis.read(buffer); 73 | if (bytesRead == -1 || buffer[0] == 0) { 74 | break; 75 | } 76 | entry = new FgTarEntry(buffer); 77 | if (entry.name.isEmpty()) { 78 | continue; 79 | } 80 | 81 | if (!entry.isSymlink && !entry.isHardlink && !entry.name.startsWith(lastDirectory)) { 82 | var common = hasCommonPathPrefix(lastDirectory, entry.name); 83 | var combined = lastDirectory + entry.name; 84 | if (!common && combined.length() >= 100) { // Kludge! Tar entry name limit 85 | entry.name = combined; 86 | } 87 | } 88 | 89 | var outputFile = new File(outDir, entry.name).getCanonicalFile(); 90 | if (entry.isDirectory) { 91 | createDirectories(outputFile.toPath()); 92 | lastDirectory = entry.name; 93 | } else { 94 | createDirectories(outputFile.getParentFile().toPath()); 95 | if (entry.isSymlink) { 96 | createSymbolicLink(outputFile.toPath(), Paths.get(entry.linkName)); 97 | } else if (entry.isHardlink) { 98 | var linkFile = new File(outDir, entry.linkName).getCanonicalFile(); 99 | createLink(outputFile.toPath(), linkFile.toPath()); 100 | } else if (entry.isFile()) { 101 | copy(entry, bis, buffer, outputFile, tempBuffer); 102 | } 103 | } 104 | if (entry.isFile() || entry.isDirectory) { 105 | entriesWithPermissions.add(entry.withFsPath(outputFile.toPath())); 106 | } 107 | } catch (Exception e) { 108 | onError.accept(entry, e); 109 | } finally { 110 | if (entry != null) { 111 | afterEntry(entry, bis); 112 | } 113 | } 114 | } 115 | } catch (IOException e) { 116 | onError.accept(null, e); 117 | } 118 | return entriesWithPermissions; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/net/FgDhcpDiscover.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.net; 2 | 3 | import static java.lang.System.arraycopy; 4 | 5 | public class FgDhcpDiscover { 6 | 7 | public byte[] packet; 8 | 9 | public byte[] txId() { 10 | var txId = new byte[4]; 11 | arraycopy(packet, 4, txId, 0, 4); 12 | return txId; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/net/FgDhcpRequests.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.net; 2 | 3 | import io.vacco.ff.schema.FgIpConfig; 4 | import org.slf4j.*; 5 | import java.util.*; 6 | 7 | import static java.lang.String.format; 8 | import static java.lang.Thread.currentThread; 9 | import static java.lang.System.currentTimeMillis; 10 | import static java.util.Objects.requireNonNull; 11 | import static io.vacco.ff.net.FgJni.*; 12 | import static io.vacco.ff.net.FgNetIo.*; 13 | import static io.vacco.ff.net.FgDhcpFrames.*; 14 | 15 | public class FgDhcpRequests { 16 | 17 | public static final Logger log = LoggerFactory.getLogger(FgDhcpRequests.class); 18 | 19 | public static long DhcpDiscoverTimeoutMs = 5000; 20 | public static int DhcpReadTimeoutDivisor = 8; 21 | public static int DhcpBufferSize = 2048; 22 | 23 | public static final String AnyIp = "0.0.0.0"; 24 | public static final String AllIp = "255.255.255.255"; 25 | 26 | public static FgIpConfig dhcpScan(int socketHandle, byte[] dst, byte[] txId, long scanTimeoutMs) { 27 | var t0 = currentTimeMillis(); 28 | var txIdStr = Arrays.toString(txId); 29 | var buffer = new byte[DhcpBufferSize]; 30 | var scanTimeoutSec = (int) (scanTimeoutMs / 1000); 31 | var readTimeoutSec = Math.max(1, scanTimeoutSec / DhcpReadTimeoutDivisor); 32 | while (currentTimeMillis() - t0 < scanTimeoutMs) { 33 | if (log.isDebugEnabled()) { 34 | log.debug("Awaiting DHCP response for [{}], Tx: {}", macToString(dst), txIdStr); 35 | } 36 | if (currentThread().isInterrupted()) { 37 | throw new IllegalStateException(format("Interrupted DHCP scan for [%s], %s", macToString(dst), txIdStr)); 38 | } 39 | int bytes = rawReceive(socketHandle, buffer, readTimeoutSec); 40 | if (bytes > 0) { 41 | var ethFrame = ethernetRx(trim(buffer, bytes)); 42 | if (Arrays.equals(dst, ethFrame.dst)) { 43 | var udpPacket = udpUnwrap(ethFrame.payload); 44 | if (udpPacket != null) { 45 | var lease = dhcpResponseFrame(requireNonNull(udpPacket), txId); 46 | if (lease != null) { 47 | log.info("Obtained DHCP response for [{}] {}", macToString(dst), lease); 48 | return lease.withGatewayMac(ethFrame.src); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | log.error( 55 | "Obtained no DHCP response for mac [{}] after ~{}ms, Tx: {}", 56 | macToString(dst), scanTimeoutMs, txIdStr 57 | ); 58 | return null; 59 | } 60 | 61 | public static FgIpConfig dhcpDiscover(int sock, byte[] srcMac) { 62 | var disc0 = dhcpDiscoverFrame(srcMac, true); 63 | var disc0Udp = udpWrap(disc0.packet, AnyIp, AllIp, 68, 67); 64 | var disc0Eth = ethernetTx(FgEthFrame.of(srcMac, BroadcastMac, disc0Udp)); 65 | rawSocketSend(sock, srcMac, disc0Eth); 66 | return dhcpScan(sock, srcMac, disc0.txId(), DhcpDiscoverTimeoutMs); 67 | } 68 | 69 | public static FgIpConfig dhcpRequest(int sock, byte[] srcMac, FgIpConfig offer) { 70 | var req0 = dhcpRequestFrame(srcMac, offer.txId, offer.ipAddress, offer.gateway); 71 | var req0Udp = udpWrap(req0.packet, AnyIp, AllIp, 68, 67); 72 | var req0Eth = ethernetTx(FgEthFrame.of(srcMac, BroadcastMac, req0Udp)); 73 | rawSocketSend(sock, srcMac, req0Eth); 74 | return dhcpScan(sock, srcMac, req0.txId(), DhcpDiscoverTimeoutMs); 75 | } 76 | 77 | public static FgIpConfig dhcpRenew(int sock, byte[] srcMac, FgIpConfig lease, 78 | long timeoutMs, boolean bootPBroadcast) { 79 | var targetIp = bootPBroadcast ? AllIp : lease.gateway; 80 | var targetMac = bootPBroadcast ? BroadcastMac : lease.gatewayMac; 81 | var ren0 = dhcpRenewFrame(srcMac, lease.txId, lease.ipAddress, targetIp, bootPBroadcast); 82 | var ren0Udp = udpWrap(ren0.packet, lease.ipAddress, targetIp, 68, 67); 83 | var ren0Eth = ethernetTx(FgEthFrame.of(srcMac, targetMac, ren0Udp)); 84 | rawSocketSend(sock, srcMac, ren0Eth); 85 | return dhcpScan(sock, srcMac, ren0.txId(), timeoutMs); 86 | } 87 | 88 | public static void dhcpRelease(int sock, byte[] srcMac, FgIpConfig lease) { 89 | var rel0 = dhcpReleaseFrame(srcMac, lease.ipAddress, lease.gateway); 90 | var rel0Udp = udpWrap(rel0, lease.ipAddress, lease.gateway, 68, 67); 91 | var rel0Eth = ethernetTx(FgEthFrame.of(srcMac, lease.gatewayMac, rel0Udp)); 92 | rawSocketSend(sock, srcMac, rel0Eth); 93 | } 94 | 95 | public static boolean dhcpIsActiveTime(FgIpConfig lease) { 96 | var nowMs = currentTimeMillis(); 97 | return nowMs >= lease.grantTimeMs && nowMs <= lease.getRenewalTimestampMs(); 98 | } 99 | 100 | public static boolean dhcpIsRenewalTime(FgIpConfig lease) { 101 | var nowMs = currentTimeMillis(); 102 | return nowMs >= lease.getRenewalTimestampMs() && nowMs <= lease.getRebindTimestampMs(); 103 | } 104 | 105 | public static boolean dhcpIsRebindTime(FgIpConfig lease) { 106 | var nowMs = currentTimeMillis(); 107 | return nowMs >= lease.getRebindTimestampMs() && nowMs <= lease.getLeaseExpireTimestampMs(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/net/FgEthFrame.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.net; 2 | 3 | import java.util.Objects; 4 | 5 | public class FgEthFrame { 6 | 7 | public byte[] src; 8 | public byte[] dst; 9 | public byte[] payload; 10 | 11 | public static FgEthFrame of(byte[] src, byte[] dst, byte[] payload) { 12 | var f = new FgEthFrame(); 13 | f.src = Objects.requireNonNull(src); 14 | f.dst = Objects.requireNonNull(dst); 15 | f.payload = Objects.requireNonNull(payload); 16 | return f; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/net/FgNetIo.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.net; 2 | 3 | import java.io.File; 4 | import java.util.function.Function; 5 | import java.util.function.Supplier; 6 | 7 | import static io.vacco.ff.net.FgJni.*; 8 | import static java.lang.String.format; 9 | 10 | public class FgNetIo { 11 | 12 | public static T withUnixSocket(File socketPath, Function fn) { 13 | var sock = -1; 14 | try { 15 | sock = unixOpen(socketPath.getAbsolutePath()); 16 | return fn.apply(sock); 17 | } finally { 18 | if (sock != -1) { 19 | unixClose(sock); 20 | } 21 | } 22 | } 23 | 24 | public static T withRawSocket(String ifName, Function fn) { 25 | var sock = -1; 26 | try { 27 | sock = rawCreate(ifName); 28 | return fn.apply(sock); 29 | } finally { 30 | if (sock != -1) { 31 | rawClose(sock); 32 | } 33 | } 34 | } 35 | 36 | private static void setPromisc(String ifName, boolean enabled) { 37 | if (rawPromisc(ifName, enabled) != 0) { 38 | throw new IllegalStateException(format( 39 | "Unable to configure interface [%s] in promiscuous mode [%s]", ifName, enabled 40 | )); 41 | } 42 | } 43 | 44 | public static T withPromiscIf(String ifName, Supplier supp) { 45 | setPromisc(ifName, true); 46 | var result = supp.get(); 47 | setPromisc(ifName, false); 48 | return result; 49 | } 50 | 51 | public static void rawSocketSend(int sock, byte[] srcMac, byte[] payload) { 52 | int sent = rawSend(sock, payload); 53 | if (sent < 0) { 54 | throw new IllegalStateException(format( 55 | "Unable to send raw: socket [%d] mac [%s], [%d]", 56 | sock, macToString(srcMac), sent 57 | )); 58 | } 59 | } 60 | 61 | public static String unixSendReceive(int sock, byte[] payload, int recBuffSize, int timeoutMs) { 62 | if (unixSend(sock, payload) == -1) { 63 | throw new IllegalStateException(format("Unable to send UNIX socket data, socket [%d]", sock)); 64 | } 65 | var recBuff = new byte[recBuffSize]; 66 | var recBytes = unixReceive(sock, recBuff, timeoutMs); 67 | if (recBytes < 0) { 68 | throw new IllegalStateException(format("Unable to receive UNIX socket data, socket [%d, %d]", sock, recBytes)); 69 | } 70 | return new String(recBuff).trim(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FcApiResponse.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | public class FcApiResponse { 4 | 5 | public int statusCode; 6 | public String body; 7 | 8 | public static FcApiResponse of(int statusCode, String body) { 9 | var r = new FcApiResponse(); 10 | r.statusCode = statusCode; 11 | r.body = body; 12 | return r; 13 | } 14 | 15 | @Override public String toString() { 16 | return String.format("%d%s", 17 | statusCode, 18 | body != null && body.isEmpty() ? "" : " - " + body 19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgConfig.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import io.vacco.ff.firecracker.*; 4 | import java.util.List; 5 | 6 | /** 7 | * This class exists because: 8 | * 9 | *
    10 | *
  • 11 | * Amazon chose poor names for their FullVmConfiguration schema (e.g. fields requiring SerializedName annotations). 12 | *
  • 13 | *
  • 14 | * Typescript, in all its *infinite* wisdom, doesn't know how to deserialize SerializedName fields. 15 | *
  • 16 | *
17 | * 18 | * So yeah, this is a copy of Firecracker's FullVmConfiguration. Genius... 19 | */ 20 | public class FgConfig { 21 | 22 | public Balloon balloon; 23 | public BootSource bootsource; 24 | public List drives; 25 | public Logger logger; 26 | public MachineConfiguration machineconfig; 27 | public Metrics metrics; 28 | public MmdsConfig mmdsconfig; 29 | public List networkinterfaces; 30 | public Vsock vsock; 31 | 32 | public List driveList() { 33 | return drives; 34 | } 35 | 36 | public List networkInterfaces() { 37 | return networkinterfaces; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgEnvVar.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.Objects; 4 | 5 | public class FgEnvVar { 6 | 7 | public String key, val; 8 | 9 | public static FgEnvVar of(String key, String val) { 10 | var v = new FgEnvVar(); 11 | v.key = Objects.requireNonNull(key); 12 | v.val = val; 13 | return v; 14 | } 15 | 16 | @Override public String toString() { 17 | return String.format( 18 | "%s%s", key, 19 | val != null 20 | ? String.format(" -> %s", val) 21 | : "" 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgImage.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public class FgImage { 7 | 8 | public String source; 9 | public String rootDir; 10 | public String workingDir; 11 | public String[] entryPoint; 12 | public String[] cmd; 13 | 14 | public List env; 15 | public List envUsr; 16 | 17 | public FgImage withSource(String source) { 18 | this.source = Objects.requireNonNull(source); 19 | return this; 20 | } 21 | 22 | public FgImage withEnvUsr(List envUsr) { 23 | this.envUsr = envUsr; 24 | return this; 25 | } 26 | 27 | public String[] entryPointList() { 28 | return entryPoint; 29 | } 30 | 31 | public String[] cmdList() { 32 | return cmd; 33 | } 34 | 35 | public List envList() { 36 | return env; 37 | } 38 | 39 | public List envUsrList() { 40 | return envUsr; 41 | } 42 | 43 | public static FgImage of(String rootDir, String[] entryPoint, String[] cmd, 44 | List env, String workingDir) { 45 | var d = new FgImage(); 46 | d.rootDir = Objects.requireNonNull(rootDir); 47 | d.entryPoint = entryPoint; 48 | d.cmd = cmd; 49 | d.env = Objects.requireNonNull(env); 50 | d.workingDir = workingDir; 51 | return d; 52 | } 53 | 54 | @Override public String toString() { 55 | return source; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgIpConfig.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | import static java.lang.String.format; 8 | 9 | public class FgIpConfig { 10 | 11 | public String ipAddress; 12 | public String subnetMask; 13 | public String gateway; 14 | 15 | public List dnsServers; 16 | 17 | public long grantTimeMs; 18 | public int leaseTimeSec; 19 | public int renewalTimeSec; 20 | public int rebindTimeSec; 21 | 22 | public byte[] txId; 23 | public byte[] gatewayMac; 24 | 25 | public FgIpConfig(String ipAddress, String subnetMask, String gateway, 26 | List dnsServers, long grantTimeMs, 27 | int leaseTimeSec, int renewalTimeSec, int rebindTimeSec, 28 | byte[] txId) { 29 | this.ipAddress = Objects.requireNonNull(ipAddress); 30 | this.subnetMask = Objects.requireNonNull(subnetMask); 31 | this.gateway = Objects.requireNonNull(gateway); 32 | this.dnsServers = Objects.requireNonNull(dnsServers); 33 | this.txId = Objects.requireNonNull(txId); 34 | this.grantTimeMs = grantTimeMs; 35 | this.leaseTimeSec = leaseTimeSec; 36 | this.renewalTimeSec = renewalTimeSec; 37 | this.rebindTimeSec = rebindTimeSec; 38 | } 39 | 40 | public FgIpConfig withGatewayMac(byte[] gatewayMac) { 41 | this.gatewayMac = Objects.requireNonNull(gatewayMac); 42 | return this; 43 | } 44 | 45 | public List getDnsServers() { 46 | return dnsServers; 47 | } 48 | 49 | public long getLeaseTimeMs() { 50 | return leaseTimeSec * 1000L; 51 | } 52 | 53 | public long getRenewalTimeMs() { 54 | return renewalTimeSec * 1000L; 55 | } 56 | 57 | public long getRebindTimeMs() { 58 | return rebindTimeSec * 1000L; 59 | } 60 | 61 | public long getLeaseExpireTimestampMs() { 62 | return grantTimeMs + (leaseTimeSec * 1000L); 63 | } 64 | 65 | public long getRenewalTimestampMs() { 66 | return grantTimeMs + (renewalTimeSec * 1000L); 67 | } 68 | 69 | public long getRebindTimestampMs() { 70 | return grantTimeMs + (rebindTimeSec * 1000L); 71 | } 72 | 73 | @Override public String toString() { 74 | return format( 75 | "IP: %s, Sub: %s, Gw: %s, DNS: %s%s", 76 | ipAddress, subnetMask, gateway, dnsServers, 77 | txId != null ? format(", Tx: %s", Arrays.toString(txId)) : "" 78 | ); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgNetConfig.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.Objects; 4 | 5 | public class FgNetConfig { 6 | 7 | public String brIf; 8 | public FgIpConfig ipConfig; 9 | public boolean dhcp; 10 | 11 | public FgNetConfig withIpConfig(FgIpConfig ipConfig) { 12 | this.ipConfig = Objects.requireNonNull(ipConfig); 13 | return this; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgRequest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | public class FgRequest { 9 | 10 | public List errors; 11 | 12 | @SuppressWarnings("unchecked") 13 | public T withErrors(List errors) { 14 | this.errors = requireNonNull(errors); 15 | return (T) this; 16 | } 17 | 18 | @SuppressWarnings("unchecked") 19 | public T withError(String error) { 20 | if (this.errors == null) { 21 | this.errors = new ArrayList<>(); 22 | } 23 | this.errors.add(requireNonNull(error)); 24 | return (T) this; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVm.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | public class FgVm { 4 | 5 | public FgVmTag tag; 6 | public FgImage image; 7 | public FgConfig config; 8 | 9 | @Override public String toString() { 10 | return String.format("%s %s", tag, image); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmCreate.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.List; 4 | 5 | import static java.lang.String.format; 6 | import static java.util.Objects.requireNonNull; 7 | 8 | public class FgVmCreate extends FgRequest { 9 | 10 | // input parameters 11 | public FgVm vm; 12 | public FgNetConfig network; 13 | public boolean rebuildInitRamFs; 14 | 15 | // output parameters 16 | public List warnings; 17 | 18 | public FgVmCreate withWarnings(List warnings) { 19 | this.warnings = requireNonNull(warnings); 20 | return this; 21 | } 22 | 23 | public FgVmCreate withVm(FgVm vm) { 24 | this.vm = requireNonNull(vm); 25 | return this; 26 | } 27 | 28 | public FgVmCreate withNetwork(FgNetConfig network) { 29 | this.network = network; 30 | return this; 31 | } 32 | 33 | @Override public String toString() { 34 | return format( 35 | "[%s, %s%s]", 36 | vm != null && vm.image != null && vm.image.source != null 37 | ? vm.image.source 38 | : "", 39 | vm != null && vm.config != null && vm.config.bootsource != null 40 | ? vm.config.bootsource.kernel_image_path 41 | : "", 42 | vm != null && vm.config != null && vm.config.machineconfig != null 43 | ? format(" %svCpu, %sMiB",vm.config.machineconfig.vcpu_count, vm.config.machineconfig.mem_size_mib) 44 | : "" 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmFiles.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.io.File; 4 | import java.util.Objects; 5 | 6 | public class FgVmFiles { 7 | 8 | public static final String VmCfgJson = "vm.json"; 9 | public static final String VmNetCfgJson = "net.json"; 10 | public static final String VmFcSock = "fc.sock"; 11 | public static final String VmLog = "vm.log"; 12 | public static final String VmInitRamFs = "initramfs.cpio"; 13 | 14 | public File vmRoot, vmCfg, vmNetCfg, vmSock, vmLog, vmInitRamFs; 15 | 16 | public static FgVmFiles of(File vmRoot) { 17 | var vmf = new FgVmFiles(); 18 | vmf.vmRoot = Objects.requireNonNull(vmRoot); 19 | vmf.vmCfg = new File(vmf.vmRoot, VmCfgJson); 20 | vmf.vmNetCfg = new File(vmf.vmRoot, VmNetCfgJson); 21 | vmf.vmSock = new File(vmf.vmRoot, VmFcSock); 22 | vmf.vmLog = new File(vmf.vmRoot, VmLog); 23 | vmf.vmInitRamFs = new File(vmf.vmRoot, VmInitRamFs); 24 | return vmf; 25 | } 26 | 27 | public static FgVmFiles of(File vmDir, String vmId) { 28 | return of(new File(vmDir, vmId)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmList.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class FgVmList extends FgRequest { 7 | public List vms = new ArrayList<>(); 8 | } 9 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmLogs.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.Objects; 4 | 5 | public class FgVmLogs extends FgRequest { 6 | 7 | public String vmId; 8 | public String logData; 9 | 10 | public FgVmLogs withLogData(String logData) { 11 | this.logData = Objects.requireNonNull(logData); 12 | return this; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmResourceList.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public class FgVmResourceList extends FgRequest { 7 | 8 | public List items; 9 | 10 | public FgVmResourceList withItems(List items) { 11 | this.items = Objects.requireNonNull(items); 12 | return this; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmStart.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | public class FgVmStart extends FgRequest { 8 | 9 | // input parameters 10 | public String vmId; 11 | 12 | // output parameters 13 | public FgVmStatus status; 14 | public FcApiResponse machineConfig, bootConfig, init; 15 | public List drives; 16 | 17 | public FgVmStart withStatus(FgVmStatus status) { 18 | this.status = Objects.requireNonNull(status); 19 | return this; 20 | } 21 | 22 | public FgVmStart withMachineConfig(FcApiResponse machineConfig) { 23 | this.machineConfig = Objects.requireNonNull(machineConfig); 24 | return this; 25 | } 26 | 27 | public FgVmStart withInit(FcApiResponse init) { 28 | this.init = Objects.requireNonNull(init); 29 | return this; 30 | } 31 | 32 | public FgVmStart withDrive(FcApiResponse drive) { 33 | if (this.drives == null) { 34 | this.drives = new ArrayList<>(); 35 | } 36 | this.drives.add(drive); 37 | return this; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmStatus.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | import java.util.Objects; 4 | 5 | public class FgVmStatus { 6 | 7 | public int fcPid; 8 | public FgVm vm; 9 | public FgNetConfig network; 10 | 11 | public FgVmStatus withNetwork(FgNetConfig network) { 12 | this.network = Objects.requireNonNull(network); 13 | return this; 14 | } 15 | 16 | public FgVmStatus withFcPid(int fcPid) { 17 | this.fcPid = fcPid; 18 | return this; 19 | } 20 | 21 | public FgVmStatus withVm(FgVm vm) { 22 | this.vm = Objects.requireNonNull(vm); 23 | return this; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmStop.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | public class FgVmStop extends FgRequest { 4 | 5 | public int fcPid; 6 | public String vmId; 7 | 8 | public FgVmStop withFcPid(int fcPid) { 9 | this.fcPid = fcPid; 10 | return this; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/schema/FgVmTag.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.schema; 2 | 3 | public class FgVmTag { 4 | 5 | public String id, label, description; 6 | 7 | @Override public String toString() { 8 | return String.format("[%s, %s, %s]", id, label, description); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgContext.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import com.google.gson.Gson; 4 | import io.vacco.ff.api.FgApi; 5 | import io.vacco.ff.util.FgIo; 6 | 7 | import java.io.Closeable; 8 | 9 | import static java.lang.String.join; 10 | import static io.vacco.shax.logging.ShOption.*; 11 | import static io.vacco.ff.service.FgOptions.*; 12 | import static io.vacco.ff.util.FgIo.*; 13 | 14 | public class FgContext implements Closeable { 15 | 16 | private FgApi api; 17 | 18 | public void init() { 19 | setSysProp(IO_VACCO_SHAX_DEVMODE, logFormat == LogFormat.text ? "true" : "false"); 20 | setSysProp(IO_VACCO_SHAX_LOGLEVEL, logLevel.toString()); 21 | var log = org.slf4j.LoggerFactory.getLogger(FgContext.class); 22 | log.info( 23 | join("\n", "", 24 | " ________ ___ ______", 25 | " / __/ __/___/ _ \\/_ __/", 26 | " / _// _//___/ , _/ / / ", 27 | "/_/ /_/ /_/|_| /_/ " 28 | ) 29 | ); 30 | exists(vmDir); 31 | exists(krnDir); 32 | exists(fcPath); 33 | this.api = new FgApi(new Gson()).open(); 34 | } 35 | 36 | @Override public void close() { 37 | FgIo.close(api); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgLogging.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import org.slf4j.Logger; 4 | 5 | public class FgLogging { 6 | 7 | public static Throwable rootCauseOf(Throwable t){ 8 | var root = t; 9 | while (root.getCause() != null && root.getCause() != root) { 10 | root = root.getCause(); 11 | } 12 | return root; 13 | } 14 | 15 | public static String messageFor(Throwable t) { 16 | var x = rootCauseOf(t); 17 | return String.format("%s: %s", x.getClass().getSimpleName(), x.getMessage()); 18 | } 19 | 20 | private static Object[] merge(boolean exceptionAsMessage, Exception e, Object ... args) { 21 | var args0 = new Object[args.length + 1]; 22 | args0[args0.length - 1] = exceptionAsMessage ? messageFor(e) : e; 23 | System.arraycopy(args, 0, args0, 0, args.length); 24 | return args0; 25 | } 26 | 27 | public static void onError(Logger log, String message, Exception e, Object ... args) { 28 | if (log.isDebugEnabled()) { 29 | log.debug(message, merge(false, e, args)); 30 | } else if (e != null && args != null) { 31 | log.warn(String.format("%s - {}", message), merge(true, e, args)); 32 | } else { 33 | log.warn(message, args); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgOptions.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import java.io.File; 4 | import java.util.Arrays; 5 | import java.util.stream.Collectors; 6 | 7 | public class FgOptions { 8 | 9 | public enum LogLevel { error, warning, info, debug, trace } 10 | public enum LogFormat { text, json } 11 | 12 | public static final String 13 | kVmDir = "--vm-dir", 14 | kKrnDir = "--krn-dir", 15 | kFcPath = "--fc-path", 16 | kApiHost = "--api-host", kApiPort = "--api-port", 17 | kLogFormat = "--log-format", kLogLevel = "--log-level"; 18 | 19 | public static File vmDir, krnDir, fcPath; 20 | public static LogFormat logFormat = LogFormat.text; 21 | public static LogLevel logLevel = LogLevel.info; 22 | public static String host = "127.0.0.1"; 23 | public static int port = 7070; 24 | 25 | public static String usage() { 26 | // - TODO - clarify in documentation that external VM storage is not managed by the app itself, you configure it. 27 | return String.join("\n", 28 | "Usage:", 29 | " flc [options]", 30 | "Options:", 31 | " --vm-dir=string VM metadata directory. Required.", 32 | " - Stores VM configs, Firecracker metadata and initramfs images", 33 | " --krn-dir=string Linux Kernel storage directory. Required.", 34 | " - Stores Kernel images used to boot VMs.", 35 | " --fc-path=string Path to the firecracker binary. Required.", 36 | " --api-host=string API/UI IP address. Default: " + host, 37 | " --api-port=number API/UI port. Default: " + port, 38 | " --log-format=string Log output format ('text' or 'json'). Default: " + logFormat, 39 | " --log-level=string Log level ('error', 'warning', 'info', 'debug', 'trace'). Default: " + logLevel, 40 | " --help Prints this help message." 41 | ); 42 | } 43 | 44 | public static void setFrom(String[] args) { 45 | var argIdx = Arrays.stream(args) 46 | .filter(arg -> arg.startsWith("--")) 47 | .map(arg -> arg.split("=")) 48 | .filter(pair -> pair.length == 2) 49 | .filter(pair -> pair[0] != null && pair[1] != null) 50 | .collect(Collectors.toMap(pair -> pair[0], pair -> pair[1])); 51 | 52 | vmDir = new File(argIdx.get(kVmDir)); 53 | krnDir = new File(argIdx.get(kKrnDir)); 54 | fcPath = new File(argIdx.get(kFcPath)); 55 | 56 | var vHost = argIdx.get(kApiHost); 57 | var vPort = argIdx.get(kApiPort); 58 | var vLogFormat = argIdx.get(kLogFormat); 59 | var vLogLevel = argIdx.get(kLogLevel); 60 | 61 | host = vHost != null ? vHost : host; 62 | port = vPort != null ? Integer.parseInt(vPort) : port; 63 | logFormat = vLogFormat != null ? LogFormat.valueOf(vLogFormat) : logFormat; 64 | logLevel = vLogLevel != null ? LogLevel.valueOf(vLogLevel) : logLevel; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgVmSvc.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import io.vacco.ff.schema.*; 4 | import java.io.*; 5 | 6 | public class FgVmSvc implements Closeable { 7 | 8 | public FgVmCreate vmBuild(FgVmCreate vmCreate) { 9 | return FgVmSvcBuild.vmBuild(vmCreate); 10 | } 11 | 12 | public FgVmCreate vmGet(String vmId) { 13 | return FgVmSvcBuild.vmGet(vmId); 14 | } 15 | 16 | public FgVmStart vmStart(FgVmStart req) { 17 | return FgVmSvcControl.vmStart(req); 18 | } 19 | 20 | public FgVmStop vmStop(FgVmStop req) { 21 | return FgVmSvcControl.vmStop(req); 22 | } 23 | 24 | public FgVmLogs vmLogs(FgVmLogs req) { 25 | return FgVmSvcControl.vmLogs(req); 26 | } 27 | 28 | public FgVmLogs vmLogsDelete(String vmId) { 29 | return FgVmSvcControl.vmLogsDelete(vmId); 30 | } 31 | 32 | public FgVmList vmList() { 33 | return FgVmSvcStatus.vmList(); 34 | } 35 | 36 | public FgVmResourceList brIfList() { 37 | return FgVmSvcStatus.brIfList(); 38 | } 39 | 40 | public FgVmResourceList krnList() { 41 | return FgVmSvcStatus.krnList(); 42 | } 43 | 44 | public void start() { 45 | FgVmSvcDhcp.vmDhcpStart(); 46 | } 47 | 48 | @Override public void close() { 49 | FgVmSvcDhcp.vmDhcpClose(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgVmSvcBuild.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import com.google.gson.*; 4 | import io.vacco.ff.schema.*; 5 | import org.slf4j.*; 6 | import java.io.File; 7 | import java.nio.file.attribute.PosixFilePermission; 8 | import java.util.*; 9 | 10 | import static io.vacco.ff.api.FgRoute.VmIdNew; 11 | import static io.vacco.ff.firecracker.NetworkInterface.networkInterface; 12 | import static io.vacco.ff.initramfs.FgConstants.*; 13 | import static io.vacco.ff.initramfs.FgCpio.archive; 14 | import static io.vacco.ff.initramfs.FgDockerIo.extract; 15 | import static io.vacco.ff.net.FgJni.*; 16 | import static io.vacco.ff.service.FgLogging.*; 17 | import static io.vacco.ff.service.FgValid.*; 18 | import static io.vacco.ff.util.FgIo.*; 19 | import static java.lang.String.format; 20 | 21 | public class FgVmSvcBuild { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(FgVmSvcBuild.class); 24 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 25 | private static final Random rnd = new Random(); 26 | 27 | public static String newId() { 28 | var n = (short) rnd.nextInt(Short.MAX_VALUE + 1); 29 | return Integer.toHexString(n & 0xFFFF); 30 | } 31 | 32 | private static FgImage vmInitRamFs(File workDir, String dockerImageUri) { 33 | if (!workDir.exists() || !workDir.isDirectory()) { 34 | throw new IllegalArgumentException("Invalid work directory: " + workDir.getAbsolutePath()); 35 | } 36 | var img = extract( 37 | dockerImageUri, workDir, dockerArch, dockerOs, 38 | (entry, err) -> log.warn("Unable to extract entry [{}] - {}", entry, err.getMessage()) 39 | ); 40 | var extractDir = new File(workDir, pExtract); 41 | var ffRtUri = uri(Objects.requireNonNull(FgVmSvc.class.getResource("/io/vacco/ff/ffrt"))); 42 | var ffRtPath = new File(extractDir, "init"); 43 | copyURIToFile(ffRtUri, ffRtPath.toPath()); 44 | addPermissions(ffRtPath.toPath(), PosixFilePermission.OWNER_EXECUTE); 45 | 46 | var initRamFs = new File(workDir, fInitRamFs); 47 | archive( 48 | extractDir, initRamFs, 49 | (path, err) -> log.warn("Unable to archive path [{}] - {}", path, err.getMessage()) 50 | ); 51 | img.rootDir = initRamFs.getAbsolutePath(); 52 | delete(extractDir, e -> onError(log, "Unable to delete extract directory [{}]", e, extractDir)); 53 | return img; 54 | } 55 | 56 | private static FgImage vmUpdateInitRamFs(File vmRoot, String source, List envUsr) { 57 | var ramFs = vmInitRamFs(vmRoot, source).withEnvUsr(envUsr); 58 | if (ramFs.entryPoint == null) { 59 | log.warn("Docker image [{}] has no entry point. Trying to use CMD instead", ramFs.source); 60 | ramFs.entryPoint = ramFs.cmd; 61 | ramFs.cmd = null; 62 | } 63 | return ramFs; 64 | } 65 | 66 | private static FgVmCreate vmUpdate(FgVmCreate req, FgVmFiles vms) { 67 | var vm0 = fromJson(vms.vmCfg, FgVm.class, gson); 68 | if (!vm0.image.source.equals(req.vm.image.source) || req.rebuildInitRamFs) { 69 | delete(vms.vmInitRamFs, e -> { throw new IllegalStateException("Unable to delete VM initramfs", e); }); 70 | req.vm.image = vmUpdateInitRamFs(vms.vmRoot, req.vm.image.source, req.vm.image.envUsr); 71 | } 72 | toJson(req.vm, vms.vmCfg, gson); 73 | toJson(req.network, vms.vmNetCfg, gson); 74 | return req; 75 | } 76 | 77 | public static FgVmCreate vmBuild(FgVmCreate req) { 78 | try { 79 | var warnings = validationsOf(FgVmCreateVld.validate(req)); 80 | if (!warnings.isEmpty()) { 81 | return req.withWarnings(warnings); 82 | } 83 | if (!VmIdNew.equals(req.vm.tag.id)) { 84 | return vmUpdate(req, FgVmFiles.of(FgOptions.vmDir, req.vm.tag.id)); 85 | } 86 | 87 | req.vm.tag.id = newId(); 88 | var vms = FgVmFiles.of(FgOptions.vmDir, req.vm.tag.id); 89 | mkDirs(vms.vmRoot); 90 | 91 | req.vm.image = vmUpdateInitRamFs(vms.vmRoot, req.vm.image.source, req.vm.image.envUsr); 92 | req.vm.config.networkinterfaces = List.of( // TODO allow for multiple interfaces if there is demand. 93 | networkInterface() 94 | .host_dev_name(format("tap%s.%d", req.vm.tag.id, 0)) 95 | .guest_mac(macToString(newMacAddress())) 96 | .iface_id(format("eth%d", 0)) 97 | ); 98 | 99 | toJson(req.vm, vms.vmCfg, gson); 100 | toJson(req.network, vms.vmNetCfg, gson); 101 | return req; 102 | } catch (Exception e) { 103 | onError(log, "Unable to build VM {}", e, req); 104 | return req.withError(messageFor(e)); 105 | } 106 | } 107 | 108 | public static FgVmCreate vmGet(String vmId) { 109 | var res = new FgVmCreate(); 110 | try { 111 | var vms = FgVmFiles.of(FgOptions.vmDir, vmId); 112 | var vm = fromJson(vms.vmCfg, FgVm.class, gson); 113 | var net = fromJson(vms.vmNetCfg, FgNetConfig.class, gson); 114 | var c = new FgVmCreate().withVm(vm).withNetwork(net); 115 | if (c.network.dhcp) { 116 | c.network.ipConfig = null; 117 | } 118 | return res.withVm(vm).withNetwork(net); 119 | } catch (Exception e) { 120 | onError(log, "Unable to retrieve metadata for VM {}", e, vmId); 121 | return res.withError(messageFor(e)); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /ff-api/src/main/java/io/vacco/ff/service/FgVmSvcStatus.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff.service; 2 | 3 | import com.google.gson.*; 4 | import io.vacco.ff.firecracker.MachineConfiguration; 5 | import io.vacco.ff.net.FgJni; 6 | import io.vacco.ff.schema.*; 7 | import org.slf4j.*; 8 | import java.io.File; 9 | import java.util.Arrays; 10 | 11 | import static io.vacco.ff.service.FgFirecracker.fcMachineConfigOf; 12 | import static io.vacco.ff.service.FgLogging.*; 13 | import static io.vacco.ff.util.FgIo.*; 14 | import static java.util.Objects.requireNonNull; 15 | import static java.util.stream.Collectors.toList; 16 | 17 | public class FgVmSvcStatus { 18 | 19 | public static final String ProcPath = "/proc"; 20 | private static final Logger log = LoggerFactory.getLogger(FgVmSvcStatus.class); 21 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 22 | 23 | public static FgVmStatus vmStatusOf(FgVmFiles vms) { 24 | var vm = fromJson(vms.vmCfg, FgVm.class, gson); 25 | var netCfg = fromJson(vms.vmNetCfg, FgNetConfig.class, gson); 26 | var fcPid = pidOf(ProcPath, vm.tag.id); 27 | if (fcPid != -1) { 28 | vm.config.machineconfig = gson.fromJson( 29 | fcMachineConfigOf(vms.vmSock).body, MachineConfiguration.class 30 | ); 31 | } 32 | return new FgVmStatus() 33 | .withFcPid(fcPid) 34 | .withVm(vm) 35 | .withNetwork(netCfg); 36 | } 37 | 38 | public static FgVmList vmList() { 39 | var res = new FgVmList(); 40 | for (var vmDir : requireNonNull(FgOptions.vmDir.listFiles())) { 41 | try { 42 | if (vmDir.isDirectory()) { 43 | res.vms.add(vmStatusOf(FgVmFiles.of(vmDir))); 44 | } 45 | } catch (Exception e) { 46 | onError(log, "Unable to retrieve status for VM [{}]", e, vmDir); 47 | res.withError(messageFor(e)); 48 | } 49 | } 50 | return res; 51 | } 52 | 53 | public static FgVmResourceList brIfList() { 54 | var l = new FgVmResourceList(); 55 | try { 56 | return l.withItems(FgJni.getLinuxBridgeInterfaces()); 57 | } catch (Exception e) { 58 | onError(log, "Unable to retrieve Linux bridge list", e); 59 | return l.withError(messageFor(e)); 60 | } 61 | } 62 | 63 | public static FgVmResourceList krnList() { 64 | var l = new FgVmResourceList(); 65 | try { 66 | return l.withItems( 67 | Arrays.stream(requireNonNull(FgOptions.krnDir.listFiles())) 68 | .filter(File::isFile) 69 | .map(File::getAbsolutePath) 70 | .sorted() 71 | .collect(toList()) 72 | ); 73 | } catch (Exception e) { 74 | onError(log, "Unable to retrieve Linux Kernel list", e); 75 | return l.withError(messageFor(e)); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /ff-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | id("org.graalvm.buildtools.native") version "0.10.2" 4 | } 5 | 6 | dependencies { implementation(project(":ff-api")) } 7 | application { mainClass.set("io.vacco.ff.FgMain") } 8 | 9 | graalvmNative { 10 | binaries { 11 | named("main") { 12 | configurationFileDirectories.from(file("src/main/resources")) 13 | buildArgs.add("--enable-url-protocols=http,https") 14 | buildArgs.add("-march=compatibility") 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ff-app/src/main/resources/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": { 3 | "includes": [ 4 | {"pattern": "ui/.*"}, 5 | {"pattern": "io/vacco/ff/.*"}] 6 | } 7 | } -------------------------------------------------------------------------------- /ff-jni/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/src/**/*.h", 7 | "/home/jjzazuet/Applications/zulu17.42.21-ca-crac-jdk17.0.7-linux_x64/include", 8 | "/home/jjzazuet/Applications/zulu17.42.21-ca-crac-jdk17.0.7-linux_x64/include/linux", 9 | "/usr/include", 10 | "/usr/local/include", 11 | "/usr/include/linux", 12 | "/usr/include/net" 13 | ], 14 | "defines": [], 15 | "compilerPath": "/home/jjzazuet/Applications/zig-linux-x86_64-0.13.0/zig", 16 | "cStandard": "c11", 17 | "intelliSenseMode": "gcc-x64" 18 | } 19 | ], 20 | "version": 4 21 | } -------------------------------------------------------------------------------- /ff-jni/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug with GDB", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/out/fg_test", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceFolder}", 12 | "environment": [], 13 | "externalConsole": false, 14 | "MIMode": "gdb", 15 | "setupCommands": [ 16 | { 17 | "description": "Enable pretty-printing for gdb", 18 | "text": "print pretty", 19 | "ignoreFailures": true 20 | } 21 | ], 22 | "preLaunchTask": "build", 23 | "miDebuggerPath": "/usr/bin/gdb", 24 | "internalConsoleOptions": "openOnSessionStart" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /ff-jni/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "jni.h": "c", 4 | "if_packet.h": "c", 5 | "types.h": "c", 6 | "fg_tap.h": "c", 7 | "cerrno": "c", 8 | "string.h": "c", 9 | "fcntl.h": "c", 10 | "socket.h": "c", 11 | "stdlib.h": "c", 12 | "unistd.h": "c", 13 | "signal.h": "c" 14 | } 15 | } -------------------------------------------------------------------------------- /ff-jni/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "shell", 7 | "command": "make", 8 | "args": ["tests"], 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher": ["$gcc"], 14 | "detail": "Generated task by VSCode." 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /ff-jni/Makefile: -------------------------------------------------------------------------------- 1 | CC = zig cc 2 | CFLAGS = -Wall -I./include -g \ 3 | -I/home/jjzazuet/Applications/zulu17.42.21-ca-crac-jdk17.0.7-linux_x64/include \ 4 | -I/home/jjzazuet/Applications/zulu17.42.21-ca-crac-jdk17.0.7-linux_x64/include/linux 5 | 6 | $(shell mkdir -p ./out) 7 | 8 | all: compile jni 9 | 10 | compile: 11 | $(CC) $(CFLAGS) -c ./src/fg/fg_tap.c -o ./out/fg_tap.o 12 | $(CC) $(CFLAGS) -c ./src/fg/fg_raw.c -o ./out/fg_raw.o 13 | $(CC) $(CFLAGS) -c ./src/fg/fg_proc.c -o ./out/fg_proc.o 14 | $(CC) $(CFLAGS) -c ./src/fg/fg_unix.c -o ./out/fg_unix.o 15 | $(CC) $(CFLAGS) -c ./src/fg/fg_vsock.c -o ./out/fg_vsock.o 16 | 17 | jni: 18 | $(CC) $(CFLAGS) -fPIC -c ./src/jni/fg_jni.c -o ./out/fg_jni.o 19 | $(CC) -shared -o ./out/fg_jni.so ./out/fg_jni.o \ 20 | ./out/fg_tap.o ./out/fg_raw.o ./out/fg_proc.o ./out/fg_unix.o ./out/fg_vsock.o 21 | 22 | tests: all 23 | $(CC) $(CFLAGS) -c ./src/test/fg_tap_test.c -o ./out/fg_tap_test.o 24 | $(CC) -o ./out/fg_test \ 25 | ./out/fg_tap.o ./out/fg_tap_test.o 26 | sudo setcap 'cap_net_admin,cap_net_bind_service,cap_net_raw+eip' ./out/fg_test 27 | 28 | clean: 29 | rm -rf ./out -------------------------------------------------------------------------------- /ff-jni/build.gradle.kts: -------------------------------------------------------------------------------- 1 | configure { 2 | sharedLibrary(false, false) 3 | } 4 | 5 | val copyFfJni = tasks.register("copyFfJni") { 6 | from("./out/fg_jni.so") 7 | into("./build/resources/main/io/vacco/ff") 8 | } 9 | 10 | tasks.processResources { 11 | dependsOn(copyFfJni) 12 | } 13 | -------------------------------------------------------------------------------- /ff-jni/out/fg_jni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaccovecrana/frag-falcon/abcd3955daaeb5adb30f25191ed03e4bdef01b09/ff-jni/out/fg_jni.so -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_proc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | extern char **environ; 15 | 16 | int spawn_process(const char *vm_id, const char *cmd, char **argv, const char *log_path) { 17 | pid_t pid = fork(); 18 | if (pid == -1) { 19 | return -1; 20 | } else if (pid == 0) { 21 | if (setsid() == -1) { 22 | perror("setsid"); 23 | exit(EXIT_FAILURE); 24 | } 25 | if (log_path != NULL) { 26 | int log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644); 27 | if (log_fd != -1) { 28 | dup2(log_fd, STDIN_FILENO); 29 | dup2(log_fd, STDOUT_FILENO); 30 | dup2(log_fd, STDERR_FILENO); 31 | close(log_fd); // No longer needed after duplication 32 | } else { 33 | perror("Failed to open log file"); 34 | exit(EXIT_FAILURE); 35 | } 36 | } 37 | 38 | struct rlimit rlim; 39 | if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { 40 | for (int fd = 3; fd < rlim.rlim_max; fd++) { 41 | close(fd); 42 | } 43 | } 44 | 45 | char env_var[256]; 46 | snprintf(env_var, sizeof(env_var), "FF_VMID=%s", vm_id); 47 | putenv(env_var); 48 | 49 | execve(cmd, argv, environ); 50 | perror("execve"); // execve only returns on error 51 | exit(EXIT_FAILURE); 52 | } else { 53 | // Parent process 54 | return pid; 55 | } 56 | } 57 | 58 | int terminate_process(pid_t pid) { 59 | return kill(pid, SIGTERM); 60 | } 61 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_proc.h: -------------------------------------------------------------------------------- 1 | #ifndef FG_PROC_H 2 | #define FG_PROC_H 3 | 4 | #include 5 | 6 | int spawn_process(const char *vm_id, const char *cmd, char **argv, const char *log_path); 7 | int terminate_process(pid_t pid); 8 | 9 | #endif -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_raw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "fg_raw.h" 15 | 16 | int create_raw_socket(const char *iface) { 17 | int sock; 18 | struct sockaddr_ll socket_address; 19 | struct ifreq ifr; 20 | int ifindex; 21 | 22 | // Create a raw socket 23 | sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 24 | if (sock == -1) { 25 | perror("Socket creation failed"); 26 | return -1; 27 | } 28 | 29 | // Set socket to blocking mode 30 | int flags = fcntl(sock, F_GETFL); 31 | if (flags == -1 || fcntl(sock, F_SETFL, flags & ~O_NONBLOCK) == -1) { 32 | perror("Failed to set socket to blocking mode"); 33 | close(sock); 34 | return -2; 35 | } 36 | 37 | // Get interface index 38 | memset(&ifr, 0, sizeof(ifr)); 39 | strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1); 40 | ifr.ifr_name[IFNAMSIZ - 1] = '\0'; 41 | 42 | if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) { 43 | perror("Getting interface index failed"); 44 | close(sock); 45 | return -3; 46 | } 47 | ifindex = ifr.ifr_ifindex; 48 | 49 | // Prepare sockaddr_ll 50 | memset(&socket_address, 0, sizeof(struct sockaddr_ll)); 51 | socket_address.sll_family = AF_PACKET; 52 | socket_address.sll_protocol = htons(ETH_P_ALL); 53 | socket_address.sll_ifindex = ifindex; 54 | 55 | // Bind the socket to the network interface 56 | if (bind(sock, (struct sockaddr *)&socket_address, sizeof(struct sockaddr_ll)) == -1) { 57 | perror("Socket bind failed"); 58 | close(sock); 59 | return -4; 60 | } 61 | 62 | return sock; 63 | } 64 | 65 | int send_raw_packet(int socketHandle, const unsigned char *buffer, size_t len) { 66 | int sent = send(socketHandle, buffer, len, 0); 67 | if (sent < 0) { 68 | return -1; 69 | } 70 | return sent; 71 | } 72 | 73 | int receive_raw_packet(int socketHandle, unsigned char *buffer, size_t bufferSize, int timeoutSeconds) { 74 | struct timeval timeout; 75 | timeout.tv_sec = timeoutSeconds; 76 | timeout.tv_usec = 0; 77 | if (setsockopt(socketHandle, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { 78 | perror("Failed to set socket timeout"); 79 | return -1; 80 | } 81 | int received = recv(socketHandle, buffer, bufferSize, 0); 82 | if (received == -1) { 83 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 84 | fprintf(stderr, "Receive timed out after %d seconds\n", timeoutSeconds); 85 | } else { 86 | perror("Failed to receive data"); 87 | } 88 | return -2; 89 | } 90 | return received; 91 | } 92 | 93 | void close_raw_socket(int socketHandle) { 94 | close(socketHandle); 95 | } 96 | 97 | int set_promiscuous_mode(const char *interface, int enable) { 98 | int sock; 99 | struct ifreq ifr; 100 | 101 | if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 102 | perror("Socket error"); 103 | return -1; 104 | } 105 | 106 | strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1); 107 | ifr.ifr_name[IFNAMSIZ - 1] = '\0'; 108 | if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { 109 | perror("ioctl error getting flags"); 110 | close(sock); 111 | return -2; 112 | } 113 | if (enable) { 114 | ifr.ifr_flags |= IFF_PROMISC; 115 | } else { 116 | ifr.ifr_flags &= ~IFF_PROMISC; 117 | } 118 | if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { 119 | perror("ioctl error setting flags"); 120 | close(sock); 121 | return -3; 122 | } 123 | 124 | close(sock); 125 | return 0; 126 | } 127 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_raw.h: -------------------------------------------------------------------------------- 1 | #ifndef FG_RAW_H 2 | #define FG_RAW_H 3 | 4 | int create_raw_socket(const char *iface); 5 | int send_raw_packet(int socketHandle, const unsigned char *buffer, size_t len); 6 | int receive_raw_packet(int socketHandle, unsigned char *buffer, size_t bufferSize, int timeoutSeconds); 7 | void close_raw_socket(int socketHandle); 8 | int set_promiscuous_mode(const char *interface, int enable); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_tap.h: -------------------------------------------------------------------------------- 1 | #ifndef FG_TAP_H 2 | #define FG_TAP_H 3 | 4 | int create_tap_device(const char *if_name); 5 | int delete_tap_device(const char *if_name); 6 | int attach_tap_to_bridge(const char *if_name, const char *br_name); 7 | int detach_tap_from_bridge(const char *if_name, const char *br_name); 8 | int get_mac_address(const char *if_name, unsigned char *mac); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_unix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | int unix_open(const char *socket_path) { 12 | int sockfd; 13 | struct sockaddr_un addr;\ 14 | if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 15 | perror("socket error"); 16 | return -1; 17 | } 18 | memset(&addr, 0, sizeof(addr)); 19 | addr.sun_family = AF_UNIX; 20 | strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); 21 | if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 22 | perror("connect error"); 23 | close(sockfd); 24 | return -2; 25 | } 26 | return sockfd; 27 | } 28 | 29 | int unix_send(int socket_handle, const void *buffer, size_t buffer_len) { 30 | int bytes_sent = send(socket_handle, buffer, buffer_len, 0); 31 | if (bytes_sent == -1) { 32 | perror("send error"); 33 | } 34 | return bytes_sent; 35 | } 36 | 37 | int unix_receive(int socket_handle, void *buffer, size_t buffer_len, int timeout_ms) { 38 | struct timeval tv; 39 | tv.tv_sec = timeout_ms / 1000; // Convert milliseconds to seconds 40 | tv.tv_usec = (timeout_ms % 1000) * 1000; // Remainder to microseconds 41 | if (setsockopt(socket_handle, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { 42 | perror("setsockopt failed"); 43 | return -2; 44 | } 45 | int bytes_received = recv(socket_handle, buffer, buffer_len, 0); 46 | if (bytes_received == -1) { 47 | if (errno == EWOULDBLOCK || errno == EAGAIN) { 48 | perror("recv timeout"); 49 | return -3; 50 | } else { 51 | perror("recv error"); 52 | } 53 | } 54 | return bytes_received; 55 | } 56 | 57 | int unix_close(int socket_handle) { 58 | if (close(socket_handle) == -1) { 59 | perror("close error"); 60 | return -1; 61 | } 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_unix.h: -------------------------------------------------------------------------------- 1 | #ifndef FG_UNIX_H 2 | #define FG_UNIX_H 3 | 4 | int unix_open(const char *socket_path); 5 | int unix_send(int socket_handle, const void *buffer, size_t buffer_len); 6 | int unix_receive(int socket_handle, void *buffer, size_t buffer_len, int timeout_ms); 7 | int unix_close(int socket_handle); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_vsock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int vsock_open(const char *socket_addr) { 9 | int sockfd; 10 | struct sockaddr_vm addr; 11 | 12 | unsigned int cid, port; 13 | if (sscanf(socket_addr, "%u:%u", &cid, &port) != 2) { 14 | perror("invalid socket path format"); 15 | return -1; 16 | } 17 | if ((sockfd = socket(AF_VSOCK, SOCK_STREAM, 0)) == -1) { 18 | perror("socket error"); 19 | return -2; 20 | } 21 | 22 | memset(&addr, 0, sizeof(addr)); 23 | addr.svm_family = AF_VSOCK; 24 | addr.svm_cid = cid; 25 | addr.svm_port = port; 26 | 27 | if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 28 | perror("connect error"); 29 | close(sockfd); 30 | return -4; 31 | } 32 | 33 | return sockfd; 34 | } 35 | 36 | int vsock_send(int socket_handle, const void *buffer, size_t buffer_len) { 37 | int bytes_sent = send(socket_handle, buffer, buffer_len, 0); 38 | if (bytes_sent == -1) { 39 | perror("send error"); 40 | } 41 | return bytes_sent; 42 | } 43 | 44 | int vsock_receive(int socket_handle, void *buffer, size_t buffer_len) { 45 | int bytes_received = recv(socket_handle, buffer, buffer_len, 0); 46 | if (bytes_received == -1) { 47 | perror("recv error"); 48 | } 49 | return bytes_received; 50 | } 51 | 52 | int vsock_close(int socket_handle) { 53 | if (close(socket_handle) == -1) { 54 | perror("close error"); 55 | return -1; 56 | } 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /ff-jni/src/fg/fg_vsock.h: -------------------------------------------------------------------------------- 1 | #ifndef FG_VSOCK_H 2 | #define FG_VSOCK_H 3 | 4 | int vsock_open(const char *socket_addr); 5 | int vsock_send(int socket_handle, const void *buffer, size_t buffer_len); 6 | int vsock_receive(int socket_handle, void *buffer, size_t buffer_len); 7 | int vsock_close(int socket_handle); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /ff-jni/src/test/fg_tap_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "../fg/fg_tap.h" 17 | 18 | #define BUFFER_SIZE 1600 19 | 20 | void send_dummy_udp_packet(int tap_fd) { 21 | char buffer[BUFFER_SIZE]; 22 | memset(buffer, 0, sizeof(buffer)); 23 | struct iphdr *iph = (struct iphdr *)buffer; 24 | struct udphdr *udph = (struct udphdr *)(buffer + sizeof(struct iphdr)); 25 | 26 | // Fill in the IP Header 27 | iph->ihl = 5; 28 | iph->version = 4; 29 | iph->tos = 0; 30 | iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr)); 31 | iph->id = htonl(54321); 32 | iph->frag_off = 0; 33 | iph->ttl = 255; 34 | iph->protocol = IPPROTO_UDP; 35 | iph->check = 0; // Set to 0 before calculating checksum 36 | iph->saddr = inet_addr("192.168.1.2"); 37 | iph->daddr = inet_addr("192.168.1.1"); 38 | 39 | // Fill in the UDP Header 40 | udph->source = htons(12345); 41 | udph->dest = htons(80); 42 | udph->len = htons(sizeof(struct udphdr)); 43 | udph->check = 0; // No checksum for simplicity 44 | 45 | // Send the dummy packet 46 | if (write(tap_fd, buffer, sizeof(struct iphdr) + sizeof(struct udphdr)) < 0) { 47 | perror("Writing to TAP interface"); 48 | close(tap_fd); 49 | exit(EXIT_FAILURE); 50 | } 51 | } 52 | 53 | void test_tap_device_lifecycle(const char *tap_name, const char *bridge_name) { 54 | int result; 55 | 56 | printf("Creating TAP device %s\n", tap_name); 57 | int tap_fd = create_tap_device(tap_name, 1); 58 | if (tap_fd < 0) { 59 | fprintf(stderr, "Failed to create TAP device %s: %d\n", tap_name, tap_fd); 60 | exit(EXIT_FAILURE); 61 | } 62 | printf("Created TAP device %s\n", tap_name); 63 | 64 | printf("Attaching TAP device %s to bridge %s\n", tap_name, bridge_name); 65 | result = attach_tap_to_bridge(tap_name, bridge_name); 66 | if (result < 0) { 67 | fprintf(stderr, "Failed to attach TAP device %s to bridge %s: %d\n", tap_name, bridge_name, result); 68 | exit(EXIT_FAILURE); 69 | } 70 | printf("Attached TAP device %s to bridge %s\n", tap_name, bridge_name); 71 | 72 | // Send dummy UDP packet through the tap device 73 | for (int i = 0; i < 60; i++) { 74 | printf("Sending dummy UDP packet through %s\n", tap_name); 75 | send_dummy_udp_packet(tap_fd); 76 | sleep(1); 77 | } 78 | 79 | close(tap_fd); 80 | 81 | printf("Detaching TAP device %s from bridge %s\n", tap_name, bridge_name); 82 | result = detach_tap_from_bridge(tap_name, bridge_name); 83 | if (result < 0) { 84 | fprintf(stderr, "Failed to detach TAP device %s from bridge %s: %d\n", tap_name, bridge_name, result); 85 | exit(EXIT_FAILURE); 86 | } 87 | printf("Detached TAP device %s from bridge %s\n", tap_name, bridge_name); 88 | 89 | printf("Deleting TAP device %s\n", tap_name); 90 | result = delete_tap_device(tap_name); 91 | if (result < 0) { 92 | fprintf(stderr, "Failed to delete TAP device %s: %d\n", tap_name, result); 93 | exit(EXIT_FAILURE); 94 | } 95 | printf("Deleted TAP device %s\n", tap_name); 96 | } 97 | 98 | int main() { 99 | const char *tap_name = "tap04"; 100 | const char *bridge_name = "br0"; 101 | 102 | test_tap_device_lifecycle(tap_name, bridge_name); 103 | 104 | printf("Test completed successfully\n"); 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /ff-test/README.md: -------------------------------------------------------------------------------- 1 | # Development/Debugging 2 | 3 | The following commands are needed in order to run machine local tests. 4 | 5 | TAP interfaces: 6 | 7 | # Create a bridge 8 | sudo ip link add name br0 type bridge 9 | 10 | # Create TAP interface, attach to bridge and assign IP 11 | sudo ip tuntap add name tap05 mode tap 12 | sudo ip link set tap05 master br0 13 | sudo ip address add 172.16.0.138/24 dev eth0 14 | sudo ip route add default via 172.16.0.1 dev eth0 15 | 16 | # Delete tap interface 17 | sudo ip link delete tap2 18 | 19 | # Send arp requests 20 | nping --arp --arp-target-mac f2:70:47:a5:39:5a --arp-target-ip 192.168.1.10 --interface eth0 192.168.1.255 21 | 22 | Capture layer 2 traffic: 23 | 24 | sudo tcpdump -i br0 ether host f2:70:47:a5:39:5a 25 | 26 | DHCP requests: 27 | 28 | tcpdump -i any 'udp and (port 67 or port 68)' -e -n -vv 29 | 30 | Java/C network permissions: 31 | 32 | sudo setcap 'cap_net_admin+ep cap_net_bind_service+ep cap_net_raw+ep cap_sys_admin+ep' ~/Applications/zulu21.36.17-ca-jdk21.0.4-linux_x64/bin/java 33 | sudo setcap 'cap_net_admin+ep cap_net_bind_service+ep cap_net_raw+ep' ./ff-jni/build/core_test 34 | getcap ./build/fg_test 35 | 36 | Network interface configurations in Arch Linux are at `/etc/systemd/network`. 37 | 38 | Ignore specific interfaces in `/etc/NetworkManager/conf.d`. 39 | 40 | [keyfile] 41 | unmanaged-devices=interface-name:br0;interface-name:enp0s20f0u3 42 | 43 | Firecracker control: 44 | 45 | rm -fv ./fc.sock && firecracker --api-sock ./fc.socket 46 | 47 | VSOCK configuration: 48 | 49 | # Load the VSOCK modules 50 | modprobe vsock 51 | modprobe vmw_vsock_virtio_transport 52 | 53 | # Verify that /dev/vsock exists 54 | ls -la /dev/vsock 55 | 56 | VSOCK echo process: 57 | 58 | socat vsock-listen:1234,fork EXEC:'/bin/cat' 59 | 60 | Send a test payload: 61 | 62 | echo "Hello, VSOCK!" | socat - VSOCK-CONNECT:2:1234 63 | 64 | Machine definition with volume mounts: 65 | 66 | ```yaml 67 | vm: 68 | tag: 69 | label: uptime-kuma 70 | description: Uptime Kuma 71 | image: 72 | source: docker.io/louislam/uptime-kuma:latest 73 | envUsr: 74 | - key: FF_MOUNT_0 75 | val: '"{"Device": "/dev/vda", "Path": "/app/data", "Sync": true, "ReadOnly": false}"' 76 | config: 77 | bootsource: 78 | kernel_image_path: /home/jjzazuet/code/frag-falcon/ff-test/./src/test/resources/kernel/vmlinux-6.1.98 79 | machineconfig: 80 | mem_size_mib: 2048 81 | vcpu_count: 1 82 | drives: 83 | - drive_id: rootfs 84 | path_on_host: /home/jjzazuet/code/frag-falcon/ff-test/src/test/resources/disk.img 85 | is_root_device: false 86 | network: 87 | brIf: br0 88 | dhcp: true 89 | ``` -------------------------------------------------------------------------------- /ff-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | configure { 2 | addJ8Spec() 3 | } 4 | 5 | tasks.withType { 6 | sourceSets(project(":ff-api").sourceSets.main.get(),) 7 | } 8 | 9 | tasks.jacocoTestReport { 10 | classDirectories.setFrom(files(classDirectories.files.map { 11 | fileTree(it) { 12 | exclude("io/vacco/ff/firecracker/**") 13 | } 14 | })) 15 | } 16 | 17 | dependencies { 18 | implementation(project(":ff-api")) 19 | } 20 | -------------------------------------------------------------------------------- /ff-test/etc/cpio/tree-ref.cpio: -------------------------------------------------------------------------------- 1 | 07070100F40367000081A4000003E8000003E8000000016693837A0000001D000001030000000500000000000000000000001300000000lib/libnss_dns.txt[content for libnss_dns.txt] 2 | 07070100F403C5000041ED000003E8000003E8000000026693838200000000000001030000000500000000000000000000000400000000lib07070100F403BE000041ED000003E8000003E80000000266937C4400000000000001030000000500000000000000000000001400000000etc/network/if-up.d07070100F4036E000041ED000003E8000003E80000000366937E1F00000000000001030000000500000000000000000000000C00000000etc/network07070100F4036C000081A4000003E8000003E80000000166937C4400000072000001030000000500000000000000000000000E00000000etc/localtimeTZif2UTCTZif2UTC 3 | UTC0 4 | 07070100F4036A000041ED000003E8000003E80000000366937E0800000000000001030000000500000000000000000000000400000000etc07070100F403D80000A1FF000003E8000003E80000000166937C440000000D000001030000000500000000000000000000000C00000000usr/bin/env../../bin/env07070100F403D7000041ED000003E8000003E80000000266937C4400000000000001030000000500000000000000000000000800000000usr/bin07070100F403D9000041ED000003E8000003E80000000266937C4400000000000001030000000500000000000000000000000900000000usr/sbin07070100F403D6000041ED000003E8000003E80000000466937C4400000000000001030000000500000000000000000000000400000000usr07070100F403D5000041FF000003E8000003E80000000266937C4400000000000001030000000500000000000000000000000400000000tmp07070100F40378000081A4000003E8000003E8000000016693834400000016000001030000000500000000000000000000000C00000000bin/yes.txt[content for yes.txt] 5 | 07070100F40366000041ED000003E8000003E8000000026693834A00000000000001030000000500000000000000000000000400000000bin07070100F403D30000A1FF000003E8000003E80000000166937C4400000003000001030000000500000000000000000000000600000000lib64lib07070100F40369000041ED000003E8000003E80000000266937C4400000000000001030000000500000000000000000000000400000000dev07070100F24498000041ED000003E8000003E80000000866937EEE00000000000001030000000500000000000000000000000200000000.07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!! -------------------------------------------------------------------------------- /ff-test/etc/cpio/tree/etc/localtime: -------------------------------------------------------------------------------- 1 | TZif2UTCTZif2UTC 2 | UTC0 3 | -------------------------------------------------------------------------------- /ff-test/etc/cpio/tree/lib/libnss_dns.txt: -------------------------------------------------------------------------------- 1 | [content for libnss_dns.txt] 2 | -------------------------------------------------------------------------------- /ff-test/etc/cpio/tree/lib64: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /ff-test/etc/kernel-build/README.md: -------------------------------------------------------------------------------- 1 | ## Kernel build 2 | 3 | wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.1.98.tar.xz 4 | cp microvm-kernel-ci-x86_64-6.1.config ./linux-6.1.98/.config 5 | make -j28 6 | 7 | This obscure stack overflow answer helped in getting Kernel 6.1 to compile and run under firecracker: 8 | 9 | - https://unix.stackexchange.com/questions/779763/which-linux-kernel-config-options-are-required-to-get-qemu-virtio-to-work 10 | 11 | Set `CONFIG_X86_MPPARSE=y`. Compiles with no questions asked. Why? 12 | -------------------------------------------------------------------------------- /ff-test/etc/vsock-proxy/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is experimental code that can proxy a UNIX socket in the guest VM to a UNIX socket in the host machine. 4 | 5 | It could be used to grant access to a Docker socket on the host machine. 6 | 7 | - On the Guest side, a goroutine would need to implement `vsock-proxy.c`. 8 | - On the Host side, a Java thread would need to implement `unix-proxy.c`. 9 | -------------------------------------------------------------------------------- /ff-test/etc/vsock-proxy/unix-proxy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define LISTEN_SOCKET_PATH "./fc_podman.sock_1234" 11 | #define PODMAN_SOCKET_PATH "/run/podman/podman.sock" 12 | #define BUFFER_SIZE 1024 13 | 14 | int create_unix_listener(const char *path) { 15 | int sock; 16 | struct sockaddr_un addr; 17 | 18 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 19 | perror("socket"); 20 | exit(EXIT_FAILURE); 21 | } 22 | 23 | memset(&addr, 0, sizeof(addr)); 24 | addr.sun_family = AF_UNIX; 25 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 26 | unlink(path); 27 | 28 | if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 29 | perror("bind"); 30 | close(sock); 31 | exit(EXIT_FAILURE); 32 | } 33 | 34 | if (listen(sock, 5) == -1) { 35 | perror("listen"); 36 | close(sock); 37 | exit(EXIT_FAILURE); 38 | } 39 | 40 | return sock; 41 | } 42 | 43 | int connect_to_unix_socket(const char *path) { 44 | int sock; 45 | struct sockaddr_un addr; 46 | 47 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 48 | perror("socket"); 49 | return -1; 50 | } 51 | 52 | memset(&addr, 0, sizeof(addr)); 53 | addr.sun_family = AF_UNIX; 54 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 55 | 56 | if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 57 | perror("connect"); 58 | close(sock); 59 | return -1; 60 | } 61 | 62 | return sock; 63 | } 64 | 65 | void bidirectional_forwarding(int sock1, int sock2) { 66 | fd_set readfds; 67 | int maxfd = (sock1 > sock2 ? sock1 : sock2) + 1; 68 | char buffer[BUFFER_SIZE]; 69 | ssize_t bytes; 70 | 71 | while (1) { 72 | FD_ZERO(&readfds); 73 | FD_SET(sock1, &readfds); 74 | FD_SET(sock2, &readfds); 75 | 76 | if (select(maxfd, &readfds, NULL, NULL, NULL) < 0) { 77 | perror("select"); 78 | break; 79 | } 80 | 81 | if (FD_ISSET(sock1, &readfds)) { 82 | bytes = recv(sock1, buffer, sizeof(buffer), 0); 83 | if (bytes <= 0) { 84 | if (bytes < 0) perror("recv"); 85 | break; 86 | } 87 | if (send(sock2, buffer, bytes, 0) != bytes) { 88 | perror("send"); 89 | break; 90 | } 91 | } 92 | 93 | if (FD_ISSET(sock2, &readfds)) { 94 | bytes = recv(sock2, buffer, sizeof(buffer), 0); 95 | if (bytes <= 0) { 96 | if (bytes < 0) perror("recv"); 97 | break; 98 | } 99 | if (send(sock1, buffer, bytes, 0) != bytes) { 100 | perror("send"); 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | 107 | int main() { 108 | int listen_sock, client_sock, podman_sock; 109 | struct sockaddr_un client_addr; 110 | socklen_t client_addr_len = sizeof(client_addr); 111 | 112 | // Create UNIX socket listener 113 | listen_sock = create_unix_listener(LISTEN_SOCKET_PATH); 114 | printf("Listening on UNIX socket: %s\n", LISTEN_SOCKET_PATH); 115 | 116 | while (1) { 117 | // Accept a connection on the listening UNIX socket 118 | client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_addr_len); 119 | if (client_sock < 0) { 120 | perror("accept"); 121 | continue; 122 | } 123 | 124 | printf("Accepted connection on UNIX socket\n"); 125 | 126 | // Connect to the real Podman UNIX socket 127 | podman_sock = connect_to_unix_socket(PODMAN_SOCKET_PATH); 128 | if (podman_sock < 0) { 129 | close(client_sock); 130 | continue; 131 | } 132 | 133 | printf("Connected to Podman UNIX socket\n"); 134 | 135 | // Forward data between the listening UNIX socket and the Podman UNIX socket 136 | bidirectional_forwarding(client_sock, podman_sock); 137 | 138 | // Close sockets 139 | close(client_sock); 140 | close(podman_sock); 141 | } 142 | 143 | close(listen_sock); 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /ff-test/etc/vsock-proxy/vsock-proxy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define PODMAN_SOCKET_PATH "/run/podman/podman.sock" 12 | #define BUFFER_SIZE 1024 13 | #define HOST_CID 2 14 | #define VSOCK_PORT 1234 15 | 16 | int create_unix_socket(const char *path) { 17 | int sock; 18 | struct sockaddr_un addr; 19 | 20 | if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 21 | perror("socket"); 22 | exit(EXIT_FAILURE); 23 | } 24 | 25 | memset(&addr, 0, sizeof(addr)); 26 | addr.sun_family = AF_UNIX; 27 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 28 | unlink(path); 29 | 30 | if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 31 | perror("bind"); 32 | close(sock); 33 | exit(EXIT_FAILURE); 34 | } 35 | 36 | if (listen(sock, 5) == -1) { 37 | perror("listen"); 38 | close(sock); 39 | exit(EXIT_FAILURE); 40 | } 41 | 42 | return sock; 43 | } 44 | 45 | int connect_to_vsock(int cid, int port) { 46 | int sock; 47 | struct sockaddr_vm sa; 48 | 49 | sock = socket(AF_VSOCK, SOCK_STREAM, 0); 50 | if (sock < 0) { 51 | perror("socket"); 52 | exit(EXIT_FAILURE); 53 | } 54 | 55 | memset(&sa, 0, sizeof(sa)); 56 | sa.svm_family = AF_VSOCK; 57 | sa.svm_cid = cid; 58 | sa.svm_port = port; 59 | 60 | if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { 61 | perror("connect"); 62 | close(sock); 63 | exit(EXIT_FAILURE); 64 | } 65 | 66 | return sock; 67 | } 68 | 69 | void forward_data(int src_sock, int dst_sock) { 70 | char buffer[BUFFER_SIZE]; 71 | ssize_t bytes; 72 | 73 | while ((bytes = recv(src_sock, buffer, sizeof(buffer), 0)) > 0) { 74 | if (send(dst_sock, buffer, bytes, 0) != bytes) { 75 | perror("send"); 76 | break; 77 | } 78 | } 79 | 80 | if (bytes < 0) { 81 | perror("recv"); 82 | } 83 | } 84 | 85 | void bidirectional_forwarding(int sock1, int sock2) { 86 | fd_set readfds; 87 | int maxfd = (sock1 > sock2 ? sock1 : sock2) + 1; 88 | char buffer[BUFFER_SIZE]; 89 | ssize_t bytes; 90 | 91 | while (1) { 92 | FD_ZERO(&readfds); 93 | FD_SET(sock1, &readfds); 94 | FD_SET(sock2, &readfds); 95 | 96 | if (select(maxfd, &readfds, NULL, NULL, NULL) < 0) { 97 | perror("select"); 98 | break; 99 | } 100 | 101 | if (FD_ISSET(sock1, &readfds)) { 102 | bytes = recv(sock1, buffer, sizeof(buffer), 0); 103 | if (bytes <= 0) { 104 | if (bytes < 0) perror("recv"); 105 | break; 106 | } 107 | if (send(sock2, buffer, bytes, 0) != bytes) { 108 | perror("send"); 109 | break; 110 | } 111 | } 112 | 113 | if (FD_ISSET(sock2, &readfds)) { 114 | bytes = recv(sock2, buffer, sizeof(buffer), 0); 115 | if (bytes <= 0) { 116 | if (bytes < 0) perror("recv"); 117 | break; 118 | } 119 | if (send(sock1, buffer, bytes, 0) != bytes) { 120 | perror("send"); 121 | break; 122 | } 123 | } 124 | } 125 | } 126 | 127 | int main() { 128 | int unix_sock, client_sock, vsock; 129 | struct sockaddr_un client_addr; 130 | socklen_t client_addr_len = sizeof(client_addr); 131 | 132 | // Create UNIX socket 133 | unix_sock = create_unix_socket(PODMAN_SOCKET_PATH); 134 | printf("Listening on UNIX socket: %s\n", PODMAN_SOCKET_PATH); 135 | 136 | while (1) { 137 | // Accept a connection on the UNIX socket 138 | client_sock = accept(unix_sock, (struct sockaddr *)&client_addr, &client_addr_len); 139 | if (client_sock < 0) { 140 | perror("accept"); 141 | continue; 142 | } 143 | 144 | printf("Accepted connection on UNIX socket\n"); 145 | 146 | // Connect to the VSOCK on the host 147 | vsock = connect_to_vsock(HOST_CID, VSOCK_PORT); 148 | printf("Connected to VSOCK (CID %d, Port %d)\n", HOST_CID, VSOCK_PORT); 149 | 150 | // Forward data between the UNIX socket and the VSOCK 151 | bidirectional_forwarding(client_sock, vsock); 152 | 153 | // Close sockets 154 | close(client_sock); 155 | close(vsock); 156 | } 157 | 158 | close(unix_sock); 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgContextTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import io.vacco.ff.service.FgOptions; 4 | import io.vacco.ff.service.*; 5 | import j8spec.annotation.DefinedOrder; 6 | import j8spec.junit.J8SpecRunner; 7 | import org.junit.runner.RunWith; 8 | import java.io.File; 9 | 10 | import static j8spec.J8Spec.*; 11 | import static io.vacco.ff.FgTest.localTest; 12 | import static io.vacco.ff.util.FgIo.mkDirs; 13 | 14 | @DefinedOrder 15 | @RunWith(J8SpecRunner.class) 16 | public class FgContextTest { 17 | static { 18 | it("Starts a test app context", localTest(() -> { 19 | FgOptions.setFrom(new String[]{ 20 | "--vm-dir=./build/vms", 21 | "--krn-dir=./src/test/resources/kernel", 22 | "--fc-path=/usr/local/bin/firecracker", 23 | "--log-level=debug" 24 | }); 25 | var vmd = new File("./build/vms"); 26 | mkDirs(vmd); 27 | try (var ctx = new FgContext()) { 28 | ctx.init(); 29 | // Thread.sleep(Integer.MAX_VALUE); 30 | } 31 | })); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgCpioTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import io.vacco.ff.initramfs.FgCpio; 4 | import j8spec.annotation.DefinedOrder; 5 | import j8spec.junit.J8SpecRunner; 6 | import org.junit.runner.RunWith; 7 | import java.io.File; 8 | 9 | import static j8spec.J8Spec.*; 10 | import static io.vacco.ff.util.FgIo.mkDirs; 11 | 12 | @DefinedOrder 13 | @RunWith(J8SpecRunner.class) 14 | public class FgCpioTest { 15 | static { 16 | it("creates a cpio archive from a file tree", () -> { 17 | var root = new File("./etc/cpio/tree"); 18 | var cpioDir = new File("./build/cpio"); 19 | var cpio = new File(cpioDir, "tree.cpio"); 20 | mkDirs(cpioDir); 21 | FgCpio.archive( 22 | root, cpio, 23 | (path, err) -> System.out.printf("Unable to archive path %s - %s%n", path, err.getMessage()) 24 | ); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgDockerIoTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import j8spec.annotation.DefinedOrder; 4 | import j8spec.junit.J8SpecRunner; 5 | import org.junit.runner.RunWith; 6 | import java.io.File; 7 | import java.nio.file.FileAlreadyExistsException; 8 | 9 | import static j8spec.J8Spec.*; 10 | import static io.vacco.ff.FgTest.*; 11 | import static io.vacco.ff.util.FgIo.mkDirs; 12 | import static io.vacco.ff.initramfs.FgDockerIo.extract; 13 | import static io.vacco.ff.initramfs.FgCpio.archive; 14 | 15 | @DefinedOrder 16 | @RunWith(J8SpecRunner.class) 17 | public class FgDockerIoTest { 18 | 19 | public static File buildDir = new File("./build"); 20 | 21 | private static final String[] images = new String[] { 22 | "ghcr.io/siderolabs/installer:v1.4.0", 23 | "docker.io/hashicorp/http-echo:latest", 24 | "docker.io/louislam/uptime-kuma:latest", 25 | "quay.io/argoproj/argocd:latest", 26 | "docker.io/nats:latest", 27 | "docker.io/postgres:latest", 28 | "docker.io/cockroachdb/cockroach", 29 | "docker.io/busybox:latest", 30 | "docker.io/drone/drone-runner-docker:linux-amd64" 31 | }; 32 | 33 | static { 34 | initLog(); 35 | it("Extracts remote Docker images", () -> { 36 | for (var image : images) { 37 | var imgId = Integer.toHexString(image.hashCode()); 38 | var imgDir = new File(buildDir, imgId); 39 | var imgExtractDir = new File(imgDir, "extract"); 40 | var imgCpioDir = new File(imgDir, "cpio"); 41 | var imgCpio = new File(imgCpioDir, String.format("%s.cpio", imgId)); 42 | mkDirs(imgCpioDir); 43 | extract(image, imgDir, "amd64", "linux", (tarEntry, ex) -> { 44 | if (ex instanceof FileAlreadyExistsException) { 45 | System.out.printf("File already exists: %s%n", tarEntry.name); 46 | } else { 47 | System.out.printf("Unable to extract entry %s - %s%n", tarEntry, ex.getMessage()); 48 | } 49 | }); 50 | archive(imgExtractDir, imgCpio, (file, e) -> System.out.printf("Unable to archive path: %s, %s%n", file, e.getMessage())); 51 | } 52 | System.out.println("done"); 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgNetSocketTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import j8spec.annotation.DefinedOrder; 4 | import j8spec.junit.J8SpecRunner; 5 | import org.junit.runner.RunWith; 6 | 7 | import java.io.File; 8 | 9 | import static java.lang.Thread.sleep; 10 | import static j8spec.J8Spec.*; 11 | import static org.junit.Assert.*; 12 | import static io.vacco.ff.net.FgJni.*; 13 | import static io.vacco.ff.util.FgIo.*; 14 | import static io.vacco.ff.FgTest.localTest; 15 | import static io.vacco.ff.service.FgFirecracker.fcMachineConfigOf; 16 | 17 | @DefinedOrder 18 | @RunWith(J8SpecRunner.class) 19 | public class FgNetSocketTest { 20 | static { 21 | it("Forks a detached Linux process", () -> { 22 | int pid = fork("BEE", "/usr/bin/env", new String[] { }, "./build/bee.log"); 23 | System.out.println("PID: " + pid); 24 | }); 25 | it("Finds a process by environment variable", () -> { 26 | int pid = pidOf("./src/test/resources/proc", "1984"); 27 | System.out.println("PID: " + pid); 28 | }); 29 | it("Sends an API request to a Firecracker UNIX socket", localTest(() -> { 30 | var fcSock = new File("./build/fctest.sock"); 31 | var args = new String[] { "--api-sock", fcSock.getAbsolutePath() }; 32 | if (fcSock.exists()) { 33 | delete(fcSock, e -> { throw new IllegalStateException(e); }); 34 | } 35 | int pid = fork("fcTest", "/usr/local/bin/firecracker", args, "./build/fcTest.log"); 36 | sleep(2000); 37 | var cfg = fcMachineConfigOf(fcSock); 38 | System.out.println("VM status: " + cfg.body); 39 | assertEquals(200, cfg.statusCode); 40 | terminate(pid); 41 | })); 42 | it("Sends a raw message to a VSOCK socket", localTest(() -> { 43 | var args = new String[] { "vsock-listen:1234,fork", "EXEC:'/bin/cat'" }; 44 | var pid = fork("vSockTest", "/usr/bin/socat", args, "./build/vSockTest.log"); 45 | sleep(2000); 46 | var buff = new byte[64]; 47 | var sock = vSocketOpen("2:1234"); 48 | vSockSend(sock, "Hello vSocket".getBytes()); 49 | vSockReceive(sock, buff); 50 | vSocketClose(sock); 51 | var data = new String(buff).trim(); 52 | System.out.println(data); 53 | terminate(pid); 54 | })); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgNetTapTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import j8spec.annotation.DefinedOrder; 4 | import j8spec.junit.J8SpecRunner; 5 | import org.junit.runner.RunWith; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import static io.vacco.ff.service.FgVmSvcDhcp.vmDhcpConfigure; 10 | import static io.vacco.ff.net.FgDhcpRequests.*; 11 | import static io.vacco.ff.net.FgJni.*; 12 | import static io.vacco.ff.net.FgNetIo.*; 13 | import static io.vacco.ff.FgTest.*; 14 | 15 | import static j8spec.J8Spec.*; 16 | import static org.junit.Assert.*; 17 | 18 | @DefinedOrder 19 | @RunWith(J8SpecRunner.class) 20 | public class FgNetTapTest { 21 | 22 | static { initLog(); } 23 | 24 | private static final Logger log = LoggerFactory.getLogger(FgNetTapTest.class); 25 | public static final String br0 = "br0"; 26 | 27 | static { 28 | it("Creates and deletes a tap device", localTest(() -> { 29 | var tap03 = "tap03"; 30 | var res0 = tapCreate(tap03); 31 | assertTrue(res0 > 0); 32 | var res1 = tapDelete(tap03); 33 | assertEquals(0, res1); 34 | })); 35 | it("Creates a tap device, attaches to a bridge, detaches from it, and deletes the tap device", localTest(() -> { 36 | var tap04 = "tap04"; 37 | var res0 = tapCreate(tap04); 38 | assertTrue(res0 > 0); 39 | var res1 = tapAttach(tap04, br0); 40 | assertEquals(0, res1); 41 | var res2 = tapDetach(tap04, br0); 42 | assertEquals(0, res2); 43 | var res3 = tapDelete(tap04); 44 | assertEquals(0, res3); 45 | })); 46 | it("Requests a dhcp lease for a mac address, then renews, then releases", localTest(() -> { 47 | var vmMac = newMacAddress(); 48 | var vmMacStr = macToString(vmMac); 49 | log.info(">> Discover + Request"); 50 | var lease0 = vmDhcpConfigure(br0, vmMacStr, null); 51 | log.info(">> Awaiting active time, then renew"); 52 | var lease1 = vmDhcpConfigure(br0, vmMacStr, lease0); 53 | log.info(">> Release"); 54 | assertNotNull(lease1); 55 | withPromiscIf(br0, () -> withRawSocket(br0, sock -> { 56 | dhcpRelease(sock, vmMac, lease1); 57 | return null; 58 | })); 59 | })); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgTarIoTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import io.vacco.ff.initramfs.FgTarIo; 4 | import j8spec.annotation.DefinedOrder; 5 | import j8spec.junit.J8SpecRunner; 6 | import org.junit.runner.RunWith; 7 | import java.io.File; 8 | import java.util.TreeSet; 9 | 10 | import static j8spec.J8Spec.*; 11 | 12 | @DefinedOrder 13 | @RunWith(J8SpecRunner.class) 14 | public class FgTarIoTest { 15 | static { 16 | it("Extracts tar data", () -> { 17 | var tarFile = new File("./src/test/resources/blob.tar"); 18 | var outDir = new File("./build/untar"); 19 | var entries = FgTarIo.extract(tarFile, outDir, (entry, err) -> { 20 | System.out.printf("Unable to extract entry %s - %s%n", entry, err.getMessage()); 21 | }); 22 | var entrySet = new TreeSet<>(entries); 23 | for (var e : entrySet) { 24 | System.out.println(e.toString()); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ff-test/src/test/java/io/vacco/ff/FgTest.java: -------------------------------------------------------------------------------- 1 | package io.vacco.ff; 2 | 3 | import io.vacco.shax.logging.ShOption; 4 | import j8spec.UnsafeBlock; 5 | import java.awt.*; 6 | 7 | public class FgTest { 8 | 9 | public static void initLog() { 10 | ShOption.setSysProp(ShOption.IO_VACCO_SHAX_DEVMODE, "true"); 11 | ShOption.setSysProp(ShOption.IO_VACCO_SHAX_LOGLEVEL, "debug"); 12 | } 13 | 14 | public static UnsafeBlock localTest(UnsafeBlock test) { 15 | if (!GraphicsEnvironment.isHeadless()) { 16 | return test; 17 | } 18 | return () -> System.out.println("CI/CD, nothing to do"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /ff-test/src/test/resources/blob.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaccovecrana/frag-falcon/abcd3955daaeb5adb30f25191ed03e4bdef01b09/ff-test/src/test/resources/blob.tar -------------------------------------------------------------------------------- /ff-test/src/test/resources/disk.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaccovecrana/frag-falcon/abcd3955daaeb5adb30f25191ed03e4bdef01b09/ff-test/src/test/resources/disk.img -------------------------------------------------------------------------------- /ff-test/src/test/resources/kernel/extract-vmlinux: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # ---------------------------------------------------------------------- 4 | # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 | # 6 | # Inspired from extract-ikconfig 7 | # (c) 2009,2010 Dick Streefland 8 | # 9 | # (c) 2011 Corentin Chary 10 | # 11 | # ---------------------------------------------------------------------- 12 | 13 | check_vmlinux() 14 | { 15 | # Use readelf to check if it's a valid ELF 16 | # TODO: find a better to way to check that it's really vmlinux 17 | # and not just an elf 18 | readelf -h $1 > /dev/null 2>&1 || return 1 19 | 20 | cat $1 21 | exit 0 22 | } 23 | 24 | try_decompress() 25 | { 26 | # The obscure use of the "tr" filter is to work around older versions of 27 | # "grep" that report the byte offset of the line instead of the pattern. 28 | 29 | # Try to find the header ($1) and decompress from here 30 | for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 31 | do 32 | pos=${pos%%:*} 33 | tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 34 | check_vmlinux $tmp 35 | done 36 | } 37 | 38 | # Check invocation: 39 | me=${0##*/} 40 | img=$1 41 | if [ $# -ne 1 -o ! -s "$img" ] 42 | then 43 | echo "Usage: $me " >&2 44 | exit 2 45 | fi 46 | 47 | # Prepare temp files: 48 | tmp=$(mktemp /tmp/vmlinux-XXX) 49 | trap "rm -f $tmp" 0 50 | 51 | # That didn't work, so retry after decompression. 52 | try_decompress '\037\213\010' xy gunzip 53 | try_decompress '\3757zXZ\000' abcde unxz 54 | try_decompress 'BZh' xy bunzip2 55 | try_decompress '\135\0\0\0' xxx unlzma 56 | try_decompress '\211\114\132' xy 'lzop -d' 57 | try_decompress '\002!L\030' xxx 'lz4 -d' 58 | try_decompress '(\265/\375' xxx unzstd 59 | 60 | # Finally check for uncompressed images or objects: 61 | check_vmlinux $img 62 | 63 | # Bail out: 64 | echo "$me: Cannot find vmlinux." >&2 65 | -------------------------------------------------------------------------------- /ff-test/src/test/resources/kernel/vmlinux-6.1.98: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaccovecrana/frag-falcon/abcd3955daaeb5adb30f25191ed03e4bdef01b09/ff-test/src/test/resources/kernel/vmlinux-6.1.98 -------------------------------------------------------------------------------- /ff-test/src/test/resources/proc/1982/environ: -------------------------------------------------------------------------------- 1 | PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/home/jjzazuet/.local/share/flatpak/exports/bin:/var/lib/flatpak/exports/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perlINVOCATION_ID=8098f4c6eba94d9b9340b824836bf306XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.WHBHR2MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=GDMSESSION=gnomeXDG_DATA_DIRS=/home/jjzazuet/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share/:/usr/share/DOCKER_HOST=unix:///run/user/1000/podman/podman.sockMEMORY_PRESSURE_WATCH=/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/session.slice/org.gnome.Shell@wayland.service/memory.pressureMOTD_SHOWN=pamTERM=xterm-256colorDBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/busLANG=en_US.UTF-8XDG_SESSION_TYPE=waylandXDG_ACTIVATION_TOKEN=42f75b4e-a26e-40c2-8ead-ac7cf1499526__INTELLIJ_COMMAND_HISTFILE__=/home/jjzazuet/.cache/JetBrains/IdeaIC2024.1/terminal/history/frag-falcon-historyXDG_CURRENT_DESKTOP=GNOMEJOURNAL_STREAM=8:4460DISPLAY=:0MAIL=/var/spool/mail/jjzazuetWAYLAND_DISPLAY=wayland-0USERNAME=jjzazuetSESSION_MANAGER=local/echoes:@/tmp/.ICE-unix/1796,unix/echoes:/tmp/.ICE-unix/1796LOGNAME=jjzazuetMANAGERPID=1697PWD=/home/jjzazuetXDG_SESSION_CLASS=userGJS_DEBUG_TOPICS=JS ERROR;JS LOGGDM_LANG=en_US.UTF-8SHELL=/bin/bashGIO_LAUNCHED_DESKTOP_FILE=/home/jjzazuet/.local/share/applications/jetbrains-idea-ce.desktopDESKTOP_SESSION=gnomeUSER=jjzazuetFF_VMID=1984XDG_MENU_PREFIX=gnome-GIO_LAUNCHED_DESKTOP_FILE_PID=8002TERMINAL_EMULATOR=JetBrains-JediTermGJS_DEBUG_OUTPUT=stderrSSH_AUTH_SOCK=/run/user/1000/gcr/sshTERM_SESSION_ID=673e499c-76aa-48b8-9fb3-1f554823baf0DEBUGINFOD_URLS=https://debuginfod.archlinux.org SYSTEMD_EXEC_PID=1822XDG_RUNTIME_DIR=/run/user/1000GNOME_SETUP_DISPLAY=:1XDG_SESSION_DESKTOP=gnomeSHLVL=0HOME=/home/jjzazuet -------------------------------------------------------------------------------- /ff-ui/@ff/components/FgLock.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "preact/compat" 2 | import {RenderableProps} from "preact" 3 | import { useContext } from "preact/hooks" 4 | import { FgContext, usrErrorClear, usrMsgClear } from "@ff/store" 5 | 6 | const FgLock = (props: RenderableProps<{}>) => { 7 | const {dispatch: d, state} = useContext(FgContext) 8 | if (state && state.lastMessage) { 9 | alert(JSON.stringify(state.lastMessage, null, 2)) 10 | usrMsgClear(d) 11 | } 12 | if (state && state.lastError) { 13 | console.log(state.lastError) 14 | usrErrorClear(d) 15 | } 16 | return ( // TODO add FgToast error message display 17 |
18 | {props.children} 19 | {state && state.uiLocked ?
: []} 20 |
21 | ) 22 | } 23 | 24 | export default FgLock 25 | -------------------------------------------------------------------------------- /ff-ui/@ff/components/FgLogViewer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "preact/compat"; 2 | import { useEffect, useRef } from "preact/hooks"; 3 | import { JSX } from "preact"; 4 | 5 | const stripAnsiColors = (input: string): string => { 6 | const ansiRegex = /\x1B\[[0-9;]*m/g; 7 | return input.replace(ansiRegex, ''); 8 | } 9 | 10 | type FgLogViewerProps = { 11 | logData: string; 12 | }; 13 | 14 | const FgLogViewer = ({ logData }: FgLogViewerProps): JSX.Element => { 15 | const textareaRef = useRef(null); 16 | 17 | useEffect(() => { 18 | const textarea = textareaRef.current; 19 | if (textarea) { 20 | textarea.scrollTop = textarea.scrollHeight; 21 | } 22 | }, [logData]); 23 | 24 | return ( 25 |