diff --git a/pkg/ebpf/bpf_bpfeb.o b/pkg/ebpf/bpf_bpfeb.o index 92303ee5b710b77162cc4a4ebdea73096ff99b66..b606e40943a81445fb93153d1c64678ddf091639 100644 Binary files a/pkg/ebpf/bpf_bpfeb.o and b/pkg/ebpf/bpf_bpfeb.o differ diff --git a/pkg/ebpf/bpf_bpfel.o b/pkg/ebpf/bpf_bpfel.o index 4e3eb08e6013c0cb8dee3457fb206dfb289988a3..3e563be5bf9bf32c326400fadfa82b30382904ef 100644 Binary files a/pkg/ebpf/bpf_bpfel.o and b/pkg/ebpf/bpf_bpfel.o differ diff --git a/pkg/ebpf/tracer.go b/pkg/ebpf/tracer.go index c59cab71155515ac9e5f7fc0bd10040fce7febb8..f9ff359557a3b4e271fd6ed1d2257ee4a49f7bf4 100644 --- a/pkg/ebpf/tracer.go +++ b/pkg/ebpf/tracer.go @@ -6,12 +6,14 @@ import ( "io/fs" "strings" + "github.com/netobserv/netobserv-ebpf-agent/pkg/ifaces" + "github.com/netobserv/netobserv-ebpf-agent/pkg/utils" + "github.com/cilium/ebpf" "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/ringbuf" "github.com/cilium/ebpf/rlimit" - "github.com/netobserv/netobserv-ebpf-agent/pkg/ifaces" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -29,6 +31,8 @@ const ( constSampling = "sampling" constTraceMessages = "trace_messages" constEnableRtt = "enable_rtt" + tcpDropHook = "kfree_skb" + dnsTraceHook = "net_dev_queue" ) var log = logrus.WithField("component", "ebpf.FlowFetcher") @@ -105,14 +109,53 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { return nil, fmt.Errorf("rewriting BPF constants definition: %w", err) } - if err := spec.LoadAndAssign(&objects, nil); err != nil { - var ve *ebpf.VerifierError - if errors.As(err, &ve) { - // Using %+v will print the whole verifier error, not just the last - // few lines. - log.Infof("Verifier error: %+v", ve) + oldKernel := utils.IskernelOlderthan514() + + // For older kernel (< 5.14) kfree_sbk drop hook doesn't exists + if oldKernel { + // Here we define another structure similar to the bpf2go created one but w/o the hooks that does not exist in older kernel + // Note: if new hooks are added in the future we need to update the following structures manually + type NewBpfPrograms struct { + EgressFlowParse *ebpf.Program `ebpf:"egress_flow_parse"` + IngressFlowParse *ebpf.Program `ebpf:"ingress_flow_parse"` + TraceNetPackets *ebpf.Program `ebpf:"trace_net_packets"` + } + type NewBpfObjects struct { + NewBpfPrograms + BpfMaps + } + var newObjects NewBpfObjects + // remove tcpdrop hook from the spec + delete(spec.Programs, tcpDropHook) + newObjects.NewBpfPrograms = NewBpfPrograms{} + if err := spec.LoadAndAssign(&newObjects, nil); err != nil { + var ve *ebpf.VerifierError + if errors.As(err, &ve) { + // Using %+v will print the whole verifier error, not just the last + // few lines. + log.Infof("Verifier error: %+v", ve) + } + return nil, fmt.Errorf("loading and assigning BPF objects: %w", err) + } + // Manually assign maps and programs to the original objects variable + // Note for any future maps or programs make sure to copy them manually here + objects.DirectFlows = newObjects.DirectFlows + objects.AggregatedFlows = newObjects.AggregatedFlows + objects.FlowSequences = newObjects.FlowSequences + objects.EgressFlowParse = newObjects.EgressFlowParse + objects.IngressFlowParse = newObjects.IngressFlowParse + objects.TraceNetPackets = newObjects.TraceNetPackets + objects.KfreeSkb = nil + } else { + if err := spec.LoadAndAssign(&objects, nil); err != nil { + var ve *ebpf.VerifierError + if errors.As(err, &ve) { + // Using %+v will print the whole verifier error, not just the last + // few lines. + log.Infof("Verifier error: %+v", ve) + } + return nil, fmt.Errorf("loading and assigning BPF objects: %w", err) } - return nil, fmt.Errorf("loading and assigning BPF objects: %w", err) } /* @@ -123,8 +166,8 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { btf.FlushKernelSpec() var tcpDropsLink link.Link - if cfg.TCPDrops { - tcpDropsLink, err = link.Tracepoint("skb", "kfree_skb", objects.KfreeSkb, nil) + if cfg.TCPDrops && !oldKernel { + tcpDropsLink, err = link.Tracepoint("skb", tcpDropHook, objects.KfreeSkb, nil) if err != nil { return nil, fmt.Errorf("failed to attach the BPF program to kfree_skb tracepoint: %w", err) } @@ -132,7 +175,7 @@ func NewFlowFetcher(cfg *FlowFetcherConfig) (*FlowFetcher, error) { var dnsTrackerLink link.Link if cfg.DNSTracker { - dnsTrackerLink, err = link.Tracepoint("net", "net_dev_queue", objects.TraceNetPackets, nil) + dnsTrackerLink, err = link.Tracepoint("net", dnsTraceHook, objects.TraceNetPackets, nil) if err != nil { return nil, fmt.Errorf("failed to attach the BPF program to trace_net_packets: %w", err) } diff --git a/pkg/pbflow/flow.pb.go b/pkg/pbflow/flow.pb.go index c14ac8d33ad8755c1ee730146baebc13138468d5..846d7467b8e50b3924cc8164a32bc56ed99e7e91 100644 --- a/pkg/pbflow/flow.pb.go +++ b/pkg/pbflow/flow.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v3.19.4 +// protoc-gen-go v1.25.0-devel +// protoc v3.14.0 // source: proto/flow.proto package pbflow diff --git a/pkg/pbflow/flow_grpc.pb.go b/pkg/pbflow/flow_grpc.pb.go index fccb880954ed93e893e8284125931954f4962fb1..6b2e9616cc2f543ee6833cfee36292cdfb3a7d2c 100644 --- a/pkg/pbflow/flow_grpc.pb.go +++ b/pkg/pbflow/flow_grpc.pb.go @@ -1,8 +1,4 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v3.19.4 -// source: proto/flow.proto package pbflow @@ -18,10 +14,6 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 -const ( - Collector_Send_FullMethodName = "/pbflow.Collector/Send" -) - // CollectorClient is the client API for Collector service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -39,7 +31,7 @@ func NewCollectorClient(cc grpc.ClientConnInterface) CollectorClient { func (c *collectorClient) Send(ctx context.Context, in *Records, opts ...grpc.CallOption) (*CollectorReply, error) { out := new(CollectorReply) - err := c.cc.Invoke(ctx, Collector_Send_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, "/pbflow.Collector/Send", in, out, opts...) if err != nil { return nil, err } @@ -84,7 +76,7 @@ func _Collector_Send_Handler(srv interface{}, ctx context.Context, dec func(inte } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Collector_Send_FullMethodName, + FullMethod: "/pbflow.Collector/Send", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(CollectorServer).Send(ctx, req.(*Records)) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f0cb22cf3b643153e8da12c9b5f0f7f210f855a1..1c67d70e762c88704457b4ff172973acd2deed36 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,6 +3,17 @@ package utils import ( "fmt" "net" + "regexp" + "strconv" + "strings" + "syscall" + + "github.com/sirupsen/logrus" +) + +var ( + getCurrentKernelVersion = currentKernelVersion + log = logrus.WithField("component", "utils") ) // GetSocket returns socket string in the correct format based on address family @@ -14,3 +25,69 @@ func GetSocket(hostIP string, hostPort int) string { } return socket } + +func IskernelOlderthan514() bool { + kernelVersion514, err := kernelVersionFromReleaseString("5.14.0") + if err != nil { + log.Warnf("failed to get kernel version from release string: %v", err) + return false + } + currentVersion, err := getCurrentKernelVersion() + if err != nil { + log.Warnf("failed to get current kernel version: %v", err) + return false + } + if currentVersion < kernelVersion514 { + log.Infof("older kernel version not all hooks will be supported") + return true + } + return false +} + +var versionRegex = regexp.MustCompile(`^(\d+)\.(\d+).(\d+).*$`) + +// kernelVersionFromReleaseString converts a release string with format +// 4.4.2[-1] to a kernel version number in LINUX_VERSION_CODE format. +// That is, for kernel "a.b.c", the version number will be (a<<16 + b<<8 + c) +func kernelVersionFromReleaseString(releaseString string) (uint32, error) { + versionParts := versionRegex.FindStringSubmatch(releaseString) + if len(versionParts) != 4 { + return 0, fmt.Errorf("got invalid release version %q (expected format '4.3.2-1')", releaseString) + } + major, err := strconv.Atoi(versionParts[1]) + if err != nil { + return 0, err + } + + minor, err := strconv.Atoi(versionParts[2]) + if err != nil { + return 0, err + } + + patch, err := strconv.Atoi(versionParts[3]) + if err != nil { + return 0, err + } + out := major*256*256 + minor*256 + patch + return uint32(out), nil +} + +func currentKernelVersion() (uint32, error) { + var buf syscall.Utsname + if err := syscall.Uname(&buf); err != nil { + return 0, err + } + releaseString := strings.Trim(utsnameStr(buf.Release[:]), "\x00") + return kernelVersionFromReleaseString(releaseString) +} + +func utsnameStr(in []int8) string { + out := make([]byte, len(in)) + for i := 0; i < len(in); i++ { + if in[i] == 0 { + break + } + out = append(out, byte(in[i])) + } + return string(out) +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8f212a8d4033eb58a3dfffc0cb80414780f2aba4 --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,55 @@ +package utils + +import ( + "errors" + "testing" +) + +func TestIskernelOlderthan514(t *testing.T) { + tests := []struct { + name string + mockKernelVersion func() (uint32, error) + want bool + }{ + { + name: "Kernel version < 5.14.0", + mockKernelVersion: func() (uint32, error) { + ver, _ := kernelVersionFromReleaseString("5.13.0") + return ver, nil + }, + want: true, + }, + { + name: "Kernel version = 5.14.0", + mockKernelVersion: func() (uint32, error) { + ver, _ := kernelVersionFromReleaseString("5.14.0") + return ver, nil + }, + want: false, + }, + { + name: "Kernel version > 5.14.0", + mockKernelVersion: func() (uint32, error) { + ver, _ := kernelVersionFromReleaseString("5.15.0") + return ver, nil + }, + want: false, + }, + { + name: "Error getting kernel version", + mockKernelVersion: func() (uint32, error) { + return 0, errors.New("error") + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + getCurrentKernelVersion = tt.mockKernelVersion + got := IskernelOlderthan514() + if got != tt.want { + t.Errorf("%s: IskernelOlderthan514() = %v, want %v", tt.name, got, tt.want) + } + }) + } +}