-
Notifications
You must be signed in to change notification settings - Fork 324
/
map.go
245 lines (198 loc) · 5.71 KB
/
map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon
package policyfilter
import (
"fmt"
"os"
"path"
"path/filepath"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/tetragon/pkg/bpf"
"github.com/cilium/tetragon/pkg/kernels"
"github.com/cilium/tetragon/pkg/option"
)
const (
MapName = "policy_filter_maps"
)
// map operations used by policyfilter.
// PfMap is a simple wrapper for ebpf.Map so that we can write methods for it
type PfMap struct {
*ebpf.Map
}
// newMap returns a new policy filter map.
func newPfMap() (PfMap, error) {
// use the generic kprobe program, to find the policy filter map spec
objName, _ := kernels.GenericKprobeObjs()
objPath := path.Join(option.Config.HubbleLib, objName)
spec, err := ebpf.LoadCollectionSpec(objPath)
if err != nil {
return PfMap{}, fmt.Errorf("loading spec for %s failed: %w", objPath, err)
}
policyMapSpec, ok := spec.Maps[MapName]
if !ok {
return PfMap{}, fmt.Errorf("%s not found in %s", MapName, objPath)
}
// bpf-side sets max_entries to 1. Later kernels (5.10) can deal with
// inserting a different size of inner-map, but for older kernels, we
// fix the spec here.
policyMapSpec.InnerMap.MaxEntries = polMapSize
ret, err := ebpf.NewMap(policyMapSpec)
if err != nil {
return PfMap{}, err
}
mapDir := bpf.MapPrefixPath()
pinPath := filepath.Join(mapDir, MapName)
os.Remove(pinPath)
os.Mkdir(mapDir, os.ModeDir)
err = ret.Pin(pinPath)
if err != nil {
ret.Close()
return PfMap{}, fmt.Errorf("failed to pin policy filter map in %s: %w", pinPath, err)
}
return PfMap{ret}, err
}
// release closes the policy filter bpf map and remove (unpin) the bpffs file
func (m PfMap) release() error {
if err := m.Close(); err != nil {
return err
}
// nolint:revive // ignore "if-return: redundant if just return error" for clarity
if err := m.Unpin(); err != nil {
return err
}
return nil
}
// addPolicyMap adds and initializes a new policy map
func (m PfMap) newPolicyMap(polID PolicyID, cgIDs []CgroupID) (polMap, error) {
name := fmt.Sprintf("policy_%d_map", polID)
innerSpec := &ebpf.MapSpec{
Name: name,
Type: ebpf.Hash,
KeySize: uint32(unsafe.Sizeof(CgroupID(0))),
ValueSize: uint32(1),
MaxEntries: uint32(polMapSize),
}
inner, err := ebpf.NewMap(innerSpec)
if err != nil {
return polMap{}, fmt.Errorf("failed to create policy (id=%d) map: %w", polID, err)
}
// update inner map with ids
ret := polMap{inner}
if err := ret.addCgroupIDs(cgIDs); err != nil {
ret.Close()
return polMap{}, fmt.Errorf("failed to update policy (id=%d): %w", polID, err)
}
// update outer map
// NB(kkourt): use UpdateNoExist because we expect only a single policy with a given id
if err := m.Update(polID, uint32(ret.FD()), ebpf.UpdateNoExist); err != nil {
ret.Close()
return polMap{}, fmt.Errorf("failed to insert inner policy (id=%d) map: %w", polID, err)
}
return ret, nil
}
func (m PfMap) readAll() (map[PolicyID]map[CgroupID]struct{}, error) {
readInner := func(id uint32) (map[CgroupID]struct{}, error) {
inMap, err := ebpf.NewMapFromID(ebpf.MapID(id))
if err != nil {
return nil, fmt.Errorf("error opening inner map: %w", err)
}
defer inMap.Close()
inIter := inMap.Iterate()
var key CgroupID
var val uint8
ret := map[CgroupID]struct{}{}
for inIter.Next(&key, &val) {
ret[key] = struct{}{}
}
if err := inIter.Err(); err != nil {
return nil, fmt.Errorf("error iterating inner map: %w", err)
}
return ret, nil
}
ret := make(map[PolicyID]map[CgroupID]struct{})
var key PolicyID
var id uint32
iter := m.Iterate()
for iter.Next(&key, &id) {
cgids, err := readInner(id)
if err != nil {
return nil, err
}
ret[key] = cgids
}
if err := iter.Err(); err != nil {
return nil, fmt.Errorf("error iterating outer map: %w", err)
}
return ret, nil
}
// polMap is a simple wrapper for ebpf.Map so that we can write methods for it
type polMap struct {
*ebpf.Map
}
type batchError struct {
// SuccCount is the number of successful operations
SuccCount int
err error
}
func (e *batchError) Error() string {
return e.err.Error()
}
func (e *batchError) Unwrap() error {
return e.err
}
// addCgroupIDs add cgroups ids to the policy map
// todo: use batch operations when supported
func (m polMap) addCgroupIDs(cgIDs []CgroupID) error {
var zero uint8
for i, cgID := range cgIDs {
if err := m.Update(&cgID, zero, ebpf.UpdateAny); err != nil {
return &batchError{
SuccCount: i,
err: fmt.Errorf("failed to update policy map (cgroup id: %d): %w", cgID, err),
}
}
}
return nil
}
// addCgroupIDs delete cgroups ids from the policy map
// todo: use batch operations when supported
func (m polMap) delCgroupIDs(cgIDs []CgroupID) error {
for i, cgID := range cgIDs {
if err := m.Delete(&cgID); err != nil {
return &batchError{
SuccCount: i,
err: fmt.Errorf("failed to delete items from policy map (cgroup id: %d): %w", cgID, err),
}
}
}
return nil
}
func OpenMap(fname string) (PfMap, error) {
m, err := ebpf.LoadPinnedMap(fname, &ebpf.LoadPinOptions{
ReadOnly: true,
})
if err != nil {
return PfMap{}, err
}
return PfMap{m}, err
}
func (m PfMap) Dump() (map[PolicyID]map[CgroupID]struct{}, error) {
return m.readAll()
}
func (m PfMap) AddCgroup(polID PolicyID, cgID CgroupID) error {
var innerID uint32
if err := m.Lookup(&polID, &innerID); err != nil {
return fmt.Errorf("failed to lookup policy id %d: %w", polID, err)
}
inMap, err := ebpf.NewMapFromID(ebpf.MapID(innerID))
if err != nil {
return fmt.Errorf("error opening inner map: %w", err)
}
defer inMap.Close()
val := uint8(0)
if err := inMap.Update(&cgID, &val, ebpf.UpdateAny); err != nil {
return fmt.Errorf("error updating inner map: %w", err)
}
return nil
}