Skip to content

Instantly share code, notes, and snippets.

@yadutaf
Last active November 24, 2025 12:56
Show Gist options
  • Select an option

  • Save yadutaf/1615ddc0dc7e9a02a4781b872e34c222 to your computer and use it in GitHub Desktop.

Select an option

Save yadutaf/1615ddc0dc7e9a02a4781b872e34c222 to your computer and use it in GitHub Desktop.
Linux Netkit interface eBPF "Hello World".

This Gist demoes Linux' netkit interface pairs. These interfaces are successors for veth tailor made for eBPF and high performance.

Usage

Create a 'lab' setup:

# Create the 'lab' namespace
sudo ip netns add lab

# Create and setup the interface pair with both sides in blackhole mode
sudo ip link add nk-host type netkit blackhole peer blackhole name nk-container
sudo ip link set nk-container netns lab
sudo ip netns exec lab ip addr add 10.42.0.2/8 dev nk-container
sudo ip netns exec lab ip link set lo up
sudo ip netns exec lab ip link set nk-container up
sudo ip addr add 10.42.0.1/8 dev nk-host
sudo ip link set nk-host up

Build and run:

go mod init hello-netkit
go mod tidy
go get github.com/cilium/ebpf/cmd/bpf2go
go generate && go build && sudo ./hello-netkit

The setup can be tested with a simple ping 10.42.0.2 in the host, with the program running, and without.

Reference

See https://blog.yadutaf.fr/2025/07/01/introduction-to-linux-netkit-interfaces-with-a-grain-of-ebpf/ for the full blog post.

package main
import (
"flag"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
)
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags linux netkit netkit.c
const (
defaultInterfaceName = "nk-host"
)
func main() {
// Get host interface name from the command line
interfaceName := flag.String("interface", defaultInterfaceName, "Host side netkit interface")
if interfaceName == nil || *interfaceName == "" {
flag.Usage()
}
// Resolve the interface name to an interface index
ifIndex, err := net.InterfaceByName(*interfaceName)
if err != nil {
panic(fmt.Errorf("could not resolve interface %s index: %w", *interfaceName, err))
}
fmt.Printf("Interface %s index is %d\n", *interfaceName, ifIndex.Index)
// Load the programs into the Kernel
collSpec, err := loadNetkit()
if err != nil {
panic(fmt.Errorf("could not load collection spec: %w", err))
}
coll, err := ebpf.NewCollection(collSpec)
if err != nil {
panic(fmt.Errorf("could not load BPF objects from collection spec: %w", err))
}
defer coll.Close()
// Attach the program to the primary interface
primaryLink, err := link.AttachNetkit(link.NetkitOptions{
Program: coll.Programs["netkit_primary"],
Interface: ifIndex.Index,
Attach: ebpf.AttachNetkitPrimary,
})
if err != nil {
panic(fmt.Errorf("could not attach primary prog %w", err))
}
defer primaryLink.Close()
// Attach the program to the peer, directly from the host, via the primary
peerLink, err := link.AttachNetkit(link.NetkitOptions{
Program: coll.Programs["netkit_peer"],
Interface: ifIndex.Index,
Attach: ebpf.AttachNetkitPeer,
})
if err != nil {
panic(fmt.Errorf("could not attach peer prog %w", err))
}
defer peerLink.Close()
// Forwarding is now enabled until exit
done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("%s is now forwarding IP packets until Ctrl+C is pressed.\n", *interfaceName)
<-done
}
//go:build ignore
#include <linux/if_link.h>
#include <bpf/bpf_helpers.h>
char LICENSE[] SEC("license") = "GPL";
SEC("netkit/primary")
int netkit_primary(struct __sk_buff *skb) {
return NETKIT_PASS;
}
SEC("netkit/peer")
int netkit_peer(struct __sk_buff *skb) {
return NETKIT_PASS;
}
@cassamajor
Copy link

Great blog post, thank you for sharing what you've learned!

The NETKIT_* return-code macros reside in the UAPI link-layer header.

If you want to use NETKIT_PASS instead of TCX_PASS, replace #include <linux/bpf.h> with #include <linux/if_link.h>.

@yadutaf
Copy link
Author

yadutaf commented Sep 6, 2025

@cassamajor Thanks for the tip!

@cassamajor
Copy link

cassamajor commented Sep 6, 2025

You're welcome!

@adeleke01042021
Copy link

thank you. great blog post.
do you have any pointers to docs/articles/samples for physical NIC -> container -> physical NIC using netkit? I've been trying to get something like that working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment