├── .devcontainer ├── .gitignore ├── devcontainer.json └── Dockerfile ├── cmd ├── clab_main │ ├── clab-topologies │ │ ├── .gitignore │ │ └── switch.clab.yml │ └── main.go ├── test_agent │ ├── test_agent_grpc.server_bootstrap.go │ ├── test_agent.proto │ ├── test_agent.Dockerfile │ └── impl │ │ └── test_agent_grpc.service_impl.go ├── dummy_xdp │ └── main.go ├── switch_agent │ └── main.go └── vxlan_agent │ └── main.go ├── google0e4f4b4e9d93323d.html ├── test └── e2e │ ├── switch_agent │ ├── clab-topologies │ │ ├── .gitignore │ │ └── switch.clab.yml │ └── switch_agent_test.go │ └── vxlan_agent │ ├── clab-topologies │ ├── .gitignore │ └── vxlan.clab.yml │ └── vxlan_agent_test.go ├── readme └── vxlan_headers.jpg ├── scripts ├── vxlan_agent_run_tests.sh ├── generate_grpc.sh ├── edgeshark_start.sh ├── build_images.sh ├── ebpf_explorer_start.sh └── generate_vmlinux_header.sh ├── internal ├── dummy_xdp │ ├── ebpf │ │ ├── dummyxdp_bpfeb.o │ │ ├── dummyxdp_bpfel.o │ │ ├── gen.go │ │ ├── c │ │ │ └── dummy_xdp.bpf.c │ │ ├── dummyxdp_bpfeb.go │ │ └── dummyxdp_bpfel.go │ └── dummy_xdp.go ├── switch_agent │ ├── ebpf │ │ ├── switchagentxdp_bpfeb.o │ │ ├── switchagentxdp_bpfel.o │ │ ├── switchagentunknownunicastflooding_bpfeb.o │ │ ├── switchagentunknownunicastflooding_bpfel.o │ │ ├── gen.go │ │ ├── c │ │ │ ├── switch_agent_unknown_unicast_flooding.bpf.c │ │ │ └── switch_agent_xdp.bpf.c │ │ ├── switchagentxdp_bpfeb.go │ │ ├── switchagentxdp_bpfel.go │ │ ├── switchagentunknownunicastflooding_bpfeb.go │ │ └── switchagentunknownunicastflooding_bpfel.go │ ├── mapper.go │ └── switch_agent.go └── vxlan_agent │ ├── ebpf │ ├── gen │ │ ├── vxlancommon_bpfeb.o │ │ ├── vxlancommon_bpfel.o │ │ ├── vxlantcexternal_bpfeb.o │ │ ├── vxlantcexternal_bpfel.o │ │ ├── vxlantcinternal_bpfeb.o │ │ ├── vxlantcinternal_bpfel.o │ │ ├── vxlanxdpexternal_bpfeb.o │ │ ├── vxlanxdpexternal_bpfel.o │ │ ├── vxlanxdpinternal_bpfeb.o │ │ ├── vxlanxdpinternal_bpfel.o │ │ ├── vxlantcexternal_bpfeb.go │ │ ├── vxlantcexternal_bpfel.go │ │ ├── vxlanxdpexternal_bpfeb.go │ │ ├── vxlanxdpexternal_bpfel.go │ │ ├── vxlantcinternal_bpfeb.go │ │ ├── vxlantcinternal_bpfel.go │ │ ├── vxlanxdpinternal_bpfeb.go │ │ ├── vxlanxdpinternal_bpfel.go │ │ ├── vxlancommon_bpfeb.go │ │ └── vxlancommon_bpfel.go │ ├── c │ │ ├── vxlan_common.bpf.c │ │ ├── vxlan_tc_external.bpf.c │ │ ├── vxlan_common.bpf.h │ │ └── vxlan_tc_internal.bpf.c │ └── gen.go │ └── mapper.go ├── .vscode ├── launch.json ├── settings.json ├── extensions.json └── restore-terminals.json ├── .gitignore ├── go.mod └── .zellij ├── zellij_docker_exec_layout.kdl └── zellij_docker_exec_layout_2.kdl /.devcontainer/.gitignore: -------------------------------------------------------------------------------- 1 | **/.devpod-internal/ -------------------------------------------------------------------------------- /cmd/clab_main/clab-topologies/.gitignore: -------------------------------------------------------------------------------- 1 | **/clab-switch/ 2 | .switch.clab.yml.bak -------------------------------------------------------------------------------- /google0e4f4b4e9d93323d.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google0e4f4b4e9d93323d.html -------------------------------------------------------------------------------- /test/e2e/switch_agent/clab-topologies/.gitignore: -------------------------------------------------------------------------------- 1 | **/clab-switch/ 2 | .switch.clab.yml.bak -------------------------------------------------------------------------------- /test/e2e/vxlan_agent/clab-topologies/.gitignore: -------------------------------------------------------------------------------- 1 | **/clab-vxlan/ 2 | .vxlan.clab.yml.bak -------------------------------------------------------------------------------- /readme/vxlan_headers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/readme/vxlan_headers.jpg -------------------------------------------------------------------------------- /scripts/vxlan_agent_run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go clean -testcache 4 | go test -v ./test/e2e/vxlan_agent/ -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/dummyxdp_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/dummy_xdp/ebpf/dummyxdp_bpfeb.o -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/dummyxdp_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/dummy_xdp/ebpf/dummyxdp_bpfel.o -------------------------------------------------------------------------------- /scripts/generate_grpc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | protoc --go_out=./cmd/test_agent/ --go-grpc_out=./cmd/test_agent/ cmd/test_agent/test_agent.proto -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentxdp_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/switch_agent/ebpf/switchagentxdp_bpfeb.o -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentxdp_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/switch_agent/ebpf/switchagentxdp_bpfel.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlancommon_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlancommon_bpfeb.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlancommon_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlancommon_bpfel.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfeb.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfel.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfeb.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfel.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfeb.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfel.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfeb.o -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfel.o -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/gen.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" DummyXdp ./c/dummy_xdp.bpf.c 4 | -------------------------------------------------------------------------------- /scripts/edgeshark_start.sh: -------------------------------------------------------------------------------- 1 | curl -sL \ 2 | https://github.com/siemens/edgeshark/raw/main/deployments/wget/docker-compose.yaml \ 3 | | DOCKER_DEFAULT_PLATFORM= docker compose -f - up -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfeb.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfeb.o -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfel.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unikzforce/wormhole/HEAD/internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfel.o -------------------------------------------------------------------------------- /scripts/build_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./scripts/generate_vmlinux_header.sh 4 | ./scripts/generate_grpc.sh 5 | 6 | # build switch_agent image 7 | docker build -f ./cmd/test_agent/test_agent.Dockerfile -t wormhole/test_agent:latest . -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/gen.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -type mac_address_iface_entry SwitchAgentXDP ./c/switch_agent_xdp.bpf.c 4 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go SwitchAgentUnknownUnicastFlooding ./c/switch_agent_unknown_unicast_flooding.bpf.c 5 | -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/c/dummy_xdp.bpf.c: -------------------------------------------------------------------------------- 1 | #include "../../../../include/vmlinux.h" 2 | 3 | #include 4 | 5 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 6 | 7 | 8 | // -------------------------------------------------------- 9 | // main xdp entry point 10 | 11 | SEC("xdp.frags") 12 | long dummy_xdp(struct xdp_md *ctx) 13 | { 14 | return XDP_PASS; 15 | } -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/c/vxlan_common.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vxlan_common.bpf.h" 2 | 3 | // to force the bpf2go to generate go struct for these types 4 | // we have to create some dummy maps for these types 5 | GENERATE_DUMMY_MAP(in_addr) 6 | GENERATE_DUMMY_MAP(external_route_info) 7 | GENERATE_DUMMY_MAP(mac_address) 8 | GENERATE_DUMMY_MAP(mac_table_entry) 9 | GENERATE_DUMMY_MAP(ipv4_lpm_key) 10 | GENERATE_DUMMY_MAP(network_vni) 11 | GENERATE_DUMMY_MAP(network_vni_light) -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | .idea -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "bpf_helpers.h": "c", 4 | "udp.h": "c", 5 | "ip.h": "c", 6 | "in.h": "c", 7 | "if_tunnel.h": "c", 8 | "vmlinux.h": "c", 9 | "typeinfo": "c", 10 | "if_ether.h": "c", 11 | "vxlan_agent.bpf.h": "c", 12 | "pkt_cls.h": "c", 13 | "bpf_helper_defs.h": "c", 14 | "time.h": "c", 15 | "time_types.h": "c", 16 | "types.h": "c", 17 | "posix_types.h": "c", 18 | "bpf_endian.h": "c", 19 | "vxlan_common.bpf.h": "c" 20 | } 21 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-azuretools.vscode-docker", 6 | "tuxtina.json2yaml", 7 | "vscode-icons-team.vscode-icons", 8 | "mutantdino.resourcemonitor", 9 | "ms-vscode.cpptools-extension-pack", 10 | "ms-vscode.cpptools", 11 | "ms-vscode.cpptools-themes", 12 | "EthanSK.restore-terminals", 13 | "golang.go", 14 | "kdl-org.kdl", 15 | "GitHub.copilot", 16 | "srl-labs.vscode-containerlab", 17 | "hediet.vscode-drawio" 18 | ], 19 | "unwantedRecommendations": [ 20 | ] 21 | } -------------------------------------------------------------------------------- /scripts/ebpf_explorer_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Try running the ARM64 image first 4 | echo "Trying to run the ARM64 image..." 5 | docker run -ti --rm -p 8070:80 \ 6 | --cap-add CAP_SYS_ADMIN --pid=host \ 7 | -e BPF_DIR=/sys/fs/bpf -v /sys/fs/bpf:/sys/fs/bpf \ 8 | ghcr.io/ebpfdev/explorer:v0.0.7 9 | 10 | # Check if the ARM64 image is not available 11 | if [ $? -ne 0 ]; then 12 | echo "ARM64 image not found. Trying to run the AMD64 image on Apple Silicon (with emulation)..." 13 | 14 | # Fallback to running the AMD64 image 15 | docker run --platform linux/amd64 -ti --rm -p 8070:80 \ 16 | --cap-add CAP_SYS_ADMIN --pid=host \ 17 | -e BPF_DIR=/sys/fs/bpf -v /sys/fs/bpf:/sys/fs/bpf \ 18 | ghcr.io/ebpfdev/explorer:v0.0.7 19 | fi -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" --output-dir ./gen VxlanCommon ./c/vxlan_common.bpf.c 4 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" --output-dir ./gen VxlanXDPExternal ./c/vxlan_xdp_external.bpf.c 5 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" --output-dir ./gen VxlanXDPInternal ./c/vxlan_xdp_internal.bpf.c 6 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" --output-dir ./gen VxlanTCExternal ./c/vxlan_tc_external.bpf.c 7 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-I../../../include/vmlinux.h" --output-dir ./gen VxlanTCInternal ./c/vxlan_tc_internal.bpf.c 8 | -------------------------------------------------------------------------------- /cmd/test_agent/test_agent_grpc.server_bootstrap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "google.golang.org/grpc" 6 | "net" 7 | "wormhole/cmd/test_agent/generated" 8 | "wormhole/cmd/test_agent/impl" 9 | ) 10 | 11 | func main() { 12 | log.Println("Starting grpc server 1") 13 | lis, err := net.Listen("tcp", ":9000") 14 | if err != nil { 15 | log.Fatalf("Failed to listen on port 9000: %v", err) 16 | } 17 | 18 | s := impl.TestAgentServiceImpl{} 19 | 20 | log.Println("Starting grpc server 2") 21 | 22 | grpcServer := grpc.NewServer() 23 | 24 | log.Println("Starting grpc server 3") 25 | 26 | generated.RegisterTestAgentServiceServer(grpcServer, &s) 27 | 28 | log.Println("Starting grpc server 4") 29 | 30 | if err := grpcServer.Serve(lis); err != nil { 31 | log.Fatalf("Failed to serve grpc server over port 9000: %v", err) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/generate_vmlinux_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the output file 4 | OUTPUT_FILE="./include/vmlinux.h" 5 | 6 | # Check if bpftool is installed 7 | if ! command -v pahole &> /dev/null 8 | then 9 | echo "pahol could not be found. Please install pahole (dwarves package)." 10 | exit 1 11 | fi 12 | 13 | # Check if bpftool is installed 14 | if ! command -v bpftool &> /dev/null 15 | then 16 | echo "bpftool could not be found. Please install bpftool." 17 | exit 1 18 | fi 19 | 20 | # Genereting vmlinux 21 | echo "Generating vmlinux" 22 | pahole -J /sys/kernel/btf/vmlinux 23 | 24 | # Generate vmlinux.h using bpftool 25 | echo "Generating $OUTPUT_FILE..." 26 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > $OUTPUT_FILE 27 | 28 | # Check if the generation was successful 29 | if [ $? -eq 0 ]; then 30 | echo "$OUTPUT_FILE has been successfully generated." 31 | else 32 | echo "Failed to generate $OUTPUT_FILE." 33 | exit 1 34 | fi -------------------------------------------------------------------------------- /.vscode/restore-terminals.json: -------------------------------------------------------------------------------- 1 | { 2 | "artificialDelayMilliseconds": 700, 3 | "keepExistingTerminalsOpen": false, 4 | "runOnStartup": true, 5 | "terminals": [ 6 | { 7 | "splitTerminals": [ 8 | { 9 | "name": "ebpf_explorer", 10 | "commands": ["./scripts/ebpf_explorer_start.sh"], 11 | "shouldRunCommands": false 12 | } 13 | ] 14 | }, 15 | { 16 | "splitTerminals": [ 17 | { 18 | "name": "edgeshark", 19 | "commands": ["./scripts/edgeshark_start.sh"] 20 | } 21 | ] 22 | }, 23 | { 24 | "splitTerminals": [ 25 | { 26 | "name": "clab", 27 | "commands": ["cd ./test/e2e/vxlan_agent/clab-topologies/"] 28 | }, 29 | { 30 | "name": "build_image", 31 | "commands": [] 32 | } 33 | ] 34 | }, 35 | { 36 | "splitTerminals": [ 37 | { 38 | "name": "zellij", 39 | "commands": ["zellij --layout .zellij/zellij_docker_exec_layout_2.kdl options --default-shell bash"] 40 | } 41 | ] 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /internal/vxlan_agent/mapper.go: -------------------------------------------------------------------------------- 1 | package vxlan_agent 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "sync" 8 | vxlanAgentEbpfGen "wormhole/internal/vxlan_agent/ebpf/gen" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // Pool to cache Encoder and Decoder instances 14 | var ( 15 | gobPool = sync.Pool{ 16 | New: func() interface{} { 17 | return &bytes.Buffer{} 18 | }, 19 | } 20 | ) 21 | 22 | func ConvertMacToString(mac vxlanAgentEbpfGen.VxlanCommonMacAddress) string { 23 | return fmt.Sprintf("%02X%02X%02X%02X%02X%02X", 24 | mac.Addr[0], mac.Addr[1], mac.Addr[2], mac.Addr[3], mac.Addr[4], mac.Addr[5]) 25 | } 26 | 27 | func ConvertMacBytesToMac(macBytes []byte) vxlanAgentEbpfGen.VxlanCommonMacAddress { 28 | var mac [6]uint8 29 | copy(mac[:], macBytes) 30 | return vxlanAgentEbpfGen.VxlanCommonMacAddress{Addr: mac} 31 | } 32 | 33 | func ConvertStringToMac(macStr string) vxlanAgentEbpfGen.VxlanCommonMacAddress { 34 | hwAddr, err := net.ParseMAC(macStr) 35 | if err != nil { 36 | logrus.Fatalln("Error decoding MAC address:", err) 37 | } 38 | 39 | macBytes := []byte(hwAddr) 40 | 41 | var mac [6]uint8 42 | copy(mac[:], macBytes) 43 | 44 | return vxlanAgentEbpfGen.VxlanCommonMacAddress{Addr: mac} 45 | } 46 | -------------------------------------------------------------------------------- /cmd/clab_main/clab-topologies/switch.clab.yml: -------------------------------------------------------------------------------- 1 | name: switch 2 | 3 | topology: 4 | nodes: 5 | src: 6 | kind: linux 7 | image: wormhole/test_agent:latest 8 | exec: 9 | - ip addr add 2.2.2.1/24 dev eth1 10 | sw: 11 | kind: linux 12 | image: wormhole/test_agent:latest 13 | # we cannot run program using entrypoint 14 | # or cmd here cause the network interface 15 | # at this point is not created by containerlab 16 | # yet. so we should run our commands with 17 | # docker exec programmatically in our tests, 18 | # so this won't work: 19 | # cmd: "sh -c '/build/switch_agent --if-name eth1'" 20 | binds: 21 | - /sys/kernel/debug:/sys/kernel/debug 22 | dst: 23 | kind: linux 24 | image: wormhole/test_agent:latest 25 | exec: 26 | - ip addr add 2.2.2.2/24 dev eth1 27 | links: 28 | - endpoints: [src:eth1, sw:eth1] 29 | # sometimes by default the containerlab would set MTU to 9500. 30 | # if the MTU is high the libbpf would complain like this: 31 | # libbpf: Kernel error message: veth: Peer MTU is too large to set XDP 32 | # Error: interface xdp attach failed: Numerical result out of range 33 | mtu: 1500 34 | - endpoints: [dst:eth1, sw:eth2] 35 | mtu: 1500 -------------------------------------------------------------------------------- /cmd/clab_main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/mostlygeek/arp" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/vishvananda/netlink" 10 | ) 11 | 12 | func main() { 13 | //conf := &traceroute.TraceConfig{ 14 | // Debug: true, 15 | // FirstTTL: 1, 16 | // MaxTTL: 1, 17 | // Retry: 0, 18 | // WaitSec: 1, 19 | //} 20 | // 21 | //var destAddr = "1.1.1.1" 22 | // 23 | //fmt.Printf("traceroute to %s %d hots max\n", destAddr, conf.MaxTTL) 24 | //results, err := traceroute.Traceroute(destAddr, conf) 25 | //if err != nil { 26 | // fmt.Println(err.Error()) 27 | // return 28 | //} 29 | // 30 | //fmt.Printf("%s : %s\n", results[0].NextHot, arp.Search(results[0].NextHot)) 31 | 32 | // via the new vay to `ip route get 1.1.1.1` 33 | destination := "1.1.1.1" 34 | dst := net.ParseIP(destination) 35 | 36 | routes, err := netlink.RouteGet(dst) 37 | if err != nil { 38 | log.Fatalf("Failed to get route: %v", err) 39 | } 40 | 41 | for _, route := range routes { 42 | fmt.Printf("Route to %s: %+v\n", destination, route) 43 | if route.Gw != nil { 44 | log.Printf("Gateway: %s Mac: %s\n", route.Gw.String(), arp.Search(route.Gw.String())) 45 | } 46 | if route.LinkIndex != 0 { 47 | link, err := netlink.LinkByIndex(route.LinkIndex) 48 | if err != nil { 49 | log.Fatalf("Failed to get link by index: %v", err) 50 | } 51 | log.Printf("Interface: %s\n", link.Attrs().Name) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cmd/dummy_xdp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "wormhole/internal/dummy_xdp" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/urfave/cli" 10 | "github.com/vishvananda/netlink" 11 | ) 12 | 13 | func main() { 14 | app := &cli.App{ 15 | Name: "dummy_xdp", 16 | Usage: "dummy_xdp, is just a dummy xdp_pass program that will attach itself on a network interface", 17 | Flags: []cli.Flag{ 18 | &cli.StringFlag{ 19 | Name: "interface-name", 20 | Value: "", 21 | Usage: "the name of the network interface we want to attach to", 22 | }, 23 | }, 24 | Action: ActivateDummyXdp, 25 | } 26 | 27 | if err := app.Run(os.Args); err != nil { 28 | logrus.Fatalf("error %s", err) 29 | panic(err) 30 | } 31 | } 32 | 33 | func ActivateDummyXdp(cCtx *cli.Context) error { 34 | networkInterface := findNetworkInterface(cCtx, "interface-name") 35 | 36 | dummyXdp := dummy_xdp.NewDummyXdp(networkInterface) 37 | 38 | return dummyXdp.ActivateDummyXdp() 39 | } 40 | 41 | func findNetworkInterface(cCtx *cli.Context, interfaceName string) netlink.Link { 42 | cliInterfaceName := strings.TrimSpace(cCtx.String(interfaceName)) 43 | if cliInterfaceName == "" { 44 | logrus.Fatalf("%s should be present and not empty", interfaceName) 45 | } 46 | 47 | logrus.Println(cliInterfaceName) 48 | 49 | iface, err := netlink.LinkByName(cliInterfaceName) 50 | if err != nil { 51 | logrus.Fatalf("Getting interface %s: %s", cliInterfaceName, err) 52 | } 53 | 54 | return iface 55 | } 56 | -------------------------------------------------------------------------------- /cmd/test_agent/test_agent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | package generated; 4 | 5 | option go_package = "./generated"; 6 | 7 | message WaitUntilReadyResponse { 8 | bool success = 1; 9 | } 10 | 11 | message PingRequest { 12 | string ipV4Address = 1; 13 | int32 count = 2; 14 | int32 timeout = 3; 15 | } 16 | 17 | message PingResponse { 18 | bool success = 1; 19 | int32 packetsRecv = 2; 20 | int32 packetsSent = 3; 21 | int32 packetsRecvDuplicates = 4; 22 | float packetLoss = 5; 23 | } 24 | 25 | message EnableSwitchAgentRequest { 26 | repeated string interfaceNames = 1; 27 | } 28 | 29 | message EnableSwitchAgentResponse { 30 | string resp = 1; 31 | string command = 2; 32 | int32 pid = 3; 33 | string hostname = 4; 34 | } 35 | 36 | message EnableVxlanAgentRequest { 37 | string config_yaml_content = 1; 38 | } 39 | 40 | message EnableVxlanAgentResponse { 41 | string resp = 1; 42 | string command = 2; 43 | string output = 3; 44 | int32 pid = 4; 45 | string hostname = 5; 46 | } 47 | 48 | message EnableDummyXdpAgentRequest { 49 | string interfaceName = 1; 50 | } 51 | 52 | service TestAgentService { 53 | rpc WaitUntilReady(google.protobuf.Empty) returns (WaitUntilReadyResponse) {} 54 | rpc Ping(PingRequest) returns (PingResponse) {} 55 | rpc EnableSwitchAgent(EnableSwitchAgentRequest) returns (EnableSwitchAgentResponse) {} 56 | rpc DisableSwitchAgent(google.protobuf.Empty) returns (google.protobuf.Empty) {} 57 | rpc EnableVxlanAgent(EnableVxlanAgentRequest) returns (EnableVxlanAgentResponse) {} 58 | rpc DisableVxlanAgent(google.protobuf.Empty) returns (google.protobuf.Empty) {} 59 | rpc EnableDummyXdpAgent(EnableDummyXdpAgentRequest) returns (google.protobuf.Empty) {} 60 | } -------------------------------------------------------------------------------- /cmd/switch_agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/urfave/cli" 6 | "github.com/vishvananda/netlink" 7 | "os" 8 | "strings" 9 | "wormhole/internal/switch_agent" 10 | ) 11 | 12 | func main() { 13 | 14 | app := &cli.App{ 15 | Name: "switch_agent", 16 | Usage: "switch_agent, is the program that will reside in a network and will act as a switch device", 17 | Flags: []cli.Flag{ 18 | &cli.StringFlag{ 19 | Name: "interface-names", 20 | Value: "", 21 | Usage: "the name of the network interfaces to attach", 22 | }, 23 | }, 24 | Action: ActivateSwitchAgent, 25 | } 26 | 27 | if err := app.Run(os.Args); err != nil { 28 | logrus.Fatalf("error %s", err) 29 | panic(err) 30 | } 31 | } 32 | 33 | func ActivateSwitchAgent(cCtx *cli.Context) error { 34 | networkInterfaces := findNetworkInterfaces(cCtx, "interface-names") 35 | switchAgent := switch_agent.NewSwitchAgent(networkInterfaces) 36 | 37 | return switchAgent.ActivateSwitchAgent() 38 | } 39 | 40 | func findNetworkInterfaces(cCtx *cli.Context, argumentName string) []netlink.Link { 41 | cliInterfaceNames := strings.TrimSpace(cCtx.String(argumentName)) 42 | if cliInterfaceNames == "" { 43 | logrus.Fatal("--interface-names should be present and not empty") 44 | } 45 | 46 | logrus.Println(cliInterfaceNames) 47 | 48 | interfaceNames := strings.Split(cliInterfaceNames, ",") 49 | logrus.Println(interfaceNames) 50 | 51 | var ifaces []netlink.Link 52 | 53 | for _, ifaceName := range interfaceNames { 54 | iface, err := netlink.LinkByName(ifaceName) 55 | if err != nil { 56 | logrus.Fatalf("Getting interface %s: %s", ifaceName, err) 57 | } 58 | 59 | ifaces = append(ifaces, iface) 60 | } 61 | return ifaces 62 | } 63 | -------------------------------------------------------------------------------- /test/e2e/switch_agent/clab-topologies/switch.clab.yml: -------------------------------------------------------------------------------- 1 | name: switch 2 | 3 | topology: 4 | nodes: 5 | src: 6 | kind: linux 7 | image: wormhole/test_agent:latest 8 | # publish: 9 | # - tcp/9000/clab-switch-src 10 | # ports: 11 | # - 9000:9000 12 | exec: 13 | - ip addr add 2.2.2.1/24 dev eth1 14 | - touch /tmp/is_clab_container_ready 15 | 16 | 17 | sw: 18 | kind: linux 19 | image: wormhole/test_agent:latest 20 | # we cannot run program using entrypoint 21 | # or cmd here cause the network interface 22 | # at this point is not created by containerlab 23 | # yet. so we should run our commands with 24 | # docker exec programmatically in our tests, 25 | # so this won't work: 26 | # cmd: "sh -c '/build/switch_agent --if-name eth1'" 27 | binds: 28 | - /sys/kernel/debug:/sys/kernel/debug 29 | # publish: 30 | # - tcp/9000/clab-switch-sw 31 | # ports: 32 | # - 9001:9000 33 | exec: 34 | - touch /tmp/is_clab_container_ready 35 | 36 | 37 | dst: 38 | kind: linux 39 | image: wormhole/test_agent:latest 40 | # publish: 41 | # - tcp/9000/clab-switch-dst 42 | # ports: 43 | # - 9000:9000 44 | exec: 45 | - ip addr add 2.2.2.2/24 dev eth1 46 | - touch /tmp/is_clab_container_ready 47 | links: 48 | - endpoints: [src:eth1, sw:eth1] 49 | # sometimes by default the containerlab would set MTU to 9500. 50 | # if the MTU is high the libbpf would complain like this: 51 | # libbpf: Kernel error message: veth: Peer MTU is too large to set XDP 52 | # Error: interface xdp attach failed: Numerical result out of range 53 | mtu: 1500 54 | - endpoints: [dst:eth1, sw:eth2] 55 | mtu: 1500 -------------------------------------------------------------------------------- /internal/dummy_xdp/dummy_xdp.go: -------------------------------------------------------------------------------- 1 | package dummy_xdp 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "time" 7 | 8 | "github.com/cilium/ebpf/link" 9 | "github.com/cilium/ebpf/rlimit" 10 | "github.com/sirupsen/logrus" 11 | "github.com/vishvananda/netlink" 12 | 13 | dummyXdpEbpfGen "wormhole/internal/dummy_xdp/ebpf" 14 | ) 15 | 16 | type DummyXdp struct { 17 | networkInterface netlink.Link 18 | dummyXdpObjects dummyXdpEbpfGen.DummyXdpObjects 19 | } 20 | 21 | func NewDummyXdp(networkInterface netlink.Link) *DummyXdp { 22 | return &DummyXdp{ 23 | networkInterface: networkInterface, 24 | } 25 | } 26 | 27 | func (dummyXdp *DummyXdp) ActivateDummyXdp() error { 28 | logrus.Print("0. check if we can remove memlock") 29 | if err := rlimit.RemoveMemlock(); err != nil { 30 | logrus.Error("Error removing memlock:", err) 31 | return err 32 | } 33 | 34 | dummyXdpSpec, err := dummyXdpEbpfGen.LoadDummyXdp() 35 | if err != nil { 36 | logrus.Error("Error loading dummy XDP skeleton into user memory", err) 37 | return err 38 | } 39 | 40 | err = dummyXdpSpec.LoadAndAssign(&dummyXdp.dummyXdpObjects, nil) 41 | defer dummyXdp.dummyXdpObjects.Close() 42 | if err != nil { 43 | logrus.Error("Error loading dummy XDP program into kernel memory", err) 44 | return err 45 | } 46 | 47 | attachedXdpLink, err := link.AttachXDP(link.XDPOptions{ 48 | Program: dummyXdp.dummyXdpObjects.DummyXdp, 49 | Interface: dummyXdp.networkInterface.Attrs().Index, 50 | Flags: link.XDPDriverMode, 51 | }) 52 | defer attachedXdpLink.Close() 53 | if err != nil { 54 | logrus.Error("Error attaching dummy XDP program to network interface") 55 | return err 56 | } 57 | 58 | return dummyXdp.waitForCtrlC() 59 | } 60 | 61 | func (dummyXdp *DummyXdp) waitForCtrlC() error { 62 | // Periodically print user mac table 63 | // exit the program when interrupted. 64 | ticker := time.NewTicker(time.Second) 65 | defer ticker.Stop() 66 | stop := make(chan os.Signal, 5) 67 | signal.Notify(stop, os.Interrupt) 68 | for { 69 | select { 70 | case <-stop: 71 | logrus.Print("Received signal, exiting..") 72 | return nil 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /internal/switch_agent/mapper.go: -------------------------------------------------------------------------------- 1 | package switch_agent 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "encoding/hex" 7 | "fmt" 8 | "github.com/sirupsen/logrus" 9 | "sync" 10 | "wormhole/internal/switch_agent/ebpf" 11 | ) 12 | 13 | // Pool to cache Encoder and Decoder instances 14 | var ( 15 | gobPool = sync.Pool{ 16 | New: func() interface{} { 17 | return &bytes.Buffer{} 18 | }, 19 | } 20 | ) 21 | 22 | func ConvertMacToString(mac ebpf.SwitchAgentXDPMacAddress) string { 23 | return fmt.Sprintf("%02X%02X%02X%02X%02X%02X", 24 | mac.Mac[0], mac.Mac[1], mac.Mac[2], mac.Mac[3], mac.Mac[4], mac.Mac[5]) 25 | } 26 | 27 | func ConvertStringToMac(macStr string) ebpf.SwitchAgentXDPMacAddress { 28 | macBytes, err := hex.DecodeString(macStr) 29 | if err != nil { 30 | logrus.Fatalln("Error decoding MAC address:", err) 31 | } 32 | 33 | var mac [6]uint8 34 | copy(mac[:], macBytes) 35 | 36 | return ebpf.SwitchAgentXDPMacAddress{Mac: mac} 37 | } 38 | 39 | func EncodeIfaceIndex(s ebpf.SwitchAgentXDPIfaceIndex) ([]byte, error) { 40 | // Get a buffer from the pool 41 | buf := gobPool.Get().(*bytes.Buffer) 42 | defer gobPool.Put(buf) 43 | buf.Reset() // Reset buffer before encoding 44 | 45 | // Encode the struct into the buffer 46 | encoder := gob.NewEncoder(buf) 47 | err := encoder.Encode(s) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return buf.Bytes(), nil 52 | } 53 | 54 | // Function to decode a byte slice into a struct 55 | func decodeMacIfaceEntry(data []byte) (ebpf.SwitchAgentXDPMacAddressIfaceEntry, error) { 56 | // Get a buffer from the pool 57 | buf := gobPool.Get().(*bytes.Buffer) 58 | defer gobPool.Put(buf) 59 | buf.Reset() // Reset buffer before decoding 60 | 61 | // Write data to buffer 62 | _, err := buf.Write(data) 63 | if err != nil { 64 | return ebpf.SwitchAgentXDPMacAddressIfaceEntry{}, err 65 | } 66 | 67 | // Decode the buffer into a struct 68 | var decodedStruct ebpf.SwitchAgentXDPMacAddressIfaceEntry 69 | decoder := gob.NewDecoder(buf) 70 | err = decoder.Decode(&decodedStruct) 71 | if err != nil { 72 | return ebpf.SwitchAgentXDPMacAddressIfaceEntry{}, err 73 | } 74 | return decodedStruct, nil 75 | } 76 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/go 3 | { 4 | "name": "wormhole", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "build": { 7 | "dockerfile": "Dockerfile", 8 | "args": { 9 | "_CLAB_VERSION": "0.61.0" 10 | } 11 | }, 12 | "features": { 13 | "ghcr.io/devcontainers/features/docker-in-docker:latest": {}, 14 | "ghcr.io/devcontainers/features/go:latest": {}, 15 | "ghcr.io/guiyomh/features/golangci-lint:latest": {}, 16 | "ghcr.io/guiyomh/features/goreleaser:latest": {}, 17 | "ghcr.io/guiyomh/features/gotestsum:latest": {}, 18 | "ghcr.io/guiyomh/features/pact-go:latest": {}, 19 | "ghcr.io/azutake/devcontainer-features/go-packages-install:latest": {}, 20 | "ghcr.io/marcozac/devcontainer-features/gofumpt:latest": {} 21 | }, 22 | "runArgs": ["--privileged"], 23 | "privileged": true, 24 | 25 | "customizations": { 26 | "cursor": { 27 | "extensions": [ 28 | "ms-azuretools.vscode-docker", 29 | "tuxtina.json2yaml", 30 | "vscode-icons-team.vscode-icons", 31 | "mutantdino.resourcemonitor", 32 | "ms-vscode.cpptools-extension-pack", 33 | "ms-vscode.cpptools", 34 | "ms-vscode.cpptools-themes", 35 | "EthanSK.restore-terminals" 36 | ] 37 | }, 38 | "vscode": { 39 | "extensions": [ 40 | "ms-azuretools.vscode-docker", 41 | "tuxtina.json2yaml", 42 | "vscode-icons-team.vscode-icons", 43 | "mutantdino.resourcemonitor", 44 | "ms-vscode.cpptools-extension-pack", 45 | "ms-vscode.cpptools", 46 | "ms-vscode.cpptools-themes", 47 | "EthanSK.restore-terminals" 48 | ] 49 | } 50 | }, 51 | "mounts": [ 52 | "source=clab-vscode-home-dir,target=/home/vscode,type=volume", 53 | "source=clab-docker-root-config,target=/root/.docker,type=volume", 54 | // "target=/home/vscode/.vscode-server,type=volume", 55 | "source=/sys/kernel/debug,target=/sys/kernel/debug,type=bind", 56 | "source=/sys/fs/bpf,target=/sys/fs/bpf,type=bind" 57 | ], 58 | 59 | 60 | // Features to add to the dev container. More info: https://containers.dev/features. 61 | // "features": {}, 62 | 63 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 64 | "forwardPorts": [5001, 8070], 65 | 66 | // Use 'postCreateCommand' to run commands after the container is created. 67 | "postStartCommand": "if [ ! -f ~/.first_run_done ]; then echo '' > ~/.bash_history && touch ~/.first_run_done; fi" 68 | 69 | // Configure tool-specific properties. 70 | // "customizations": {}, 71 | 72 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 73 | // "remoteUser": "root" 74 | } -------------------------------------------------------------------------------- /cmd/test_agent/test_agent.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt-get update && \ 4 | echo "wireshark-common wireshark-common/install-setuid boolean true" | debconf-set-selections && \ 5 | DEBIAN_FRONTEND=noninteractive apt-get install -y tshark 6 | 7 | RUN apt install -y golang-go 8 | RUN apt install -y build-essential 9 | RUN apt install -y pkg-config 10 | RUN apt install -y clang 11 | RUN apt install -y llvm 12 | RUN apt install -y m4 13 | RUN apt install -y git 14 | RUN apt install -y libelf-dev 15 | RUN apt install -y libpcap-dev 16 | RUN apt install -y iproute2 17 | RUN apt install -y iputils-ping 18 | RUN apt install -y linux-headers-generic 19 | RUN apt install -y libbpf-dev 20 | RUN apt install -y linux-libc-dev 21 | RUN apt install -y cmake 22 | RUN apt install -y libpcap-dev 23 | RUN apt install -y libcap-ng-dev 24 | RUN apt install -y libbfd-dev 25 | RUN ln -sf /usr/include/asm-generic/ /usr/include/asm 26 | RUN apt install -y libcap-dev 27 | RUN apt install -y dwarves 28 | RUN apt install -y ethtool 29 | RUN echo 'alias ll="ls -al"' >> ~/.bashrc 30 | RUN git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf 31 | RUN ~/.fzf/install --all 32 | 33 | RUN mkdir /tools/ 34 | 35 | WORKDIR /tools/ 36 | 37 | RUN git clone --recurse-submodules https://github.com/libbpf/bpftool.git 38 | 39 | RUN make -C bpftool/src/ install 40 | 41 | RUN git clone --recurse-submodules https://github.com/xdp-project/xdp-tools.git 42 | 43 | RUN make -C xdp-tools/ install 44 | 45 | RUN mkdir /wormhole-source/ 46 | 47 | COPY ../../go.mod /wormhole-source 48 | COPY ../../go.sum /wormhole-source 49 | COPY ../../internal/ /wormhole-source/internal/ 50 | COPY ../../include/ /wormhole-source/include/ 51 | COPY ../../cmd/ /wormhole-source/cmd/ 52 | 53 | WORKDIR /wormhole-source/ 54 | 55 | #RUN go install github.com/go-delve/delve/cmd/dlv@latest 56 | RUN go mod download 57 | RUN go generate ./internal/switch_agent/ebpf/ 58 | RUN go generate ./internal/vxlan_agent/ebpf/ 59 | RUN go generate ./internal/dummy_xdp/ebpf/ 60 | 61 | RUN go build -o /build/switch_agent ./cmd/switch_agent/main.go 62 | RUN go build -o /build/vxlan_agent ./cmd/vxlan_agent/main.go 63 | RUN go build -o /build/dummy_xdp ./cmd/dummy_xdp/main.go 64 | RUN go build -o /build/test_agent ./cmd/test_agent/test_agent_grpc.server_bootstrap.go 65 | # for debuging comment above and uncomment below 2 commands & comment above 66 | #RUN go build -gcflags="all=-N -l" -o /build/switch_agent ./cmd/switch_agent/main.go 67 | #EXPOSE 40000 68 | 69 | 70 | 71 | #ENTRYPOINT ["dlv", "--listen=:40000", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/build/test_agent"] 72 | 73 | #ENTRYPOINT ["sh", "-c", "trap 'exit 0' SIGTERM SIGINT; while true; do echo 'Container is running...'; sleep 10; done"] 74 | 75 | ENTRYPOINT ["/build/test_agent"] -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt-get update && \ 4 | echo "wireshark-common wireshark-common/install-setuid boolean true" | debconf-set-selections && \ 5 | DEBIAN_FRONTEND=noninteractive apt-get install -y tshark 6 | 7 | RUN apt install -y golang-go 8 | RUN apt install -y build-essential 9 | RUN apt install -y pkg-config 10 | RUN apt install -y clang 11 | RUN apt install -y llvm 12 | RUN apt install -y m4 13 | RUN apt install -y git 14 | RUN apt install -y libelf-dev 15 | RUN apt install -y libpcap-dev 16 | RUN apt install -y iproute2 17 | RUN apt install -y iputils-ping 18 | RUN apt install -y linux-headers-generic 19 | RUN apt install -y libbpf-dev 20 | RUN apt install -y linux-libc-dev 21 | RUN apt install -y cmake 22 | RUN apt install -y libpcap-dev 23 | RUN apt install -y libcap-ng-dev 24 | RUN apt install -y libbfd-dev 25 | RUN ln -sf /usr/include/asm-generic/ /usr/include/asm 26 | RUN apt install -y libcap-dev 27 | RUN apt install -y dwarves 28 | RUN apt install -y locales 29 | RUN apt install -y bash-completion 30 | RUN apt install -y cargo 31 | RUN locale-gen en_US.UTF-8 32 | RUN update-locale LANG=en_US.UTF-8 33 | RUN echo 'alias ll="ls -al"' >> ~/.bashrc 34 | RUN echo 'if [ -f /etc/bash_completion ]; then . /etc/bash_completion; fi' >> ~/.bashrc 35 | RUN mkdir -p ~/.local/share/bash-completion/completions 36 | RUN echo 'docker completion bash > ~/.local/share/bash-completion/completions/docker' >> ~/.bashrc 37 | RUN git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf 38 | RUN ~/.fzf/install --all 39 | 40 | RUN cargo install --locked zellij 41 | 42 | RUN echo 'PATH=$PATH:$HOME/.cargo/bin' >> ~/.bashrc 43 | 44 | RUN mkdir /tools/ 45 | 46 | WORKDIR /tools/ 47 | 48 | RUN git clone --recurse-submodules https://github.com/libbpf/bpftool.git 49 | 50 | RUN make -C bpftool/src/ install 51 | 52 | RUN git clone --recurse-submodules https://github.com/xdp-project/xdp-tools.git 53 | 54 | RUN make -C xdp-tools/ install 55 | 56 | WORKDIR / 57 | 58 | ENV GOPATH=/go 59 | ENV PATH=$PATH:$GOPATH/bin 60 | RUN mkdir -p $GOPATH/bin 61 | 62 | 63 | RUN apt install -y protobuf-compiler 64 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 65 | RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 66 | 67 | # install CLAB 68 | # containelab version will be set in devcontainer.json 69 | # ARG _CLAB_VERSION 70 | 71 | # Set permissions for mounts in devcontainer.json 72 | #RUN mkdir -p /home/vscode/.vscode-server/bin 73 | #RUN chown -R vscode:vscode /home/vscode/.vscode-server 74 | 75 | # install some basic tools inside the container 76 | # adjust this list based on your demands 77 | RUN apt-get update && \ 78 | apt-get upgrade -y && \ 79 | apt-get install -y --no-install-recommends \ 80 | sshpass \ 81 | curl \ 82 | iputils-ping \ 83 | htop \ 84 | yamllint \ 85 | && rm -rf /var/lib/apt/lists/* \ 86 | && rm -Rf /usr/share/doc && rm -Rf /usr/share/man \ 87 | && apt-get clean 88 | 89 | # install preferred version of the containerlab 90 | RUN bash -c "$(curl -sL https://get.containerlab.dev)" 91 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/c/switch_agent_unknown_unicast_flooding.bpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 7 | 8 | #define MAX_INTERFACES 10 9 | 10 | struct { 11 | __uint(type, BPF_MAP_TYPE_ARRAY); 12 | __type(key, __u32); 13 | __type(value, __u32); 14 | __uint(max_entries, MAX_INTERFACES); 15 | } interfaces_array SEC(".maps"); 16 | 17 | struct { 18 | __uint(type, BPF_MAP_TYPE_ARRAY); 19 | __type(key, __u32); 20 | __type(value, __u32); 21 | __uint(max_entries, 1); 22 | } interfaces_array_length SEC(".maps"); 23 | 24 | SEC("tcx/ingress") 25 | int switch_agent_unknown_unicast_flooding(struct __sk_buff *skb) 26 | { 27 | bpf_printk( 28 | "///////////////////////////////////////////////////////////////////////////////////////////////////"); 29 | // we can use current_time as something like a unique identifier for packet 30 | __u64 current_time = bpf_ktime_get_tai_ns(); 31 | struct ethhdr *eth = (void *)(long)skb->data; 32 | 33 | if ((void *)(eth + 1) > (void *)(long)skb->data_end) 34 | return BPF_DROP; 35 | 36 | bpf_printk( 37 | "///////////// id = %llu, interface = %d, Packet received, source MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", 38 | current_time, skb->ingress_ifindex, eth->h_source[0], eth->h_source[1], 39 | eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]); 40 | 41 | bpf_printk( 42 | "///////////// id = %llu, interface = %d, Packet received, dest MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", 43 | current_time, skb->ingress_ifindex, eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], 44 | eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); 45 | 46 | int ingress_ifindex = skb->ingress_ifindex; 47 | 48 | 49 | int zero = 0; // Key for the first element 50 | __u32* number_of_interfaces_ptr = bpf_map_lookup_elem(&interfaces_array_length, &zero); 51 | if (!number_of_interfaces_ptr || *number_of_interfaces_ptr == 0) { 52 | return TC_ACT_OK; 53 | } 54 | __u32 number_of_interfaces = *number_of_interfaces_ptr; 55 | 56 | bpf_printk("///////////// id = %llu, interface = %d, start to multicast\n", current_time, skb->ingress_ifindex); 57 | 58 | if (number_of_interfaces>MAX_INTERFACES) 59 | { 60 | number_of_interfaces = MAX_INTERFACES; 61 | } 62 | 63 | int i; 64 | __u32* interface_index_ptr; 65 | 66 | bpf_for(i, 0, 10) { 67 | if (i >= number_of_interfaces) { 68 | return TC_ACT_OK; 69 | } 70 | interface_index_ptr = bpf_map_lookup_elem(&interfaces_array, &i); 71 | 72 | if (interface_index_ptr) { 73 | __u32 interface_index = *interface_index_ptr; 74 | 75 | if (interface_index != ingress_ifindex) { 76 | bpf_clone_redirect(skb, interface_index, 0); 77 | bpf_printk("///////////// id = %llu, multicast: redirection to %d \n", 78 | current_time, interface_index); 79 | } 80 | } else { 81 | return TC_ACT_OK; 82 | } 83 | } 84 | 85 | return TC_ACT_OK; 86 | } -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/dummyxdp_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | // LoadDummyXdp returns the embedded CollectionSpec for DummyXdp. 16 | func LoadDummyXdp() (*ebpf.CollectionSpec, error) { 17 | reader := bytes.NewReader(_DummyXdpBytes) 18 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 19 | if err != nil { 20 | return nil, fmt.Errorf("can't load DummyXdp: %w", err) 21 | } 22 | 23 | return spec, err 24 | } 25 | 26 | // LoadDummyXdpObjects loads DummyXdp and converts it into a struct. 27 | // 28 | // The following types are suitable as obj argument: 29 | // 30 | // *DummyXdpObjects 31 | // *DummyXdpPrograms 32 | // *DummyXdpMaps 33 | // 34 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 35 | func LoadDummyXdpObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 36 | spec, err := LoadDummyXdp() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return spec.LoadAndAssign(obj, opts) 42 | } 43 | 44 | // DummyXdpSpecs contains maps and programs before they are loaded into the kernel. 45 | // 46 | // It can be passed ebpf.CollectionSpec.Assign. 47 | type DummyXdpSpecs struct { 48 | DummyXdpProgramSpecs 49 | DummyXdpMapSpecs 50 | } 51 | 52 | // DummyXdpSpecs contains programs before they are loaded into the kernel. 53 | // 54 | // It can be passed ebpf.CollectionSpec.Assign. 55 | type DummyXdpProgramSpecs struct { 56 | DummyXdp *ebpf.ProgramSpec `ebpf:"dummy_xdp"` 57 | } 58 | 59 | // DummyXdpMapSpecs contains maps before they are loaded into the kernel. 60 | // 61 | // It can be passed ebpf.CollectionSpec.Assign. 62 | type DummyXdpMapSpecs struct { 63 | } 64 | 65 | // DummyXdpObjects contains all objects after they have been loaded into the kernel. 66 | // 67 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 68 | type DummyXdpObjects struct { 69 | DummyXdpPrograms 70 | DummyXdpMaps 71 | } 72 | 73 | func (o *DummyXdpObjects) Close() error { 74 | return _DummyXdpClose( 75 | &o.DummyXdpPrograms, 76 | &o.DummyXdpMaps, 77 | ) 78 | } 79 | 80 | // DummyXdpMaps contains all maps after they have been loaded into the kernel. 81 | // 82 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 83 | type DummyXdpMaps struct { 84 | } 85 | 86 | func (m *DummyXdpMaps) Close() error { 87 | return _DummyXdpClose() 88 | } 89 | 90 | // DummyXdpPrograms contains all programs after they have been loaded into the kernel. 91 | // 92 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 93 | type DummyXdpPrograms struct { 94 | DummyXdp *ebpf.Program `ebpf:"dummy_xdp"` 95 | } 96 | 97 | func (p *DummyXdpPrograms) Close() error { 98 | return _DummyXdpClose( 99 | p.DummyXdp, 100 | ) 101 | } 102 | 103 | func _DummyXdpClose(closers ...io.Closer) error { 104 | for _, closer := range closers { 105 | if err := closer.Close(); err != nil { 106 | return err 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // Do not access this directly. 113 | // 114 | //go:embed dummyxdp_bpfeb.o 115 | var _DummyXdpBytes []byte 116 | -------------------------------------------------------------------------------- /internal/dummy_xdp/ebpf/dummyxdp_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | // LoadDummyXdp returns the embedded CollectionSpec for DummyXdp. 16 | func LoadDummyXdp() (*ebpf.CollectionSpec, error) { 17 | reader := bytes.NewReader(_DummyXdpBytes) 18 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 19 | if err != nil { 20 | return nil, fmt.Errorf("can't load DummyXdp: %w", err) 21 | } 22 | 23 | return spec, err 24 | } 25 | 26 | // LoadDummyXdpObjects loads DummyXdp and converts it into a struct. 27 | // 28 | // The following types are suitable as obj argument: 29 | // 30 | // *DummyXdpObjects 31 | // *DummyXdpPrograms 32 | // *DummyXdpMaps 33 | // 34 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 35 | func LoadDummyXdpObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 36 | spec, err := LoadDummyXdp() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return spec.LoadAndAssign(obj, opts) 42 | } 43 | 44 | // DummyXdpSpecs contains maps and programs before they are loaded into the kernel. 45 | // 46 | // It can be passed ebpf.CollectionSpec.Assign. 47 | type DummyXdpSpecs struct { 48 | DummyXdpProgramSpecs 49 | DummyXdpMapSpecs 50 | } 51 | 52 | // DummyXdpSpecs contains programs before they are loaded into the kernel. 53 | // 54 | // It can be passed ebpf.CollectionSpec.Assign. 55 | type DummyXdpProgramSpecs struct { 56 | DummyXdp *ebpf.ProgramSpec `ebpf:"dummy_xdp"` 57 | } 58 | 59 | // DummyXdpMapSpecs contains maps before they are loaded into the kernel. 60 | // 61 | // It can be passed ebpf.CollectionSpec.Assign. 62 | type DummyXdpMapSpecs struct { 63 | } 64 | 65 | // DummyXdpObjects contains all objects after they have been loaded into the kernel. 66 | // 67 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 68 | type DummyXdpObjects struct { 69 | DummyXdpPrograms 70 | DummyXdpMaps 71 | } 72 | 73 | func (o *DummyXdpObjects) Close() error { 74 | return _DummyXdpClose( 75 | &o.DummyXdpPrograms, 76 | &o.DummyXdpMaps, 77 | ) 78 | } 79 | 80 | // DummyXdpMaps contains all maps after they have been loaded into the kernel. 81 | // 82 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 83 | type DummyXdpMaps struct { 84 | } 85 | 86 | func (m *DummyXdpMaps) Close() error { 87 | return _DummyXdpClose() 88 | } 89 | 90 | // DummyXdpPrograms contains all programs after they have been loaded into the kernel. 91 | // 92 | // It can be passed to LoadDummyXdpObjects or ebpf.CollectionSpec.LoadAndAssign. 93 | type DummyXdpPrograms struct { 94 | DummyXdp *ebpf.Program `ebpf:"dummy_xdp"` 95 | } 96 | 97 | func (p *DummyXdpPrograms) Close() error { 98 | return _DummyXdpClose( 99 | p.DummyXdp, 100 | ) 101 | } 102 | 103 | func _DummyXdpClose(closers ...io.Closer) error { 104 | for _, closer := range closers { 105 | if err := closer.Close(); err != nil { 106 | return err 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // Do not access this directly. 113 | // 114 | //go:embed dummyxdp_bpfel.o 115 | var _DummyXdpBytes []byte 116 | -------------------------------------------------------------------------------- /cmd/vxlan_agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strconv" 7 | "strings" 8 | "wormhole/internal/vxlan_agent" 9 | 10 | "github.com/sirupsen/logrus" 11 | "github.com/urfave/cli" 12 | "github.com/vishvananda/netlink" 13 | "gopkg.in/yaml.v3" 14 | ) 15 | 16 | type VxlanAgentConfig struct { 17 | Networks []VxlanAgentNetworkConfig `yaml:"networks"` 18 | } 19 | 20 | type VxlanAgentNetworkConfig struct { 21 | VNI int `yaml:"vni"` 22 | Address string `yaml:"address"` 23 | InternalNetworkInterfaces []string `yaml:"internal-network-interfaces"` 24 | ExternalNetworkInterfaces []string `yaml:"external-network-interfaces"` 25 | BorderIPs []string `yaml:"border-ips"` 26 | } 27 | 28 | func main() { 29 | app := &cli.App{ 30 | Name: "vxlan_agent", 31 | Usage: "vxlan_agent, is the program that will reside in each network and facilitate forwarding packets to other networks and also will report to controller", 32 | Flags: []cli.Flag{ 33 | &cli.StringFlag{ 34 | Name: "config", 35 | Value: "", 36 | Usage: "path to vxlan network config file", 37 | }, 38 | }, 39 | Action: ActivateVxlanAgent, 40 | } 41 | 42 | if err := app.Run(os.Args); err != nil { 43 | logrus.Fatalf("error %s", err) 44 | panic(err) 45 | } 46 | } 47 | 48 | func ActivateVxlanAgent(cCtx *cli.Context) error { 49 | networks := extractVxlanNetworks(readVxlanConfigFromFile(cCtx)) 50 | 51 | vxlanAgent := vxlan_agent.NewVxlanAgent(networks) 52 | 53 | return vxlanAgent.ActivateVxlanAgent() 54 | } 55 | 56 | func readVxlanConfigFromFile(cCtx *cli.Context) VxlanAgentConfig { 57 | configFilePath := strings.TrimSpace(cCtx.String("config")) 58 | 59 | file, err := os.Open(configFilePath) 60 | if err != nil { 61 | logrus.Fatalf("failed to open file: %s", err) 62 | } 63 | defer file.Close() 64 | 65 | content, err := io.ReadAll(file) 66 | if err != nil { 67 | logrus.Fatalf("failed to read file: %s", err) 68 | } 69 | 70 | var config VxlanAgentConfig 71 | 72 | err = yaml.Unmarshal(content, &config) 73 | if err != nil { 74 | logrus.Fatalf("failed to convert yaml to struct: %s", err) 75 | } 76 | return config 77 | } 78 | 79 | func extractVxlanNetworks(config VxlanAgentConfig) []vxlan_agent.VxlanAgentNetwork { 80 | networks := []vxlan_agent.VxlanAgentNetwork{} 81 | 82 | for _, netConfig := range config.Networks { 83 | 84 | parts := strings.Split(netConfig.Address, "/") 85 | if len(parts) != 2 { 86 | logrus.Fatalf("Invalid CIDR format %s", netConfig.Address) 87 | } 88 | 89 | address := parts[0] 90 | 91 | prefixlen, err := strconv.Atoi(parts[1]) 92 | if err != nil { 93 | logrus.Fatalf("Error parsing prefix length: %s", err) 94 | } 95 | 96 | networks = append(networks, vxlan_agent.VxlanAgentNetwork{ 97 | VNI: netConfig.VNI, 98 | Prefix: prefixlen, 99 | Address: address, 100 | InternalNetworkInterfaces: findNetworkInterfaces(netConfig.InternalNetworkInterfaces), 101 | ExternalNetworkInterfaces: findNetworkInterfaces(netConfig.ExternalNetworkInterfaces), 102 | BorderIPs: netConfig.BorderIPs, 103 | }) 104 | } 105 | return networks 106 | } 107 | 108 | func findNetworkInterfaces(interfaceNames []string) []netlink.Link { 109 | 110 | var ifaces []netlink.Link 111 | 112 | for _, ifaceName := range interfaceNames { 113 | iface, err := netlink.LinkByName(ifaceName) 114 | if err != nil { 115 | logrus.Fatalf("Getting interface %s: %s", ifaceName, err) 116 | } 117 | 118 | ifaces = append(ifaces, iface) 119 | } 120 | 121 | return ifaces 122 | } 123 | -------------------------------------------------------------------------------- /test/e2e/vxlan_agent/clab-topologies/vxlan.clab.yml: -------------------------------------------------------------------------------- 1 | name: vxlan 2 | 3 | topology: 4 | nodes: 5 | src: 6 | kind: linux 7 | image: wormhole/test_agent:latest 8 | # we cannot run program using entrypoint 9 | # or cmd here cause the network interface 10 | # at this point is not created by containerlab 11 | # yet. so we should run our commands with 12 | # docker exec programmatically in our tests, 13 | # so this won't work: 14 | # cmd: "sh -c '/build/vxlan_agent --if-name eth1'" 15 | binds: 16 | - /sys/kernel/debug:/sys/kernel/debug 17 | - /sys/fs/bpf:/sys/fs/bpf 18 | exec: 19 | - sh -c 'echo "ping 192.168.1.11" >> /root/.bash_history' 20 | - sh -c 'echo "/build/dummy_xdp --interface-name eth1" >> /root/.bash_history' 21 | - ip addr add 192.168.1.10/24 dev eth1 22 | - touch /tmp/is_clab_container_ready 23 | 24 | border1: 25 | kind: linux 26 | image: wormhole/test_agent:latest 27 | binds: 28 | - /sys/kernel/debug:/sys/kernel/debug 29 | - /sys/fs/bpf:/sys/fs/bpf 30 | exec: 31 | - sh -c 'echo "/build/vxlan_agent --config /build/vxlan_agent.config.yaml" >> /root/.bash_history' 32 | - ip addr add 3.3.3.1/24 dev eth2 33 | - | 34 | sh -c 'cat << EOF > /build/vxlan_agent.config.yaml 35 | networks: 36 | - vni: 0 37 | address: 192.168.1.0/24 38 | internal-network-interfaces: 39 | - eth1 40 | external-network-interfaces: 41 | - eth2 42 | border-ips: 43 | - 3.3.3.2 44 | EOF' 45 | - touch /tmp/is_clab_container_ready 46 | 47 | border2: 48 | kind: linux 49 | image: wormhole/test_agent:latest 50 | binds: 51 | - /sys/kernel/debug:/sys/kernel/debug 52 | - /sys/fs/bpf:/sys/fs/bpf 53 | exec: 54 | - sh -c 'echo "/build/vxlan_agent --config /build/vxlan_agent.config.yaml" >> /root/.bash_history' 55 | - ip addr add 3.3.3.2/24 dev eth2 56 | - | 57 | sh -c 'cat << EOF > /build/vxlan_agent.config.yaml 58 | networks: 59 | - vni: 0 60 | address: 192.168.1.0/24 61 | internal-network-interfaces: 62 | - eth1 63 | external-network-interfaces: 64 | - eth2 65 | border-ips: 66 | - 3.3.3.1 67 | EOF' 68 | - touch /tmp/is_clab_container_ready 69 | 70 | dst: 71 | kind: linux 72 | image: wormhole/test_agent:latest 73 | # we cannot run program using entrypoint 74 | # or cmd here cause the network interface 75 | # at this point is not created by containerlab 76 | # yet. so we should run our commands with 77 | # docker exec programmatically in our tests, 78 | # so this won't work: 79 | # cmd: "sh -c '/build/vxlan_agent --if-name eth1'" 80 | binds: 81 | - /sys/kernel/debug:/sys/kernel/debug 82 | - /sys/fs/bpf:/sys/fs/bpf 83 | exec: 84 | - sh -c 'echo "ping 192.168.1.10" >> /root/.bash_history' 85 | - sh -c 'echo "/build/dummy_xdp --interface-name eth1" >> /root/.bash_history' 86 | - ip addr add 192.168.1.11/24 dev eth1 87 | - touch /tmp/is_clab_container_ready 88 | 89 | links: 90 | - endpoints: [src:eth1, border1:eth1] 91 | # sometimes by default the containerlab would set MTU to 9500. 92 | # if the MTU is high the libbpf would complain like this: 93 | # libbpf: Kernel error message: veth: Peer MTU is too large to set XDP 94 | # Error: interface xdp attach failed: Numerical result out of range 95 | # But if you change the program type from SEC("xdp") to SEC("xdp.frags") 96 | # then the mtu can be anything, you'll be able to deal with the first page 97 | # of the jumbo frames in xdp.frags easily. 98 | # mtu: 1500 99 | - endpoints: [border1:eth2, border2:eth2] 100 | # mtu: 1500 101 | - endpoints: [border2:eth1, dst:eth1] 102 | # mtu: 1500 -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanTCExternalIpv4LpmKey struct { 16 | Prefixlen uint32 17 | Data [4]uint8 18 | } 19 | 20 | type VxlanTCExternalNetworkVni struct { 21 | Vni uint32 22 | Network VxlanTCExternalIpv4LpmKey 23 | InternalIfindexes [10]uint32 24 | InternalIfindexesSize uint32 25 | BorderIps [10]struct{ S_addr uint32 } 26 | BorderIpsSize uint32 27 | } 28 | 29 | // LoadVxlanTCExternal returns the embedded CollectionSpec for VxlanTCExternal. 30 | func LoadVxlanTCExternal() (*ebpf.CollectionSpec, error) { 31 | reader := bytes.NewReader(_VxlanTCExternalBytes) 32 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 33 | if err != nil { 34 | return nil, fmt.Errorf("can't load VxlanTCExternal: %w", err) 35 | } 36 | 37 | return spec, err 38 | } 39 | 40 | // LoadVxlanTCExternalObjects loads VxlanTCExternal and converts it into a struct. 41 | // 42 | // The following types are suitable as obj argument: 43 | // 44 | // *VxlanTCExternalObjects 45 | // *VxlanTCExternalPrograms 46 | // *VxlanTCExternalMaps 47 | // 48 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 49 | func LoadVxlanTCExternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 50 | spec, err := LoadVxlanTCExternal() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return spec.LoadAndAssign(obj, opts) 56 | } 57 | 58 | // VxlanTCExternalSpecs contains maps and programs before they are loaded into the kernel. 59 | // 60 | // It can be passed ebpf.CollectionSpec.Assign. 61 | type VxlanTCExternalSpecs struct { 62 | VxlanTCExternalProgramSpecs 63 | VxlanTCExternalMapSpecs 64 | } 65 | 66 | // VxlanTCExternalSpecs contains programs before they are loaded into the kernel. 67 | // 68 | // It can be passed ebpf.CollectionSpec.Assign. 69 | type VxlanTCExternalProgramSpecs struct { 70 | VxlanTcExternal *ebpf.ProgramSpec `ebpf:"vxlan_tc_external"` 71 | } 72 | 73 | // VxlanTCExternalMapSpecs contains maps before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type VxlanTCExternalMapSpecs struct { 77 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 78 | } 79 | 80 | // VxlanTCExternalObjects contains all objects after they have been loaded into the kernel. 81 | // 82 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 83 | type VxlanTCExternalObjects struct { 84 | VxlanTCExternalPrograms 85 | VxlanTCExternalMaps 86 | } 87 | 88 | func (o *VxlanTCExternalObjects) Close() error { 89 | return _VxlanTCExternalClose( 90 | &o.VxlanTCExternalPrograms, 91 | &o.VxlanTCExternalMaps, 92 | ) 93 | } 94 | 95 | // VxlanTCExternalMaps contains all maps after they have been loaded into the kernel. 96 | // 97 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 98 | type VxlanTCExternalMaps struct { 99 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 100 | } 101 | 102 | func (m *VxlanTCExternalMaps) Close() error { 103 | return _VxlanTCExternalClose( 104 | m.NetworksMap, 105 | ) 106 | } 107 | 108 | // VxlanTCExternalPrograms contains all programs after they have been loaded into the kernel. 109 | // 110 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 111 | type VxlanTCExternalPrograms struct { 112 | VxlanTcExternal *ebpf.Program `ebpf:"vxlan_tc_external"` 113 | } 114 | 115 | func (p *VxlanTCExternalPrograms) Close() error { 116 | return _VxlanTCExternalClose( 117 | p.VxlanTcExternal, 118 | ) 119 | } 120 | 121 | func _VxlanTCExternalClose(closers ...io.Closer) error { 122 | for _, closer := range closers { 123 | if err := closer.Close(); err != nil { 124 | return err 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | // Do not access this directly. 131 | // 132 | //go:embed vxlantcexternal_bpfeb.o 133 | var _VxlanTCExternalBytes []byte 134 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcexternal_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanTCExternalIpv4LpmKey struct { 16 | Prefixlen uint32 17 | Data [4]uint8 18 | } 19 | 20 | type VxlanTCExternalNetworkVni struct { 21 | Vni uint32 22 | Network VxlanTCExternalIpv4LpmKey 23 | InternalIfindexes [10]uint32 24 | InternalIfindexesSize uint32 25 | BorderIps [10]struct{ S_addr uint32 } 26 | BorderIpsSize uint32 27 | } 28 | 29 | // LoadVxlanTCExternal returns the embedded CollectionSpec for VxlanTCExternal. 30 | func LoadVxlanTCExternal() (*ebpf.CollectionSpec, error) { 31 | reader := bytes.NewReader(_VxlanTCExternalBytes) 32 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 33 | if err != nil { 34 | return nil, fmt.Errorf("can't load VxlanTCExternal: %w", err) 35 | } 36 | 37 | return spec, err 38 | } 39 | 40 | // LoadVxlanTCExternalObjects loads VxlanTCExternal and converts it into a struct. 41 | // 42 | // The following types are suitable as obj argument: 43 | // 44 | // *VxlanTCExternalObjects 45 | // *VxlanTCExternalPrograms 46 | // *VxlanTCExternalMaps 47 | // 48 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 49 | func LoadVxlanTCExternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 50 | spec, err := LoadVxlanTCExternal() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return spec.LoadAndAssign(obj, opts) 56 | } 57 | 58 | // VxlanTCExternalSpecs contains maps and programs before they are loaded into the kernel. 59 | // 60 | // It can be passed ebpf.CollectionSpec.Assign. 61 | type VxlanTCExternalSpecs struct { 62 | VxlanTCExternalProgramSpecs 63 | VxlanTCExternalMapSpecs 64 | } 65 | 66 | // VxlanTCExternalSpecs contains programs before they are loaded into the kernel. 67 | // 68 | // It can be passed ebpf.CollectionSpec.Assign. 69 | type VxlanTCExternalProgramSpecs struct { 70 | VxlanTcExternal *ebpf.ProgramSpec `ebpf:"vxlan_tc_external"` 71 | } 72 | 73 | // VxlanTCExternalMapSpecs contains maps before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type VxlanTCExternalMapSpecs struct { 77 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 78 | } 79 | 80 | // VxlanTCExternalObjects contains all objects after they have been loaded into the kernel. 81 | // 82 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 83 | type VxlanTCExternalObjects struct { 84 | VxlanTCExternalPrograms 85 | VxlanTCExternalMaps 86 | } 87 | 88 | func (o *VxlanTCExternalObjects) Close() error { 89 | return _VxlanTCExternalClose( 90 | &o.VxlanTCExternalPrograms, 91 | &o.VxlanTCExternalMaps, 92 | ) 93 | } 94 | 95 | // VxlanTCExternalMaps contains all maps after they have been loaded into the kernel. 96 | // 97 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 98 | type VxlanTCExternalMaps struct { 99 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 100 | } 101 | 102 | func (m *VxlanTCExternalMaps) Close() error { 103 | return _VxlanTCExternalClose( 104 | m.NetworksMap, 105 | ) 106 | } 107 | 108 | // VxlanTCExternalPrograms contains all programs after they have been loaded into the kernel. 109 | // 110 | // It can be passed to LoadVxlanTCExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 111 | type VxlanTCExternalPrograms struct { 112 | VxlanTcExternal *ebpf.Program `ebpf:"vxlan_tc_external"` 113 | } 114 | 115 | func (p *VxlanTCExternalPrograms) Close() error { 116 | return _VxlanTCExternalClose( 117 | p.VxlanTcExternal, 118 | ) 119 | } 120 | 121 | func _VxlanTCExternalClose(closers ...io.Closer) error { 122 | for _, closer := range closers { 123 | if err := closer.Close(); err != nil { 124 | return err 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | // Do not access this directly. 131 | // 132 | //go:embed vxlantcexternal_bpfel.o 133 | var _VxlanTCExternalBytes []byte 134 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentxdp_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type SwitchAgentXDPIfaceIndex struct { 16 | InterfaceIndex uint32 17 | _ [4]byte 18 | Timestamp uint64 19 | } 20 | 21 | type SwitchAgentXDPMacAddress struct{ Mac [6]uint8 } 22 | 23 | type SwitchAgentXDPMacAddressIfaceEntry struct { 24 | Mac SwitchAgentXDPMacAddress 25 | _ [2]byte 26 | Iface SwitchAgentXDPIfaceIndex 27 | } 28 | 29 | // LoadSwitchAgentXDP returns the embedded CollectionSpec for SwitchAgentXDP. 30 | func LoadSwitchAgentXDP() (*ebpf.CollectionSpec, error) { 31 | reader := bytes.NewReader(_SwitchAgentXDPBytes) 32 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 33 | if err != nil { 34 | return nil, fmt.Errorf("can't load SwitchAgentXDP: %w", err) 35 | } 36 | 37 | return spec, err 38 | } 39 | 40 | // LoadSwitchAgentXDPObjects loads SwitchAgentXDP and converts it into a struct. 41 | // 42 | // The following types are suitable as obj argument: 43 | // 44 | // *SwitchAgentXDPObjects 45 | // *SwitchAgentXDPPrograms 46 | // *SwitchAgentXDPMaps 47 | // 48 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 49 | func LoadSwitchAgentXDPObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 50 | spec, err := LoadSwitchAgentXDP() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return spec.LoadAndAssign(obj, opts) 56 | } 57 | 58 | // SwitchAgentXDPSpecs contains maps and programs before they are loaded into the kernel. 59 | // 60 | // It can be passed ebpf.CollectionSpec.Assign. 61 | type SwitchAgentXDPSpecs struct { 62 | SwitchAgentXDPProgramSpecs 63 | SwitchAgentXDPMapSpecs 64 | } 65 | 66 | // SwitchAgentXDPSpecs contains programs before they are loaded into the kernel. 67 | // 68 | // It can be passed ebpf.CollectionSpec.Assign. 69 | type SwitchAgentXDPProgramSpecs struct { 70 | SwitchAgentXdp *ebpf.ProgramSpec `ebpf:"switch_agent_xdp"` 71 | } 72 | 73 | // SwitchAgentXDPMapSpecs contains maps before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type SwitchAgentXDPMapSpecs struct { 77 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 78 | NewDiscoveredEntriesRb *ebpf.MapSpec `ebpf:"new_discovered_entries_rb"` 79 | } 80 | 81 | // SwitchAgentXDPObjects contains all objects after they have been loaded into the kernel. 82 | // 83 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 84 | type SwitchAgentXDPObjects struct { 85 | SwitchAgentXDPPrograms 86 | SwitchAgentXDPMaps 87 | } 88 | 89 | func (o *SwitchAgentXDPObjects) Close() error { 90 | return _SwitchAgentXDPClose( 91 | &o.SwitchAgentXDPPrograms, 92 | &o.SwitchAgentXDPMaps, 93 | ) 94 | } 95 | 96 | // SwitchAgentXDPMaps contains all maps after they have been loaded into the kernel. 97 | // 98 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 99 | type SwitchAgentXDPMaps struct { 100 | MacTable *ebpf.Map `ebpf:"mac_table"` 101 | NewDiscoveredEntriesRb *ebpf.Map `ebpf:"new_discovered_entries_rb"` 102 | } 103 | 104 | func (m *SwitchAgentXDPMaps) Close() error { 105 | return _SwitchAgentXDPClose( 106 | m.MacTable, 107 | m.NewDiscoveredEntriesRb, 108 | ) 109 | } 110 | 111 | // SwitchAgentXDPPrograms contains all programs after they have been loaded into the kernel. 112 | // 113 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 114 | type SwitchAgentXDPPrograms struct { 115 | SwitchAgentXdp *ebpf.Program `ebpf:"switch_agent_xdp"` 116 | } 117 | 118 | func (p *SwitchAgentXDPPrograms) Close() error { 119 | return _SwitchAgentXDPClose( 120 | p.SwitchAgentXdp, 121 | ) 122 | } 123 | 124 | func _SwitchAgentXDPClose(closers ...io.Closer) error { 125 | for _, closer := range closers { 126 | if err := closer.Close(); err != nil { 127 | return err 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | // Do not access this directly. 134 | // 135 | //go:embed switchagentxdp_bpfeb.o 136 | var _SwitchAgentXDPBytes []byte 137 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentxdp_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type SwitchAgentXDPIfaceIndex struct { 16 | InterfaceIndex uint32 17 | _ [4]byte 18 | Timestamp uint64 19 | } 20 | 21 | type SwitchAgentXDPMacAddress struct{ Mac [6]uint8 } 22 | 23 | type SwitchAgentXDPMacAddressIfaceEntry struct { 24 | Mac SwitchAgentXDPMacAddress 25 | _ [2]byte 26 | Iface SwitchAgentXDPIfaceIndex 27 | } 28 | 29 | // LoadSwitchAgentXDP returns the embedded CollectionSpec for SwitchAgentXDP. 30 | func LoadSwitchAgentXDP() (*ebpf.CollectionSpec, error) { 31 | reader := bytes.NewReader(_SwitchAgentXDPBytes) 32 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 33 | if err != nil { 34 | return nil, fmt.Errorf("can't load SwitchAgentXDP: %w", err) 35 | } 36 | 37 | return spec, err 38 | } 39 | 40 | // LoadSwitchAgentXDPObjects loads SwitchAgentXDP and converts it into a struct. 41 | // 42 | // The following types are suitable as obj argument: 43 | // 44 | // *SwitchAgentXDPObjects 45 | // *SwitchAgentXDPPrograms 46 | // *SwitchAgentXDPMaps 47 | // 48 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 49 | func LoadSwitchAgentXDPObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 50 | spec, err := LoadSwitchAgentXDP() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | return spec.LoadAndAssign(obj, opts) 56 | } 57 | 58 | // SwitchAgentXDPSpecs contains maps and programs before they are loaded into the kernel. 59 | // 60 | // It can be passed ebpf.CollectionSpec.Assign. 61 | type SwitchAgentXDPSpecs struct { 62 | SwitchAgentXDPProgramSpecs 63 | SwitchAgentXDPMapSpecs 64 | } 65 | 66 | // SwitchAgentXDPSpecs contains programs before they are loaded into the kernel. 67 | // 68 | // It can be passed ebpf.CollectionSpec.Assign. 69 | type SwitchAgentXDPProgramSpecs struct { 70 | SwitchAgentXdp *ebpf.ProgramSpec `ebpf:"switch_agent_xdp"` 71 | } 72 | 73 | // SwitchAgentXDPMapSpecs contains maps before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type SwitchAgentXDPMapSpecs struct { 77 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 78 | NewDiscoveredEntriesRb *ebpf.MapSpec `ebpf:"new_discovered_entries_rb"` 79 | } 80 | 81 | // SwitchAgentXDPObjects contains all objects after they have been loaded into the kernel. 82 | // 83 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 84 | type SwitchAgentXDPObjects struct { 85 | SwitchAgentXDPPrograms 86 | SwitchAgentXDPMaps 87 | } 88 | 89 | func (o *SwitchAgentXDPObjects) Close() error { 90 | return _SwitchAgentXDPClose( 91 | &o.SwitchAgentXDPPrograms, 92 | &o.SwitchAgentXDPMaps, 93 | ) 94 | } 95 | 96 | // SwitchAgentXDPMaps contains all maps after they have been loaded into the kernel. 97 | // 98 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 99 | type SwitchAgentXDPMaps struct { 100 | MacTable *ebpf.Map `ebpf:"mac_table"` 101 | NewDiscoveredEntriesRb *ebpf.Map `ebpf:"new_discovered_entries_rb"` 102 | } 103 | 104 | func (m *SwitchAgentXDPMaps) Close() error { 105 | return _SwitchAgentXDPClose( 106 | m.MacTable, 107 | m.NewDiscoveredEntriesRb, 108 | ) 109 | } 110 | 111 | // SwitchAgentXDPPrograms contains all programs after they have been loaded into the kernel. 112 | // 113 | // It can be passed to LoadSwitchAgentXDPObjects or ebpf.CollectionSpec.LoadAndAssign. 114 | type SwitchAgentXDPPrograms struct { 115 | SwitchAgentXdp *ebpf.Program `ebpf:"switch_agent_xdp"` 116 | } 117 | 118 | func (p *SwitchAgentXDPPrograms) Close() error { 119 | return _SwitchAgentXDPClose( 120 | p.SwitchAgentXdp, 121 | ) 122 | } 123 | 124 | func _SwitchAgentXDPClose(closers ...io.Closer) error { 125 | for _, closer := range closers { 126 | if err := closer.Close(); err != nil { 127 | return err 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | // Do not access this directly. 134 | // 135 | //go:embed switchagentxdp_bpfel.o 136 | var _SwitchAgentXDPBytes []byte 137 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module wormhole 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/allegro/bigcache/v3 v3.1.0 7 | github.com/cilium/ebpf v0.15.0 8 | github.com/docker/docker v24.0.9+incompatible 9 | github.com/janog-netcon/netcon-problem-management-subsystem v0.0.0-20240127085854-07c265d714d1 10 | github.com/mostlygeek/arp v0.0.0-20170424181311-541a2129847a 11 | github.com/onsi/ginkgo v1.16.5 12 | github.com/onsi/gomega v1.32.0 13 | github.com/prometheus-community/pro-bing v0.4.0 14 | github.com/sirupsen/logrus v1.9.3 15 | github.com/urfave/cli v1.22.14 16 | github.com/vishvananda/netlink v1.1.0 17 | golang.org/x/net v0.29.0 18 | google.golang.org/grpc v1.66.2 19 | google.golang.org/protobuf v1.34.2 20 | gopkg.in/yaml.v3 v3.0.1 21 | ) 22 | 23 | require ( 24 | github.com/Microsoft/go-winio v0.6.0 // indirect 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 27 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/docker/distribution v2.8.2+incompatible // indirect 30 | github.com/docker/go-connections v0.4.0 // indirect 31 | github.com/docker/go-units v0.5.0 // indirect 32 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 33 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 34 | github.com/fsnotify/fsnotify v1.7.0 // indirect 35 | github.com/go-logr/logr v1.3.0 // indirect 36 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 37 | github.com/go-openapi/jsonreference v0.20.2 // indirect 38 | github.com/go-openapi/swag v0.22.4 // indirect 39 | github.com/gogo/protobuf v1.3.2 // indirect 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 41 | github.com/golang/protobuf v1.5.4 // indirect 42 | github.com/google/gnostic-models v0.6.8 // indirect 43 | github.com/google/go-cmp v0.6.0 // indirect 44 | github.com/google/gofuzz v1.2.0 // indirect 45 | github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect 46 | github.com/google/uuid v1.6.0 // indirect 47 | github.com/imdario/mergo v0.3.13 // indirect 48 | github.com/josharian/intern v1.0.0 // indirect 49 | github.com/json-iterator/go v1.1.12 // indirect 50 | github.com/mailru/easyjson v0.7.7 // indirect 51 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 53 | github.com/modern-go/reflect2 v1.0.2 // indirect 54 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 55 | github.com/nxadm/tail v1.4.8 // indirect 56 | github.com/opencontainers/go-digest v1.0.0 // indirect 57 | github.com/opencontainers/image-spec v1.0.2 // indirect 58 | github.com/pkg/errors v0.9.1 // indirect 59 | github.com/prometheus/client_golang v1.17.0 // indirect 60 | github.com/prometheus/client_model v0.5.0 // indirect 61 | github.com/prometheus/common v0.44.0 // indirect 62 | github.com/prometheus/procfs v0.11.1 // indirect 63 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 64 | github.com/spf13/pflag v1.0.5 // indirect 65 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect 66 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect 67 | golang.org/x/mod v0.17.0 // indirect 68 | golang.org/x/oauth2 v0.21.0 // indirect 69 | golang.org/x/sync v0.8.0 // indirect 70 | golang.org/x/sys v0.25.0 // indirect 71 | golang.org/x/term v0.24.0 // indirect 72 | golang.org/x/text v0.18.0 // indirect 73 | golang.org/x/time v0.3.0 // indirect 74 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 75 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 76 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect 77 | gopkg.in/inf.v0 v0.9.1 // indirect 78 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 79 | gopkg.in/yaml.v2 v2.4.0 // indirect 80 | k8s.io/api v0.29.1 // indirect 81 | k8s.io/apiextensions-apiserver v0.28.3 // indirect 82 | k8s.io/apimachinery v0.29.1 // indirect 83 | k8s.io/client-go v0.29.1 // indirect 84 | k8s.io/component-base v0.28.3 // indirect 85 | k8s.io/klog/v2 v2.110.1 // indirect 86 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect 87 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 88 | sigs.k8s.io/controller-runtime v0.16.3 // indirect 89 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 90 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 91 | sigs.k8s.io/yaml v1.4.0 // indirect 92 | ) 93 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanXDPExternalIpv4LpmKey struct { 16 | Prefixlen uint32 17 | Data [4]uint8 18 | } 19 | 20 | type VxlanXDPExternalMacAddress struct{ Addr [6]uint8 } 21 | 22 | type VxlanXDPExternalMacTableEntry struct { 23 | ExpirationTimer struct{ Opaque [2]uint64 } 24 | Ifindex uint32 25 | _ [4]byte 26 | LastSeenTimestampNs uint64 27 | BorderIp struct{ S_addr uint32 } 28 | _ [4]byte 29 | } 30 | 31 | type VxlanXDPExternalNetworkVniLight struct { 32 | Vni uint32 33 | Network VxlanXDPExternalIpv4LpmKey 34 | } 35 | 36 | // LoadVxlanXDPExternal returns the embedded CollectionSpec for VxlanXDPExternal. 37 | func LoadVxlanXDPExternal() (*ebpf.CollectionSpec, error) { 38 | reader := bytes.NewReader(_VxlanXDPExternalBytes) 39 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 40 | if err != nil { 41 | return nil, fmt.Errorf("can't load VxlanXDPExternal: %w", err) 42 | } 43 | 44 | return spec, err 45 | } 46 | 47 | // LoadVxlanXDPExternalObjects loads VxlanXDPExternal and converts it into a struct. 48 | // 49 | // The following types are suitable as obj argument: 50 | // 51 | // *VxlanXDPExternalObjects 52 | // *VxlanXDPExternalPrograms 53 | // *VxlanXDPExternalMaps 54 | // 55 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 56 | func LoadVxlanXDPExternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 57 | spec, err := LoadVxlanXDPExternal() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return spec.LoadAndAssign(obj, opts) 63 | } 64 | 65 | // VxlanXDPExternalSpecs contains maps and programs before they are loaded into the kernel. 66 | // 67 | // It can be passed ebpf.CollectionSpec.Assign. 68 | type VxlanXDPExternalSpecs struct { 69 | VxlanXDPExternalProgramSpecs 70 | VxlanXDPExternalMapSpecs 71 | } 72 | 73 | // VxlanXDPExternalSpecs contains programs before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type VxlanXDPExternalProgramSpecs struct { 77 | VxlanXdpExternal *ebpf.ProgramSpec `ebpf:"vxlan_xdp_external"` 78 | } 79 | 80 | // VxlanXDPExternalMapSpecs contains maps before they are loaded into the kernel. 81 | // 82 | // It can be passed ebpf.CollectionSpec.Assign. 83 | type VxlanXDPExternalMapSpecs struct { 84 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 85 | NetworksLightMap *ebpf.MapSpec `ebpf:"networks_light_map"` 86 | } 87 | 88 | // VxlanXDPExternalObjects contains all objects after they have been loaded into the kernel. 89 | // 90 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 91 | type VxlanXDPExternalObjects struct { 92 | VxlanXDPExternalPrograms 93 | VxlanXDPExternalMaps 94 | } 95 | 96 | func (o *VxlanXDPExternalObjects) Close() error { 97 | return _VxlanXDPExternalClose( 98 | &o.VxlanXDPExternalPrograms, 99 | &o.VxlanXDPExternalMaps, 100 | ) 101 | } 102 | 103 | // VxlanXDPExternalMaps contains all maps after they have been loaded into the kernel. 104 | // 105 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 106 | type VxlanXDPExternalMaps struct { 107 | MacTable *ebpf.Map `ebpf:"mac_table"` 108 | NetworksLightMap *ebpf.Map `ebpf:"networks_light_map"` 109 | } 110 | 111 | func (m *VxlanXDPExternalMaps) Close() error { 112 | return _VxlanXDPExternalClose( 113 | m.MacTable, 114 | m.NetworksLightMap, 115 | ) 116 | } 117 | 118 | // VxlanXDPExternalPrograms contains all programs after they have been loaded into the kernel. 119 | // 120 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 121 | type VxlanXDPExternalPrograms struct { 122 | VxlanXdpExternal *ebpf.Program `ebpf:"vxlan_xdp_external"` 123 | } 124 | 125 | func (p *VxlanXDPExternalPrograms) Close() error { 126 | return _VxlanXDPExternalClose( 127 | p.VxlanXdpExternal, 128 | ) 129 | } 130 | 131 | func _VxlanXDPExternalClose(closers ...io.Closer) error { 132 | for _, closer := range closers { 133 | if err := closer.Close(); err != nil { 134 | return err 135 | } 136 | } 137 | return nil 138 | } 139 | 140 | // Do not access this directly. 141 | // 142 | //go:embed vxlanxdpexternal_bpfeb.o 143 | var _VxlanXDPExternalBytes []byte 144 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpexternal_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanXDPExternalIpv4LpmKey struct { 16 | Prefixlen uint32 17 | Data [4]uint8 18 | } 19 | 20 | type VxlanXDPExternalMacAddress struct{ Addr [6]uint8 } 21 | 22 | type VxlanXDPExternalMacTableEntry struct { 23 | ExpirationTimer struct{ Opaque [2]uint64 } 24 | Ifindex uint32 25 | _ [4]byte 26 | LastSeenTimestampNs uint64 27 | BorderIp struct{ S_addr uint32 } 28 | _ [4]byte 29 | } 30 | 31 | type VxlanXDPExternalNetworkVniLight struct { 32 | Vni uint32 33 | Network VxlanXDPExternalIpv4LpmKey 34 | } 35 | 36 | // LoadVxlanXDPExternal returns the embedded CollectionSpec for VxlanXDPExternal. 37 | func LoadVxlanXDPExternal() (*ebpf.CollectionSpec, error) { 38 | reader := bytes.NewReader(_VxlanXDPExternalBytes) 39 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 40 | if err != nil { 41 | return nil, fmt.Errorf("can't load VxlanXDPExternal: %w", err) 42 | } 43 | 44 | return spec, err 45 | } 46 | 47 | // LoadVxlanXDPExternalObjects loads VxlanXDPExternal and converts it into a struct. 48 | // 49 | // The following types are suitable as obj argument: 50 | // 51 | // *VxlanXDPExternalObjects 52 | // *VxlanXDPExternalPrograms 53 | // *VxlanXDPExternalMaps 54 | // 55 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 56 | func LoadVxlanXDPExternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 57 | spec, err := LoadVxlanXDPExternal() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return spec.LoadAndAssign(obj, opts) 63 | } 64 | 65 | // VxlanXDPExternalSpecs contains maps and programs before they are loaded into the kernel. 66 | // 67 | // It can be passed ebpf.CollectionSpec.Assign. 68 | type VxlanXDPExternalSpecs struct { 69 | VxlanXDPExternalProgramSpecs 70 | VxlanXDPExternalMapSpecs 71 | } 72 | 73 | // VxlanXDPExternalSpecs contains programs before they are loaded into the kernel. 74 | // 75 | // It can be passed ebpf.CollectionSpec.Assign. 76 | type VxlanXDPExternalProgramSpecs struct { 77 | VxlanXdpExternal *ebpf.ProgramSpec `ebpf:"vxlan_xdp_external"` 78 | } 79 | 80 | // VxlanXDPExternalMapSpecs contains maps before they are loaded into the kernel. 81 | // 82 | // It can be passed ebpf.CollectionSpec.Assign. 83 | type VxlanXDPExternalMapSpecs struct { 84 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 85 | NetworksLightMap *ebpf.MapSpec `ebpf:"networks_light_map"` 86 | } 87 | 88 | // VxlanXDPExternalObjects contains all objects after they have been loaded into the kernel. 89 | // 90 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 91 | type VxlanXDPExternalObjects struct { 92 | VxlanXDPExternalPrograms 93 | VxlanXDPExternalMaps 94 | } 95 | 96 | func (o *VxlanXDPExternalObjects) Close() error { 97 | return _VxlanXDPExternalClose( 98 | &o.VxlanXDPExternalPrograms, 99 | &o.VxlanXDPExternalMaps, 100 | ) 101 | } 102 | 103 | // VxlanXDPExternalMaps contains all maps after they have been loaded into the kernel. 104 | // 105 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 106 | type VxlanXDPExternalMaps struct { 107 | MacTable *ebpf.Map `ebpf:"mac_table"` 108 | NetworksLightMap *ebpf.Map `ebpf:"networks_light_map"` 109 | } 110 | 111 | func (m *VxlanXDPExternalMaps) Close() error { 112 | return _VxlanXDPExternalClose( 113 | m.MacTable, 114 | m.NetworksLightMap, 115 | ) 116 | } 117 | 118 | // VxlanXDPExternalPrograms contains all programs after they have been loaded into the kernel. 119 | // 120 | // It can be passed to LoadVxlanXDPExternalObjects or ebpf.CollectionSpec.LoadAndAssign. 121 | type VxlanXDPExternalPrograms struct { 122 | VxlanXdpExternal *ebpf.Program `ebpf:"vxlan_xdp_external"` 123 | } 124 | 125 | func (p *VxlanXDPExternalPrograms) Close() error { 126 | return _VxlanXDPExternalClose( 127 | p.VxlanXdpExternal, 128 | ) 129 | } 130 | 131 | func _VxlanXDPExternalClose(closers ...io.Closer) error { 132 | for _, closer := range closers { 133 | if err := closer.Close(); err != nil { 134 | return err 135 | } 136 | } 137 | return nil 138 | } 139 | 140 | // Do not access this directly. 141 | // 142 | //go:embed vxlanxdpexternal_bpfel.o 143 | var _VxlanXDPExternalBytes []byte 144 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanTCInternalExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac struct{ Addr [6]uint8 } 18 | ExternalIfaceNextHopMac struct{ Addr [6]uint8 } 19 | ExternalIfaceIp VxlanTCInternalInAddr 20 | } 21 | 22 | type VxlanTCInternalInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanTCInternalIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanTCInternalNetworkVni struct { 30 | Vni uint32 31 | Network VxlanTCInternalIpv4LpmKey 32 | InternalIfindexes [10]uint32 33 | InternalIfindexesSize uint32 34 | BorderIps [10]VxlanTCInternalInAddr 35 | BorderIpsSize uint32 36 | } 37 | 38 | // LoadVxlanTCInternal returns the embedded CollectionSpec for VxlanTCInternal. 39 | func LoadVxlanTCInternal() (*ebpf.CollectionSpec, error) { 40 | reader := bytes.NewReader(_VxlanTCInternalBytes) 41 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 42 | if err != nil { 43 | return nil, fmt.Errorf("can't load VxlanTCInternal: %w", err) 44 | } 45 | 46 | return spec, err 47 | } 48 | 49 | // LoadVxlanTCInternalObjects loads VxlanTCInternal and converts it into a struct. 50 | // 51 | // The following types are suitable as obj argument: 52 | // 53 | // *VxlanTCInternalObjects 54 | // *VxlanTCInternalPrograms 55 | // *VxlanTCInternalMaps 56 | // 57 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 58 | func LoadVxlanTCInternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 59 | spec, err := LoadVxlanTCInternal() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return spec.LoadAndAssign(obj, opts) 65 | } 66 | 67 | // VxlanTCInternalSpecs contains maps and programs before they are loaded into the kernel. 68 | // 69 | // It can be passed ebpf.CollectionSpec.Assign. 70 | type VxlanTCInternalSpecs struct { 71 | VxlanTCInternalProgramSpecs 72 | VxlanTCInternalMapSpecs 73 | } 74 | 75 | // VxlanTCInternalSpecs contains programs before they are loaded into the kernel. 76 | // 77 | // It can be passed ebpf.CollectionSpec.Assign. 78 | type VxlanTCInternalProgramSpecs struct { 79 | VxlanTcInternal *ebpf.ProgramSpec `ebpf:"vxlan_tc_internal"` 80 | } 81 | 82 | // VxlanTCInternalMapSpecs contains maps before they are loaded into the kernel. 83 | // 84 | // It can be passed ebpf.CollectionSpec.Assign. 85 | type VxlanTCInternalMapSpecs struct { 86 | BorderIpToRouteInfoMap *ebpf.MapSpec `ebpf:"border_ip_to_route_info_map"` 87 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 88 | } 89 | 90 | // VxlanTCInternalObjects contains all objects after they have been loaded into the kernel. 91 | // 92 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 93 | type VxlanTCInternalObjects struct { 94 | VxlanTCInternalPrograms 95 | VxlanTCInternalMaps 96 | } 97 | 98 | func (o *VxlanTCInternalObjects) Close() error { 99 | return _VxlanTCInternalClose( 100 | &o.VxlanTCInternalPrograms, 101 | &o.VxlanTCInternalMaps, 102 | ) 103 | } 104 | 105 | // VxlanTCInternalMaps contains all maps after they have been loaded into the kernel. 106 | // 107 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 108 | type VxlanTCInternalMaps struct { 109 | BorderIpToRouteInfoMap *ebpf.Map `ebpf:"border_ip_to_route_info_map"` 110 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 111 | } 112 | 113 | func (m *VxlanTCInternalMaps) Close() error { 114 | return _VxlanTCInternalClose( 115 | m.BorderIpToRouteInfoMap, 116 | m.NetworksMap, 117 | ) 118 | } 119 | 120 | // VxlanTCInternalPrograms contains all programs after they have been loaded into the kernel. 121 | // 122 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 123 | type VxlanTCInternalPrograms struct { 124 | VxlanTcInternal *ebpf.Program `ebpf:"vxlan_tc_internal"` 125 | } 126 | 127 | func (p *VxlanTCInternalPrograms) Close() error { 128 | return _VxlanTCInternalClose( 129 | p.VxlanTcInternal, 130 | ) 131 | } 132 | 133 | func _VxlanTCInternalClose(closers ...io.Closer) error { 134 | for _, closer := range closers { 135 | if err := closer.Close(); err != nil { 136 | return err 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | // Do not access this directly. 143 | // 144 | //go:embed vxlantcinternal_bpfeb.o 145 | var _VxlanTCInternalBytes []byte 146 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlantcinternal_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanTCInternalExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac struct{ Addr [6]uint8 } 18 | ExternalIfaceNextHopMac struct{ Addr [6]uint8 } 19 | ExternalIfaceIp VxlanTCInternalInAddr 20 | } 21 | 22 | type VxlanTCInternalInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanTCInternalIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanTCInternalNetworkVni struct { 30 | Vni uint32 31 | Network VxlanTCInternalIpv4LpmKey 32 | InternalIfindexes [10]uint32 33 | InternalIfindexesSize uint32 34 | BorderIps [10]VxlanTCInternalInAddr 35 | BorderIpsSize uint32 36 | } 37 | 38 | // LoadVxlanTCInternal returns the embedded CollectionSpec for VxlanTCInternal. 39 | func LoadVxlanTCInternal() (*ebpf.CollectionSpec, error) { 40 | reader := bytes.NewReader(_VxlanTCInternalBytes) 41 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 42 | if err != nil { 43 | return nil, fmt.Errorf("can't load VxlanTCInternal: %w", err) 44 | } 45 | 46 | return spec, err 47 | } 48 | 49 | // LoadVxlanTCInternalObjects loads VxlanTCInternal and converts it into a struct. 50 | // 51 | // The following types are suitable as obj argument: 52 | // 53 | // *VxlanTCInternalObjects 54 | // *VxlanTCInternalPrograms 55 | // *VxlanTCInternalMaps 56 | // 57 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 58 | func LoadVxlanTCInternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 59 | spec, err := LoadVxlanTCInternal() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return spec.LoadAndAssign(obj, opts) 65 | } 66 | 67 | // VxlanTCInternalSpecs contains maps and programs before they are loaded into the kernel. 68 | // 69 | // It can be passed ebpf.CollectionSpec.Assign. 70 | type VxlanTCInternalSpecs struct { 71 | VxlanTCInternalProgramSpecs 72 | VxlanTCInternalMapSpecs 73 | } 74 | 75 | // VxlanTCInternalSpecs contains programs before they are loaded into the kernel. 76 | // 77 | // It can be passed ebpf.CollectionSpec.Assign. 78 | type VxlanTCInternalProgramSpecs struct { 79 | VxlanTcInternal *ebpf.ProgramSpec `ebpf:"vxlan_tc_internal"` 80 | } 81 | 82 | // VxlanTCInternalMapSpecs contains maps before they are loaded into the kernel. 83 | // 84 | // It can be passed ebpf.CollectionSpec.Assign. 85 | type VxlanTCInternalMapSpecs struct { 86 | BorderIpToRouteInfoMap *ebpf.MapSpec `ebpf:"border_ip_to_route_info_map"` 87 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 88 | } 89 | 90 | // VxlanTCInternalObjects contains all objects after they have been loaded into the kernel. 91 | // 92 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 93 | type VxlanTCInternalObjects struct { 94 | VxlanTCInternalPrograms 95 | VxlanTCInternalMaps 96 | } 97 | 98 | func (o *VxlanTCInternalObjects) Close() error { 99 | return _VxlanTCInternalClose( 100 | &o.VxlanTCInternalPrograms, 101 | &o.VxlanTCInternalMaps, 102 | ) 103 | } 104 | 105 | // VxlanTCInternalMaps contains all maps after they have been loaded into the kernel. 106 | // 107 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 108 | type VxlanTCInternalMaps struct { 109 | BorderIpToRouteInfoMap *ebpf.Map `ebpf:"border_ip_to_route_info_map"` 110 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 111 | } 112 | 113 | func (m *VxlanTCInternalMaps) Close() error { 114 | return _VxlanTCInternalClose( 115 | m.BorderIpToRouteInfoMap, 116 | m.NetworksMap, 117 | ) 118 | } 119 | 120 | // VxlanTCInternalPrograms contains all programs after they have been loaded into the kernel. 121 | // 122 | // It can be passed to LoadVxlanTCInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 123 | type VxlanTCInternalPrograms struct { 124 | VxlanTcInternal *ebpf.Program `ebpf:"vxlan_tc_internal"` 125 | } 126 | 127 | func (p *VxlanTCInternalPrograms) Close() error { 128 | return _VxlanTCInternalClose( 129 | p.VxlanTcInternal, 130 | ) 131 | } 132 | 133 | func _VxlanTCInternalClose(closers ...io.Closer) error { 134 | for _, closer := range closers { 135 | if err := closer.Close(); err != nil { 136 | return err 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | // Do not access this directly. 143 | // 144 | //go:embed vxlantcinternal_bpfel.o 145 | var _VxlanTCInternalBytes []byte 146 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | // LoadSwitchAgentUnknownUnicastFlooding returns the embedded CollectionSpec for SwitchAgentUnknownUnicastFlooding. 16 | func LoadSwitchAgentUnknownUnicastFlooding() (*ebpf.CollectionSpec, error) { 17 | reader := bytes.NewReader(_SwitchAgentUnknownUnicastFloodingBytes) 18 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 19 | if err != nil { 20 | return nil, fmt.Errorf("can't load SwitchAgentUnknownUnicastFlooding: %w", err) 21 | } 22 | 23 | return spec, err 24 | } 25 | 26 | // LoadSwitchAgentUnknownUnicastFloodingObjects loads SwitchAgentUnknownUnicastFlooding and converts it into a struct. 27 | // 28 | // The following types are suitable as obj argument: 29 | // 30 | // *SwitchAgentUnknownUnicastFloodingObjects 31 | // *SwitchAgentUnknownUnicastFloodingPrograms 32 | // *SwitchAgentUnknownUnicastFloodingMaps 33 | // 34 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 35 | func LoadSwitchAgentUnknownUnicastFloodingObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 36 | spec, err := LoadSwitchAgentUnknownUnicastFlooding() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return spec.LoadAndAssign(obj, opts) 42 | } 43 | 44 | // SwitchAgentUnknownUnicastFloodingSpecs contains maps and programs before they are loaded into the kernel. 45 | // 46 | // It can be passed ebpf.CollectionSpec.Assign. 47 | type SwitchAgentUnknownUnicastFloodingSpecs struct { 48 | SwitchAgentUnknownUnicastFloodingProgramSpecs 49 | SwitchAgentUnknownUnicastFloodingMapSpecs 50 | } 51 | 52 | // SwitchAgentUnknownUnicastFloodingSpecs contains programs before they are loaded into the kernel. 53 | // 54 | // It can be passed ebpf.CollectionSpec.Assign. 55 | type SwitchAgentUnknownUnicastFloodingProgramSpecs struct { 56 | SwitchAgentUnknownUnicastFlooding *ebpf.ProgramSpec `ebpf:"switch_agent_unknown_unicast_flooding"` 57 | } 58 | 59 | // SwitchAgentUnknownUnicastFloodingMapSpecs contains maps before they are loaded into the kernel. 60 | // 61 | // It can be passed ebpf.CollectionSpec.Assign. 62 | type SwitchAgentUnknownUnicastFloodingMapSpecs struct { 63 | InterfacesArray *ebpf.MapSpec `ebpf:"interfaces_array"` 64 | InterfacesArrayLength *ebpf.MapSpec `ebpf:"interfaces_array_length"` 65 | } 66 | 67 | // SwitchAgentUnknownUnicastFloodingObjects contains all objects after they have been loaded into the kernel. 68 | // 69 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 70 | type SwitchAgentUnknownUnicastFloodingObjects struct { 71 | SwitchAgentUnknownUnicastFloodingPrograms 72 | SwitchAgentUnknownUnicastFloodingMaps 73 | } 74 | 75 | func (o *SwitchAgentUnknownUnicastFloodingObjects) Close() error { 76 | return _SwitchAgentUnknownUnicastFloodingClose( 77 | &o.SwitchAgentUnknownUnicastFloodingPrograms, 78 | &o.SwitchAgentUnknownUnicastFloodingMaps, 79 | ) 80 | } 81 | 82 | // SwitchAgentUnknownUnicastFloodingMaps contains all maps after they have been loaded into the kernel. 83 | // 84 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 85 | type SwitchAgentUnknownUnicastFloodingMaps struct { 86 | InterfacesArray *ebpf.Map `ebpf:"interfaces_array"` 87 | InterfacesArrayLength *ebpf.Map `ebpf:"interfaces_array_length"` 88 | } 89 | 90 | func (m *SwitchAgentUnknownUnicastFloodingMaps) Close() error { 91 | return _SwitchAgentUnknownUnicastFloodingClose( 92 | m.InterfacesArray, 93 | m.InterfacesArrayLength, 94 | ) 95 | } 96 | 97 | // SwitchAgentUnknownUnicastFloodingPrograms contains all programs after they have been loaded into the kernel. 98 | // 99 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 100 | type SwitchAgentUnknownUnicastFloodingPrograms struct { 101 | SwitchAgentUnknownUnicastFlooding *ebpf.Program `ebpf:"switch_agent_unknown_unicast_flooding"` 102 | } 103 | 104 | func (p *SwitchAgentUnknownUnicastFloodingPrograms) Close() error { 105 | return _SwitchAgentUnknownUnicastFloodingClose( 106 | p.SwitchAgentUnknownUnicastFlooding, 107 | ) 108 | } 109 | 110 | func _SwitchAgentUnknownUnicastFloodingClose(closers ...io.Closer) error { 111 | for _, closer := range closers { 112 | if err := closer.Close(); err != nil { 113 | return err 114 | } 115 | } 116 | return nil 117 | } 118 | 119 | // Do not access this directly. 120 | // 121 | //go:embed switchagentunknownunicastflooding_bpfeb.o 122 | var _SwitchAgentUnknownUnicastFloodingBytes []byte 123 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/switchagentunknownunicastflooding_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | // LoadSwitchAgentUnknownUnicastFlooding returns the embedded CollectionSpec for SwitchAgentUnknownUnicastFlooding. 16 | func LoadSwitchAgentUnknownUnicastFlooding() (*ebpf.CollectionSpec, error) { 17 | reader := bytes.NewReader(_SwitchAgentUnknownUnicastFloodingBytes) 18 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 19 | if err != nil { 20 | return nil, fmt.Errorf("can't load SwitchAgentUnknownUnicastFlooding: %w", err) 21 | } 22 | 23 | return spec, err 24 | } 25 | 26 | // LoadSwitchAgentUnknownUnicastFloodingObjects loads SwitchAgentUnknownUnicastFlooding and converts it into a struct. 27 | // 28 | // The following types are suitable as obj argument: 29 | // 30 | // *SwitchAgentUnknownUnicastFloodingObjects 31 | // *SwitchAgentUnknownUnicastFloodingPrograms 32 | // *SwitchAgentUnknownUnicastFloodingMaps 33 | // 34 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 35 | func LoadSwitchAgentUnknownUnicastFloodingObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 36 | spec, err := LoadSwitchAgentUnknownUnicastFlooding() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return spec.LoadAndAssign(obj, opts) 42 | } 43 | 44 | // SwitchAgentUnknownUnicastFloodingSpecs contains maps and programs before they are loaded into the kernel. 45 | // 46 | // It can be passed ebpf.CollectionSpec.Assign. 47 | type SwitchAgentUnknownUnicastFloodingSpecs struct { 48 | SwitchAgentUnknownUnicastFloodingProgramSpecs 49 | SwitchAgentUnknownUnicastFloodingMapSpecs 50 | } 51 | 52 | // SwitchAgentUnknownUnicastFloodingSpecs contains programs before they are loaded into the kernel. 53 | // 54 | // It can be passed ebpf.CollectionSpec.Assign. 55 | type SwitchAgentUnknownUnicastFloodingProgramSpecs struct { 56 | SwitchAgentUnknownUnicastFlooding *ebpf.ProgramSpec `ebpf:"switch_agent_unknown_unicast_flooding"` 57 | } 58 | 59 | // SwitchAgentUnknownUnicastFloodingMapSpecs contains maps before they are loaded into the kernel. 60 | // 61 | // It can be passed ebpf.CollectionSpec.Assign. 62 | type SwitchAgentUnknownUnicastFloodingMapSpecs struct { 63 | InterfacesArray *ebpf.MapSpec `ebpf:"interfaces_array"` 64 | InterfacesArrayLength *ebpf.MapSpec `ebpf:"interfaces_array_length"` 65 | } 66 | 67 | // SwitchAgentUnknownUnicastFloodingObjects contains all objects after they have been loaded into the kernel. 68 | // 69 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 70 | type SwitchAgentUnknownUnicastFloodingObjects struct { 71 | SwitchAgentUnknownUnicastFloodingPrograms 72 | SwitchAgentUnknownUnicastFloodingMaps 73 | } 74 | 75 | func (o *SwitchAgentUnknownUnicastFloodingObjects) Close() error { 76 | return _SwitchAgentUnknownUnicastFloodingClose( 77 | &o.SwitchAgentUnknownUnicastFloodingPrograms, 78 | &o.SwitchAgentUnknownUnicastFloodingMaps, 79 | ) 80 | } 81 | 82 | // SwitchAgentUnknownUnicastFloodingMaps contains all maps after they have been loaded into the kernel. 83 | // 84 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 85 | type SwitchAgentUnknownUnicastFloodingMaps struct { 86 | InterfacesArray *ebpf.Map `ebpf:"interfaces_array"` 87 | InterfacesArrayLength *ebpf.Map `ebpf:"interfaces_array_length"` 88 | } 89 | 90 | func (m *SwitchAgentUnknownUnicastFloodingMaps) Close() error { 91 | return _SwitchAgentUnknownUnicastFloodingClose( 92 | m.InterfacesArray, 93 | m.InterfacesArrayLength, 94 | ) 95 | } 96 | 97 | // SwitchAgentUnknownUnicastFloodingPrograms contains all programs after they have been loaded into the kernel. 98 | // 99 | // It can be passed to LoadSwitchAgentUnknownUnicastFloodingObjects or ebpf.CollectionSpec.LoadAndAssign. 100 | type SwitchAgentUnknownUnicastFloodingPrograms struct { 101 | SwitchAgentUnknownUnicastFlooding *ebpf.Program `ebpf:"switch_agent_unknown_unicast_flooding"` 102 | } 103 | 104 | func (p *SwitchAgentUnknownUnicastFloodingPrograms) Close() error { 105 | return _SwitchAgentUnknownUnicastFloodingClose( 106 | p.SwitchAgentUnknownUnicastFlooding, 107 | ) 108 | } 109 | 110 | func _SwitchAgentUnknownUnicastFloodingClose(closers ...io.Closer) error { 111 | for _, closer := range closers { 112 | if err := closer.Close(); err != nil { 113 | return err 114 | } 115 | } 116 | return nil 117 | } 118 | 119 | // Do not access this directly. 120 | // 121 | //go:embed switchagentunknownunicastflooding_bpfel.o 122 | var _SwitchAgentUnknownUnicastFloodingBytes []byte 123 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanXDPInternalExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac VxlanXDPInternalMacAddress 18 | ExternalIfaceNextHopMac VxlanXDPInternalMacAddress 19 | ExternalIfaceIp VxlanXDPInternalInAddr 20 | } 21 | 22 | type VxlanXDPInternalInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanXDPInternalIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanXDPInternalMacAddress struct{ Addr [6]uint8 } 30 | 31 | type VxlanXDPInternalMacTableEntry struct { 32 | ExpirationTimer struct{ Opaque [2]uint64 } 33 | Ifindex uint32 34 | _ [4]byte 35 | LastSeenTimestampNs uint64 36 | BorderIp VxlanXDPInternalInAddr 37 | _ [4]byte 38 | } 39 | 40 | type VxlanXDPInternalNetworkVni struct { 41 | Vni uint32 42 | Network VxlanXDPInternalIpv4LpmKey 43 | InternalIfindexes [10]uint32 44 | InternalIfindexesSize uint32 45 | BorderIps [10]VxlanXDPInternalInAddr 46 | BorderIpsSize uint32 47 | } 48 | 49 | // LoadVxlanXDPInternal returns the embedded CollectionSpec for VxlanXDPInternal. 50 | func LoadVxlanXDPInternal() (*ebpf.CollectionSpec, error) { 51 | reader := bytes.NewReader(_VxlanXDPInternalBytes) 52 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 53 | if err != nil { 54 | return nil, fmt.Errorf("can't load VxlanXDPInternal: %w", err) 55 | } 56 | 57 | return spec, err 58 | } 59 | 60 | // LoadVxlanXDPInternalObjects loads VxlanXDPInternal and converts it into a struct. 61 | // 62 | // The following types are suitable as obj argument: 63 | // 64 | // *VxlanXDPInternalObjects 65 | // *VxlanXDPInternalPrograms 66 | // *VxlanXDPInternalMaps 67 | // 68 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 69 | func LoadVxlanXDPInternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 70 | spec, err := LoadVxlanXDPInternal() 71 | if err != nil { 72 | return err 73 | } 74 | 75 | return spec.LoadAndAssign(obj, opts) 76 | } 77 | 78 | // VxlanXDPInternalSpecs contains maps and programs before they are loaded into the kernel. 79 | // 80 | // It can be passed ebpf.CollectionSpec.Assign. 81 | type VxlanXDPInternalSpecs struct { 82 | VxlanXDPInternalProgramSpecs 83 | VxlanXDPInternalMapSpecs 84 | } 85 | 86 | // VxlanXDPInternalSpecs contains programs before they are loaded into the kernel. 87 | // 88 | // It can be passed ebpf.CollectionSpec.Assign. 89 | type VxlanXDPInternalProgramSpecs struct { 90 | VxlanXdpInternal *ebpf.ProgramSpec `ebpf:"vxlan_xdp_internal"` 91 | } 92 | 93 | // VxlanXDPInternalMapSpecs contains maps before they are loaded into the kernel. 94 | // 95 | // It can be passed ebpf.CollectionSpec.Assign. 96 | type VxlanXDPInternalMapSpecs struct { 97 | BorderIpToRouteInfoMap *ebpf.MapSpec `ebpf:"border_ip_to_route_info_map"` 98 | IfindexIsInternalMap *ebpf.MapSpec `ebpf:"ifindex_is_internal_map"` 99 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 100 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 101 | } 102 | 103 | // VxlanXDPInternalObjects contains all objects after they have been loaded into the kernel. 104 | // 105 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 106 | type VxlanXDPInternalObjects struct { 107 | VxlanXDPInternalPrograms 108 | VxlanXDPInternalMaps 109 | } 110 | 111 | func (o *VxlanXDPInternalObjects) Close() error { 112 | return _VxlanXDPInternalClose( 113 | &o.VxlanXDPInternalPrograms, 114 | &o.VxlanXDPInternalMaps, 115 | ) 116 | } 117 | 118 | // VxlanXDPInternalMaps contains all maps after they have been loaded into the kernel. 119 | // 120 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 121 | type VxlanXDPInternalMaps struct { 122 | BorderIpToRouteInfoMap *ebpf.Map `ebpf:"border_ip_to_route_info_map"` 123 | IfindexIsInternalMap *ebpf.Map `ebpf:"ifindex_is_internal_map"` 124 | MacTable *ebpf.Map `ebpf:"mac_table"` 125 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 126 | } 127 | 128 | func (m *VxlanXDPInternalMaps) Close() error { 129 | return _VxlanXDPInternalClose( 130 | m.BorderIpToRouteInfoMap, 131 | m.IfindexIsInternalMap, 132 | m.MacTable, 133 | m.NetworksMap, 134 | ) 135 | } 136 | 137 | // VxlanXDPInternalPrograms contains all programs after they have been loaded into the kernel. 138 | // 139 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 140 | type VxlanXDPInternalPrograms struct { 141 | VxlanXdpInternal *ebpf.Program `ebpf:"vxlan_xdp_internal"` 142 | } 143 | 144 | func (p *VxlanXDPInternalPrograms) Close() error { 145 | return _VxlanXDPInternalClose( 146 | p.VxlanXdpInternal, 147 | ) 148 | } 149 | 150 | func _VxlanXDPInternalClose(closers ...io.Closer) error { 151 | for _, closer := range closers { 152 | if err := closer.Close(); err != nil { 153 | return err 154 | } 155 | } 156 | return nil 157 | } 158 | 159 | // Do not access this directly. 160 | // 161 | //go:embed vxlanxdpinternal_bpfeb.o 162 | var _VxlanXDPInternalBytes []byte 163 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlanxdpinternal_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanXDPInternalExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac VxlanXDPInternalMacAddress 18 | ExternalIfaceNextHopMac VxlanXDPInternalMacAddress 19 | ExternalIfaceIp VxlanXDPInternalInAddr 20 | } 21 | 22 | type VxlanXDPInternalInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanXDPInternalIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanXDPInternalMacAddress struct{ Addr [6]uint8 } 30 | 31 | type VxlanXDPInternalMacTableEntry struct { 32 | ExpirationTimer struct{ Opaque [2]uint64 } 33 | Ifindex uint32 34 | _ [4]byte 35 | LastSeenTimestampNs uint64 36 | BorderIp VxlanXDPInternalInAddr 37 | _ [4]byte 38 | } 39 | 40 | type VxlanXDPInternalNetworkVni struct { 41 | Vni uint32 42 | Network VxlanXDPInternalIpv4LpmKey 43 | InternalIfindexes [10]uint32 44 | InternalIfindexesSize uint32 45 | BorderIps [10]VxlanXDPInternalInAddr 46 | BorderIpsSize uint32 47 | } 48 | 49 | // LoadVxlanXDPInternal returns the embedded CollectionSpec for VxlanXDPInternal. 50 | func LoadVxlanXDPInternal() (*ebpf.CollectionSpec, error) { 51 | reader := bytes.NewReader(_VxlanXDPInternalBytes) 52 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 53 | if err != nil { 54 | return nil, fmt.Errorf("can't load VxlanXDPInternal: %w", err) 55 | } 56 | 57 | return spec, err 58 | } 59 | 60 | // LoadVxlanXDPInternalObjects loads VxlanXDPInternal and converts it into a struct. 61 | // 62 | // The following types are suitable as obj argument: 63 | // 64 | // *VxlanXDPInternalObjects 65 | // *VxlanXDPInternalPrograms 66 | // *VxlanXDPInternalMaps 67 | // 68 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 69 | func LoadVxlanXDPInternalObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 70 | spec, err := LoadVxlanXDPInternal() 71 | if err != nil { 72 | return err 73 | } 74 | 75 | return spec.LoadAndAssign(obj, opts) 76 | } 77 | 78 | // VxlanXDPInternalSpecs contains maps and programs before they are loaded into the kernel. 79 | // 80 | // It can be passed ebpf.CollectionSpec.Assign. 81 | type VxlanXDPInternalSpecs struct { 82 | VxlanXDPInternalProgramSpecs 83 | VxlanXDPInternalMapSpecs 84 | } 85 | 86 | // VxlanXDPInternalSpecs contains programs before they are loaded into the kernel. 87 | // 88 | // It can be passed ebpf.CollectionSpec.Assign. 89 | type VxlanXDPInternalProgramSpecs struct { 90 | VxlanXdpInternal *ebpf.ProgramSpec `ebpf:"vxlan_xdp_internal"` 91 | } 92 | 93 | // VxlanXDPInternalMapSpecs contains maps before they are loaded into the kernel. 94 | // 95 | // It can be passed ebpf.CollectionSpec.Assign. 96 | type VxlanXDPInternalMapSpecs struct { 97 | BorderIpToRouteInfoMap *ebpf.MapSpec `ebpf:"border_ip_to_route_info_map"` 98 | IfindexIsInternalMap *ebpf.MapSpec `ebpf:"ifindex_is_internal_map"` 99 | MacTable *ebpf.MapSpec `ebpf:"mac_table"` 100 | NetworksMap *ebpf.MapSpec `ebpf:"networks_map"` 101 | } 102 | 103 | // VxlanXDPInternalObjects contains all objects after they have been loaded into the kernel. 104 | // 105 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 106 | type VxlanXDPInternalObjects struct { 107 | VxlanXDPInternalPrograms 108 | VxlanXDPInternalMaps 109 | } 110 | 111 | func (o *VxlanXDPInternalObjects) Close() error { 112 | return _VxlanXDPInternalClose( 113 | &o.VxlanXDPInternalPrograms, 114 | &o.VxlanXDPInternalMaps, 115 | ) 116 | } 117 | 118 | // VxlanXDPInternalMaps contains all maps after they have been loaded into the kernel. 119 | // 120 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 121 | type VxlanXDPInternalMaps struct { 122 | BorderIpToRouteInfoMap *ebpf.Map `ebpf:"border_ip_to_route_info_map"` 123 | IfindexIsInternalMap *ebpf.Map `ebpf:"ifindex_is_internal_map"` 124 | MacTable *ebpf.Map `ebpf:"mac_table"` 125 | NetworksMap *ebpf.Map `ebpf:"networks_map"` 126 | } 127 | 128 | func (m *VxlanXDPInternalMaps) Close() error { 129 | return _VxlanXDPInternalClose( 130 | m.BorderIpToRouteInfoMap, 131 | m.IfindexIsInternalMap, 132 | m.MacTable, 133 | m.NetworksMap, 134 | ) 135 | } 136 | 137 | // VxlanXDPInternalPrograms contains all programs after they have been loaded into the kernel. 138 | // 139 | // It can be passed to LoadVxlanXDPInternalObjects or ebpf.CollectionSpec.LoadAndAssign. 140 | type VxlanXDPInternalPrograms struct { 141 | VxlanXdpInternal *ebpf.Program `ebpf:"vxlan_xdp_internal"` 142 | } 143 | 144 | func (p *VxlanXDPInternalPrograms) Close() error { 145 | return _VxlanXDPInternalClose( 146 | p.VxlanXdpInternal, 147 | ) 148 | } 149 | 150 | func _VxlanXDPInternalClose(closers ...io.Closer) error { 151 | for _, closer := range closers { 152 | if err := closer.Close(); err != nil { 153 | return err 154 | } 155 | } 156 | return nil 157 | } 158 | 159 | // Do not access this directly. 160 | // 161 | //go:embed vxlanxdpinternal_bpfel.o 162 | var _VxlanXDPInternalBytes []byte 163 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlancommon_bpfeb.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build mips || mips64 || ppc64 || s390x 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanCommonExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac VxlanCommonMacAddress 18 | ExternalIfaceNextHopMac VxlanCommonMacAddress 19 | ExternalIfaceIp VxlanCommonInAddr 20 | } 21 | 22 | type VxlanCommonInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanCommonIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanCommonMacAddress struct{ Addr [6]uint8 } 30 | 31 | type VxlanCommonMacTableEntry struct { 32 | ExpirationTimer struct{ Opaque [2]uint64 } 33 | Ifindex uint32 34 | _ [4]byte 35 | LastSeenTimestampNs uint64 36 | BorderIp VxlanCommonInAddr 37 | _ [4]byte 38 | } 39 | 40 | type VxlanCommonNetworkVni struct { 41 | Vni uint32 42 | Network VxlanCommonIpv4LpmKey 43 | InternalIfindexes [10]uint32 44 | InternalIfindexesSize uint32 45 | BorderIps [10]VxlanCommonInAddr 46 | BorderIpsSize uint32 47 | } 48 | 49 | type VxlanCommonNetworkVniLight struct { 50 | Vni uint32 51 | Network VxlanCommonIpv4LpmKey 52 | } 53 | 54 | // LoadVxlanCommon returns the embedded CollectionSpec for VxlanCommon. 55 | func LoadVxlanCommon() (*ebpf.CollectionSpec, error) { 56 | reader := bytes.NewReader(_VxlanCommonBytes) 57 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 58 | if err != nil { 59 | return nil, fmt.Errorf("can't load VxlanCommon: %w", err) 60 | } 61 | 62 | return spec, err 63 | } 64 | 65 | // LoadVxlanCommonObjects loads VxlanCommon and converts it into a struct. 66 | // 67 | // The following types are suitable as obj argument: 68 | // 69 | // *VxlanCommonObjects 70 | // *VxlanCommonPrograms 71 | // *VxlanCommonMaps 72 | // 73 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 74 | func LoadVxlanCommonObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 75 | spec, err := LoadVxlanCommon() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return spec.LoadAndAssign(obj, opts) 81 | } 82 | 83 | // VxlanCommonSpecs contains maps and programs before they are loaded into the kernel. 84 | // 85 | // It can be passed ebpf.CollectionSpec.Assign. 86 | type VxlanCommonSpecs struct { 87 | VxlanCommonProgramSpecs 88 | VxlanCommonMapSpecs 89 | } 90 | 91 | // VxlanCommonSpecs contains programs before they are loaded into the kernel. 92 | // 93 | // It can be passed ebpf.CollectionSpec.Assign. 94 | type VxlanCommonProgramSpecs struct { 95 | } 96 | 97 | // VxlanCommonMapSpecs contains maps before they are loaded into the kernel. 98 | // 99 | // It can be passed ebpf.CollectionSpec.Assign. 100 | type VxlanCommonMapSpecs struct { 101 | DummyExternalRouteInfo *ebpf.MapSpec `ebpf:"dummy_external_route_info"` 102 | DummyInAddr *ebpf.MapSpec `ebpf:"dummy_in_addr"` 103 | DummyIpv4LpmKey *ebpf.MapSpec `ebpf:"dummy_ipv4_lpm_key"` 104 | DummyMacAddress *ebpf.MapSpec `ebpf:"dummy_mac_address"` 105 | DummyMacTableEntry *ebpf.MapSpec `ebpf:"dummy_mac_table_entry"` 106 | DummyNetworkVni *ebpf.MapSpec `ebpf:"dummy_network_vni"` 107 | DummyNetworkVniLight *ebpf.MapSpec `ebpf:"dummy_network_vni_light"` 108 | } 109 | 110 | // VxlanCommonObjects contains all objects after they have been loaded into the kernel. 111 | // 112 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 113 | type VxlanCommonObjects struct { 114 | VxlanCommonPrograms 115 | VxlanCommonMaps 116 | } 117 | 118 | func (o *VxlanCommonObjects) Close() error { 119 | return _VxlanCommonClose( 120 | &o.VxlanCommonPrograms, 121 | &o.VxlanCommonMaps, 122 | ) 123 | } 124 | 125 | // VxlanCommonMaps contains all maps after they have been loaded into the kernel. 126 | // 127 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 128 | type VxlanCommonMaps struct { 129 | DummyExternalRouteInfo *ebpf.Map `ebpf:"dummy_external_route_info"` 130 | DummyInAddr *ebpf.Map `ebpf:"dummy_in_addr"` 131 | DummyIpv4LpmKey *ebpf.Map `ebpf:"dummy_ipv4_lpm_key"` 132 | DummyMacAddress *ebpf.Map `ebpf:"dummy_mac_address"` 133 | DummyMacTableEntry *ebpf.Map `ebpf:"dummy_mac_table_entry"` 134 | DummyNetworkVni *ebpf.Map `ebpf:"dummy_network_vni"` 135 | DummyNetworkVniLight *ebpf.Map `ebpf:"dummy_network_vni_light"` 136 | } 137 | 138 | func (m *VxlanCommonMaps) Close() error { 139 | return _VxlanCommonClose( 140 | m.DummyExternalRouteInfo, 141 | m.DummyInAddr, 142 | m.DummyIpv4LpmKey, 143 | m.DummyMacAddress, 144 | m.DummyMacTableEntry, 145 | m.DummyNetworkVni, 146 | m.DummyNetworkVniLight, 147 | ) 148 | } 149 | 150 | // VxlanCommonPrograms contains all programs after they have been loaded into the kernel. 151 | // 152 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 153 | type VxlanCommonPrograms struct { 154 | } 155 | 156 | func (p *VxlanCommonPrograms) Close() error { 157 | return _VxlanCommonClose() 158 | } 159 | 160 | func _VxlanCommonClose(closers ...io.Closer) error { 161 | for _, closer := range closers { 162 | if err := closer.Close(); err != nil { 163 | return err 164 | } 165 | } 166 | return nil 167 | } 168 | 169 | // Do not access this directly. 170 | // 171 | //go:embed vxlancommon_bpfeb.o 172 | var _VxlanCommonBytes []byte 173 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/gen/vxlancommon_bpfel.go: -------------------------------------------------------------------------------- 1 | // Code generated by bpf2go; DO NOT EDIT. 2 | //go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 3 | 4 | package ebpf 5 | 6 | import ( 7 | "bytes" 8 | _ "embed" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/cilium/ebpf" 13 | ) 14 | 15 | type VxlanCommonExternalRouteInfo struct { 16 | ExternalIfaceIndex uint32 17 | ExternalIfaceMac VxlanCommonMacAddress 18 | ExternalIfaceNextHopMac VxlanCommonMacAddress 19 | ExternalIfaceIp VxlanCommonInAddr 20 | } 21 | 22 | type VxlanCommonInAddr struct{ S_addr uint32 } 23 | 24 | type VxlanCommonIpv4LpmKey struct { 25 | Prefixlen uint32 26 | Data [4]uint8 27 | } 28 | 29 | type VxlanCommonMacAddress struct{ Addr [6]uint8 } 30 | 31 | type VxlanCommonMacTableEntry struct { 32 | ExpirationTimer struct{ Opaque [2]uint64 } 33 | Ifindex uint32 34 | _ [4]byte 35 | LastSeenTimestampNs uint64 36 | BorderIp VxlanCommonInAddr 37 | _ [4]byte 38 | } 39 | 40 | type VxlanCommonNetworkVni struct { 41 | Vni uint32 42 | Network VxlanCommonIpv4LpmKey 43 | InternalIfindexes [10]uint32 44 | InternalIfindexesSize uint32 45 | BorderIps [10]VxlanCommonInAddr 46 | BorderIpsSize uint32 47 | } 48 | 49 | type VxlanCommonNetworkVniLight struct { 50 | Vni uint32 51 | Network VxlanCommonIpv4LpmKey 52 | } 53 | 54 | // LoadVxlanCommon returns the embedded CollectionSpec for VxlanCommon. 55 | func LoadVxlanCommon() (*ebpf.CollectionSpec, error) { 56 | reader := bytes.NewReader(_VxlanCommonBytes) 57 | spec, err := ebpf.LoadCollectionSpecFromReader(reader) 58 | if err != nil { 59 | return nil, fmt.Errorf("can't load VxlanCommon: %w", err) 60 | } 61 | 62 | return spec, err 63 | } 64 | 65 | // LoadVxlanCommonObjects loads VxlanCommon and converts it into a struct. 66 | // 67 | // The following types are suitable as obj argument: 68 | // 69 | // *VxlanCommonObjects 70 | // *VxlanCommonPrograms 71 | // *VxlanCommonMaps 72 | // 73 | // See ebpf.CollectionSpec.LoadAndAssign documentation for details. 74 | func LoadVxlanCommonObjects(obj interface{}, opts *ebpf.CollectionOptions) error { 75 | spec, err := LoadVxlanCommon() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return spec.LoadAndAssign(obj, opts) 81 | } 82 | 83 | // VxlanCommonSpecs contains maps and programs before they are loaded into the kernel. 84 | // 85 | // It can be passed ebpf.CollectionSpec.Assign. 86 | type VxlanCommonSpecs struct { 87 | VxlanCommonProgramSpecs 88 | VxlanCommonMapSpecs 89 | } 90 | 91 | // VxlanCommonSpecs contains programs before they are loaded into the kernel. 92 | // 93 | // It can be passed ebpf.CollectionSpec.Assign. 94 | type VxlanCommonProgramSpecs struct { 95 | } 96 | 97 | // VxlanCommonMapSpecs contains maps before they are loaded into the kernel. 98 | // 99 | // It can be passed ebpf.CollectionSpec.Assign. 100 | type VxlanCommonMapSpecs struct { 101 | DummyExternalRouteInfo *ebpf.MapSpec `ebpf:"dummy_external_route_info"` 102 | DummyInAddr *ebpf.MapSpec `ebpf:"dummy_in_addr"` 103 | DummyIpv4LpmKey *ebpf.MapSpec `ebpf:"dummy_ipv4_lpm_key"` 104 | DummyMacAddress *ebpf.MapSpec `ebpf:"dummy_mac_address"` 105 | DummyMacTableEntry *ebpf.MapSpec `ebpf:"dummy_mac_table_entry"` 106 | DummyNetworkVni *ebpf.MapSpec `ebpf:"dummy_network_vni"` 107 | DummyNetworkVniLight *ebpf.MapSpec `ebpf:"dummy_network_vni_light"` 108 | } 109 | 110 | // VxlanCommonObjects contains all objects after they have been loaded into the kernel. 111 | // 112 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 113 | type VxlanCommonObjects struct { 114 | VxlanCommonPrograms 115 | VxlanCommonMaps 116 | } 117 | 118 | func (o *VxlanCommonObjects) Close() error { 119 | return _VxlanCommonClose( 120 | &o.VxlanCommonPrograms, 121 | &o.VxlanCommonMaps, 122 | ) 123 | } 124 | 125 | // VxlanCommonMaps contains all maps after they have been loaded into the kernel. 126 | // 127 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 128 | type VxlanCommonMaps struct { 129 | DummyExternalRouteInfo *ebpf.Map `ebpf:"dummy_external_route_info"` 130 | DummyInAddr *ebpf.Map `ebpf:"dummy_in_addr"` 131 | DummyIpv4LpmKey *ebpf.Map `ebpf:"dummy_ipv4_lpm_key"` 132 | DummyMacAddress *ebpf.Map `ebpf:"dummy_mac_address"` 133 | DummyMacTableEntry *ebpf.Map `ebpf:"dummy_mac_table_entry"` 134 | DummyNetworkVni *ebpf.Map `ebpf:"dummy_network_vni"` 135 | DummyNetworkVniLight *ebpf.Map `ebpf:"dummy_network_vni_light"` 136 | } 137 | 138 | func (m *VxlanCommonMaps) Close() error { 139 | return _VxlanCommonClose( 140 | m.DummyExternalRouteInfo, 141 | m.DummyInAddr, 142 | m.DummyIpv4LpmKey, 143 | m.DummyMacAddress, 144 | m.DummyMacTableEntry, 145 | m.DummyNetworkVni, 146 | m.DummyNetworkVniLight, 147 | ) 148 | } 149 | 150 | // VxlanCommonPrograms contains all programs after they have been loaded into the kernel. 151 | // 152 | // It can be passed to LoadVxlanCommonObjects or ebpf.CollectionSpec.LoadAndAssign. 153 | type VxlanCommonPrograms struct { 154 | } 155 | 156 | func (p *VxlanCommonPrograms) Close() error { 157 | return _VxlanCommonClose() 158 | } 159 | 160 | func _VxlanCommonClose(closers ...io.Closer) error { 161 | for _, closer := range closers { 162 | if err := closer.Close(); err != nil { 163 | return err 164 | } 165 | } 166 | return nil 167 | } 168 | 169 | // Do not access this directly. 170 | // 171 | //go:embed vxlancommon_bpfel.o 172 | var _VxlanCommonBytes []byte 173 | -------------------------------------------------------------------------------- /cmd/test_agent/impl/test_agent_grpc.service_impl.go: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "syscall" 9 | "time" 10 | "wormhole/cmd/test_agent/generated" 11 | 12 | probing "github.com/prometheus-community/pro-bing" 13 | log "github.com/sirupsen/logrus" 14 | "golang.org/x/net/context" 15 | "google.golang.org/protobuf/types/known/emptypb" 16 | ) 17 | 18 | var switchAgentProcess *os.Process 19 | var vxlanAgentProcess *os.Process 20 | 21 | type TestAgentServiceImpl struct { 22 | generated.UnimplementedTestAgentServiceServer 23 | } 24 | 25 | func fileExists(filename string) bool { 26 | _, err := os.Stat(filename) 27 | return err == nil 28 | } 29 | 30 | func (s *TestAgentServiceImpl) WaitUntilReady(ctx context.Context, in *emptypb.Empty) (*generated.WaitUntilReadyResponse, error) { 31 | for { 32 | if fileExists("/tmp/is_clab_container_ready") { 33 | break 34 | } else { 35 | time.Sleep(1 * time.Second) 36 | } 37 | } 38 | 39 | return &generated.WaitUntilReadyResponse{Success: true}, nil 40 | } 41 | 42 | func (s *TestAgentServiceImpl) Ping(ctx context.Context, pingRequest *generated.PingRequest) (*generated.PingResponse, error) { 43 | log.Printf("Received ping request: %s", pingRequest.IpV4Address) 44 | 45 | pinger, err := probing.NewPinger(pingRequest.IpV4Address) 46 | if err != nil { 47 | return &generated.PingResponse{Success: false}, nil 48 | } 49 | 50 | pinger.Count = int(pingRequest.Count) 51 | pinger.Timeout = time.Duration(pingRequest.Timeout) * time.Second 52 | //pinger.Count = 3 53 | //pinger.Timeout = 2 * time.Second 54 | err = pinger.Run() 55 | if err != nil { 56 | return &generated.PingResponse{ 57 | Success: false, 58 | PacketsRecv: 0, 59 | PacketsSent: 0, 60 | PacketsRecvDuplicates: 0, 61 | PacketLoss: 0, 62 | }, nil 63 | } 64 | 65 | log.Printf("returning the ping result") 66 | 67 | stats := pinger.Statistics() 68 | return &generated.PingResponse{Success: stats.PacketLoss < 100, 69 | PacketsRecv: int32(stats.PacketsRecv), 70 | PacketsSent: int32(stats.PacketsSent), 71 | PacketsRecvDuplicates: int32(stats.PacketsRecvDuplicates), 72 | PacketLoss: float32(stats.PacketLoss), 73 | }, nil 74 | } 75 | 76 | func (s *TestAgentServiceImpl) EnableSwitchAgent(ctx context.Context, in *generated.EnableSwitchAgentRequest) (*generated.EnableSwitchAgentResponse, error) { 77 | fileName := "/build/switch_agent" 78 | 79 | interfacesCommaSeparated := strings.Join(in.InterfaceNames, ",") 80 | 81 | hostname, err := os.Hostname() 82 | if err != nil { 83 | log.Errorf("error obtaining the hostname %v", err) 84 | } 85 | 86 | cmd := exec.Command(fileName, "--interface-names", interfacesCommaSeparated) 87 | 88 | fmt.Println("cmd", cmd.String()) 89 | log.Errorln("cmd", cmd.String()) 90 | 91 | err = cmd.Start() 92 | 93 | switchAgentProcess = cmd.Process 94 | 95 | if err != nil { 96 | fmt.Printf("error %v", err) 97 | return &generated.EnableSwitchAgentResponse{ 98 | Resp: fmt.Sprintf("Error: %v", err), 99 | Command: cmd.String(), 100 | Pid: int32(cmd.Process.Pid), 101 | Hostname: hostname, 102 | }, nil 103 | } 104 | 105 | return &generated.EnableSwitchAgentResponse{ 106 | Resp: "Success", 107 | Command: cmd.String(), 108 | Pid: int32(cmd.Process.Pid), 109 | Hostname: hostname, 110 | }, nil 111 | } 112 | 113 | func (s *TestAgentServiceImpl) DisableSwitchAgent(ctx context.Context, in *emptypb.Empty) (*emptypb.Empty, error) { 114 | 115 | err := switchAgentProcess.Signal(syscall.SIGINT) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return &emptypb.Empty{}, nil 121 | } 122 | 123 | func (s *TestAgentServiceImpl) EnableVxlanAgent(ctx context.Context, in *generated.EnableVxlanAgentRequest) (*generated.EnableVxlanAgentResponse, error) { 124 | fileName := "/build/vxlan_agent" 125 | 126 | hostname, err := os.Hostname() 127 | if err != nil { 128 | log.Errorf("error obtaining the hostname %v", err) 129 | } 130 | 131 | // remove /build/vxlan_agent.config.yaml if it exists 132 | err = os.Remove("/build/vxlan_agent.config.yaml") 133 | if err != nil && !os.IsNotExist(err) { 134 | log.Errorf("error removing the file %v", err) 135 | } 136 | 137 | // create and write the content of ConfigYamlContent to /build/vxlan_agent.config.yaml 138 | file, err := os.Create("/build/vxlan_agent.config.yaml") 139 | if err != nil { 140 | log.Errorf("error creating the file %v", err) 141 | return nil, err 142 | } 143 | defer file.Close() 144 | 145 | _, err = file.WriteString(in.ConfigYamlContent) 146 | if err != nil { 147 | log.Errorf("error writing to the file %v", err) 148 | return nil, err 149 | } 150 | 151 | cmd := exec.Command(fileName, "--config", "/build/vxlan_agent.config.yaml") 152 | 153 | fmt.Println("cmd", cmd.String()) 154 | log.Errorln("cmd", cmd.String()) 155 | 156 | // Start the command 157 | stdout, err := cmd.StdoutPipe() 158 | if err != nil { 159 | log.Errorf("error obtaining stdout pipe %v", err) 160 | return nil, err 161 | } 162 | 163 | if err := cmd.Start(); err != nil { 164 | log.Errorf("error starting command %v", err) 165 | return &generated.EnableVxlanAgentResponse{ 166 | Resp: fmt.Sprintf("Error: %v", err), 167 | Command: cmd.String(), 168 | Output: "", 169 | Pid: 0, 170 | Hostname: hostname, 171 | }, nil 172 | } 173 | 174 | // Create a channel to signal when the timeout is reached 175 | done := make(chan struct{}) 176 | var output []byte 177 | 178 | go func() { 179 | defer close(done) 180 | buf := make([]byte, 1024) 181 | for { 182 | n, err := stdout.Read(buf) 183 | if n > 0 { 184 | output = append(output, buf[:n]...) 185 | } 186 | if err != nil { 187 | break 188 | } 189 | } 190 | }() 191 | 192 | select { 193 | case <-done: 194 | case <-time.After(time.Second * 3): 195 | } 196 | 197 | vxlanAgentProcess = cmd.Process 198 | 199 | return &generated.EnableVxlanAgentResponse{ 200 | Resp: "Success", 201 | Command: cmd.String(), 202 | Output: string(output), 203 | Pid: int32(cmd.Process.Pid), 204 | Hostname: hostname, 205 | }, nil 206 | } 207 | 208 | func (s *TestAgentServiceImpl) DisableVxlanAgent(ctx context.Context, in *emptypb.Empty) (*emptypb.Empty, error) { 209 | 210 | err := vxlanAgentProcess.Signal(syscall.SIGINT) 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | return &emptypb.Empty{}, nil 216 | } 217 | 218 | func (s *TestAgentServiceImpl) EnableDummyXdpAgent(ctx context.Context, in *generated.EnableDummyXdpAgentRequest) (*emptypb.Empty, error) { 219 | fileName := "/build/dummy_xdp" 220 | 221 | cmd := exec.Command(fileName, "--interface-name", in.InterfaceName) 222 | 223 | fmt.Println("cmd", cmd.String()) 224 | log.Errorln("cmd", cmd.String()) 225 | 226 | err := cmd.Start() 227 | if err != nil { 228 | fmt.Printf("error %v", err) 229 | return nil, err 230 | } 231 | 232 | return &emptypb.Empty{}, nil 233 | } 234 | 235 | func formatStringArray(arr []string) string { 236 | if len(arr) == 0 { 237 | return "" 238 | } 239 | return " - " + strings.Join(arr, "\n - ") + "\n" 240 | } 241 | -------------------------------------------------------------------------------- /internal/switch_agent/ebpf/c/switch_agent_xdp.bpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 7 | 8 | struct mac_address { 9 | unsigned char mac[ETH_ALEN]; // MAC address 10 | }; 11 | 12 | struct iface_index { 13 | __u32 interface_index; 14 | __u64 timestamp; 15 | }; 16 | 17 | struct mac_address_iface_entry { 18 | struct mac_address mac; 19 | struct iface_index iface; 20 | } *unused_mac_address_iface_entry __attribute__((unused)); 21 | // because mac_address_iface_entry is not directly mentioned 22 | // as a type in new_discovered_entries_rb then it will be 23 | // omitted in bpf2go generation procedure, unless we 24 | // directly add an unused instance of it to prevent it from 25 | // being omitted by optimization and also in bpf2go generate 26 | // command we must explicitly ask bpf2g to generate this struct 27 | // using '-type mac_address_iface_entry' option. 28 | 29 | 30 | struct { 31 | __uint(type, BPF_MAP_TYPE_HASH); 32 | __type(key, struct mac_address); 33 | // __uint(key_size, sizeof(struct mac_address)); 34 | __type(value, struct iface_index); 35 | // __uint(value_size, sizeof(struct iface_index)); 36 | __uint(max_entries, 4 * 1024 * 1024); 37 | // __uint(pinning, LIBBPF_PIN_BY_NAME); 38 | } mac_table SEC(".maps") /*__weak */; 39 | // TODO in above check if lack of __weak would 40 | // create any error in program logic or not, 41 | // if it's creating a problem maybe we should 42 | // change the map to pinned version, 43 | // we don't need more than one instance of 44 | // this map, so probably pinning is the solution 45 | 46 | struct { 47 | __uint(type, BPF_MAP_TYPE_RINGBUF); 48 | __uint(max_entries, 1024 * 1024); 49 | // __uint(pinning, LIBBPF_PIN_BY_NAME); 50 | } new_discovered_entries_rb SEC(".maps") /*__weak */; 51 | 52 | void register_source_mac_address_if_required(const struct xdp_md *ctx, const struct ethhdr *eth, 53 | __u64 current_time); 54 | 55 | SEC("xdp") 56 | long switch_agent_xdp(struct xdp_md *ctx) 57 | { 58 | bpf_printk( 59 | "----------------------------------------------------------------------------------------------------"); 60 | // we can use current_time as something like a unique identifier for packet 61 | __u64 current_time = bpf_ktime_get_tai_ns(); 62 | 63 | struct ethhdr *eth = (void *)(long)ctx->data; 64 | 65 | // Additional check after the adjustment 66 | if ((void *)(eth + 1) > (void *)(long)ctx->data_end) 67 | return XDP_ABORTED; 68 | 69 | bpf_printk( 70 | "id = %llu, interface = %d, Packet received, source MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", 71 | current_time, ctx->ingress_ifindex, eth->h_source[0], eth->h_source[1], 72 | eth->h_source[2], eth->h_source[3], eth->h_source[4], eth->h_source[5]); 73 | 74 | bpf_printk( 75 | "id = %llu, interface = %d, Packet received, dest MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", 76 | current_time, ctx->ingress_ifindex, eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], 77 | eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); 78 | 79 | register_source_mac_address_if_required(ctx, eth, current_time); 80 | 81 | struct mac_address dest_mac_addr; 82 | __builtin_memcpy(dest_mac_addr.mac, eth->h_dest, 83 | ETH_ALEN); // Changed from h_source to h_dest 84 | 85 | bpf_printk( 86 | "id = %llu, interface = %d, lookup mac_table for matching redirect iface, h_dest MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", 87 | current_time, ctx->ingress_ifindex, eth->h_dest[0], eth->h_dest[1], eth->h_dest[2], 88 | eth->h_dest[3], eth->h_dest[4], eth->h_dest[5]); 89 | 90 | struct iface_index *iface_to_redirect = bpf_map_lookup_elem(&mac_table, &dest_mac_addr); 91 | 92 | if (!iface_to_redirect) { 93 | bpf_printk( 94 | "id = %llu, interface = %d, in case eth-h_dest==ff:ff:ff:ff:ff:ff we should do unknown unicast flooding\n", 95 | current_time, ctx->ingress_ifindex); 96 | // Unknown Unicast Flooding: 97 | // in this case we need to redirect to interfaces that is not equal to ctx->ingress_ifindex, 98 | // but normal xdp program at this layer can only redirect a network packet to a single interface, 99 | // So we should pass it to upper layer. another ebpf program at TC layer should handle redirection. 100 | // cause in TC layer we can clone a packet and redirect it to more than one network interface. 101 | return XDP_PASS; 102 | 103 | // TODO: check if the number of network interfaces was just 2 then we don't need TC for UNKNOWN UNICAST FLOODING 104 | // TODO: because in this case we would only need to redirect to one other network interface, so we can handle redirection 105 | // TODO: just here 106 | 107 | // if (ctx->ingress_ifindex != first_interface) { 108 | // bpf_printk("id = %llu, interface = %d, redirecting to interface %d \n", current_time, ctx->ingress_ifindex, first_interface); 109 | // return bpf_redirect(first_interface, 0); 110 | // } 111 | // else if (ctx->ingress_ifindex != second_interface) { 112 | // bpf_printk("id = %llu, interface = %d, redirecting to interface %d \n", current_time, ctx->ingress_ifindex, second_interface); 113 | // return bpf_redirect(second_interface, 0); 114 | // } else { 115 | // bpf_printk("id = %llu, interface = %d, nothing has been found so will do XDP_PASS\n", current_time, ctx->ingress_ifindex); 116 | // return XDP_PASS; // If the destination MAC isn't found, simply pass the packet 117 | // } 118 | } 119 | 120 | bpf_printk("id = %llu, interface = %d, match found. do the redirection\n", current_time, 121 | ctx->ingress_ifindex); 122 | return bpf_redirect(iface_to_redirect->interface_index, 0); 123 | } 124 | 125 | void register_source_mac_address_if_required(const struct xdp_md *ctx, const struct ethhdr *eth, 126 | __u64 current_time) 127 | { 128 | bpf_printk("id = %llu, learning-process: register source mac address if required\n", 129 | current_time); 130 | struct mac_address source_mac_addr; 131 | __builtin_memcpy(source_mac_addr.mac, eth->h_source, ETH_ALEN); 132 | 133 | bpf_printk( 134 | "id = %llu, learning-process: check if we already have registered source mac address \n", 135 | current_time); 136 | 137 | struct iface_index *iface_for_source_mac = 138 | bpf_map_lookup_elem(&mac_table, &source_mac_addr); 139 | 140 | if (!iface_for_source_mac) { 141 | bpf_printk( 142 | "id = %llu, learning-process: have NOT Found an already registered entry for source mac address \n", 143 | current_time); 144 | 145 | struct mac_address_iface_entry new_entry; 146 | __builtin_memset(&new_entry, 0, sizeof(new_entry)); 147 | 148 | __builtin_memcpy(new_entry.mac.mac, eth->h_source, ETH_ALEN); 149 | new_entry.iface.interface_index = ctx->ingress_ifindex; 150 | new_entry.iface.timestamp = current_time; 151 | 152 | bpf_printk( 153 | "id = %llu, learning-process: have NOT found + trying to update mac_table map\n", 154 | current_time); 155 | 156 | bpf_map_update_elem(&mac_table, &(new_entry.mac), &(new_entry.iface), BPF_ANY); 157 | // bpf_ringbuf_submit(new_entry, 0); 158 | 159 | bpf_printk( 160 | "id = %llu, learning-process: have NOT found + trying to submit data to new_discovered map\n", 161 | current_time); 162 | bpf_ringbuf_output(&new_discovered_entries_rb, &new_entry, sizeof(new_entry), 0); 163 | } else { 164 | bpf_printk( 165 | "id = %llu, learning-process: have Found an already registered entry for source mac address \n", 166 | current_time); 167 | iface_for_source_mac->timestamp = current_time; 168 | bpf_map_update_elem(&mac_table, &source_mac_addr, iface_for_source_mac, BPF_ANY); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/c/vxlan_tc_external.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vxlan_common.bpf.h" 2 | #include "../../../../include/vmlinux.h" 3 | 4 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 5 | 6 | // -------------------------------------------------------- 7 | 8 | // a LPM trie data structure 9 | // for example 192.168.1.0/24 --> VNI 0 10 | 11 | struct 12 | { 13 | __uint(type, BPF_MAP_TYPE_LPM_TRIE); 14 | __type(key, struct ipv4_lpm_key); 15 | __type(value, struct network_vni); 16 | __uint(map_flags, BPF_F_NO_PREALLOC); 17 | __uint(max_entries, 255); 18 | __uint(pinning, LIBBPF_PIN_BY_NAME); 19 | } networks_map SEC(".maps"); 20 | 21 | // -------------------------------------------------------- 22 | 23 | static int __always_inline clone_external_packet_and_send_to_all_internal_ifaces(struct __sk_buff *skb); 24 | 25 | // -------------------------------------------------------- 26 | 27 | SEC("tcx/ingress") 28 | int vxlan_tc_external(struct __sk_buff *skb) 29 | { 30 | my_bpf_printk("%d 1. packet received", skb->ifindex); 31 | 32 | if (skb == NULL) 33 | return TC_ACT_SHOT; 34 | 35 | __u64 current_time = bpf_ktime_get_tai_ns(); 36 | 37 | struct ethhdr *eth = (void *)(long)skb->data; 38 | 39 | if ((void *)(eth + 1) > (void *)(long)skb->data_end) 40 | return TC_ACT_SHOT; 41 | 42 | my_bpf_printk("%d 2. packet recieved", skb->ifindex); 43 | 44 | return clone_external_packet_and_send_to_all_internal_ifaces(skb); 45 | } 46 | 47 | // -------------------------------------------------------- 48 | 49 | static int __always_inline clone_external_packet_and_send_to_all_internal_ifaces(struct __sk_buff *skb) 50 | { 51 | my_bpf_printk("%d 5. start clone_external_packet_and_send_to_all_internal_ifaces", skb->ingress_ifindex); 52 | int i; 53 | __u32 *ifindex_ptr; 54 | struct ipv4_lpm_key dst_key = {.prefixlen = 32}; 55 | struct ipv4_lpm_key src_key = {.prefixlen = 32}; 56 | struct in_addr src_ip; 57 | 58 | void *data = (void *)(long)skb->data; 59 | void *data_end = (void *)(long)skb->data_end; 60 | 61 | if (data + ETH_HLEN + IP_HDR_LEN + UDP_HDR_LEN + VXLAN_HDR_LEN > data_end) 62 | { 63 | my_bpf_printk("%d 7. invalid data & data_end after decreasing packet head size", skb->ingress_ifindex); 64 | return TC_ACT_SHOT; 65 | } 66 | 67 | struct ethhdr *inner_eth = data + ETH_HLEN + IP_HDR_LEN + UDP_HDR_LEN + VXLAN_HDR_LEN; 68 | 69 | // Ensure the inner Ethernet header is valid 70 | if ((void *)(inner_eth + 1) > data_end) 71 | return TC_ACT_SHOT; 72 | 73 | __u16 h_proto = bpf_ntohs(inner_eth->h_proto); 74 | 75 | if (h_proto == ETH_P_ARP) 76 | { 77 | struct arphdr *inner_arph = (void *)(inner_eth + 1); 78 | 79 | // Ensure the inner ARP header is valid 80 | if ((void *)(inner_arph + 1) > data_end) 81 | return TC_ACT_SHOT; 82 | 83 | struct arp_payload *inner_arp_payload = (void *)(inner_arph + 1); 84 | 85 | // Ensure the inner ARP payload is valid 86 | if ((void *)(inner_arp_payload + 1) > data_end) 87 | return TC_ACT_SHOT; 88 | 89 | create_in_addr_from_arp_ip(inner_arp_payload->ar_sip, &src_ip); 90 | my_bpf_printk("src_ip obtained by ARP. %u", src_ip.s_addr); 91 | 92 | __builtin_memcpy(dst_key.data, inner_arp_payload->ar_tip, sizeof(dst_key.data)); 93 | } 94 | else if (h_proto == ETH_P_IP) 95 | { 96 | struct iphdr *inner_iph = (void *)(inner_eth + 1); 97 | 98 | // Ensure the inner IP header is valid 99 | if ((void *)(inner_iph + 1) > data_end) 100 | return TC_ACT_SHOT; 101 | 102 | // Extract inner source and destination IP addresses 103 | __u32 inner_dst_ip = inner_iph->daddr; 104 | 105 | src_ip.s_addr = inner_iph->saddr; 106 | my_bpf_printk("src_ip obtained by IP. %u", src_ip.s_addr); 107 | 108 | // Check if packet really belongs to internal network 109 | __builtin_memcpy(dst_key.data, &inner_dst_ip, sizeof(dst_key.data)); 110 | } 111 | else 112 | { 113 | return TC_ACT_SHOT; 114 | } 115 | 116 | // if the packet is not for internal network do TC_ACT_OK here and 117 | struct network_vni *dst_network_vni = bpf_map_lookup_elem(&networks_map, &dst_key); 118 | if (dst_network_vni == NULL) 119 | { 120 | my_bpf_printk("does not belong to internal network. pass it up"); 121 | return TC_ACT_OK; 122 | } 123 | 124 | if (!is_ip_in_network(&(dst_network_vni->network), &src_ip)) 125 | { 126 | my_bpf_printk("src & dst does not belong to same network. pass it up. dst_network_vni %u.%u.%u.%u/%u", 127 | dst_network_vni->network.data[0], 128 | dst_network_vni->network.data[1], 129 | dst_network_vni->network.data[2], 130 | dst_network_vni->network.data[3], 131 | dst_network_vni->network.prefixlen); 132 | 133 | unsigned char bytes[4]; 134 | bytes[0] = src_ip.s_addr & 0xFF; // Lowest byte 135 | bytes[1] = (src_ip.s_addr >> 8) & 0xFF; // Second byte 136 | bytes[2] = (src_ip.s_addr >> 16) & 0xFF; // Third byte 137 | bytes[3] = (src_ip.s_addr >> 24) & 0xFF; // Highest byte 138 | 139 | my_bpf_printk("src & dst does not belong to same network. pass it up. src %u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]); 140 | return AGENT_PASS; 141 | }; 142 | 143 | struct ethhdr *outer_eth = data; 144 | 145 | if ((void *)outer_eth + sizeof(struct ethhdr) > data_end) 146 | { 147 | my_bpf_printk("%d 7. invalid outer_eth pointer", skb->ingress_ifindex); 148 | return TC_ACT_SHOT; 149 | } 150 | 151 | if ((void *)inner_eth + sizeof(struct ethhdr) > data_end) 152 | { 153 | my_bpf_printk("%d 7. invalid outer_eth pointer", skb->ingress_ifindex); 154 | return TC_ACT_SHOT; 155 | } 156 | 157 | __builtin_memcpy(outer_eth->h_source, inner_eth->h_source, ETH_ALEN); 158 | __builtin_memcpy(outer_eth->h_dest, inner_eth->h_dest, ETH_ALEN); 159 | outer_eth->h_proto = inner_eth->h_proto; 160 | 161 | // Resize the packet buffer by decreasing the headroom. 162 | // in bpf_xdp_adjust_head() if we want to decrease the packet length, we must use positive number 163 | // in bpf_skb_adjust_room() if we want to decrease the packet length, we must use negative number 164 | // TODO: check if this is the correct way to decrease the packet length 165 | long ret = bpf_skb_adjust_room(skb, -NEW_HDR_LEN, BPF_ADJ_ROOM_MAC, 0); 166 | if (ret) 167 | { 168 | my_bpf_printk("%d 6. failed to decrease the packet head using bpf_skb_change_head(), error = %d", skb->ingress_ifindex, ret); 169 | return TC_ACT_SHOT; 170 | } 171 | else 172 | { 173 | my_bpf_printk("%d 6. sucessful decreasing of the packet head using bpf_skb_change_head()", skb->ingress_ifindex); 174 | } 175 | 176 | // Recalculate data and data_end pointers after adjustment 177 | data = (void *)(long)skb->data; 178 | data_end = (void *)(long)skb->data_end; 179 | 180 | // Ensure the packet is still valid after adjustment 181 | if (data + sizeof(struct ethhdr) > data_end) 182 | { 183 | my_bpf_printk("%d 7. invalid data & data_end after decreasing packet head size", skb->ingress_ifindex); 184 | return TC_ACT_SHOT; 185 | } 186 | 187 | bpf_for(i, 0, MAX_INTERNAL_IFINDEXES) 188 | { 189 | if (i >= dst_network_vni->internal_ifindexes_size) 190 | break; 191 | 192 | my_bpf_printk("%d 8. redirecting the packet to if index. i=%d", skb->ingress_ifindex, i); 193 | bpf_clone_redirect(skb, dst_network_vni->internal_ifindexes[i], 0); 194 | } 195 | 196 | return TC_ACT_OK; 197 | } -------------------------------------------------------------------------------- /test/e2e/switch_agent/switch_agent_test.go: -------------------------------------------------------------------------------- 1 | package switch_agent 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "slices" 7 | "strings" 8 | "testing" 9 | "wormhole/cmd/test_agent/generated" 10 | 11 | "github.com/docker/docker/api/types" 12 | "github.com/docker/docker/client" 13 | "github.com/janog-netcon/netcon-problem-management-subsystem/pkg/containerlab" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials/insecure" 18 | "google.golang.org/protobuf/types/known/emptypb" 19 | ) 20 | 21 | func TestSwitchAgent(t *testing.T) { 22 | RegisterFailHandler(Fail) 23 | RunSpecs(t, "Switch Agent Test Suite") 24 | } 25 | 26 | var _ = AfterSuite(func() { 27 | 28 | clabClient := containerlab.NewContainerLabClient("./clab-topologies/switch.clab.yml") 29 | ctx := context.Background() 30 | 31 | inspect, err := clabClient.Inspect(ctx) 32 | if err != nil { 33 | return 34 | } 35 | 36 | if len(inspect.Containers) != 0 { 37 | err := clabClient.Destroy(ctx) 38 | if err != nil { 39 | GinkgoT().Fatalf("error happened " + err.Error()) 40 | } 41 | } 42 | }) 43 | 44 | var _ = Describe("checking switch_agent", func() { 45 | 46 | var sourceClient generated.TestAgentServiceClient 47 | var switchClient generated.TestAgentServiceClient 48 | var destClient generated.TestAgentServiceClient 49 | 50 | clabClient := containerlab.NewContainerLabClient("./clab-topologies/switch.clab.yml") 51 | ctx := context.Background() 52 | 53 | dockerClient, err := client.NewClientWithOpts(client.FromEnv) 54 | if err != nil { 55 | GinkgoT().Fatalf("unable to create docker client %v", err) 56 | } 57 | 58 | BeforeEach(func() { 59 | err := clabClient.Destroy(ctx) 60 | if err != nil { 61 | } 62 | 63 | err = clabClient.Deploy(ctx) 64 | if err != nil { 65 | GinkgoT().Fatalf("error happened %v", err) 66 | } 67 | 68 | containers, err := dockerClient.ContainerList(context.Background(), types.ContainerListOptions{}) 69 | if err != nil { 70 | fmt.Println("Error listing containers:", err) 71 | return 72 | } 73 | 74 | sourceIp, err := findContainerIp(containers, "clab-switch-src") 75 | if err != nil { 76 | GinkgoT().Fatalf("Enable to find container ip %v", err) 77 | } 78 | sourceConn, err := grpc.NewClient(sourceIp+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 79 | if err != nil { 80 | GinkgoT().Fatalf("Could not connect: %s", err) 81 | } 82 | sourceClient = generated.NewTestAgentServiceClient(sourceConn) 83 | _, err = sourceClient.WaitUntilReady(ctx, &emptypb.Empty{}) 84 | if err != nil { 85 | GinkgoT().Fatalf("Failed to wait for source container to become ready: %s", err) 86 | } 87 | 88 | switchIp, err := findContainerIp(containers, "clab-switch-sw") 89 | switchConn, err := grpc.NewClient(switchIp+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 90 | if err != nil { 91 | GinkgoT().Fatalf("Could not connect: %s", err) 92 | } 93 | switchClient = generated.NewTestAgentServiceClient(switchConn) 94 | _, err = switchClient.WaitUntilReady(ctx, &emptypb.Empty{}) 95 | if err != nil { 96 | GinkgoT().Fatalf("Failed to wait for switch container to become ready: %s", err) 97 | } 98 | 99 | destIp, err := findContainerIp(containers, "clab-switch-dst") 100 | destConn, err := grpc.NewClient(destIp+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 101 | if err != nil { 102 | GinkgoT().Fatalf("Could not connect: %s", err) 103 | } 104 | destClient = generated.NewTestAgentServiceClient(destConn) 105 | _, err = destClient.WaitUntilReady(ctx, &emptypb.Empty{}) 106 | if err != nil { 107 | GinkgoT().Fatalf("Failed to wait for dest container to become ready: %s", err) 108 | } 109 | }) 110 | 111 | AfterEach(func() { 112 | err := clabClient.Destroy(ctx) 113 | if err != nil { 114 | GinkgoT().Fatalf("error happened " + err.Error()) 115 | } 116 | }) 117 | 118 | When("the switch_agent is not running", func() { 119 | 120 | It("source & dest should not be able to ping each other", func() { 121 | 122 | pingResp, err := sourceClient.Ping(ctx, &generated.PingRequest{ 123 | IpV4Address: "2.2.2.1", 124 | Count: 1, 125 | Timeout: 1, 126 | }) 127 | //log.Printf("source ping itself stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 128 | if err != nil { 129 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 130 | } 131 | Expect(pingResp.Success).To(BeTrue()) 132 | 133 | pingResp, err = sourceClient.Ping(ctx, &generated.PingRequest{ 134 | IpV4Address: "2.2.2.2", 135 | Count: 2, 136 | Timeout: 2, 137 | }) 138 | //log.Printf("source ping dest stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 139 | if err != nil { 140 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 141 | } 142 | Expect(pingResp.Success).To(BeFalse()) 143 | 144 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 145 | IpV4Address: "2.2.2.2", 146 | Count: 1, 147 | Timeout: 1, 148 | }) 149 | //log.Printf("dest ping itself stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 150 | if err != nil { 151 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 152 | } 153 | Expect(pingResp.Success).To(BeTrue()) 154 | 155 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 156 | IpV4Address: "2.2.2.1", 157 | Count: 2, 158 | Timeout: 2, 159 | }) 160 | //log.Printf("dest ping source stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 161 | if err != nil { 162 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 163 | } 164 | Expect(pingResp.Success).To(BeFalse()) 165 | }) 166 | }) 167 | 168 | When("the switch_agent is running", func() { 169 | 170 | It("source and dest should be able to ping each other", func() { 171 | 172 | enableResp, err := switchClient.EnableSwitchAgent(ctx, &generated.EnableSwitchAgentRequest{InterfaceNames: []string{"eth1", "eth2"}}) 173 | if err != nil { 174 | GinkgoT().Fatalf("error while enabling switch_agent: %v", err) 175 | } 176 | 177 | Expect(enableResp.Resp).To(Equal("Success")) 178 | 179 | pingResp, err := sourceClient.Ping(ctx, &generated.PingRequest{ 180 | IpV4Address: "2.2.2.2", 181 | Count: 3, 182 | Timeout: 2, 183 | }) 184 | 185 | if err != nil { 186 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 187 | } 188 | Expect(pingResp.Success).To(BeTrue()) 189 | 190 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 191 | IpV4Address: "2.2.2.1", 192 | Count: 3, 193 | Timeout: 2, 194 | }) 195 | 196 | if err != nil { 197 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 198 | } 199 | Expect(pingResp.Success).To(BeTrue()) 200 | }) 201 | }) 202 | 203 | }) 204 | 205 | func findContainerIp(containers []types.Container, containerName string) (string, error) { 206 | sourceIdx := slices.IndexFunc(containers, func(container types.Container) bool { 207 | return slices.ContainsFunc(container.Names, func(name string) bool { 208 | return strings.Contains(name, containerName) 209 | }) 210 | }) 211 | 212 | if sourceIdx < 0 { 213 | return "", fmt.Errorf("IP address not found for container '%s'", containerName) 214 | } 215 | 216 | for _, value := range containers[sourceIdx].NetworkSettings.Networks { 217 | return value.IPAddress, nil 218 | } 219 | 220 | return "", fmt.Errorf("IP address not found for container '%s'", containerName) 221 | } 222 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/c/vxlan_common.bpf.h: -------------------------------------------------------------------------------- 1 | #ifndef VXLAN_COMMON_BPF_H 2 | #define VXLAN_COMMON_BPF_H 3 | 4 | #include "../../../../include/vmlinux.h" 5 | 6 | #include 7 | #include 8 | 9 | #define TC_ACT_SHOT 2 10 | #define TC_ACT_OK 0 11 | 12 | #define ETH_P_IP 0x0800 /* Internet Protocol packet */ 13 | #define ETH_P_ARP 0x0806 /* Address Resolution packet */ 14 | 15 | #define ARPOP_REQUEST 1 /* ARP request */ 16 | #define ARPOP_REPLY 2 /* ARP reply */ 17 | 18 | #define CLOCK_BOOTTIME 7 /* Monotonic system-wide clock that includes time spent in suspension. */ 19 | 20 | #define ETH_ALEN 6 /* Ethernet address length */ 21 | #define ETH_HLEN 14 /* Total octets in header. */ 22 | #define IP_HDR_LEN (int)sizeof(struct iphdr) 23 | #define UDP_HDR_LEN (int)sizeof(struct udphdr) 24 | #define VXLAN_HDR_LEN (int)sizeof(struct vxlanhdr) 25 | #define NEW_HDR_LEN (ETH_HLEN + IP_HDR_LEN + UDP_HDR_LEN + VXLAN_HDR_LEN) 26 | 27 | #define FIVE_MINUTES_IN_NS 20000000000 28 | 29 | static volatile const struct 30 | { 31 | char hostname[64]; 32 | char type[64]; 33 | char sub_type[64]; 34 | } vxlan_agent_metadata SEC(".rodata"); 35 | 36 | #define my_bpf_printk(fmt, args...) bpf_printk("%s :: %s :: %s :::: " fmt, vxlan_agent_metadata.hostname, vxlan_agent_metadata.type, vxlan_agent_metadata.sub_type, ##args) 37 | 38 | struct vxlanhdr 39 | { 40 | __be32 vx_flags; 41 | __be32 vx_vni; 42 | }; 43 | 44 | enum vxlan_agent_processing_error 45 | { 46 | AGENT_ERROR_ABORT = 0, 47 | AGENT_ERROR_DROP = 1, 48 | AGENT_NO_ERROR = 2, 49 | AGENT_PASS = 3, 50 | }; 51 | 52 | struct mac_table_entry 53 | { 54 | struct bpf_timer expiration_timer; // the timer object to expire this mac entry from the map in 5 minutes 55 | __u32 ifindex; // interface which mac address is learned from 56 | __u64 last_seen_timestamp_ns; // last time this mac address was seen 57 | struct in_addr border_ip; // remote agent border ip address that this mac address is learned from. 58 | // - in case of an internal mac address, this field is not used and set to 0.0.0.0 59 | // - in case of an external mac address, this field is used and set to the remote agent border ip address 60 | }; 61 | 62 | struct external_route_info 63 | { 64 | __u32 external_iface_index; 65 | struct mac_address external_iface_mac; 66 | struct mac_address external_iface_next_hop_mac; 67 | struct in_addr external_iface_ip; 68 | }; 69 | 70 | struct arp_payload 71 | { 72 | unsigned char ar_sha[ETH_ALEN]; // Sender hardware address 73 | unsigned char ar_sip[4]; // Sender IP address 74 | unsigned char ar_tha[ETH_ALEN]; // Target hardware address 75 | unsigned char ar_tip[4]; // Target IP address 76 | }; 77 | 78 | struct ipv4_lpm_key 79 | { 80 | __u32 prefixlen; 81 | __u8 data[4]; 82 | }; 83 | 84 | #define MAX_INTERNAL_IFINDEXES 10 85 | #define MAX_BORDER_IPS 10 86 | 87 | struct network_vni 88 | { 89 | __u32 vni; 90 | struct ipv4_lpm_key network; 91 | __u32 internal_ifindexes[MAX_INTERNAL_IFINDEXES]; 92 | __u32 internal_ifindexes_size; 93 | struct in_addr border_ips[MAX_BORDER_IPS]; 94 | __u32 border_ips_size; 95 | }; 96 | 97 | struct network_vni_light 98 | { 99 | __u32 vni; 100 | struct ipv4_lpm_key network; 101 | }; 102 | 103 | // Function to get a random port within the ephemeral range 104 | static __always_inline __u16 105 | get_ephemeral_port() 106 | { 107 | return 49152 + bpf_get_prandom_u32() % (65535 - 49152 + 1); 108 | } 109 | 110 | // -------------------------------------------------------- 111 | 112 | // Function to check if the MAC address is the broadcast address 113 | static __always_inline bool is_broadcast_address(const struct mac_address *mac) 114 | { 115 | // Check if the MAC address is the broadcast address (FF:FF:FF:FF:FF:FF) 116 | if (mac->addr[0] != 0xFF) 117 | return false; 118 | if (mac->addr[1] != 0xFF) 119 | return false; 120 | if (mac->addr[2] != 0xFF) 121 | return false; 122 | if (mac->addr[3] != 0xFF) 123 | return false; 124 | if (mac->addr[4] != 0xFF) 125 | return false; 126 | if (mac->addr[5] != 0xFF) 127 | return false; 128 | return true; 129 | } 130 | 131 | // the mac table expiration callback function 132 | static int mac_table_expiration_callback(void *map, struct mac_address *key, struct mac_table_entry *value) 133 | { 134 | __u64 passed_time = bpf_ktime_get_tai_ns() - value->last_seen_timestamp_ns; 135 | 136 | if (passed_time >= FIVE_MINUTES_IN_NS) 137 | { 138 | // if the mac entry is expired 139 | bpf_map_delete_elem(map, key); 140 | } 141 | else 142 | { 143 | // if the mac entry is not expired we need to restart the timer according to the remaining time 144 | bpf_timer_start(&value->expiration_timer, FIVE_MINUTES_IN_NS - passed_time, 0); 145 | } 146 | 147 | return 0; 148 | } 149 | 150 | static __always_inline __u32 __create_mask(__u32 prefixlen) 151 | { 152 | switch (prefixlen) 153 | { 154 | case 0: 155 | return 0b00000000000000000000000000000000; 156 | case 1: 157 | return 0b00000000000000000000000000000001; 158 | case 2: 159 | return 0b00000000000000000000000000000011; 160 | case 3: 161 | return 0b00000000000000000000000000000111; 162 | case 4: 163 | return 0b00000000000000000000000000001111; 164 | case 5: 165 | return 0b00000000000000000000000000011111; 166 | case 6: 167 | return 0b00000000000000000000000000111111; 168 | case 7: 169 | return 0b00000000000000000000000001111111; 170 | case 8: 171 | return 0b00000000000000000000000011111111; 172 | case 9: 173 | return 0b00000000000000000000000111111111; 174 | case 10: 175 | return 0b00000000000000000000001111111111; 176 | case 11: 177 | return 0b00000000000000000000011111111111; 178 | case 12: 179 | return 0b00000000000000000000111111111111; 180 | case 13: 181 | return 0b00000000000000000001111111111111; 182 | case 14: 183 | return 0b00000000000000000011111111111111; 184 | case 15: 185 | return 0b00000000000000000111111111111111; 186 | case 16: 187 | return 0b00000000000000001111111111111111; 188 | case 17: 189 | return 0b00000000000000011111111111111111; 190 | case 18: 191 | return 0b00000000000000111111111111111111; 192 | case 19: 193 | return 0b00000000000001111111111111111111; 194 | case 20: 195 | return 0b00000000000011111111111111111111; 196 | case 21: 197 | return 0b00000000000111111111111111111111; 198 | case 22: 199 | return 0b00000000001111111111111111111111; 200 | case 23: 201 | return 0b00000000011111111111111111111111; 202 | case 24: 203 | return 0b00000000111111111111111111111111; 204 | case 25: 205 | return 0b00000001111111111111111111111111; 206 | case 26: 207 | return 0b00000011111111111111111111111111; 208 | case 27: 209 | return 0b00000111111111111111111111111111; 210 | case 28: 211 | return 0b00001111111111111111111111111111; 212 | case 29: 213 | return 0b00011111111111111111111111111111; 214 | case 30: 215 | return 0b00111111111111111111111111111111; 216 | case 31: 217 | return 0b01111111111111111111111111111111; 218 | case 32: 219 | return 0b11111111111111111111111111111111; 220 | default: 221 | return 0b00000000000000000000000000000000; // Invalid prefix length 222 | } 223 | } 224 | 225 | bool __always_inline is_ip_in_network(const struct ipv4_lpm_key *key, const struct in_addr *src_ip_addr) 226 | { 227 | 228 | if (key == NULL || src_ip_addr == NULL) 229 | { 230 | return false; 231 | } 232 | 233 | if (key->prefixlen > 32) 234 | { 235 | my_bpf_printk("Invalid prefix length\n"); 236 | return false; 237 | } 238 | 239 | __u32 network; 240 | __builtin_memcpy(&network, key->data, sizeof(network)); 241 | 242 | __u32 mask = __create_mask(key->prefixlen); 243 | 244 | __u32 src_masked = src_ip_addr->s_addr & mask; 245 | __u32 network_masked = network & mask; 246 | 247 | return (src_masked == network_masked); 248 | } 249 | 250 | void create_in_addr_from_arp_ip(const unsigned char ar_ip[4], struct in_addr *src_ip_addr) 251 | { 252 | if (src_ip_addr == NULL) 253 | { 254 | return; // Ensure the pointer is valid 255 | } 256 | 257 | // Copy the 4-byte IP address into the in_addr structure 258 | __builtin_memcpy(&src_ip_addr->s_addr, ar_ip, sizeof(src_ip_addr->s_addr)); 259 | } 260 | 261 | #define GENERATE_DUMMY_MAP(type_name) \ 262 | struct \ 263 | { \ 264 | __uint(type, BPF_MAP_TYPE_HASH); \ 265 | __uint(max_entries, 1); \ 266 | __type(key, int); \ 267 | __type(value, struct type_name); \ 268 | } dummy_##type_name SEC(".maps"); 269 | 270 | #endif -------------------------------------------------------------------------------- /.zellij/zellij_docker_exec_layout.kdl: -------------------------------------------------------------------------------- 1 | layout { 2 | cwd "/workspaces/devpod-wormhole" 3 | tab name="Tab #1" focus=true hide_floating_panes=true { 4 | pane size=1 borderless=true { 5 | plugin location="zellij:tab-bar" 6 | } 7 | pane split_direction="vertical" { 8 | pane command="zellij" focus=true size="25%" { 9 | args "action" "dump-layout" 10 | start_suspended true 11 | } 12 | pane size="25%" 13 | pane size="25%" 14 | pane size="25%" 15 | } 16 | pane size=1 borderless=true { 17 | plugin location="zellij:status-bar" 18 | } 19 | } 20 | new_tab_template { 21 | pane size=1 borderless=true { 22 | plugin location="zellij:tab-bar" 23 | } 24 | pane 25 | pane size=1 borderless=true { 26 | plugin location="zellij:status-bar" 27 | } 28 | } 29 | swap_tiled_layout name="vertical" { 30 | tab max_panes=5 { 31 | pane size=1 borderless=true { 32 | plugin location="tab-bar" 33 | } 34 | pane { 35 | pane split_direction="vertical" { 36 | pane 37 | pane { 38 | children 39 | } 40 | } 41 | } 42 | pane size=1 borderless=true { 43 | plugin location="status-bar" 44 | } 45 | } 46 | tab max_panes=8 { 47 | pane size=1 borderless=true { 48 | plugin location="tab-bar" 49 | } 50 | pane { 51 | pane split_direction="vertical" { 52 | pane { 53 | children 54 | } 55 | pane { 56 | pane 57 | pane 58 | pane 59 | pane 60 | } 61 | } 62 | } 63 | pane size=1 borderless=true { 64 | plugin location="status-bar" 65 | } 66 | } 67 | tab max_panes=12 { 68 | pane size=1 borderless=true { 69 | plugin location="tab-bar" 70 | } 71 | pane { 72 | pane split_direction="vertical" { 73 | pane { 74 | children 75 | } 76 | pane { 77 | pane 78 | pane 79 | pane 80 | pane 81 | } 82 | pane { 83 | pane 84 | pane 85 | pane 86 | pane 87 | } 88 | } 89 | } 90 | pane size=1 borderless=true { 91 | plugin location="status-bar" 92 | } 93 | } 94 | } 95 | swap_tiled_layout name="horizontal" { 96 | tab max_panes=5 { 97 | pane size=1 borderless=true { 98 | plugin location="tab-bar" 99 | } 100 | pane { 101 | pane 102 | pane 103 | } 104 | pane size=1 borderless=true { 105 | plugin location="status-bar" 106 | } 107 | } 108 | tab max_panes=8 { 109 | pane size=1 borderless=true { 110 | plugin location="tab-bar" 111 | } 112 | pane { 113 | pane { 114 | pane split_direction="vertical" { 115 | children 116 | } 117 | pane split_direction="vertical" { 118 | pane 119 | pane 120 | pane 121 | pane 122 | } 123 | } 124 | } 125 | pane size=1 borderless=true { 126 | plugin location="status-bar" 127 | } 128 | } 129 | tab max_panes=12 { 130 | pane size=1 borderless=true { 131 | plugin location="tab-bar" 132 | } 133 | pane { 134 | pane { 135 | pane split_direction="vertical" { 136 | children 137 | } 138 | pane split_direction="vertical" { 139 | pane 140 | pane 141 | pane 142 | pane 143 | } 144 | pane split_direction="vertical" { 145 | pane 146 | pane 147 | pane 148 | pane 149 | } 150 | } 151 | } 152 | pane size=1 borderless=true { 153 | plugin location="status-bar" 154 | } 155 | } 156 | } 157 | swap_tiled_layout name="stacked" { 158 | tab min_panes=5 { 159 | pane size=1 borderless=true { 160 | plugin location="tab-bar" 161 | } 162 | pane { 163 | pane split_direction="vertical" { 164 | pane 165 | pane stacked=true { 166 | children 167 | } 168 | } 169 | } 170 | pane size=1 borderless=true { 171 | plugin location="status-bar" 172 | } 173 | } 174 | } 175 | swap_floating_layout name="staggered" { 176 | floating_panes { 177 | } 178 | } 179 | swap_floating_layout name="enlarged" { 180 | floating_panes max_panes=10 { 181 | pane { 182 | height "90%" 183 | width "90%" 184 | x "5%" 185 | y 1 186 | } 187 | pane { 188 | height "90%" 189 | width "90%" 190 | x "5%" 191 | y 2 192 | } 193 | pane { 194 | height "90%" 195 | width "90%" 196 | x "5%" 197 | y 3 198 | } 199 | pane { 200 | height "90%" 201 | width "90%" 202 | x "5%" 203 | y 4 204 | } 205 | pane { 206 | height "90%" 207 | width "90%" 208 | x "5%" 209 | y 5 210 | } 211 | pane { 212 | height "90%" 213 | width "90%" 214 | x "5%" 215 | y 6 216 | } 217 | pane { 218 | height "90%" 219 | width "90%" 220 | x "5%" 221 | y 7 222 | } 223 | pane { 224 | height "90%" 225 | width "90%" 226 | x "5%" 227 | y 8 228 | } 229 | pane { 230 | height "90%" 231 | width "90%" 232 | x "5%" 233 | y 9 234 | } 235 | pane focus=true { 236 | height "90%" 237 | width "90%" 238 | x 10 239 | y 10 240 | } 241 | } 242 | } 243 | swap_floating_layout name="spread" { 244 | floating_panes max_panes=1 { 245 | pane { 246 | x "50%" 247 | y "50%" 248 | } 249 | } 250 | floating_panes max_panes=2 { 251 | pane { 252 | width "45%" 253 | x "1%" 254 | y "25%" 255 | } 256 | pane { 257 | width "45%" 258 | x "50%" 259 | y "25%" 260 | } 261 | } 262 | floating_panes max_panes=3 { 263 | pane focus=true { 264 | height "45%" 265 | width "45%" 266 | y "55%" 267 | } 268 | pane { 269 | width "45%" 270 | x "1%" 271 | y "1%" 272 | } 273 | pane { 274 | width "45%" 275 | x "50%" 276 | y "1%" 277 | } 278 | } 279 | floating_panes max_panes=4 { 280 | pane { 281 | height "45%" 282 | width "45%" 283 | x "1%" 284 | y "55%" 285 | } 286 | pane focus=true { 287 | height "45%" 288 | width "45%" 289 | x "50%" 290 | y "55%" 291 | } 292 | pane { 293 | height "45%" 294 | width "45%" 295 | x "1%" 296 | y "1%" 297 | } 298 | pane { 299 | height "45%" 300 | width "45%" 301 | x "50%" 302 | y "1%" 303 | } 304 | } 305 | } 306 | } 307 | 308 | -------------------------------------------------------------------------------- /test/e2e/vxlan_agent/vxlan_agent_test.go: -------------------------------------------------------------------------------- 1 | package vxlan_agent_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "slices" 7 | "strings" 8 | "testing" 9 | "wormhole/cmd/test_agent/generated" 10 | 11 | "github.com/docker/docker/api/types" 12 | "github.com/docker/docker/client" 13 | "github.com/janog-netcon/netcon-problem-management-subsystem/pkg/containerlab" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/credentials/insecure" 18 | "google.golang.org/protobuf/types/known/emptypb" 19 | ) 20 | 21 | func TestVxlanAgent(t *testing.T) { 22 | RegisterFailHandler(Fail) 23 | RunSpecs(t, "Vxlan Agent Test Suite") 24 | } 25 | 26 | var _ = AfterSuite(func() { 27 | 28 | clabClient := containerlab.NewContainerLabClient("./clab-topologies/vxlan.clab.yml") 29 | ctx := context.Background() 30 | 31 | inspect, err := clabClient.Inspect(ctx) 32 | if err != nil { 33 | return 34 | } 35 | 36 | if len(inspect.Containers) != 0 { 37 | err := clabClient.Destroy(ctx) 38 | if err != nil { 39 | GinkgoT().Fatalf("error happened " + err.Error()) 40 | } 41 | } 42 | }) 43 | 44 | var _ = Describe("checking vxlan_agent", func() { 45 | 46 | var sourceClient generated.TestAgentServiceClient 47 | var border1Client generated.TestAgentServiceClient 48 | var border2Client generated.TestAgentServiceClient 49 | var destClient generated.TestAgentServiceClient 50 | 51 | clabClient := containerlab.NewContainerLabClient("./clab-topologies/vxlan.clab.yml") 52 | ctx := context.Background() 53 | 54 | dockerClient, err := client.NewClientWithOpts(client.FromEnv) 55 | if err != nil { 56 | GinkgoT().Fatalf("unable to create docker client %v", err) 57 | } 58 | 59 | BeforeEach(func() { 60 | err := clabClient.Destroy(ctx) 61 | if err != nil { 62 | } 63 | 64 | err = clabClient.Deploy(ctx) 65 | if err != nil { 66 | GinkgoT().Fatalf("error happened %v", err) 67 | } 68 | 69 | containers, err := dockerClient.ContainerList(context.Background(), types.ContainerListOptions{}) 70 | if err != nil { 71 | fmt.Println("Error listing containers:", err) 72 | return 73 | } 74 | 75 | sourceIp, err := findContainerIp(containers, "clab-vxlan-src") 76 | if err != nil { 77 | GinkgoT().Fatalf("Enable to find container ip %v", err) 78 | } 79 | sourceConn, err := grpc.NewClient(sourceIp+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 80 | if err != nil { 81 | GinkgoT().Fatalf("Could not connect: %s", err) 82 | } 83 | sourceClient = generated.NewTestAgentServiceClient(sourceConn) 84 | _, err = sourceClient.WaitUntilReady(ctx, &emptypb.Empty{}) 85 | if err != nil { 86 | GinkgoT().Fatalf("Failed to wait for source container to become ready: %s", err) 87 | } 88 | 89 | border1Ip, err := findContainerIp(containers, "clab-vxlan-border1") 90 | border1Conn, err := grpc.NewClient(border1Ip+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 91 | if err != nil { 92 | GinkgoT().Fatalf("Could not connect: %s", err) 93 | } 94 | border1Client = generated.NewTestAgentServiceClient(border1Conn) 95 | _, err = border1Client.WaitUntilReady(ctx, &emptypb.Empty{}) 96 | if err != nil { 97 | GinkgoT().Fatalf("Failed to wait for switch container to become ready: %s", err) 98 | } 99 | 100 | border2Ip, err := findContainerIp(containers, "clab-vxlan-border2") 101 | border2Conn, err := grpc.NewClient(border2Ip+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 102 | if err != nil { 103 | GinkgoT().Fatalf("Could not connect: %s", err) 104 | } 105 | border2Client = generated.NewTestAgentServiceClient(border2Conn) 106 | _, err = border2Client.WaitUntilReady(ctx, &emptypb.Empty{}) 107 | if err != nil { 108 | GinkgoT().Fatalf("Failed to wait for switch container to become ready: %s", err) 109 | } 110 | 111 | destIp, err := findContainerIp(containers, "clab-vxlan-dst") 112 | destConn, err := grpc.NewClient(destIp+":9000", grpc.WithTransportCredentials(insecure.NewCredentials())) 113 | if err != nil { 114 | GinkgoT().Fatalf("Could not connect: %s", err) 115 | } 116 | destClient = generated.NewTestAgentServiceClient(destConn) 117 | _, err = destClient.WaitUntilReady(ctx, &emptypb.Empty{}) 118 | if err != nil { 119 | GinkgoT().Fatalf("Failed to wait for dest container to become ready: %s", err) 120 | } 121 | }) 122 | 123 | AfterEach(func() { 124 | err := clabClient.Destroy(ctx) 125 | if err != nil { 126 | GinkgoT().Fatalf("error happened " + err.Error()) 127 | } 128 | }) 129 | 130 | When("the vxlan_agent is not running", func() { 131 | 132 | It("source & dest should not be able to ping each other", func() { 133 | 134 | pingResp, err := sourceClient.Ping(ctx, &generated.PingRequest{ 135 | IpV4Address: "192.168.1.10", 136 | Count: 1, 137 | Timeout: 1, 138 | }) 139 | //log.Printf("source ping itself stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 140 | if err != nil { 141 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 142 | } 143 | Expect(pingResp.Success).To(BeTrue()) 144 | 145 | pingResp, err = sourceClient.Ping(ctx, &generated.PingRequest{ 146 | IpV4Address: "192.168.1.11", 147 | Count: 2, 148 | Timeout: 2, 149 | }) 150 | //log.Printf("source ping dest stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 151 | if err != nil { 152 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 153 | } 154 | Expect(pingResp.Success).To(BeFalse()) 155 | 156 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 157 | IpV4Address: "192.168.1.11", 158 | Count: 1, 159 | Timeout: 1, 160 | }) 161 | //log.Printf("dest ping itself stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 162 | if err != nil { 163 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 164 | } 165 | Expect(pingResp.Success).To(BeTrue()) 166 | 167 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 168 | IpV4Address: "192.168.1.10", 169 | Count: 2, 170 | Timeout: 2, 171 | }) 172 | //log.Printf("dest ping source stats, %d, %d, %d, %f", pingResp.PacketsRecv, pingResp.PacketsSent, pingResp.PacketsRecvDuplicates, pingResp.PacketLoss) 173 | if err != nil { 174 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 175 | } 176 | Expect(pingResp.Success).To(BeFalse()) 177 | }) 178 | }) 179 | 180 | When("the vxlan_agent is running on border1 & border2", func() { 181 | 182 | It("source and dest should be able to ping each other", func() { 183 | 184 | _, err := sourceClient.EnableDummyXdpAgent(ctx, &generated.EnableDummyXdpAgentRequest{ 185 | InterfaceName: "eth1", 186 | }) 187 | if err != nil { 188 | GinkgoT().Fatalf("error while enabling dummy_xdp on source: %v", err) 189 | } 190 | 191 | _, err = destClient.EnableDummyXdpAgent(ctx, &generated.EnableDummyXdpAgentRequest{ 192 | InterfaceName: "eth1", 193 | }) 194 | if err != nil { 195 | GinkgoT().Fatalf("error while enabling dummy_xdp on dest: %v", err) 196 | } 197 | 198 | enableRespBorder1, err := border1Client.EnableVxlanAgent(ctx, &generated.EnableVxlanAgentRequest{ 199 | ConfigYamlContent: strings.Join([]string{ 200 | "networks:", 201 | " - vni: 0", 202 | " address: 192.168.1.0/24", 203 | " internal-network-interfaces:", 204 | " - eth1", 205 | " external-network-interfaces:", 206 | " - eth2", 207 | " border-ips:", 208 | " - 3.3.3.2", 209 | }, "\n"), 210 | }) 211 | if err != nil { 212 | GinkgoT().Fatalf("error while enabling switch_agent: %v", err) 213 | } 214 | 215 | Expect(enableRespBorder1.Resp).To(Equal("Success")) 216 | 217 | enableRespBorder2, err := border2Client.EnableVxlanAgent(ctx, &generated.EnableVxlanAgentRequest{ 218 | ConfigYamlContent: strings.Join([]string{ 219 | "networks:", 220 | " - vni: 0", 221 | " address: 192.168.1.0/24", 222 | " internal-network-interfaces:", 223 | " - eth1", 224 | " external-network-interfaces:", 225 | " - eth2", 226 | " border-ips:", 227 | " - 3.3.3.1", 228 | }, "\n"), 229 | }) 230 | if err != nil { 231 | GinkgoT().Fatalf("error while enabling switch_agent: %v", err) 232 | } 233 | 234 | Expect(enableRespBorder2.Resp).To(Equal("Success")) 235 | 236 | pingResp, err := sourceClient.Ping(ctx, &generated.PingRequest{ 237 | IpV4Address: "192.168.1.11", 238 | Count: 2, 239 | Timeout: 2, 240 | }) 241 | 242 | if err != nil { 243 | GinkgoT().Fatalf("error while calling Ping form source: %s", err) 244 | } 245 | Expect(pingResp.Success).To(BeTrue()) 246 | 247 | pingResp, err = destClient.Ping(ctx, &generated.PingRequest{ 248 | IpV4Address: "192.168.1.10", 249 | Count: 2, 250 | Timeout: 2, 251 | }) 252 | 253 | if err != nil { 254 | GinkgoT().Fatalf("error while calling Ping form dest: %s", err) 255 | } 256 | Expect(pingResp.Success).To(BeTrue()) 257 | }) 258 | }) 259 | 260 | }) 261 | 262 | func findContainerIp(containers []types.Container, containerName string) (string, error) { 263 | sourceIdx := slices.IndexFunc(containers, func(container types.Container) bool { 264 | return slices.ContainsFunc(container.Names, func(name string) bool { 265 | return strings.Contains(name, containerName) 266 | }) 267 | }) 268 | 269 | if sourceIdx < 0 { 270 | return "", fmt.Errorf("IP address not found for container '%s'", containerName) 271 | } 272 | 273 | for _, value := range containers[sourceIdx].NetworkSettings.Networks { 274 | return value.IPAddress, nil 275 | } 276 | 277 | return "", fmt.Errorf("IP address not found for container '%s'", containerName) 278 | } 279 | -------------------------------------------------------------------------------- /internal/switch_agent/switch_agent.go: -------------------------------------------------------------------------------- 1 | package switch_agent 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/binary" 7 | "github.com/allegro/bigcache/v3" 8 | ciliumEbpf "github.com/cilium/ebpf" 9 | "github.com/cilium/ebpf/link" 10 | "github.com/cilium/ebpf/ringbuf" 11 | "github.com/cilium/ebpf/rlimit" 12 | "github.com/sirupsen/logrus" 13 | "github.com/vishvananda/netlink" 14 | "os" 15 | "os/signal" 16 | "time" 17 | switchAgentEbpfGen "wormhole/internal/switch_agent/ebpf" 18 | ) 19 | 20 | // SwitchAgent struct encapsulates all related functions and data 21 | type SwitchAgent struct { 22 | networkInterfaces []netlink.Link 23 | xdpObjects switchAgentEbpfGen.SwitchAgentXDPObjects 24 | tcObjects switchAgentEbpfGen.SwitchAgentUnknownUnicastFloodingObjects 25 | userspaceMacTable *bigcache.BigCache 26 | userspaceMacTableReInsertChannel chan switchAgentEbpfGen.SwitchAgentXDPMacAddressIfaceEntry 27 | attachedLinks []*link.Link 28 | } 29 | 30 | // NewSwitchAgent initializes and returns a new SwitchAgent instance 31 | func NewSwitchAgent(networkInterfaces []netlink.Link) *SwitchAgent { 32 | return &SwitchAgent{ 33 | userspaceMacTableReInsertChannel: make(chan switchAgentEbpfGen.SwitchAgentXDPMacAddressIfaceEntry), 34 | networkInterfaces: networkInterfaces, 35 | } 36 | } 37 | 38 | // ActivateSwitchAgent is the entry point to start the SwitchAgent 39 | func (sa *SwitchAgent) ActivateSwitchAgent() error { 40 | if err := rlimit.RemoveMemlock(); err != nil { 41 | logrus.Fatal("Removing memlock:", err) 42 | } 43 | 44 | logrus.Print("1") 45 | 46 | // Load the compiled eBPF ELF and load it into the kernel. 47 | if err := switchAgentEbpfGen.LoadSwitchAgentXDPObjects(&sa.xdpObjects, nil); err != nil { 48 | logrus.Fatal("Loading XDP eBPF objects:", err) 49 | } 50 | defer sa.xdpObjects.Close() 51 | 52 | logrus.Print("2") 53 | 54 | if err := switchAgentEbpfGen.LoadSwitchAgentUnknownUnicastFloodingObjects(&sa.tcObjects, nil); err != nil { 55 | logrus.Fatal("Loading TC eBPF objects:", err) 56 | } 57 | defer sa.tcObjects.Close() 58 | 59 | logrus.Print("size of networkInterfaces ", len(sa.networkInterfaces)) 60 | 61 | logrus.Print("3") 62 | 63 | if err := sa.addNetworkInterfacesToUnknownUnicastFloodingMaps(); err != nil { 64 | return err 65 | } 66 | 67 | logrus.Print("4") 68 | 69 | evictionCallback := sa.createUserspaceMacTableEvictionCallback() 70 | sa.userspaceMacTable = sa.createUserspaceMacTable(evictionCallback) 71 | 72 | logrus.Print("5") 73 | 74 | go sa.reInsertIntoUserspaceMacTable() 75 | 76 | logrus.Print("6") 77 | 78 | var err error 79 | sa.attachedLinks, err = sa.attachToInterfaces() 80 | defer sa.closeAttachedLinks() 81 | if err != nil { 82 | return err 83 | } 84 | 85 | logrus.Print("7") 86 | 87 | go sa.handleNewDiscoveredEntriesRingBuffer() 88 | 89 | logrus.Print("8") 90 | 91 | return sa.waitForCtrlC() 92 | } 93 | 94 | func (sa *SwitchAgent) addNetworkInterfacesToUnknownUnicastFloodingMaps() error { 95 | 96 | logrus.Print("3.1") 97 | err := sa.tcObjects.InterfacesArrayLength.Put(uint32(0), uint32(len(sa.networkInterfaces))) 98 | if err != nil { 99 | logrus.Fatalf("something bad happened %s", err) 100 | } 101 | 102 | logrus.Print("3.2") 103 | 104 | for i, networkInterface := range sa.networkInterfaces { 105 | logrus.Print("3.3_element") 106 | err = sa.tcObjects.InterfacesArray.Put(uint32(i), uint32(networkInterface.Attrs().Index)) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | 112 | logrus.Print("3.4") 113 | return nil 114 | } 115 | 116 | func (sa *SwitchAgent) createUserspaceMacTable(onRemove func(string, []byte)) *bigcache.BigCache { 117 | defaultCacheConfig := bigcache.DefaultConfig(20 * time.Second) 118 | userspaceMacTable, _ := bigcache.New( 119 | context.Background(), 120 | bigcache.Config{ 121 | Shards: defaultCacheConfig.Shards, 122 | LifeWindow: defaultCacheConfig.LifeWindow, 123 | CleanWindow: 1 * time.Second, 124 | MaxEntriesInWindow: defaultCacheConfig.MaxEntriesInWindow, 125 | MaxEntrySize: defaultCacheConfig.MaxEntrySize, 126 | StatsEnabled: defaultCacheConfig.StatsEnabled, 127 | Verbose: defaultCacheConfig.Verbose, 128 | HardMaxCacheSize: defaultCacheConfig.HardMaxCacheSize, 129 | OnRemove: onRemove, 130 | Hasher: defaultCacheConfig.Hasher, 131 | }) 132 | return userspaceMacTable 133 | } 134 | 135 | func (sa *SwitchAgent) createUserspaceMacTableEvictionCallback() func(string, []byte) { 136 | return func(key string, entry []byte) { 137 | logrus.Print("Eviction1") 138 | macKey := ConvertStringToMac(key) 139 | ifaceIndexInKernel := switchAgentEbpfGen.SwitchAgentXDPIfaceIndex{} 140 | err := sa.xdpObjects.MacTable.Lookup(&macKey, &ifaceIndexInKernel) 141 | if err != nil { 142 | logrus.Fatalf("Error parsing value in kernel for mac %s, err: %s", key, err) 143 | return 144 | } 145 | 146 | logrus.Printf("Eviction2, %d", ifaceIndexInKernel.InterfaceIndex) 147 | 148 | currentTimeNano := uint64(time.Now().UnixNano()) 149 | logrus.Printf("Eviction.currentTimeNano %d", currentTimeNano) 150 | timestampInKernelNano := ifaceIndexInKernel.Timestamp // convert timestamp to seconds 151 | 152 | timeDifferenceSeconds := (currentTimeNano - timestampInKernelNano) / 1_000_000_000 153 | 154 | logrus.Printf("Eviction.currentTimeNano %d timestampInKernelNano %d TimeDifference %d, for mac: %s", currentTimeNano, timestampInKernelNano, timeDifferenceSeconds, key) 155 | 156 | if timeDifferenceSeconds < 20 { 157 | // if upon removal of the key in userspace Mac Table we realized that 158 | // the kernel space equivalent of that item is _not_ older than 5 minutes 159 | // we will realize that this Mac address have been visible to the switch 160 | // in tha last 5 minutes, so we will re-insert the key/value for that mac 161 | // address again in the userspace table, with the latest value obtained 162 | // from the kernel space mac table. 163 | 164 | logrus.Print("Eviction.TryToReinsert") 165 | sa.userspaceMacTableReInsertChannel <- switchAgentEbpfGen.SwitchAgentXDPMacAddressIfaceEntry{ 166 | Mac: macKey, 167 | Iface: ifaceIndexInKernel, 168 | } 169 | } else { 170 | logrus.Printf("Eviction.Delete, for mac: %s", key) 171 | err := sa.xdpObjects.MacTable.Delete(macKey) 172 | if err != nil { 173 | logrus.Fatalf("Error In Deleting %s", err) 174 | } 175 | } 176 | logrus.Print("Eviction.finish") 177 | } 178 | } 179 | 180 | func (sa *SwitchAgent) reInsertIntoUserspaceMacTable() { 181 | for entry := range sa.userspaceMacTableReInsertChannel { 182 | logrus.Print("ReInsert.1") 183 | key := ConvertMacToString(entry.Mac) 184 | value, _ := EncodeIfaceIndex(entry.Iface) 185 | logrus.Print("ReInsert.2") 186 | err := sa.userspaceMacTable.Set(key, value) 187 | if err != nil { 188 | logrus.Fatalln("cannot reinsert evicted into userspaceMacTable") 189 | } 190 | logrus.Print("ReInsert.3") 191 | } 192 | } 193 | 194 | func (sa *SwitchAgent) attachToInterfaces() ([]*link.Link, error) { 195 | var attachedLinks []*link.Link 196 | 197 | logrus.Print("6.1") 198 | 199 | for _, iface := range sa.networkInterfaces { 200 | var err error 201 | 202 | logrus.Print("6.2.element") 203 | 204 | // Attach switchAgentXdp to the network interface. 205 | 206 | attachedXdpLink, err := link.AttachXDP(link.XDPOptions{ 207 | Program: sa.xdpObjects.SwitchAgentXdp, 208 | Interface: iface.Attrs().Index, 209 | Flags: link.XDPGenericMode, 210 | }) 211 | 212 | if err != nil { 213 | logrus.Fatal("Attaching XDP:", err) 214 | return attachedLinks, err 215 | } 216 | 217 | // attach switchAgentUnknownUnicastFlooding to the network interface 218 | 219 | attachedTcLink, err := link.AttachTCX(link.TCXOptions{ 220 | Interface: iface.Attrs().Index, 221 | Program: sa.tcObjects.SwitchAgentUnknownUnicastFlooding, 222 | Attach: ciliumEbpf.AttachTCXIngress, 223 | Anchor: link.Tail(), 224 | }) 225 | 226 | if err != nil { 227 | logrus.Fatal("Attaching TCX:", err) 228 | return attachedLinks, err 229 | } 230 | 231 | attachedLinks = append(attachedLinks, &attachedXdpLink, &attachedTcLink) 232 | } 233 | 234 | return attachedLinks, nil 235 | } 236 | 237 | func (sa *SwitchAgent) handleNewDiscoveredEntriesRingBuffer() { 238 | for { 239 | logrus.Print("HandleNewDiscoveredEntries.1") 240 | 241 | rd, err := ringbuf.NewReader(sa.xdpObjects.NewDiscoveredEntriesRb) 242 | if err != nil { 243 | logrus.Fatalf("opening ringbuf reader: %s", err) 244 | } 245 | defer rd.Close() 246 | 247 | logrus.Print("HandleNewDiscoveredEntries.2") 248 | var entry switchAgentEbpfGen.SwitchAgentXDPMacAddressIfaceEntry 249 | record, err := rd.Read() 250 | 251 | logrus.Print("HandleNewDiscoveredEntries.3") 252 | 253 | if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.NativeEndian, &entry); err != nil { 254 | logrus.Fatalf("failed parsing ringbuf entry: %s", err) 255 | continue 256 | } 257 | 258 | logrus.Print("HandleNewDiscoveredEntries.4") 259 | 260 | key := ConvertMacToString(entry.Mac) 261 | value, _ := EncodeIfaceIndex(entry.Iface) 262 | 263 | logrus.Print("HandleNewDiscoveredEntries.5") 264 | sa.userspaceMacTable.Set(key, value) 265 | logrus.Print("HandleNewDiscoveredEntries.6") 266 | } 267 | } 268 | 269 | func (sa *SwitchAgent) closeAttachedLinks() { 270 | for _, l := range sa.attachedLinks { 271 | (*l).Close() 272 | } 273 | } 274 | 275 | func (sa *SwitchAgent) waitForCtrlC() error { 276 | // Periodically print user mac table 277 | // exit the program when interrupted. 278 | tick := time.Tick(time.Second) 279 | stop := make(chan os.Signal, 5) 280 | signal.Notify(stop, os.Interrupt) 281 | for { 282 | select { 283 | case <-tick: 284 | logrus.Printf("userspace mac table size %d", sa.userspaceMacTable.Len()) 285 | var ( 286 | key switchAgentEbpfGen.SwitchAgentXDPMacAddress 287 | value switchAgentEbpfGen.SwitchAgentXDPIfaceIndex 288 | ) 289 | 290 | for i := 0; sa.xdpObjects.MacTable.Iterate().Next(&key, &value) && i < 3; i++ { 291 | logrus.Printf("item in kernelspace MacTable, key %s", ConvertMacToString(key)) 292 | } 293 | case <-stop: 294 | logrus.Print("Received signal, exiting..") 295 | return nil 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /.zellij/zellij_docker_exec_layout_2.kdl: -------------------------------------------------------------------------------- 1 | layout { 2 | cwd "/workspaces/devpod-wormhole" 3 | tab name="Tab #1" focus=true hide_floating_panes=true { 4 | pane size=1 borderless=true { 5 | plugin location="zellij:tab-bar" 6 | } 7 | pane size="50%" split_direction="vertical" { 8 | pane command="zellij" cwd="/workspaces/devpod-wormhole" size="25%" { 9 | args "action" "dump-layout" 10 | start_suspended true 11 | } 12 | pane size="25%" 13 | pane cwd="/workspaces/devpod-wormhole" size="50%" 14 | } 15 | pane size="50%" split_direction="vertical" { 16 | pane command="zellij" focus=true size="25%" { 17 | args "action" "dump-layout" 18 | start_suspended true 19 | } 20 | pane size="25%" 21 | pane cwd="/workspaces/devpod-wormhole" size="50%" 22 | } 23 | pane size=1 borderless=true { 24 | plugin location="zellij:status-bar" 25 | } 26 | } 27 | new_tab_template { 28 | pane size=1 borderless=true { 29 | plugin location="zellij:tab-bar" 30 | } 31 | pane cwd="/workspaces/devpod-wormhole" 32 | pane size=1 borderless=true { 33 | plugin location="zellij:status-bar" 34 | } 35 | } 36 | swap_tiled_layout name="vertical" { 37 | tab max_panes=5 { 38 | pane size=1 borderless=true { 39 | plugin location="tab-bar" 40 | } 41 | pane { 42 | pane split_direction="vertical" { 43 | pane 44 | pane { 45 | children 46 | } 47 | } 48 | } 49 | pane size=1 borderless=true { 50 | plugin location="status-bar" 51 | } 52 | } 53 | tab max_panes=8 { 54 | pane size=1 borderless=true { 55 | plugin location="tab-bar" 56 | } 57 | pane { 58 | pane split_direction="vertical" { 59 | pane { 60 | children 61 | } 62 | pane { 63 | pane 64 | pane 65 | pane 66 | pane 67 | } 68 | } 69 | } 70 | pane size=1 borderless=true { 71 | plugin location="status-bar" 72 | } 73 | } 74 | tab max_panes=12 { 75 | pane size=1 borderless=true { 76 | plugin location="tab-bar" 77 | } 78 | pane { 79 | pane split_direction="vertical" { 80 | pane { 81 | children 82 | } 83 | pane { 84 | pane 85 | pane 86 | pane 87 | pane 88 | } 89 | pane { 90 | pane 91 | pane 92 | pane 93 | pane 94 | } 95 | } 96 | } 97 | pane size=1 borderless=true { 98 | plugin location="status-bar" 99 | } 100 | } 101 | } 102 | swap_tiled_layout name="horizontal" { 103 | tab max_panes=5 { 104 | pane size=1 borderless=true { 105 | plugin location="tab-bar" 106 | } 107 | pane { 108 | pane 109 | pane 110 | } 111 | pane size=1 borderless=true { 112 | plugin location="status-bar" 113 | } 114 | } 115 | tab max_panes=8 { 116 | pane size=1 borderless=true { 117 | plugin location="tab-bar" 118 | } 119 | pane { 120 | pane { 121 | pane split_direction="vertical" { 122 | children 123 | } 124 | pane split_direction="vertical" { 125 | pane 126 | pane 127 | pane 128 | pane 129 | } 130 | } 131 | } 132 | pane size=1 borderless=true { 133 | plugin location="status-bar" 134 | } 135 | } 136 | tab max_panes=12 { 137 | pane size=1 borderless=true { 138 | plugin location="tab-bar" 139 | } 140 | pane { 141 | pane { 142 | pane split_direction="vertical" { 143 | children 144 | } 145 | pane split_direction="vertical" { 146 | pane 147 | pane 148 | pane 149 | pane 150 | } 151 | pane split_direction="vertical" { 152 | pane 153 | pane 154 | pane 155 | pane 156 | } 157 | } 158 | } 159 | pane size=1 borderless=true { 160 | plugin location="status-bar" 161 | } 162 | } 163 | } 164 | swap_tiled_layout name="stacked" { 165 | tab min_panes=5 { 166 | pane size=1 borderless=true { 167 | plugin location="tab-bar" 168 | } 169 | pane { 170 | pane split_direction="vertical" { 171 | pane 172 | pane stacked=true { 173 | children 174 | } 175 | } 176 | } 177 | pane size=1 borderless=true { 178 | plugin location="status-bar" 179 | } 180 | } 181 | } 182 | swap_floating_layout name="staggered" { 183 | floating_panes { 184 | } 185 | } 186 | swap_floating_layout name="enlarged" { 187 | floating_panes max_panes=10 { 188 | pane cwd="/workspaces/devpod-wormhole" { 189 | height "90%" 190 | width "90%" 191 | x "5%" 192 | y 1 193 | } 194 | pane cwd="/workspaces/devpod-wormhole" { 195 | height "90%" 196 | width "90%" 197 | x "5%" 198 | y 2 199 | } 200 | pane cwd="/workspaces/devpod-wormhole" { 201 | height "90%" 202 | width "90%" 203 | x "5%" 204 | y 3 205 | } 206 | pane cwd="/workspaces/devpod-wormhole" { 207 | height "90%" 208 | width "90%" 209 | x "5%" 210 | y 4 211 | } 212 | pane cwd="/workspaces/devpod-wormhole" { 213 | height "90%" 214 | width "90%" 215 | x "5%" 216 | y 5 217 | } 218 | pane cwd="/workspaces/devpod-wormhole" { 219 | height "90%" 220 | width "90%" 221 | x "5%" 222 | y 6 223 | } 224 | pane cwd="/workspaces/devpod-wormhole" { 225 | height "90%" 226 | width "90%" 227 | x "5%" 228 | y 7 229 | } 230 | pane cwd="/workspaces/devpod-wormhole" { 231 | height "90%" 232 | width "90%" 233 | x "5%" 234 | y 8 235 | } 236 | pane cwd="/workspaces/devpod-wormhole" { 237 | height "90%" 238 | width "90%" 239 | x "5%" 240 | y 9 241 | } 242 | pane cwd="/workspaces/devpod-wormhole" focus=true { 243 | height "90%" 244 | width "90%" 245 | x 10 246 | y 10 247 | } 248 | } 249 | } 250 | swap_floating_layout name="spread" { 251 | floating_panes max_panes=1 { 252 | pane cwd="/workspaces/devpod-wormhole" { 253 | x "50%" 254 | y "50%" 255 | } 256 | } 257 | floating_panes max_panes=2 { 258 | pane cwd="/workspaces/devpod-wormhole" { 259 | width "45%" 260 | x "1%" 261 | y "25%" 262 | } 263 | pane cwd="/workspaces/devpod-wormhole" { 264 | width "45%" 265 | x "50%" 266 | y "25%" 267 | } 268 | } 269 | floating_panes max_panes=3 { 270 | pane cwd="/workspaces/devpod-wormhole" focus=true { 271 | height "45%" 272 | width "45%" 273 | y "55%" 274 | } 275 | pane cwd="/workspaces/devpod-wormhole" { 276 | width "45%" 277 | x "1%" 278 | y "1%" 279 | } 280 | pane cwd="/workspaces/devpod-wormhole" { 281 | width "45%" 282 | x "50%" 283 | y "1%" 284 | } 285 | } 286 | floating_panes max_panes=4 { 287 | pane cwd="/workspaces/devpod-wormhole" { 288 | height "45%" 289 | width "45%" 290 | x "1%" 291 | y "55%" 292 | } 293 | pane cwd="/workspaces/devpod-wormhole" focus=true { 294 | height "45%" 295 | width "45%" 296 | x "50%" 297 | y "55%" 298 | } 299 | pane cwd="/workspaces/devpod-wormhole" { 300 | height "45%" 301 | width "45%" 302 | x "1%" 303 | y "1%" 304 | } 305 | pane cwd="/workspaces/devpod-wormhole" { 306 | height "45%" 307 | width "45%" 308 | x "50%" 309 | y "1%" 310 | } 311 | } 312 | } 313 | } 314 | 315 | -------------------------------------------------------------------------------- /internal/vxlan_agent/ebpf/c/vxlan_tc_internal.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vxlan_common.bpf.h" 2 | #include "../../../../include/vmlinux.h" 3 | 4 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 5 | 6 | // -------------------------------------------------------- 7 | 8 | // a LPM trie data structure 9 | // for example 192.168.1.0/24 --> VNI 0 10 | 11 | struct 12 | { 13 | __uint(type, BPF_MAP_TYPE_LPM_TRIE); 14 | __type(key, struct ipv4_lpm_key); 15 | __type(value, struct network_vni); 16 | __uint(map_flags, BPF_F_NO_PREALLOC); 17 | __uint(max_entries, 255); 18 | __uint(pinning, LIBBPF_PIN_BY_NAME); 19 | } networks_map SEC(".maps"); 20 | 21 | // -------------------------------------------------------- 22 | 23 | // will use these info in case we want to forward a packet to 24 | // external network 25 | struct 26 | { 27 | __uint(type, BPF_MAP_TYPE_HASH); 28 | __type(key, struct in_addr); 29 | __type(value, struct external_route_info); 30 | __uint(max_entries, 4 * 1024); 31 | __uint(pinning, LIBBPF_PIN_BY_NAME); 32 | } border_ip_to_route_info_map SEC(".maps"); 33 | 34 | // -------------------------------------------------------- 35 | 36 | static int __always_inline clone_internal_packet_and_send_to_all_internal_ifaces_and_external_border_ips(struct __sk_buff *skb, struct ethhdr *eth); 37 | 38 | // -------------------------------------------------------- 39 | 40 | SEC("tcx/ingress") 41 | int vxlan_tc_internal(struct __sk_buff *skb) 42 | { 43 | my_bpf_printk("%d 1. packet received", skb->ifindex); 44 | 45 | if (skb == NULL) 46 | return TC_ACT_SHOT; 47 | 48 | __u64 current_time = bpf_ktime_get_tai_ns(); 49 | 50 | struct ethhdr *eth = (void *)(long)skb->data; 51 | 52 | if ((void *)(eth + 1) > (void *)(long)skb->data_end) 53 | return TC_ACT_SHOT; 54 | 55 | my_bpf_printk("%d 2. packet recieved", skb->ifindex); 56 | 57 | return clone_internal_packet_and_send_to_all_internal_ifaces_and_external_border_ips(skb, eth); 58 | } 59 | 60 | // -------------------------------------------------------- 61 | 62 | static int __always_inline clone_internal_packet_and_send_to_all_internal_ifaces_and_external_border_ips(struct __sk_buff *skb, struct ethhdr *eth) 63 | { 64 | my_bpf_printk("%d 5. start clone_internal_packet_and_send_to_all_internal_ifaces_and_external_border_ips", skb->ingress_ifindex); 65 | int i; 66 | 67 | __u16 h_proto = bpf_ntohs(eth->h_proto); 68 | 69 | struct ipv4_lpm_key key = {.prefixlen = 32}; 70 | 71 | if (h_proto == ETH_P_ARP) 72 | { 73 | struct arphdr *arph = (void *)(eth + 1); 74 | 75 | if ((void *)(arph + 1) > (void *)(long)skb->data_end) 76 | return TC_ACT_SHOT; 77 | 78 | struct arp_payload *arp_payload = (void *)(arph + 1); 79 | 80 | if ((void *)(arp_payload + 1) > (void *)(long)skb->data_end) 81 | return TC_ACT_SHOT; 82 | 83 | __builtin_memcpy(key.data, arp_payload->ar_tip, sizeof(key.data)); 84 | 85 | my_bpf_printk("Target IP: %d.%d.%d.%d\n", 86 | key.data[0], 87 | key.data[1], 88 | key.data[2], 89 | key.data[3]); 90 | } 91 | else if (h_proto == ETH_P_IP) 92 | { 93 | 94 | struct iphdr *iph = (void *)(eth + 1); 95 | 96 | // Ensure the inner IP header is valid 97 | if ((void *)(iph + 1) > (void *)(long)skb->data_end) 98 | return TC_ACT_SHOT; 99 | 100 | // Extract inner source and destination IP addresses 101 | __builtin_memcpy(key.data, &(iph->daddr), sizeof(key.data)); 102 | } 103 | else 104 | { 105 | return TC_ACT_SHOT; 106 | } 107 | 108 | struct network_vni *dst_network_vni = bpf_map_lookup_elem(&networks_map, &key); 109 | if (dst_network_vni == NULL) 110 | { 111 | my_bpf_printk("tcx/ingres INTERNAL does not belong to internal network. pass it up"); 112 | return TC_ACT_OK; 113 | } 114 | 115 | if (dst_network_vni->internal_ifindexes_size == 0 || dst_network_vni->border_ips_size == 0) 116 | { 117 | my_bpf_printk("tcx/ingres INTERNAL invalid dst_network_vni"); 118 | return TC_ACT_SHOT; 119 | } 120 | 121 | bpf_for(i, 0, MAX_INTERNAL_IFINDEXES) 122 | { 123 | if (i >= dst_network_vni->internal_ifindexes_size) 124 | break; 125 | 126 | if (dst_network_vni->internal_ifindexes[i] != skb->ingress_ifindex) 127 | { 128 | my_bpf_printk("%d 8. cloning and redirecting packet i=%d to internal interface", skb->ingress_ifindex, i); 129 | bpf_clone_redirect(skb, dst_network_vni->internal_ifindexes[i], 0); 130 | } 131 | } 132 | 133 | void *data = (void *)(long)skb->data; 134 | void *data_end = (void *)(long)skb->data_end; 135 | struct ethhdr *inner_eth = data; 136 | 137 | // Calculate the new packet length 138 | int old_len = data_end - data; 139 | my_bpf_printk("%d 16. old_len=%d", skb->ingress_ifindex, old_len); 140 | 141 | // Resize the packet buffer by increasing the headroom. 142 | // in bpf_xdp_adjust_head() if we want to increase the packet length, we must use negative number 143 | // in bpf_skb_adjust_room() if we want to increase the packet length, we must use positive number 144 | // TODO: check if this is the correct way to increase the packet length 145 | my_bpf_printk("%d 9. cloning and redirecting packet to remote borders", skb->ingress_ifindex); 146 | long ret = bpf_skb_change_head(skb, NEW_HDR_LEN, 0); 147 | if (ret) 148 | { 149 | my_bpf_printk("%d 10. failed to adjust room for external interface %d error %d", skb->ingress_ifindex, i, ret); 150 | return TC_ACT_SHOT; 151 | } 152 | else 153 | { 154 | my_bpf_printk("%d 10. successful adjust room for external interface %d", skb->ingress_ifindex, i); 155 | } 156 | 157 | int new_len = old_len + NEW_HDR_LEN; 158 | my_bpf_printk("%d 16. new_len=%d", skb->ingress_ifindex, new_len); 159 | 160 | my_bpf_printk("%d 10. cloning & redirecting to remote borders", skb->ingress_ifindex); 161 | 162 | bpf_for(i, 0, MAX_BORDER_IPS) 163 | { 164 | 165 | // ============================================================================================================== 166 | 167 | if (i >= dst_network_vni->border_ips_size) 168 | break; 169 | 170 | struct in_addr *border_ip = &dst_network_vni->border_ips[i]; 171 | 172 | my_bpf_printk("%d 13. try to find remote border route_info i=[%d]", skb->ingress_ifindex, i); 173 | struct external_route_info *route_info = bpf_map_lookup_elem(&border_ip_to_route_info_map, border_ip); 174 | 175 | if (route_info == NULL) 176 | { 177 | my_bpf_printk("%d 14. unable to find remote border route_info i=[%d]", skb->ingress_ifindex, i); 178 | return TC_ACT_SHOT; 179 | } 180 | 181 | // ============================================================================================================== 182 | 183 | // if packet need to be forwarded to an external interface 184 | // we must add outer headers to the packet 185 | // and then forward it to the external interface 186 | // so the packet will have: 187 | // - outer ethernet header 188 | // - outer ip header 189 | // - outer udp header 190 | // - outer vxlan header 191 | // - inner original layer 2 frame 192 | 193 | my_bpf_printk("%d 15. setting packet fields before redirecting i=[%d]", skb->ingress_ifindex, i); 194 | 195 | void *data = (void *)(long)skb->data; 196 | void *data_end = (void *)(long)skb->data_end; 197 | struct ethhdr *inner_eth = data; 198 | 199 | struct ethhdr *outer_eth; 200 | struct iphdr *outer_iph; 201 | struct udphdr *outer_udph; 202 | struct vxlanhdr *outer_vxh; 203 | 204 | // Ensure the packet is still valid after adjustment 205 | if (data + NEW_HDR_LEN > data_end) 206 | { 207 | my_bpf_printk("%d 16. incorrect data & data_end size after adjusting size i=[%d]", skb->ingress_ifindex, i); 208 | return TC_ACT_SHOT; 209 | } 210 | 211 | outer_eth = data; 212 | outer_iph = data + ETH_HLEN; // ETH_HLEN == 14 & ETH_ALEN == 6 213 | outer_udph = data + ETH_HLEN + IP_HDR_LEN; 214 | outer_vxh = data + ETH_HLEN + IP_HDR_LEN + UDP_HDR_LEN; 215 | 216 | __builtin_memcpy(outer_eth->h_source, route_info->external_iface_mac.addr, ETH_ALEN); // mac address is a byte sequence, not affected by endianness 217 | __builtin_memcpy(outer_eth->h_dest, route_info->external_iface_next_hop_mac.addr, ETH_ALEN); // mac address is a byte sequence, not affected by endianness 218 | outer_eth->h_proto = bpf_htons(ETH_P_IP); // ip address is a multi-byte value, so it needs to be in network byte order 219 | 220 | outer_iph->version = 4; // ip version 221 | outer_iph->ihl = 5; // ip header length 222 | outer_iph->tos = 0; // ip type of service 223 | outer_iph->tot_len = bpf_htons(new_len - ETH_HLEN); // ip total length 224 | my_bpf_printk("%d 16. outer_iph->tot_len=%d", skb->ingress_ifindex, new_len - ETH_HLEN); 225 | outer_iph->id = 0; // ip id 226 | outer_iph->frag_off = 0; // ip fragment offset 227 | outer_iph->ttl = 64; // ip time to live 228 | outer_iph->protocol = IPPROTO_UDP; // ip protocol 229 | outer_iph->check = 0; // ip checksum will be calculated later 230 | outer_iph->saddr = bpf_htonl(route_info->external_iface_ip.s_addr); // ip source address 231 | outer_iph->daddr = bpf_htonl(border_ip->s_addr); // ip destination address 232 | 233 | outer_udph->source = bpf_htons(get_ephemeral_port()); // Source UDP port 234 | outer_udph->dest = bpf_htons(4790); // Destination UDP port (VXLAN default) 235 | outer_udph->len = bpf_htons(new_len - ETH_HLEN - IP_HDR_LEN); // UDP length 236 | outer_udph->check = 0; // UDP checksum is optional in IPv4 237 | 238 | // for now we don't set VXLAN header 239 | outer_vxh->vx_vni = bpf_htonl(dst_network_vni->vni); 240 | outer_vxh->vx_flags = bpf_htonl(0x80000000); 241 | 242 | // Calculate ip checksum 243 | my_bpf_printk("%d 16. fixing checksum before redirecting i=[%d]", skb->ingress_ifindex, i); 244 | outer_iph->check = ~bpf_csum_diff(0, 0, (__u32 *)outer_iph, IP_HDR_LEN, 0); 245 | 246 | my_bpf_printk("%d 16. performing the redirection i=[%d]", skb->ingress_ifindex, i); 247 | bpf_clone_redirect(skb, route_info->external_iface_index, 0); 248 | } 249 | 250 | return TC_ACT_OK; 251 | } 252 | 253 | // -------------------------------------------------------- --------------------------------------------------------------------------------