Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 57 additions & 22 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ on:

env:
GO_VERSION: '1.26'
# Pins every job's regeneration of internal/plumbing/ebpf/prog's bpf2go
# output (usid_bpfel/eb.go and their embedded .o's; not committed, see
# that package's doc.go) to the same explicit clang version, rather than
# each job/runner resolving its own unversioned "clang" off PATH.
# clang-18/llvm-18 is the noble/ubuntu-latest default at the time this
# was written -- reconfirm against the runner image if installation
# starts failing.
BPF2GO_CC: clang-18

jobs:
# Tier 1: Fast checks (every PR)
Expand All @@ -26,6 +34,14 @@ jobs:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install eBPF build dependencies
# `task lint` type-checks the whole module, which needs
# internal/plumbing/ebpf/prog's bpf2go output regenerated first
# (see build job's identical step below for the package pins).
run: |
sudo apt-get update
sudo apt-get install -y clang-18 llvm-18 linux-libc-dev

- name: Run lint
run: task lint

Expand All @@ -44,6 +60,14 @@ jobs:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install eBPF build dependencies
# `task test:unit` builds internal/plumbing/ebpf/prog, which needs
# its bpf2go output regenerated first (see build job's identical
# step below for the package pins).
run: |
sudo apt-get update
sudo apt-get install -y clang-18 llvm-18 linux-libc-dev

- name: Run unit tests
run: task test:unit

Expand Down Expand Up @@ -109,6 +133,18 @@ jobs:
sudo apt-get install -y --no-install-recommends "linux-modules-extra-$(uname -r)"
sudo modprobe vrf

- name: Install eBPF build dependencies
# `task test:unit-root` builds internal/plumbing/ebpf/prog, which
# needs its bpf2go output regenerated first (see build job's
# identical step below for the package pins). This runs as the
# unprivileged runner user -- it's the regeneration below, run as
# root via `sudo -E task test:unit-root`, that needs clang on
# root's PATH, which -E's preserved PATH (asserted explicitly via
# `env "PATH=$PATH"`) already covers.
run: |
sudo apt-get update
sudo apt-get install -y clang-18 llvm-18 linux-libc-dev

- name: Run root-gated unit tests as root
# sudo's secure_path policy overrides -E's preserved PATH for
# exactly the PATH variable, which would hide the go/task binaries
Expand All @@ -134,13 +170,12 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install eBPF build dependencies
# Pinned to an explicit versioned package (clang-18/llvm-18, the
# noble/ubuntu-latest default at the time this was written --
# reconfirm against the runner image if this starts failing) rather
# than the unversioned clang/llvm meta-packages: BPF object bytes
# (BTF, debug info, section layout) move with the compiler, so an
# unpinned install would let a future runner-image clang bump flip
# the drift check below red on PRs that never touched usid.c.
# Pinned to an explicit versioned package (clang-18/llvm-18) rather
# than the unversioned clang/llvm meta-packages, matching the
# workflow-level BPF2GO_CC pin above -- keeps the BPF object bytes
# (BTF, debug info, section layout) produced by this job identical
# to every other job's regeneration instead of drifting with
# whatever clang a future runner-image bump resolves to.
# linux-libc-dev is listed explicitly too -- doc.go's -idirafter
# workaround exists specifically to find its headers, so this job
# shouldn't rely on it merely happening to be preinstalled.
Expand All @@ -151,22 +186,11 @@ jobs:
- name: Build binary
# task build's build:ebpf step regenerates
# internal/plumbing/ebpf/prog's bpf2go output (usid_bpfel/eb.go
# and their embedded .o's) from usid.c as a side effect, before
# build:binaries ever runs. The check below diffs the regenerated
# artifacts against the committed ones: the .o's `go test` and
# go.datum.net/galactic/internal/plumbing/ebpf/prog itself load are
# otherwise just checked-in blobs that can drift silently out of
# sync with usid.c, since nothing else forces them to be
# regenerated on a source change. BPF2GO_CC pins this regeneration
# to the exact clang installed above (see doc.go's go:generate
# directive, which omits -cc for exactly this reason) instead of
# whatever unversioned "clang" resolves to.
# and their embedded .o's) from usid.c before build:binaries ever
# runs -- these are gitignored (see that package's doc.go), so
# there's nothing checked in for this to drift against; it's
# produced fresh on every build, here exactly as everywhere else.
run: task build
env:
BPF2GO_CC: clang-18

- name: Verify committed eBPF artifacts match usid.c
run: git diff --exit-code -- internal/plumbing/ebpf/prog

# Tier 2: Integration tests (every PR, main branch, and releases)
test-e2e:
Expand All @@ -185,5 +209,16 @@ jobs:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install eBPF build dependencies
# `task test:e2e` compiles ./tests/e2e/... on the host (the
# galactic-cni image it builds separately regenerates its own copy
# inside containers/galactic-cni/Dockerfile's builder stage), which
# needs internal/plumbing/ebpf/prog's bpf2go output regenerated
# first -- see build job's identical step above for the package
# pins.
run: |
sudo apt-get update
sudo apt-get install -y clang-18 llvm-18 linux-libc-dev

- name: Run E2E tests
run: task test:e2e
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ go.work.sum
ko-local/
*.img

# eBPF (bpf2go) generated artifacts -- regenerated from usid.c by `task
# build:ebpf` (requires clang; see internal/plumbing/ebpf/prog/doc.go).
# Unlike other generated code (e.g. *.pb.go), these embed a compiled
# binary blob and are deliberately NOT committed, so the bytes shipped
# never drift from usid.c without anyone noticing a stale commit.
internal/plumbing/ebpf/prog/usid_bpfel.go
internal/plumbing/ebpf/prog/usid_bpfel.o
internal/plumbing/ebpf/prog/usid_bpfeb.go
internal/plumbing/ebpf/prog/usid_bpfeb.o

# ============================================================
# Kubernetes / controller-runtime / kubebuilder
# ============================================================
Expand Down
46 changes: 30 additions & 16 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ tasks:

lint:
desc: Lint
deps: [golangci-lint, yamlfmt, yaml-ext]
deps: [golangci-lint, yamlfmt, yaml-ext, build:ebpf]
cmds:
- '{{.GOLANGCI_LINT}} run'
- '{{.YAMLFMT}} -lint'

lint-fix:
desc: Lint and apply fixes
deps: [golangci-lint, yamlfmt]
deps: [golangci-lint, yamlfmt, build:ebpf]
cmds:
- '{{.GOLANGCI_LINT}} run --fix'
- '{{.YAMLFMT}}'
Expand All @@ -76,30 +76,38 @@ tasks:

build:
desc: Build binaries
deps: [fmt, vet]
cmds:
# build:ebpf must run before fmt/vet, not just before build:binaries:
# internal/plumbing/ebpf/prog's usid_bpfel.o/usid_bpfeb.o (go:embed'd)
# aren't committed (see .gitignore), so `go vet ./...` -- and anything
# else that type-checks the whole module -- fails to compile that
# package on a fresh checkout until this has generated them.
- task: build:ebpf
- task: fmt
- task: vet
- task: build:binaries

build:ebpf:
desc: >-
Regenerate the eBPF uSID datapath (skips, using the committed
artifacts, if clang isn't installed)
desc: Regenerate the eBPF uSID datapath from usid.c (requires clang)
run: once
deps: [require-clang]
cmds:
- go generate ./internal/plumbing/ebpf/prog/...

require-clang:
internal: true
run: once
cmds:
- |
if command -v clang >/dev/null 2>&1; then
go generate ./internal/plumbing/ebpf/prog/...
else
echo "WARNING: clang not found; skipping regeneration of the eBPF" >&2
echo "uSID datapath (internal/plumbing/ebpf/prog/usid.c, via bpf2go)" >&2
echo "and building against the committed usid_bpfel.o/usid_bpfeb.o" >&2
echo "as-is. If you edited usid.c, install clang and re-run 'task" >&2
echo "build:ebpf' (or 'task build') before committing -- CI's build" >&2
echo "job regenerates and diffs unconditionally, so a stale commit" >&2
echo "will fail there even if it builds fine here. Install, e.g.:" >&2
if ! command -v "${BPF2GO_CC:-clang}" >/dev/null 2>&1; then
echo "ERROR: ${BPF2GO_CC:-clang} not found on PATH." >&2
echo "internal/plumbing/ebpf/prog/usid_bpfel.o/usid_bpfeb.o are" >&2
echo "generated from usid.c via bpf2go, not committed to git (see" >&2
echo "internal/plumbing/ebpf/prog/doc.go) -- clang is required to" >&2
echo "build or test this module at all. Install, e.g.:" >&2
echo " Fedora/RHEL: sudo dnf install clang llvm" >&2
echo " Debian/Ubuntu: sudo apt install clang llvm" >&2
exit 1
fi

build:binaries:
Expand Down Expand Up @@ -135,16 +143,22 @@ tasks:

test:unit:
desc: Unit tests with race detection and coverage
deps: [build:ebpf]
cmds:
- bash scripts/ci.sh unittest

test:unit-root:
desc: Re-run just the requireRoot(t)-gated unit tests, as root
deps: [build:ebpf]
cmds:
- bash scripts/ci.sh unittest-root

test:e2e:
desc: E2E tests (Kind cluster, build+load image, lifecycle)
# build:ebpf here is only for the host-side `go test ./tests/e2e/...`
# compile in scripts/ci.sh -- the galactic-cni image built inside
# ci.sh's docker build regenerates its own copy via the Dockerfile.
deps: [build:ebpf]
cmds:
- bash scripts/ci.sh e2etest

Expand Down
23 changes: 14 additions & 9 deletions containers/galactic-cni/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@ RUN go mod download
COPY cmd/ cmd/
COPY internal/ internal/

# No eBPF toolchain needed here: internal/plumbing/ebpf/prog's committed
# usid_bpfel.o/usid_bpfeb.o (go:embed'd into the galactic-cni binary below)
# ship as-is from the COPY above, rather than being regenerated from
# usid.c by this build -- so the object embedded in the image is bit-for-
# bit the one committed to git, reviewed in the PR, and verified against
# usid.c by CI's drift check (.github/workflows/ci.yaml's `build` job),
# not a separate build produced by this builder image's own clang. design
# plan .local/plan-ebpf-xdp-usid-datapath.md §6; Milestone 5.2 of
# .local/implementation-plan-ebpf-xdp-usid-datapath.md.
# Install the eBPF build toolchain and regenerate
# internal/plumbing/ebpf/prog's bpf2go output (usid_bpfel.go/.o,
# usid_bpfeb.go/.o -- go:embed'd into the galactic-cni binary below) from
# usid.c. These aren't committed to git (see that package's doc.go), so
# every build site produces them fresh instead of embedding a copy
# checked into the repo; this builder stage is no exception. Package
# names are the generic Debian ones (this base image, unlike the CI
# runner's Ubuntu, doesn't have a clang-18 package available) -- byte-
# identical output across build sites isn't the goal here, a working BPF
# program compiled from usid.c is.
RUN apt-get update && apt-get install -y --no-install-recommends \
clang llvm linux-libc-dev \
&& rm -rf /var/lib/apt/lists/*
RUN go generate ./internal/plumbing/ebpf/prog/...

# Build CNI plugin
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build \
Expand Down
9 changes: 9 additions & 0 deletions containers/galactic-router/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ RUN go mod download
COPY cmd/ cmd/
COPY internal/ internal/

# galactic-router transitively imports internal/plumbing/ebpf/prog (via
# usidmap), whose bpf2go output (usid_bpfel.go/.o, usid_bpfeb.go/.o) isn't
# committed to git (see that package's doc.go) -- regenerate it from
# usid.c here, same as containers/galactic-cni/Dockerfile.
RUN apt-get update && apt-get install -y --no-install-recommends \
clang llvm linux-libc-dev \
&& rm -rf /var/lib/apt/lists/*
RUN go generate ./internal/plumbing/ebpf/prog/...

# Build the router
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build \
-ldflags "-s -w \
Expand Down
27 changes: 18 additions & 9 deletions internal/plumbing/ebpf/prog/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@
// into a CO-RE-portable BPF object and generates matching Go bindings
// (UsidObjects, LoadUsid, LoadUsidObjects, plus per-map/per-program
// fields) in this package -- run `go generate ./...` from the repo root,
// or `go generate` from this directory, after editing usid.c. The
// generated *_bpfel.go/*_bpfel.o (and *_bpfeb.go/*_bpfeb.o) files are
// committed alongside the source they're generated from, matching this
// repo's convention for other generated code (see CLAUDE.md: "Generated
// protobuf files ... are committed; never hand-edit them" -- the same
// rule applies here to bpf2go's output).
// or `go generate` from this directory, after editing usid.c.
//
// Unlike this repo's other generated code (e.g. *.pb.go, committed
// per CLAUDE.md: "Generated protobuf files ... are committed; never
// hand-edit them"), the generated *_bpfel.go/*_bpfel.o and
// *_bpfeb.go/*_bpfeb.o files are gitignored, not committed: they embed a
// compiled binary blob rather than plain Go source, and committing a
// compiled artifact risks it silently drifting out of sync with usid.c
// with nothing to catch the mismatch short of a byte-diff. Instead, every
// build site regenerates them fresh via `task build:ebpf` -- a hard
// dependency of `task build`/`lint`/`test:unit`/`test:unit-root`/`test:e2e`
// (see Taskfile.yaml), and of containers/galactic-cni/Dockerfile and
// containers/galactic-router/Dockerfile's builder stages -- so clang must
// be on PATH (or $BPF2GO_CC) to build or test this module at all; there is
// no fallback to a checked-in copy.
//
// Placement: sibling of internal/plumbing/ebpf/uformat (Milestone 2.1)
// under the shared internal/plumbing/ebpf/ umbrella -- uformat is the
Expand Down Expand Up @@ -54,8 +63,8 @@ package prog
//
// -cc is deliberately omitted: bpf2go's own default is
// getEnv("BPF2GO_CC", "clang"), so CI can pin an exact clang version by
// setting $BPF2GO_CC (see the `build` job's drift-check step) without
// this directive needing to change, while everyone else keeps getting
// plain "clang" off their PATH.
// setting $BPF2GO_CC (see .github/workflows/ci.yaml) without this
// directive needing to change, while everyone else keeps getting plain
// "clang" off their PATH.
//
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-O2 -g -Wall -idirafter /usr/include/x86_64-linux-gnu -idirafter /usr/include/aarch64-linux-gnu" -target bpfel,bpfeb -type locator_value -type function_value -type vrf_value Usid usid.c
Loading