github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/helpers.go (about)

     1  // Copyright 2019-2024 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build !withoutebpf
    16  
    17  package gadgets
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"net"
    23  	"net/netip"
    24  	"time"
    25  	"unsafe"
    26  
    27  	"github.com/cilium/ebpf"
    28  	"github.com/cilium/ebpf/asm"
    29  	"github.com/cilium/ebpf/features"
    30  	"github.com/cilium/ebpf/link"
    31  	"golang.org/x/sys/unix"
    32  
    33  	"github.com/inspektor-gadget/inspektor-gadget/pkg/btfgen"
    34  	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    35  )
    36  
    37  // CloseLink closes l if it's not nil and returns nil
    38  func CloseLink(l link.Link) link.Link {
    39  	if l != nil {
    40  		l.Close()
    41  	}
    42  	return nil
    43  }
    44  
    45  // DataEnricherByMntNs is used to enrich events with Kubernetes information,
    46  // like node, namespace, pod name and container name when the mount namespace
    47  // is available.
    48  type DataEnricherByMntNs interface {
    49  	EnrichByMntNs(event *types.CommonData, mountnsid uint64)
    50  }
    51  
    52  // DataNodeEnricher is used to enrich events with Kubernetes node, without
    53  // needing any namespace.
    54  type DataNodeEnricher interface {
    55  	EnrichNode(event *types.CommonData)
    56  }
    57  
    58  // DataEnricherByNetNs is used to enrich events with Kubernetes information,
    59  // like node, namespace, pod name and container name when the network namespace
    60  // is available.
    61  type DataEnricherByNetNs interface {
    62  	EnrichByNetNs(event *types.CommonData, netnsid uint64)
    63  }
    64  
    65  func Htonl(hl uint32) uint32 {
    66  	var nl [4]byte
    67  	binary.BigEndian.PutUint32(nl[:], hl)
    68  	return *(*uint32)(unsafe.Pointer(&nl[0]))
    69  }
    70  
    71  func Htons(hs uint16) uint16 {
    72  	var ns [2]byte
    73  	binary.BigEndian.PutUint16(ns[:], hs)
    74  	return *(*uint16)(unsafe.Pointer(&ns[0]))
    75  }
    76  
    77  func IPStringFromBytes(ipBytes [16]byte, ipType int) string {
    78  	switch ipType {
    79  	case 4:
    80  		return netip.AddrFrom4(*(*[4]byte)(ipBytes[0:4])).String()
    81  	case 6:
    82  		return netip.AddrFrom16(ipBytes).String()
    83  	default:
    84  		return ""
    85  	}
    86  }
    87  
    88  // IPStringToByteArray converts an IP address (IPv6 only) string to a uint32
    89  // in big-endian.
    90  func IPStringToByteArray(ipAddr string) ([16]byte, error) {
    91  	addr, err := netip.ParseAddr(ipAddr)
    92  	if err != nil {
    93  		return [16]byte{}, fmt.Errorf("invalid IP address: %s", ipAddr)
    94  	}
    95  
    96  	if !addr.Is6() {
    97  		return [16]byte{}, fmt.Errorf("IP address is not IPv6: %s", addr)
    98  	}
    99  
   100  	// This function ensures us the order is big endian:
   101  	// https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/net/netip/netip.go;drc=dc98ccd836da7d22a5d270b9778fb055826fa07b;l=676
   102  	return addr.As16(), nil
   103  }
   104  
   105  // IPStringToUint32 converts an IP address (IPv4 only) string to a uint32
   106  // in big-endian.
   107  func IPStringToUint32(ipAddr string) (uint32, error) {
   108  	// Notice ipAddr is already expressed in big-endian and net.ParseIP stores
   109  	// it in a byte array in big-endian too.
   110  	ip := net.ParseIP(ipAddr).To4()
   111  	if ip == nil {
   112  		return 0, fmt.Errorf("invalid IP address: %s", ipAddr)
   113  	}
   114  	// Convert the byte array to a uint32 keeping the big-endian order. We don't
   115  	// use binary.[BigEndian|LittleEndian].Uint32() to make this code portable.
   116  	return *(*uint32)(unsafe.Pointer(&ip[0])), nil
   117  }
   118  
   119  func IPVerFromAF(af uint16) int {
   120  	switch af {
   121  	case unix.AF_INET:
   122  		return 4
   123  	case unix.AF_INET6:
   124  		return 6
   125  	default:
   126  		return 0
   127  	}
   128  }
   129  
   130  var timeDiff time.Duration
   131  
   132  func init() {
   133  	var t unix.Timespec
   134  	err := unix.ClockGettime(unix.CLOCK_BOOTTIME, &t)
   135  	if err != nil {
   136  		panic(err)
   137  	}
   138  	timeDiff = time.Duration(time.Now().UnixNano() - t.Sec*1000*1000*1000 - t.Nsec)
   139  }
   140  
   141  // WallTimeFromBootTime converts a time from bpf_ktime_get_boot_ns() to the
   142  // wall time with nano precision.
   143  //
   144  // Example:
   145  //
   146  //	fmt.Printf("Time: %s\n", WallTimeFromBootTime(ts).String())
   147  //
   148  // would display:
   149  //
   150  //	Time: 2022-12-15T16:49:00.452371948+01:00
   151  //
   152  // Shell command to convert the number to a date:
   153  //
   154  //	$ date -d @$(echo 1671447636499110634/1000000000|bc -l) +"%d-%m-%Y %H:%M:%S:%N"
   155  //	19-12-2022 12:00:36:499110634
   156  //
   157  // bpf_ktime_get_boot_ns was added in Linux 5.7. If not available and the BPF
   158  // program returns 0, just get the timestamp in userspace.
   159  func WallTimeFromBootTime(ts uint64) types.Time {
   160  	if ts == 0 {
   161  		return types.Time(time.Now().UnixNano())
   162  	}
   163  	return types.Time(time.Unix(0, int64(ts)).Add(timeDiff).UnixNano())
   164  }
   165  
   166  // HasBpfKtimeGetBootNs returns true if bpf_ktime_get_boot_ns is available
   167  func HasBpfKtimeGetBootNs() bool {
   168  	// We only care about the helper, hence test with ebpf.SocketFilter that exist in all
   169  	// kernels that support ebpf.
   170  	err := features.HaveProgramHelper(ebpf.SocketFilter, asm.FnKtimeGetBootNs)
   171  	return err == nil
   172  }
   173  
   174  // removeBpfKtimeGetBootNs removes calls to bpf_ktime_get_boot_ns and replaces
   175  // it by an assignment to zero
   176  func removeBpfKtimeGetBootNs(p *ebpf.ProgramSpec) {
   177  	iter := p.Instructions.Iterate()
   178  
   179  	for iter.Next() {
   180  		in := iter.Ins
   181  
   182  		if in.OpCode.Class().IsJump() &&
   183  			in.OpCode.JumpOp() == asm.Call &&
   184  			in.Constant == int64(asm.FnKtimeGetBootNs) {
   185  			// reset timestamp to zero
   186  			in.OpCode = asm.Mov.Op(asm.ImmSource)
   187  			in.Dst = asm.R0
   188  			in.Constant = 0
   189  		}
   190  	}
   191  }
   192  
   193  // FixBpfKtimeGetBootNs checks if bpf_ktime_get_boot_ns is supported by the
   194  // kernel and removes it if not
   195  func FixBpfKtimeGetBootNs(programSpecs map[string]*ebpf.ProgramSpec) {
   196  	if HasBpfKtimeGetBootNs() {
   197  		return
   198  	}
   199  
   200  	for _, s := range programSpecs {
   201  		removeBpfKtimeGetBootNs(s)
   202  	}
   203  }
   204  
   205  // LoadeBPFSpec is a helper to load an eBPF spec from gadgets.
   206  // It replaces filter map and calls the necessary functions to load
   207  // Maps and Programs into the kernel
   208  func LoadeBPFSpec(
   209  	mountnsMap *ebpf.Map,
   210  	spec *ebpf.CollectionSpec,
   211  	consts map[string]interface{},
   212  	objs interface{},
   213  ) error {
   214  	FixBpfKtimeGetBootNs(spec.Programs)
   215  
   216  	mapReplacements := map[string]*ebpf.Map{}
   217  	filterByMntNs := false
   218  
   219  	if mountnsMap != nil {
   220  		filterByMntNs = true
   221  		mapReplacements[MntNsFilterMapName] = mountnsMap
   222  	}
   223  
   224  	if consts == nil {
   225  		consts = map[string]interface{}{}
   226  	}
   227  
   228  	consts[FilterByMntNsName] = filterByMntNs
   229  
   230  	if err := spec.RewriteConstants(consts); err != nil {
   231  		return fmt.Errorf("rewriting constants: %w", err)
   232  	}
   233  
   234  	opts := ebpf.CollectionOptions{
   235  		MapReplacements: mapReplacements,
   236  		Programs: ebpf.ProgramOptions{
   237  			KernelTypes: btfgen.GetBTFSpec(),
   238  		},
   239  	}
   240  
   241  	if err := spec.LoadAndAssign(objs, &opts); err != nil {
   242  		return fmt.Errorf("loading maps and programs: %w", err)
   243  	}
   244  
   245  	return nil
   246  }