github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/map.go (about)

     1  package gobpfld
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"unsafe"
     7  
     8  	"github.com/dylandreimerink/gobpfld/bpfsys"
     9  	"github.com/dylandreimerink/gobpfld/bpftypes"
    10  	"github.com/dylandreimerink/gobpfld/internal/cstr"
    11  )
    12  
    13  type BPFMap interface {
    14  	GetName() ObjName
    15  	GetFD() bpfsys.BPFfd
    16  	IsLoaded() bool
    17  	GetDefinition() BPFMapDef
    18  	GetBTF() *BTF
    19  	GetBTFMapType() BTFMap
    20  	GetInitialData() map[interface{}]interface{}
    21  
    22  	// Pin pins the map to a location in the bpf filesystem, since the file system now also holds a reference
    23  	// to the map the original creator of the map can terminate without triggering the map to be closed as well.
    24  	// A map can be unpinned from the bpf FS by another process thus transferring it or persisting it across
    25  	// multiple runs of the same program.
    26  	Pin(relativePath string) error
    27  
    28  	// Unpin captures the file descriptor of the map at the given 'relativePath' from the kernel.
    29  	// The definition in this map must match the definition of the pinned map, otherwise this function
    30  	// will return an error since mismatched definitions might cause seemingly unrelated bugs in other functions.
    31  	// If 'deletePin' is true the bpf FS pin will be removed after successfully loading the map, thus transferring
    32  	// ownership of the map in a scenario where the map is not shared between multiple programs.
    33  	// Otherwise the pin will keep existing which will cause the map to not be deleted when this program exits.
    34  	Unpin(relativePath string, deletePin bool) error
    35  
    36  	// Load validates and loads the userspace map definition into the kernel.
    37  	Load() error
    38  
    39  	// Close closes the file descriptor associated with the map. The map can't be used after it is closed.
    40  	// If this is the last file descriptor pointing to the map, the map will be unloaded from the kernel.
    41  	// If a map is pinned to the filesystem, in use by a bpf program or referenced any other way it will stay loaded
    42  	// until all references are closed/removed.
    43  	Close() error
    44  }
    45  
    46  // MapFromFD creates a BPFMap object from a map that is already loaded into the kernel and for which we already have
    47  // a file descriptor.
    48  func MapFromFD(fd bpfsys.BPFfd) (BPFMap, error) {
    49  	// Check if there already is a map in the register with this FD.
    50  	m := mapRegister.getByFD(fd)
    51  	if m != nil {
    52  		return m, nil
    53  	}
    54  
    55  	// Otherwise get all required info from the kernel and create a userspace representation.
    56  
    57  	mapInfo, err := getMapInfo(fd)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	// TODO if the type is an memory mmap-able array, we should mmap it so it can be used.
    63  
    64  	m = bpfMapFromAbstractMap(
    65  		AbstractMap{
    66  			Name: ObjName{
    67  				cname: mapInfo.Name,
    68  				str:   cstr.BytesToString(mapInfo.Name[:]),
    69  			},
    70  			loaded: true,
    71  			fd:     fd,
    72  			Definition: BPFMapDef{
    73  				Type:       mapInfo.Type,
    74  				KeySize:    mapInfo.KeySize,
    75  				ValueSize:  mapInfo.ValueSize,
    76  				MaxEntries: mapInfo.MaxEntries,
    77  				Flags:      bpftypes.BPFMapFlags(mapInfo.MapFlags),
    78  			},
    79  			definition: BPFMapDef{
    80  				Type:       mapInfo.Type,
    81  				KeySize:    mapInfo.KeySize,
    82  				ValueSize:  mapInfo.ValueSize,
    83  				MaxEntries: mapInfo.MaxEntries,
    84  				Flags:      bpftypes.BPFMapFlags(mapInfo.MapFlags),
    85  			},
    86  		},
    87  	)
    88  
    89  	err = mapRegister.add(m)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("map register: %w", err)
    92  	}
    93  
    94  	return m, nil
    95  }
    96  
    97  // MapFromID creates a BPFMap object from a map that is already loaded into the kernel.
    98  func MapFromID(id uint32) (BPFMap, error) {
    99  	// First check if we already have a version of this map in the register
   100  	m := mapRegister.getByID(id)
   101  	if m != nil {
   102  		return m, nil
   103  	}
   104  
   105  	// If no, get a new FD from the ID and construct a map.
   106  
   107  	fd, err := bpfsys.MapGetFDByID(&bpfsys.BPFAttrGetID{
   108  		ID: id,
   109  	})
   110  	if err != nil {
   111  		return nil, fmt.Errorf("bpf syscall error: %w", err)
   112  	}
   113  
   114  	return MapFromFD(fd)
   115  }
   116  
   117  // mapIDRegister holds a reference to all maps currently loaded into the kernel. The map is index by the object ID
   118  // of the map which uniquely identifies it within the kernel. The purpose of this is that if the user re-gets a map
   119  // via any mechanism(FS pinning, ID, FD, or map-in-map) that the user always gets the same instance(pointer) to the map.
   120  // Because the user will always have the same object, we will also have only one FD which is easier for the user to
   121  // manage.
   122  var mapRegister = _mapRegister{
   123  	idToMap: make(map[uint32]BPFMap),
   124  	fdToMap: make(map[bpfsys.BPFfd]BPFMap),
   125  }
   126  
   127  type _mapRegister struct {
   128  	mu      sync.Mutex
   129  	idToMap map[uint32]BPFMap
   130  	fdToMap map[bpfsys.BPFfd]BPFMap
   131  }
   132  
   133  func (r *_mapRegister) add(m BPFMap) error {
   134  	if !m.IsLoaded() {
   135  		return fmt.Errorf("can only add loaded maps to the register")
   136  	}
   137  
   138  	// Get info from kernel via the FD so we can get the ID of this map
   139  	info, err := getMapInfo(m.GetFD())
   140  	if err != nil {
   141  		return fmt.Errorf("get map info: %w", err)
   142  	}
   143  
   144  	r.mu.Lock()
   145  	defer r.mu.Unlock()
   146  
   147  	r.idToMap[info.ID] = m
   148  	r.fdToMap[m.GetFD()] = m
   149  
   150  	return nil
   151  }
   152  
   153  func (r *_mapRegister) delete(m BPFMap) error {
   154  	if !m.IsLoaded() {
   155  		return fmt.Errorf("can only delete loaded maps from the register")
   156  	}
   157  
   158  	// Get info from kernel via the FD so we can get the ID of this map
   159  	info, err := getMapInfo(m.GetFD())
   160  	if err != nil {
   161  		return fmt.Errorf("get map info: %w", err)
   162  	}
   163  
   164  	r.mu.Lock()
   165  	defer r.mu.Unlock()
   166  
   167  	delete(r.idToMap, info.ID)
   168  	delete(r.fdToMap, m.GetFD())
   169  
   170  	return nil
   171  }
   172  
   173  func (r *_mapRegister) getByID(id uint32) BPFMap {
   174  	r.mu.Lock()
   175  	defer r.mu.Unlock()
   176  
   177  	return r.idToMap[id]
   178  }
   179  
   180  func (r *_mapRegister) getByFD(fd bpfsys.BPFfd) BPFMap {
   181  	r.mu.Lock()
   182  	defer r.mu.Unlock()
   183  
   184  	return r.fdToMap[fd]
   185  }
   186  
   187  func getMapInfo(fd bpfsys.BPFfd) (bpftypes.BPFMapInfo, error) {
   188  	mapInfo := bpftypes.BPFMapInfo{}
   189  	err := bpfsys.ObjectGetInfoByFD(&bpfsys.BPFAttrGetInfoFD{
   190  		BPFFD:   fd,
   191  		Info:    uintptr(unsafe.Pointer(&mapInfo)),
   192  		InfoLen: uint32(bpftypes.BPFMapInfoSize),
   193  	})
   194  	if err != nil {
   195  		return mapInfo, fmt.Errorf("bpf obj get info by fd syscall error: %w", err)
   196  	}
   197  
   198  	return mapInfo, nil
   199  }
   200  
   201  // bpfMapFromAbstractMap takes in an abstract map and uses the values in the definion to construct a specific map type
   202  // which implements BPFMap
   203  func bpfMapFromAbstractMap(am AbstractMap) BPFMap {
   204  	switch am.Definition.Type {
   205  	case bpftypes.BPF_MAP_TYPE_HASH:
   206  		return &HashMap{
   207  			AbstractMap: am,
   208  		}
   209  
   210  	case bpftypes.BPF_MAP_TYPE_ARRAY:
   211  		return &ArrayMap{
   212  			AbstractMap: am,
   213  		}
   214  
   215  	case bpftypes.BPF_MAP_TYPE_PROG_ARRAY:
   216  		return &ProgArrayMap{
   217  			AbstractMap: am,
   218  		}
   219  
   220  		// TODO BPF_MAP_TYPE_PERF_EVENT_ARRAY
   221  
   222  	case bpftypes.BPF_MAP_TYPE_PERCPU_HASH:
   223  		return &HashMap{
   224  			AbstractMap: am,
   225  		}
   226  
   227  	case bpftypes.BPF_MAP_TYPE_PERCPU_ARRAY:
   228  		return &PerCPUArrayMap{
   229  			AbstractMap: am,
   230  		}
   231  
   232  		// TODO BPF_MAP_TYPE_STACK_TRACE
   233  
   234  	case bpftypes.BPF_MAP_TYPE_LRU_HASH,
   235  		bpftypes.BPF_MAP_TYPE_LRU_PERCPU_HASH:
   236  		return &HashMap{
   237  			AbstractMap: am,
   238  		}
   239  
   240  	case bpftypes.BPF_MAP_TYPE_LPM_TRIE:
   241  		return &LPMTrieMap{
   242  			AbstractMap: am,
   243  		}
   244  
   245  	case bpftypes.BPF_MAP_TYPE_ARRAY_OF_MAPS:
   246  		return &ArrayOfMapsMap{
   247  			AbstractMap: am,
   248  		}
   249  
   250  	case bpftypes.BPF_MAP_TYPE_HASH_OF_MAPS:
   251  		return &HashOfMapsMap{
   252  			AbstractMap: am,
   253  		}
   254  
   255  		// TODO BPF_MAP_TYPE_DEVMAP
   256  		// TODO BPF_MAP_TYPE_SOCKMAP
   257  		// TODO BPF_MAP_TYPE_CPUMAP
   258  
   259  	case bpftypes.BPF_MAP_TYPE_XSKMAP:
   260  		return &XSKMap{
   261  			AbstractMap: am,
   262  		}
   263  
   264  		// TODO BPF_MAP_TYPE_SOCKHASH
   265  		// TODO BPF_MAP_TYPE_CGROUP_STORAGE
   266  		// TODO BPF_MAP_TYPE_REUSEPORT_SOCKARRAY
   267  		// TODO BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
   268  	case bpftypes.BPF_MAP_TYPE_QUEUE:
   269  		return &QueueMap{
   270  			AbstractMap: am,
   271  		}
   272  
   273  	case bpftypes.BPF_MAP_TYPE_STACK:
   274  		return &StackMap{
   275  			AbstractMap: am,
   276  		}
   277  		// TODO BPF_MAP_TYPE_SK_STORAGE
   278  		// TODO BPF_MAP_TYPE_DEVMAP_HASH
   279  		// TODO BPF_MAP_TYPE_STRUCT_OPS
   280  		// TODO BPF_MAP_TYPE_RINGBUF
   281  		// TODO BPF_MAP_TYPE_INODE_STORAGE
   282  		// TODO BPF_MAP_TYPE_TASK_STORAGE
   283  
   284  	default:
   285  		return &HashMap{
   286  			AbstractMap: am,
   287  		}
   288  	}
   289  }
   290  
   291  const maxUint32 = int(^uint32(0))