pkg/maps/loader.go (542 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//limitations under the License.
package maps
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"unsafe"
constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants"
"github.com/aws/aws-ebpf-sdk-go/pkg/logger"
"github.com/aws/aws-ebpf-sdk-go/pkg/utils"
"golang.org/x/sys/unix"
)
var log = logger.Get()
type BpfMap struct {
MapFD uint32
MapID uint32
MapMetaData CreateEBPFMapInput
}
type BpfMapPinOptions struct {
Type uint32
PinPath string
}
type CreateEBPFMapInput struct {
Type uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
Flags uint32
InnerMapFd uint32
PinOptions *BpfMapPinOptions
NumaNode uint32
Name string
}
/*
* BpfMapDef is filled based on map parsing from ELF
* Type : Map Type such as Trie, Array..
* Key size and Value size : Size of map key and value
* MaxEntries : size of map
* Flags : for instance if the map has to be preallocated
* InnerMapFd: Map in map functionality
* Pinning: if pinning is needed or not
*/
type BpfMapDef struct {
Type uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
Flags uint32
InnerMapFd uint32
Pinning uint32
}
type bpfAttrBPFMapCreate struct {
Def BpfMapDef
NumaNode uint32
Name string
}
type BpfMapInfo struct {
Type uint32
Id uint32
KeySize uint32
ValueSize uint32
MaxEntries uint32
MapFlags uint32
Name [constdef.BPFObjNameLen]byte
IfIndex uint32
BtfVmLinuxValueTypeId uint32
NetnsDev uint64
NetnsIno uint64
BTFID uint32
BTFKeyTypeID uint32
BTFValueTypeId uint32
Pad uint32
MapExtra uint64
}
/*
*
* struct { anonymous struct used by BPF_*_GET_*_ID
* union {
* __u32 start_id;
* __u32 prog_id;
* __u32 map_id;
* __u32 btf_id;
* __u32 link_id;
* };
* __u32 next_id;
* __u32 open_flags;
* };
*/
type BpfMapShowAttr struct {
MapID uint32
NextID uint32
OpenFlags uint32
}
/*
* struct { anonymous struct used by BPF_OBJ_GET_INFO_BY_FD
* __u32 bpf_fd;
* __u32 info_len;
* __aligned_u64 info;
* } info;
*
*/
type BpfObjGetInfo struct {
bpf_fd uint32
info_len uint32
info uintptr
}
/*
* struct { anonymous struct used by BPF_OBJ_* commands
* __aligned_u64 pathname;
* __u32 bpf_fd;
* __u32 file_flags;
* };
*/
type BpfObjGet struct {
pathname uintptr
bpf_fd uint32
file_flags uint32
}
type BpfMapAPIs interface {
// Wrapper for BPF_MAP_CREATE API
CreateBPFMap(mapMetaData CreateEBPFMapInput) (BpfMap, error)
// Pin map to the passed pinPath
PinMap(pinPath string, pinType uint32) error
// Delete pinPath
UnPinMap(pinPath string) error
// Add an entry to map, if the entry exists, we error out
CreateMapEntry(key, value uintptr) error
// Update an entry in map, if the entry exists, we update
UpdateMapEntry(key, value uintptr) error
// Wrapper to call create or update
CreateUpdateMapEntry(key, value uintptr, updateFlags uint64) error
// Delete a map entry
DeleteMapEntry(key uintptr) error
// Map iterator helper - Get the first map entry
GetFirstMapEntry(nextKey uintptr) error
// Map iterator helper - Get next map entry
GetNextMapEntry(key, nextKey uintptr) error
// Get map value
GetMapEntry(key, value uintptr) error
// Update multiple map entries
BulkUpdateMapEntry(keyvalue map[string][]byte) error
// Delete multiple map entries
BulkDeleteMapEntry(keyvalue map[uintptr]uintptr) error
// Wrapper for delete and update map entries
BulkRefreshMapEntries(newMapContents map[string][]byte) error
// Retrieve map info from pin path
GetMapFromPinPath(pinPath string) (BpfMapInfo, error)
// Retrieve map info without pin path
GetBPFmapInfo(mapFD uint32) (BpfMapInfo, error)
}
func (m *BpfMap) CreateBPFMap(MapMetaData CreateEBPFMapInput) (BpfMap, error) {
// Copying all contents, innerMapFD is 0 since we don't support map-in-map
mapContents := bpfAttrBPFMapCreate{
Def: BpfMapDef{
Type: uint32(MapMetaData.Type),
KeySize: MapMetaData.KeySize,
ValueSize: MapMetaData.ValueSize,
MaxEntries: MapMetaData.MaxEntries,
Flags: MapMetaData.Flags,
InnerMapFd: 0,
},
Name: MapMetaData.Name,
}
mapData := unsafe.Pointer(&mapContents)
mapDataSize := unsafe.Sizeof(mapContents)
log.Infof("Calling BPFsys for name %s mapType %d keysize %d valuesize %d max entries %d and flags %d", string(MapMetaData.Name[:]), MapMetaData.Type, MapMetaData.KeySize, MapMetaData.ValueSize, MapMetaData.MaxEntries, MapMetaData.Flags)
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_CREATE),
uintptr(mapData),
mapDataSize,
)
if (errno < 0) || (int(ret) == -1) {
log.Errorf("Unable to create map and ret %d and err %s", int(ret), errno)
return BpfMap{}, fmt.Errorf("unable to create map: %s", errno)
}
log.Infof("Create map done with fd : %d", int(ret))
bpfMap := BpfMap{
MapFD: uint32(ret),
MapMetaData: MapMetaData,
}
if MapMetaData.PinOptions != nil {
bpfMap.PinMap(MapMetaData.PinOptions.PinPath, MapMetaData.PinOptions.Type)
}
return bpfMap, nil
}
func (m *BpfMap) PinMap(pinPath string, pinType uint32) error {
if pinType == constdef.PIN_NONE.Index() {
return nil
}
if pinType == constdef.PIN_GLOBAL_NS.Index() {
//If pinPath is already present lets delete and create a new one
found, err := utils.IsfileExists(pinPath)
if err != nil {
return fmt.Errorf("unable to check file: %w", err)
}
if found {
log.Infof("Found file %s so deleting the path", pinPath)
err := utils.UnPinObject(pinPath)
if err != nil {
log.Errorf("failed to UnPinObject %v", err)
return err
}
}
err = os.MkdirAll(filepath.Dir(pinPath), 0755)
if err != nil {
log.Errorf("error creating directory %s: %v", filepath.Dir(pinPath), err)
return fmt.Errorf("error creating directory %s: %v", filepath.Dir(pinPath), err)
}
_, err = os.Stat(pinPath)
if err == nil {
log.Errorf("aborting, found file at %s", pinPath)
return fmt.Errorf("aborting, found file at %s", pinPath)
}
if err != nil && !os.IsNotExist(err) {
log.Errorf("failed to stat %s: %v", pinPath, err)
return fmt.Errorf("failed to stat %s: %v", pinPath, err)
}
return utils.PinObject(m.MapFD, pinPath)
}
return nil
}
func (m *BpfMap) UnPinMap(pinPath string) error {
err := utils.UnPinObject(pinPath)
if err != nil {
log.Errorf("failed to unpin map")
return err
}
if m.MapFD <= 0 {
log.Errorf("map FD is invalid or closed %d", m.MapFD)
return nil
}
return unix.Close(int(m.MapFD))
}
func (m *BpfMap) CreateMapEntry(key, value uintptr) error {
return m.CreateUpdateMapEntry(key, value, uint64(constdef.BPF_NOEXIST))
}
func (m *BpfMap) UpdateMapEntry(key, value uintptr) error {
return m.CreateUpdateMapEntry(key, value, uint64(constdef.BPF_ANY))
}
func (m *BpfMap) CreateUpdateMapEntry(key, value uintptr, updateFlags uint64) error {
mapFD, err := utils.GetMapFDFromID(int(m.MapID))
if err != nil {
log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Flags: updateFlags,
Key: uint64(key),
Value: uint64(value),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_UPDATE_ELEM),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
runtime.KeepAlive(key)
runtime.KeepAlive(value)
if errno != 0 {
log.Errorf("unable to create/update map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to update map: %s", errno)
}
log.Infof("Create/Update map entry done with fd : %d and err %s", int(ret), errno)
return nil
}
func (m *BpfMap) DeleteMapEntry(key uintptr) error {
mapFD, err := utils.GetMapFDFromID(int(m.MapID))
if err != nil {
log.Errorf("unable to GetMapFDfromID and ID %d and err %s", int(m.MapID), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Key: uint64(key),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_DELETE_ELEM),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
if errno != 0 {
log.Errorf("unable to delete map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to update map: %s", errno)
}
log.Infof("Delete map entry done with fd : %d and err %s", int(ret), errno)
return nil
}
// To get the first entry pass key as `nil`
func (m *BpfMap) GetFirstMapEntry(nextKey uintptr) error {
return m.GetNextMapEntry(uintptr(unsafe.Pointer(nil)), nextKey)
}
func (m *BpfMap) GetNextMapEntry(key, nextKey uintptr) error {
mapFD, err := utils.GetMapFDFromID(int(m.MapID))
if err != nil {
log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Key: uint64(key),
Value: uint64(nextKey),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_GET_NEXT_KEY),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
if errors.Is(errno, unix.ENOENT) {
log.Errorf("last entry read done")
return errno
}
if errno != 0 {
log.Errorf("unable to get next map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to get next map entry: %s", errno)
}
log.Infof("Got next map entry with fd : %d and err %s", int(ret), errno)
return nil
}
func (m *BpfMap) GetAllMapKeys() ([]string, error) {
var keyList []string
keySize := m.MapMetaData.KeySize
curKey := make([]byte, keySize)
nextKey := make([]byte, keySize)
err := m.GetFirstMapEntry(uintptr(unsafe.Pointer(&curKey[0])))
if err != nil {
log.Errorf("unable to get first key %s", err)
return nil, fmt.Errorf("unable to get first key entry: %s", err)
} else {
for {
err = m.GetNextMapEntry(uintptr(unsafe.Pointer(&curKey[0])), uintptr(unsafe.Pointer(&nextKey[0])))
log.Infof("Adding to key list %v", curKey)
keyList = append(keyList, string(curKey))
if errors.Is(err, unix.ENOENT) {
log.Infof("Done reading all entries")
return keyList, nil
}
if err != nil {
log.Infof("Unable to get next key %s", err)
break
}
//curKey = nextKey
copy(curKey, nextKey)
}
}
log.Infof("Done get all keys")
return keyList, err
}
func (m *BpfMap) GetMapEntry(key, value uintptr) error {
mapFD, err := utils.GetMapFDFromID(int(m.MapID))
if err != nil {
log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Key: uint64(key),
Value: uint64(value),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_LOOKUP_ELEM),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
if errno != 0 {
log.Errorf("unable to get map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to get next map entry: %s", errno)
}
log.Infof("Got map entry with fd : %d and err %s", int(ret), errno)
return nil
}
func (m *BpfMap) BulkDeleteMapEntry(keyvalue map[uintptr]uintptr) error {
for k, _ := range keyvalue {
err := m.DeleteMapEntry(k)
if err != nil {
log.Infof("One of the element delete failed hence returning from bulk update")
return err
}
}
log.Infof("Bulk delete is successful for mapID: %d", int(m.MapID))
return nil
}
func (m *BpfMap) BulkUpdateMapEntry(keyvalue map[string][]byte) error {
for k, v := range keyvalue {
keyByte := []byte(k)
keyPtr := uintptr(unsafe.Pointer(&keyByte[0]))
valuePtr := uintptr(unsafe.Pointer(&v[0]))
err := m.UpdateMapEntry(keyPtr, valuePtr)
if err != nil {
log.Infof("One of the element update failed hence returning from bulk update")
return err
}
}
log.Infof("Bulk update is successful for mapID: %d", int(m.MapID))
return nil
}
func (m *BpfMap) BulkRefreshMapEntries(newMapContents map[string][]byte) error {
// 1. Update all map entries
err := m.BulkUpdateMapEntry(newMapContents)
if err != nil {
log.Errorf("refresh map failed: during update %v", err)
return err
}
// 2. Read all map entries
retrievedMapKeyList, err := m.GetAllMapKeys()
if err != nil {
log.Errorf("get all map keys failed: during Refresh %v", err)
return err
}
// 3. Delete stale Keys
log.Infof("Check for stale entries and got %d entries from BPF map", len(retrievedMapKeyList))
for _, key := range retrievedMapKeyList {
log.Infof("Checking if key %s is deletable", key)
if _, ok := newMapContents[key]; !ok {
log.Infof("This can be deleted, not needed anymore...")
deletableKeyByte := []byte(key)
deletableKeyBytePtr := uintptr(unsafe.Pointer(&deletableKeyByte[0]))
err = m.DeleteMapEntry(deletableKeyBytePtr)
if err != nil {
log.Infof("Unable to delete entry %s but will continue and err %v", key, err)
}
}
}
return nil
}
func (attr *BpfMapShowAttr) isBpfMapGetNextID() bool {
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_GET_NEXT_ID),
uintptr(unsafe.Pointer(attr)),
unsafe.Sizeof(*attr),
)
if errno != 0 {
log.Infof("Done get_next_id for Map - ret %d and err %s", int(ret), errno)
return false
}
attr.MapID = attr.NextID
return true
}
func (objattr *BpfObjGetInfo) BpfGetMapInfoForFD() error {
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_OBJ_GET_INFO_BY_FD),
uintptr(unsafe.Pointer(objattr)),
unsafe.Sizeof(*objattr),
)
if errno != 0 {
log.Errorf("failed to get object info by FD - ret %d and err %s", int(ret), errno)
return errno
}
return nil
}
func GetIDFromFD(mapFD int) (int, error) {
mapInfo, err := GetBPFmapInfo(mapFD)
if err != nil {
return -1, err
}
return int(mapInfo.Id), nil
}
func (m *BpfMap) GetBPFmapInfo(mapFD uint32) (BpfMapInfo, error) {
return GetBPFmapInfo(int(mapFD))
}
func GetBPFmapInfo(mapFD int) (BpfMapInfo, error) {
var bpfMapInfo BpfMapInfo
objInfo := BpfObjGetInfo{
bpf_fd: uint32(mapFD),
info_len: uint32(unsafe.Sizeof(bpfMapInfo)),
info: uintptr(unsafe.Pointer(&bpfMapInfo)),
}
err := objInfo.BpfGetMapInfoForFD()
if err != nil {
log.Errorf("failed to get map Info for FD - ", mapFD)
return BpfMapInfo{}, err
}
return bpfMapInfo, nil
}
func BpfGetAllMapInfo() ([]BpfMapInfo, error) {
loadedMaps := []BpfMapInfo{}
attr := BpfMapShowAttr{}
log.Infof("In get all prog info")
for attr.isBpfMapGetNextID() {
log.Infof("Got ID - %d", attr.NextID)
mapfd, err := utils.GetMapFDFromID(int(attr.NextID))
if err != nil {
log.Errorf("failed to get map Info")
return nil, err
}
log.Infof("Found map FD - %d", mapfd)
bpfMapInfo, err := GetBPFmapInfo(mapfd)
if err != nil {
log.Errorf("failed to get map Info for FD", mapfd)
unix.Close(mapfd)
return nil, err
}
unix.Close(mapfd)
loadedMaps = append(loadedMaps, bpfMapInfo)
}
log.Infof("Done all map info!!!")
return loadedMaps, nil
}
func (attr *BpfObjGet) BpfGetObject() (int, error) {
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_OBJ_GET),
uintptr(unsafe.Pointer(attr)),
unsafe.Sizeof(*attr),
)
if errno != 0 {
log.Errorf("failed to get Map FD - ret %d and err %s", int(ret), errno)
return 0, errno
}
return int(ret), nil
}
func (m *BpfMap) GetMapFromPinPath(pinPath string) (BpfMapInfo, error) {
if len(pinPath) == 0 {
return BpfMapInfo{}, fmt.Errorf("invalid pinPath")
}
cPath := []byte(pinPath + "\x00")
objInfo := BpfObjGet{
pathname: uintptr(unsafe.Pointer(&cPath[0])),
}
mapFD, err := objInfo.BpfGetObject()
if err != nil {
log.Errorf("failed to get object")
return BpfMapInfo{}, err
}
bpfMapInfo, err := GetBPFmapInfo(mapFD)
if err != nil {
log.Errorf("failed to get map Info for FD - %d", mapFD)
return bpfMapInfo, err
}
err = unix.Close(int(mapFD))
if err != nil {
log.Infof("Failed to close but return the mapinfo")
}
return bpfMapInfo, nil
}
func GetFirstMapEntryByID(nextKey uintptr, mapID int) error {
return GetNextMapEntryByID(uintptr(unsafe.Pointer(nil)), nextKey, mapID)
}
func GetNextMapEntryByID(key, nextKey uintptr, mapID int) error {
mapFD, err := utils.GetMapFDFromID(mapID)
if err != nil {
log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Key: uint64(key),
Value: uint64(nextKey),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_GET_NEXT_KEY),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
if errors.Is(errno, unix.ENOENT) {
return errno
}
if errno != 0 {
log.Errorf("unable to get next map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to get next map entry: %s", errno)
}
log.Infof("Got next map entry with fd : %d and err %s", int(ret), errno)
return nil
}
func GetMapEntryByID(key, value uintptr, mapID int) error {
mapFD, err := utils.GetMapFDFromID(mapID)
if err != nil {
log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err)
return fmt.Errorf("unable to get FD: %s", err)
}
defer unix.Close(mapFD)
attr := utils.BpfMapAttr{
MapFD: uint32(mapFD),
Key: uint64(key),
Value: uint64(value),
}
ret, _, errno := unix.Syscall(
unix.SYS_BPF,
uintptr(constdef.BPF_MAP_LOOKUP_ELEM),
uintptr(unsafe.Pointer(&attr)),
unsafe.Sizeof(attr),
)
if errno != 0 {
log.Errorf("unable to get map entry and ret %d and err %s", int(ret), errno)
return fmt.Errorf("unable to get next map entry: %s", errno)
}
log.Infof("Got map entry with ret : %d and err %s", int(ret), errno)
return nil
}