Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gray/bpf test #22

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ clean-ebpf:
rm -f control/bpf_bpf*.o
@rm -f trace/bpf_bpf*.go && \
rm -f trace/bpf_bpf*.o
@rm -f control/kern/tests/bpftest_bpf*.go && \
rm -f control/kern/tests/bpftest_bpf*.o
fmt:
go fmt ./...

Expand All @@ -99,4 +101,18 @@ ebpf: submodule clean-ebpf
ebpf-lint:
./scripts/checkpatch.pl --no-tree --strict --no-summary --show-types --color=always control/kern/tproxy.c --ignore COMMIT_COMMENT_SYMBOL,NOT_UNIFIED_DIFF,COMMIT_LOG_LONG_LINE,LONG_LINE_COMMENT,VOLATILE,ASSIGN_IN_IF,PREFER_DEFINED_ATTRIBUTE_MACRO,CAMELCASE,LEADING_SPACE,OPEN_ENDED_LINE,SPACING,BLOCK_COMMENT_STYLE

ebpf-test: export BPF_CLANG := $(CLANG)
ebpf-test: export BPF_STRIP_FLAG := $(STRIP_FLAG)
ebpf-test: export BPF_CFLAGS := $(CFLAGS)
ebpf-test: export BPF_TARGET := $(TARGET)
ebpf-test: export BPF_TRACE_TARGET := $(GOARCH)
ebpf-test: submodule clean-ebpf
@unset GOOS && \
unset GOARCH && \
unset GOARM && \
echo $(STRIP_FLAG) && \
go generate ./control/kern/tests/bpf_test.go && \
go clean -testcache && \
go test -v ./control/kern/tests/...

## End Ebpf
154 changes: 154 additions & 0 deletions control/kern/tests/bpf_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (c) 2022-2024, daeuniverse Organization <[email protected]>

//go:build exclude

/*
#define __DEBUG
#define __DEBUG_ROUTING
*/

#include "../tproxy.c"

#define IP4_HLEN sizeof(struct iphdr)
#define TCP_HLEN sizeof(struct tcphdr)

struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(max_entries, 1);
__array(values, int());
} entry_call_map SEC(".maps") = {
.values = {
[0] = &tproxy_wan_egress,
},
};

SEC("tc/pktgen/hijack_by_port_80")
int testpktgen_hijack_by_port_80(struct __sk_buff *skb)
{
bpf_skb_change_tail(skb, ETH_HLEN + IP4_HLEN + TCP_HLEN, 0);

void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;

struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) {
bpf_printk("data + sizeof(*eth) > data_end\n");
return TC_ACT_SHOT;
}
eth->h_proto = bpf_htons(ETH_P_IP);

struct iphdr *ip = data + ETH_HLEN;
if ((void *)(ip + 1) > data_end) {
bpf_printk("data + sizeof(*ip) > data_end\n");
return TC_ACT_SHOT;
}
ip->ihl = 5;
ip->version = 4;
ip->protocol = IPPROTO_TCP;
ip->saddr = bpf_htonl(0xc0a80001); // 192.168.0.1
ip->daddr = bpf_htonl(0x01010101); // 1.1.1.1

struct tcphdr *tcp = data + ETH_HLEN + IP4_HLEN;
if ((void *)(tcp + 1) > data_end) {
bpf_printk("data + sizeof(*tcp) > data_end\n");
return TC_ACT_SHOT;
}
tcp->source = bpf_htons(19233);
tcp->dest = bpf_htons(79);
tcp->syn = 1;

return TC_ACT_OK;
}

SEC("tc/setup/hijack_by_port_80")
int testsetup_hijack_by_port_80(struct __sk_buff *skb)
{
__u32 linklen = ETH_HLEN;
bpf_map_update_elem(&linklen_map, &one_key, &linklen, BPF_ANY);

struct match_set ms = {};
struct port_range pr = {80, 80};
ms.port_range = pr;
ms.not = false;
ms.type = MatchType_Port;
ms.outbound = 2;
ms.must = false;
ms.mark = 0;

int i = 0;
for (i = 0; i < 50; i++) {
pr.port_start = 80 + i;
pr.port_end = 80 + i;
ms.port_range = pr;
int key = i;
bpf_map_update_elem(&routing_map, &key, &ms, BPF_ANY);
}

__builtin_memset(&ms.__value, 0, sizeof(ms.__value));
ms.not = false;
ms.type = MatchType_Fallback;
ms.outbound = 2;
ms.must = false;
ms.mark = 0;
bpf_map_update_elem(&routing_map, &i, &ms, BPF_ANY);

bpf_tail_call(skb, &entry_call_map, 0);
return TC_ACT_OK;
}

SEC("tc/check/hijack_by_port_80")
int testcheck_hijack_by_port_80(struct __sk_buff *skb)
{
__u32 *status_code;

void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;

if (data + sizeof(*status_code) > data_end) {
bpf_printk("data + sizeof(*status_code) > data_end\n");
return TC_ACT_SHOT;
}

status_code = data;
if (*status_code != TC_ACT_REDIRECT) {
bpf_printk("status_code(%d) != TC_ACT_REDIRECT\n", *status_code);
return TC_ACT_SHOT;
}

struct ethhdr *eth = data + sizeof(*status_code);
if ((void *)(eth + 1) > data_end) {
bpf_printk("data + sizeof(*eth) > data_end\n");
return TC_ACT_SHOT;
}
if (eth->h_proto != bpf_htons(ETH_P_IP)) {
bpf_printk("eth->h_proto != 0x0800\n");
return TC_ACT_SHOT;
}

struct iphdr *ip = (void *)eth + ETH_HLEN;
if ((void *)(ip + 1) > data_end) {
bpf_printk("data + sizeof(*ip) > data_end\n");
return TC_ACT_SHOT;
}
if (ip->protocol != IPPROTO_TCP) {
bpf_printk("ip->protocol != IPPROTO_TCP\n");
return TC_ACT_SHOT;
}
if (ip->daddr != bpf_htonl(0x01010101)) {
bpf_printk("ip->daddr != 1.1.1.1\n");
}

struct tcphdr *tcp = (void *)ip + IP4_HLEN;
if ((void *)(tcp + 1) > data_end) {
bpf_printk("data + sizeof(*tcp) > data_end\n");
return TC_ACT_SHOT;
}
if (tcp->dest != bpf_htons(79)) {
bpf_printk("tcp->dest != 79\n");
return TC_ACT_SHOT;
}

return TC_ACT_OK;
}
157 changes: 157 additions & 0 deletions control/kern/tests/bpf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <[email protected]>
*/

package tests

import (
"errors"
"fmt"
"os"
"reflect"
"strings"
"testing"
"time"

"github.com/cilium/ebpf"
"github.com/vishvananda/netlink/nl"
)

//go:generate go run -mod=mod github.com/cilium/ebpf/cmd/bpf2go -cc "$BPF_CLANG" "$BPF_STRIP_FLAG" -cflags "$BPF_CFLAGS" -target "$BPF_TARGET" bpftest ./bpf_test.c -- -I../headers -I.

type programSet struct {
id string
pktgen *ebpf.Program
setup *ebpf.Program
check *ebpf.Program
}

func runBpfProgram(prog *ebpf.Program, data, ctx []byte, benchmark bool) (statusCode uint32, dataOut, ctxOut []byte, err error) {
dataOut = make([]byte, len(data))
if len(dataOut) > 0 {
// See comments at https://github.com/cilium/ebpf/blob/20c4d8896bdde990ce6b80d59a4262aa3ccb891d/prog.go#L563-L567
dataOut = make([]byte, len(data)+256+2)
}
ctxOut = make([]byte, len(ctx))
opts := &ebpf.RunOptions{
Data: data,
DataOut: dataOut,
Context: ctx,
ContextOut: ctxOut,
Repeat: 1,
}
if benchmark {
opts.Repeat = 5000000
}
ret, err := prog.Run(opts)
return ret, opts.DataOut, ctxOut, err
}

func collectPrograms(t *testing.T) (progset []programSet, err error) {
obj := &bpftestObjects{}
pinPath := "/sys/fs/bpf/dae"
if err = os.MkdirAll(pinPath, 0755); err != nil && !os.IsExist(err) {
return
}

if err = loadBpftestObjects(obj,
&ebpf.CollectionOptions{
Maps: ebpf.MapOptions{
PinPath: pinPath,
},
Programs: ebpf.ProgramOptions{
LogSize: ebpf.DefaultVerifierLogSize * 10,
},
},
); err != nil {
var (
ve *ebpf.VerifierError
verifierLog string
)
if errors.As(err, &ve) {
verifierLog = fmt.Sprintf("Verifier error: %+v\n", ve)
}

t.Fatalf("Failed to load objects: %s\n%+v", verifierLog, err)

return nil, err
}

v := reflect.ValueOf(obj.bpftestPrograms)
typeOfV := v.Type()
for i := 0; i < v.NumField(); i++ {
progname := typeOfV.Field(i).Name
if strings.HasPrefix(progname, "Testsetup") {
progid := strings.TrimPrefix(progname, "Testsetup")
progset = append(progset, programSet{
id: progid,
pktgen: v.FieldByName("Testpktgen" + progid).Interface().(*ebpf.Program),
setup: v.FieldByName("Testsetup" + progid).Interface().(*ebpf.Program),
check: v.FieldByName("Testcheck" + progid).Interface().(*ebpf.Program),
})
}
}
return
}

func printBpfDebugLog(t *testing.T) {
file, err := os.Open("/sys/kernel/tracing/trace_pipe")
if err != nil {
t.Fatalf("Failed to open trace_pipe: %v", err)
}
defer file.Close()

buffer := make([]byte, 1024*64)
n, err := file.Read(buffer)
if err != nil {
t.Fatalf("Failed to read from trace_pipe: %v", err)
}

fmt.Print(string(buffer[:n]))
}

func Test(t *testing.T) {
progsets, err := collectPrograms(t)
if err != nil {
t.Fatalf("error while collecting programs: %s", err)
}

for _, progset := range progsets {
// create ctx with the max allowed size(4k - head room - tailroom)
data := make([]byte, 4096-256-320)

// sizeof(struct __sk_buff) < 256, let's make it 256
ctx := make([]byte, 256)

statusCode, data, ctx, err := runBpfProgram(progset.pktgen, data, ctx, false)
if err != nil {
t.Fatalf("error while running pktgen prog: %s", err)
}
if statusCode != 0 {
printBpfDebugLog(t)
t.Fatalf("error while running pktgen program: unexpected status code: %d", statusCode)
}

currentTime := time.Now()
statusCode, data, ctx, err = runBpfProgram(progset.setup, data, ctx, true)
if err != nil {
printBpfDebugLog(t)
t.Fatalf("error while running setup prog: %s", err)
}
println(time.Since(currentTime).String())

status := make([]byte, 4)
nl.NativeEndian().PutUint32(status, statusCode)
data = append(status, data...)

statusCode, data, ctx, err = runBpfProgram(progset.check, data, ctx, false)
if err != nil {
t.Fatalf("error while running check program: %+v", err)
}
if statusCode != 0 {
printBpfDebugLog(t)
t.Fatalf("error while running check program: unexpected status code: %d", statusCode)
}
}
}
Loading