Skip to content

Commit d0989da

Browse files
committed
feat: add Go HTTP server example
Minimal Go net/http server running on Hyperlight with host-proxied async networking via hostsock. Includes Dockerfile for cross-platform rootfs builds. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent 4579151 commit d0989da

5 files changed

Lines changed: 218 additions & 0 deletions

File tree

examples/go-http/Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
FROM golang:1.21.3-bookworm AS build
2+
3+
WORKDIR /src
4+
5+
COPY ./main.go /src/main.go
6+
7+
RUN set -xe; \
8+
CGO_ENABLED=0 \
9+
GOARCH=amd64 \
10+
go build \
11+
-buildmode=pie \
12+
-ldflags="-linkmode=internal" \
13+
-o /server main.go \
14+
;
15+
16+
# Strip PT_INTERP from the static PIE binary: Go's internal linker adds
17+
# /lib64/ld-linux-x86-64.so.2 as interpreter for PIE on Linux, but the
18+
# binary is fully self-contained and does not need a dynamic linker.
19+
# Unikraft's elfloader fails when the interpreter is listed but absent.
20+
# Patch the program header: change PT_INTERP (type=3) -> PT_NULL (type=0).
21+
COPY patch_interp.py /patch_interp.py
22+
RUN python3 /patch_interp.py /server
23+
24+
FROM scratch AS rootfs
25+
26+
COPY --from=build /server /bin/server
27+
28+
# --- CPIO rootfs builder (used by: docker build --target cpio) ---
29+
FROM alpine:3.20 AS cpio
30+
RUN apk add --no-cache cpio findutils
31+
COPY --from=rootfs / /rootfs/
32+
RUN cd /rootfs && find . | cpio -o -H newc > /output.cpio 2>/dev/null

examples/go-http/Justfile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# go-http on Hyperlight
2+
#
3+
# just run - Run the HTTP server (--port implies --net)
4+
# just rootfs - Build rootfs via Docker (cross-platform)
5+
# just build - Build/pull kernel
6+
# just clean - Remove build artifacts
7+
8+
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
9+
export DOCKER_BUILDKIT := "0"
10+
11+
kernel := ".unikraft/build/go-http-hyperlight_hyperlight-x86_64"
12+
initrd := "initrd.cpio"
13+
memory := "32Mi"
14+
image := "go-http-hyperlight"
15+
16+
# Run the HTTP server (curl http://localhost:8080 to test)
17+
run:
18+
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --port 8080 -- /bin/server
19+
20+
# Build rootfs via Docker (cross-platform)
21+
rootfs:
22+
docker build --platform linux/amd64 --target cpio -t {{image}}-cpio .
23+
- docker rm -f {{image}}-tmp
24+
docker create --name {{image}}-tmp {{image}}-cpio /bin/true
25+
docker cp {{image}}-tmp:/output.cpio ./{{initrd}}
26+
docker rm -f {{image}}-tmp
27+
28+
# Build kernel via kraft (Linux)
29+
[unix]
30+
build:
31+
-kraft-hyperlight build --plat hyperlight --arch x86_64
32+
33+
# Pull pre-built kernel from GHCR (Windows — no kernel published yet)
34+
[windows]
35+
build:
36+
Write-Host "No pre-built kernel for go-http yet. Build on Linux first."
37+
38+
# Clean + rebuild everything
39+
rebuild: clean build rootfs
40+
41+
# Clean build artifacts
42+
[unix]
43+
clean:
44+
rm -rf .unikraft {{initrd}}
45+
46+
[windows]
47+
clean:
48+
if (Test-Path .unikraft) { Remove-Item -Recurse -Force .unikraft }
49+
if (Test-Path {{initrd}}) { Remove-Item -Force {{initrd}} }

examples/go-http/kraft.yaml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
specification: '0.6'
2+
name: go-http-hyperlight
3+
4+
unikraft:
5+
source: https://github.com/unikraft/unikraft.git
6+
version: plat-hyperlight
7+
kconfig:
8+
# Platform
9+
CONFIG_PLAT_HYPERLIGHT: 'y'
10+
CONFIG_PAGING: 'n'
11+
CONFIG_LIBUKVMEM: 'n'
12+
CONFIG_LIBUKINTCTLR_HYPERLIGHT: 'y'
13+
CONFIG_HYPERLIGHT_MAX_GUEST_LOG_LEVEL: 0
14+
15+
# Suppress kernel logging for performance
16+
CONFIG_LIBUKPRINT_KLVL_CRIT: 'y'
17+
CONFIG_LIBUKPRINT_PRINT_TIME: 'n'
18+
CONFIG_LIBUKPRINT_PRINT_SRCNAME: 'n'
19+
CONFIG_LIBUKBOOT_BANNER_NONE: 'y'
20+
21+
# Size optimization
22+
CONFIG_OPTIMIZE_SIZE: 'y'
23+
CONFIG_OPTIMIZE_PIE: 'y'
24+
25+
# VFS and initrd support
26+
CONFIG_LIBVFSCORE: 'y'
27+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI: 'y'
28+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI_CUSTOM: 'y'
29+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_MP: '/'
30+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_DRIVER: 'cpiovfs'
31+
CONFIG_LIBVFSCORE_AUTOMOUNT_CI0_UKOPTS_IFINITRD0: 'y'
32+
CONFIG_LIBCPIOVFS: 'y'
33+
CONFIG_LIBUKCPIO: 'y'
34+
35+
# ELF loader - Go binary location
36+
CONFIG_APPELFLOADER: 'y'
37+
CONFIG_APPELFLOADER_VFSEXEC: 'y'
38+
CONFIG_APPELFLOADER_CUSTOMAPPNAME: 'n'
39+
CONFIG_APPELFLOADER_VFSEXEC_ENVPATH: 'n'
40+
CONFIG_APPELFLOADER_VFSEXEC_PATH: '/bin/server'
41+
CONFIG_LIBPOSIX_ENVIRON_ENVP0: '"PATH=/bin"'
42+
CONFIG_LIBPOSIX_ENVIRON_ENVP1: '"GOMEMLIMIT=64MiB"'
43+
CONFIG_LIBPOSIX_ENVIRON_ENVP2: '"GOMAXPROCS=1"'
44+
CONFIG_LIBELF: 'y'
45+
46+
# Random number support
47+
CONFIG_LIBUKRANDOM_CMDLINE_SEED: 'y'
48+
CONFIG_LIBUKRANDOM_GETRANDOM: 'y'
49+
50+
# Threading support
51+
CONFIG_LIBUKBOOT_MAINTHREAD: 'y'
52+
CONFIG_LIBPOSIX_PROCESS_ARCH_PRCTL: 'y'
53+
CONFIG_LIBPOSIX_PROCESS_MULTITHREADING: 'y'
54+
55+
# Signal and futex support (needed for Go runtime)
56+
CONFIG_LIBPOSIX_PROCESS_SIGNAL: 'y'
57+
CONFIG_LIBPOSIX_FUTEX: 'y'
58+
59+
# eventfd (needed by Go's epoll-based netpoller)
60+
CONFIG_LIBPOSIX_EVENTFD: 'y'
61+
62+
# mmap needed by Go runtime (heap reservation + page summaries)
63+
CONFIG_LIBUKMPI: 'y'
64+
CONFIG_LIBUKMMAP: 'y'
65+
66+
# devfs + /dev/hcall (required for host-proxied networking)
67+
CONFIG_LIBDEVFS: 'y'
68+
CONFIG_LIBDEVFS_AUTOMOUNT: 'y'
69+
CONFIG_HYPERLIGHT_HCALL: 'y'
70+
71+
# Networking: host-proxied sockets
72+
CONFIG_LIBPOSIX_SOCKET: 'y'
73+
CONFIG_LIBHOSTSOCK: 'y'
74+
75+
# FD infrastructure (required by posix-socket)
76+
CONFIG_LIBPOSIX_FDIO: 'y'
77+
CONFIG_LIBPOSIX_FDTAB: 'y'
78+
CONFIG_LIBUKFILE: 'y'
79+
80+
libraries:
81+
app-elfloader:
82+
source: https://github.com/unikraft/app-elfloader.git
83+
version: plat-hyperlight
84+
libelf:
85+
source: https://github.com/unikraft/lib-libelf.git
86+
version: staging
87+
88+
targets:
89+
- architecture: x86_64
90+
platform: hyperlight

examples/go-http/main.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"net/http"
7+
)
8+
9+
func helloHandler(w http.ResponseWriter, r *http.Request) {
10+
fmt.Fprint(w, "Hello from Hyperlight-Unikraft!")
11+
}
12+
13+
func main() {
14+
mux := http.NewServeMux()
15+
mux.HandleFunc("/", helloHandler)
16+
17+
fmt.Println("Server is running on http://localhost:8080")
18+
log.Fatal(http.ListenAndServe(":8080", mux))
19+
}

examples/go-http/patch_interp.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python3
2+
"""Strip PT_INTERP from a static PIE binary.
3+
4+
Go's internal linker adds /lib64/ld-linux-x86-64.so.2 as PT_INTERP for PIE
5+
binaries on Linux, but a CGO_ENABLED=0 binary is fully self-contained and
6+
does not need a dynamic linker. Patch PT_INTERP -> PT_NULL so elfloaders
7+
that reject missing interpreters will load the binary directly.
8+
"""
9+
import struct
10+
import sys
11+
12+
path = sys.argv[1]
13+
with open(path, "rb") as f:
14+
data = bytearray(f.read())
15+
16+
e_phoff, = struct.unpack_from("<Q", data, 32)
17+
e_phentsize, = struct.unpack_from("<H", data, 54)
18+
e_phnum, = struct.unpack_from("<H", data, 56)
19+
20+
for i in range(e_phnum):
21+
off = e_phoff + i * e_phentsize
22+
p_type, = struct.unpack_from("<I", data, off)
23+
if p_type == 3: # PT_INTERP
24+
struct.pack_into("<I", data, off, 0) # PT_NULL
25+
print(f"Patched PT_INTERP -> PT_NULL at program header index {i}")
26+
27+
with open(path, "wb") as f:
28+
f.write(data)

0 commit comments

Comments
 (0)