Skip to content

Commit ef48cde

Browse files
committed
feat: use ebpf for redirecting client connections to the emulator
Signed-off-by: Matheus Pimenta <[email protected]>
1 parent b77e468 commit ef48cde

File tree

21 files changed

+352
-63
lines changed

21 files changed

+352
-63
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
**/*_test.go
22
internal/testing/
3+
internal/redirect/redirect_bpf*
4+
ebpf/vmlinux.h

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
timoni/gke-metadata-server/cue.mod/** linguist-vendored
2+
ebpf/vmlinux.h linguist-vendored
23
**/*.yml linguist-detectable=true
34
**/*.yml linguist-language=YAML
45
**/*.yaml linguist-detectable=true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ terraform.*
66
*.tgz
77
Chart.yaml
88
config.cue
9+
internal/redirect/redirect_bpf*

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@
2222

2323
FROM golang:1.23.5-alpine3.21 AS builder
2424

25+
RUN apk add --no-cache clang llvm bpftool libbpf-dev
26+
2527
WORKDIR /app
2628

2729
COPY go.mod go.sum ./
2830
RUN go mod download
2931

3032
COPY ./cmd/ ./cmd/
3133
COPY ./internal/ ./internal/
34+
COPY ./ebpf/ ./ebpf/
35+
36+
RUN bpftool btf dump file /sys/kernel/btf/vmlinux format c > ebpf/vmlinux.h
37+
RUN go generate ./internal/redirect
3238

3339
# CGO_ENABLED=0 to build a statically-linked binary
3440
# -ldflags '-w -s' to strip debugging information for smaller size

Dockerfile.test

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@
2222

2323
FROM golang:1.23.5-alpine3.21
2424

25+
RUN apk add --no-cache clang llvm bpftool libbpf-dev
26+
2527
WORKDIR /app
2628

2729
COPY go.mod go.sum ./
2830
RUN go mod download
2931

3032
COPY ./cmd/ ./cmd/
3133
COPY ./internal/ ./internal/
34+
COPY ./ebpf/ ./ebpf/
35+
36+
RUN bpftool btf dump file /sys/kernel/btf/vmlinux format c > ebpf/vmlinux.h
37+
RUN go generate ./internal/redirect
3238

3339
CMD [ "go", "test", "-v", "./..." ]

Makefile

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ SHELL := /bin/bash
2525
TEST_IMAGE := ghcr.io/matheuscscp/gke-metadata-server/test
2626

2727
.PHONY: dev
28-
dev: tidy dev-cluster build dev-test
28+
dev: tidy gen-ebpf dev-cluster build dev-test
2929

3030
.PHONY: clean
3131
clean:
@@ -42,14 +42,19 @@ tidy:
4242
git status
4343

4444
.PHONY: gen
45-
gen: timoni-gen
45+
gen: gen-timoni
4646

47-
.PHONY: timoni-gen
48-
timoni-gen:
47+
.PHONY: gen-timoni
48+
gen-timoni:
4949
cd timoni/gke-metadata-server; cue get go k8s.io/api/rbac/v1
5050
cd timoni/gke-metadata-server; cue get go k8s.io/api/admissionregistration/v1
5151
cd timoni/gke-metadata-server; cue get go github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1
5252

53+
.PHONY: gen-ebpf
54+
gen-ebpf:
55+
bpftool btf dump file /sys/kernel/btf/vmlinux format c > ebpf/vmlinux.h
56+
go generate ./internal/redirect
57+
5358
.PHONY: dev-cluster
5459
dev-cluster:
5560
kind delete cluster -n gke-metadata-server || true

cmd/server.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ package main
2424

2525
import (
2626
"fmt"
27+
"net/netip"
2728
"os"
28-
"strings"
2929
"time"
3030

3131
"github.com/matheuscscp/gke-metadata-server/internal/googlecredentials"
@@ -35,6 +35,7 @@ import (
3535
watchnode "github.com/matheuscscp/gke-metadata-server/internal/node/watch"
3636
listpods "github.com/matheuscscp/gke-metadata-server/internal/pods/list"
3737
watchpods "github.com/matheuscscp/gke-metadata-server/internal/pods/watch"
38+
"github.com/matheuscscp/gke-metadata-server/internal/redirect"
3839
"github.com/matheuscscp/gke-metadata-server/internal/server"
3940
"github.com/matheuscscp/gke-metadata-server/internal/serviceaccounts"
4041
getserviceaccount "github.com/matheuscscp/gke-metadata-server/internal/serviceaccounts/get"
@@ -48,7 +49,7 @@ import (
4849

4950
func newServerCommand() *cobra.Command {
5051
var (
51-
serverAddr string
52+
serverPort int
5253
webhookAddr string
5354
workloadIdentityProvider string
5455
defaultNodeServiceAccountName string
@@ -77,6 +78,17 @@ func newServerCommand() *cobra.Command {
7778
if nodeName == "" {
7879
return fmt.Errorf("NODE_NAME environment variable must be specified")
7980
}
81+
podIP := os.Getenv("POD_IP")
82+
if podIP == "" {
83+
return fmt.Errorf("POD_IP environment variable must be specified")
84+
}
85+
emulatorIP, err := netip.ParseAddr(podIP)
86+
if err != nil {
87+
return fmt.Errorf("error parsing POD_IP environment variable: %w", err)
88+
}
89+
if !emulatorIP.Is4() {
90+
return fmt.Errorf("POD_IP environment variable must be an IPv4 address")
91+
}
8092
if defaultNodeServiceAccountName == "" {
8193
return fmt.Errorf("--default-node-service-account-name argument must be specified")
8294
}
@@ -104,6 +116,13 @@ func newServerCommand() *cobra.Command {
104116
}
105117
}()
106118

119+
// install ebpf redirect program
120+
redirBPF, err := redirect.LoadAndAttachBPF(emulatorIP, serverPort, logging.Debug())
121+
if err != nil {
122+
return fmt.Errorf("error loading eBPF redirect program: %w", err)
123+
}
124+
defer redirBPF.Close()
125+
107126
// create clients
108127
kubeClient, err := createKubernetesClient(ctx)
109128
if err != nil {
@@ -213,7 +232,7 @@ func newServerCommand() *cobra.Command {
213232
}
214233
s := server.New(ctx, server.ServerOptions{
215234
NodeName: nodeName,
216-
ServerAddr: serverAddr,
235+
ServerPort: serverPort,
217236
Pods: pods,
218237
Node: node,
219238
ServiceAccounts: serviceAccounts,
@@ -226,7 +245,7 @@ func newServerCommand() *cobra.Command {
226245
webhookServer := webhook.New(ctx, webhook.ServerOptions{
227246
ServerAddr: webhookAddr,
228247
InitNetworkImage: webhookInitNetworkImage,
229-
DaemonSetPort: strings.Split(serverAddr, ":")[1],
248+
DaemonSetPort: serverPort,
230249
MetricsRegistry: metricsRegistry,
231250
})
232251

@@ -243,7 +262,7 @@ func newServerCommand() *cobra.Command {
243262
},
244263
}
245264

246-
cmd.Flags().StringVar(&serverAddr, "server-addr", ":8080",
265+
cmd.Flags().IntVar(&serverPort, "server-port", 8080,
247266
"Network address where the metadata server must listen on")
248267
cmd.Flags().StringVar(&webhookAddr, "webhook-addr", ":8081",
249268
"Network address where the webhook server must listen on")

ebpf/redirect.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2025 Matheus Pimenta
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
#include "vmlinux.h"
24+
25+
#include <bpf/bpf_core_read.h>
26+
#include <bpf/bpf_endian.h>
27+
#include <bpf/bpf_helpers.h>
28+
#include <bpf/bpf_tracing.h>
29+
#include <bpf/bpf_helpers.h>
30+
31+
32+
#define AF_INET 2 // IPv4 family
33+
34+
struct Config {
35+
__u32 emulator_ip;
36+
__u16 emulator_port;
37+
__u16 debug;
38+
};
39+
40+
struct {
41+
__uint(type, BPF_MAP_TYPE_ARRAY);
42+
__uint(max_entries, 1);
43+
__type(key, __u32);
44+
__type(value, struct Config);
45+
} map_config SEC(".maps");
46+
47+
48+
// Hooks to connect() syscalls. Redirects connections targeting
49+
// the GKE metadata server to the emulator.
50+
SEC("cgroup/connect4")
51+
int redirect_connect4(struct bpf_sock_addr *ctx) {
52+
// Only forward IPv4 TCP connections
53+
if (ctx->user_family != AF_INET) {
54+
return 1;
55+
}
56+
if (ctx->protocol != IPPROTO_TCP) {
57+
return 1;
58+
}
59+
60+
const __u32 dst = bpf_ntohl(ctx->user_ip4);
61+
const __u32 dst_port = bpf_ntohs(ctx->user_port);
62+
63+
// 0xA9FEA9FE is 169.254.169.254, the GKE Metadata Server IP address
64+
if (dst != 0xA9FEA9FE || dst_port != 80) {
65+
return 1;
66+
}
67+
68+
// Fetch emulator configuration
69+
const __u32 key = 0;
70+
struct Config *conf = bpf_map_lookup_elem(&map_config, &key);
71+
if (!conf) {
72+
bpf_printk("Error: redirect_connect4 called without configuration");
73+
return 1;
74+
}
75+
76+
// Redirect the connection to the emulator
77+
ctx->user_ip4 = bpf_htonl(conf->emulator_ip);
78+
ctx->user_port = bpf_htons(conf->emulator_port);
79+
80+
if (conf->debug) {
81+
const __u32 emu = ctx->user_ip4;
82+
bpf_printk("Redirecting connection to %pI4:%d", &emu, conf->emulator_port);
83+
}
84+
85+
return 1;
86+
}
87+
88+
char __LICENSE[] SEC("license") = "GPL";

go.mod

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
cloud.google.com/go/compute/metadata v0.6.0
77
cloud.google.com/go/storage v1.50.0
88
github.com/cert-manager/cert-manager v1.16.3
9+
github.com/cilium/ebpf v0.17.2
910
github.com/coreos/go-oidc/v3 v3.12.0
1011
github.com/go-logr/logr v1.4.2
1112
github.com/golang-jwt/jwt/v5 v5.2.1
@@ -15,7 +16,7 @@ require (
1516
github.com/spf13/cobra v1.8.1
1617
github.com/stretchr/testify v1.10.0
1718
golang.org/x/oauth2 v0.25.0
18-
google.golang.org/api v0.218.0
19+
google.golang.org/api v0.219.0
1920
k8s.io/api v0.32.1
2021
k8s.io/apimachinery v0.32.1
2122
k8s.io/client-go v0.32.1
@@ -25,7 +26,7 @@ require (
2526
)
2627

2728
require (
28-
cel.dev/expr v0.18.0 // indirect
29+
cel.dev/expr v0.19.0 // indirect
2930
cloud.google.com/go v0.116.0 // indirect
3031
cloud.google.com/go/auth v0.14.0 // indirect
3132
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
@@ -75,14 +76,14 @@ require (
7576
github.com/spf13/pflag v1.0.5 // indirect
7677
github.com/x448/float16 v0.8.4 // indirect
7778
go.opencensus.io v0.24.0 // indirect
78-
go.opentelemetry.io/contrib/detectors/gcp v1.31.0 // indirect
79+
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
7980
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
8081
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
81-
go.opentelemetry.io/otel v1.31.0 // indirect
82-
go.opentelemetry.io/otel/metric v1.31.0 // indirect
83-
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
84-
go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect
85-
go.opentelemetry.io/otel/trace v1.31.0 // indirect
82+
go.opentelemetry.io/otel v1.32.0 // indirect
83+
go.opentelemetry.io/otel/metric v1.32.0 // indirect
84+
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
85+
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
86+
go.opentelemetry.io/otel/trace v1.32.0 // indirect
8687
golang.org/x/crypto v0.32.0 // indirect
8788
golang.org/x/net v0.34.0 // indirect
8889
golang.org/x/sync v0.10.0 // indirect
@@ -92,9 +93,9 @@ require (
9293
golang.org/x/time v0.9.0 // indirect
9394
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
9495
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
95-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
96-
google.golang.org/grpc v1.69.4 // indirect
97-
google.golang.org/protobuf v1.36.3 // indirect
96+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
97+
google.golang.org/grpc v1.70.0 // indirect
98+
google.golang.org/protobuf v1.36.4 // indirect
9899
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
99100
gopkg.in/inf.v0 v0.9.1 // indirect
100101
gopkg.in/yaml.v3 v3.0.1 // indirect

0 commit comments

Comments
 (0)