github.com/cilium/cilium@v1.16.2/pkg/recorder/recorder.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package recorder 5 6 import ( 7 "context" 8 "fmt" 9 "net" 10 "sort" 11 "strconv" 12 13 "github.com/sirupsen/logrus" 14 15 "github.com/cilium/cilium/api/v1/models" 16 "github.com/cilium/cilium/pkg/bpf" 17 "github.com/cilium/cilium/pkg/byteorder" 18 "github.com/cilium/cilium/pkg/cidr" 19 "github.com/cilium/cilium/pkg/common" 20 "github.com/cilium/cilium/pkg/datapath/types" 21 "github.com/cilium/cilium/pkg/lock" 22 "github.com/cilium/cilium/pkg/maps/recorder" 23 "github.com/cilium/cilium/pkg/option" 24 "github.com/cilium/cilium/pkg/u8proto" 25 ) 26 27 type ID uint16 28 29 // +k8s:deepcopy-gen=true 30 type RecorderTuple struct { 31 SrcPrefix cidr.CIDR 32 SrcPort uint16 33 DstPrefix cidr.CIDR 34 DstPort uint16 35 Proto u8proto.U8proto 36 } 37 38 // +k8s:deepcopy-gen=true 39 type RecorderMask struct { 40 srcMask net.IPMask 41 srcPort uint16 42 dstMask net.IPMask 43 dstPort uint16 44 proto u8proto.U8proto 45 } 46 47 // +k8s:deepcopy-gen=true 48 type RecInfo struct { 49 ID ID 50 CapLen uint16 51 Filters []RecorderTuple 52 } 53 54 // +k8s:deepcopy-gen=true 55 type RecMask struct { 56 users int 57 prio int 58 mask RecorderMask 59 } 60 61 type recQueue struct { 62 ri *RecInfo 63 add []*RecorderTuple 64 del []*RecorderTuple 65 } 66 67 type Recorder struct { 68 lock.RWMutex 69 logger logrus.FieldLogger 70 recByID map[ID]*RecInfo 71 recMask map[string]*RecMask 72 queue recQueue 73 ctx context.Context 74 loader types.Loader 75 } 76 77 func newRecorder(ctx context.Context, logger logrus.FieldLogger, loader types.Loader) *Recorder { 78 return &Recorder{ 79 recByID: map[ID]*RecInfo{}, 80 recMask: map[string]*RecMask{}, 81 queue: recQueue{ 82 add: []*RecorderTuple{}, 83 del: []*RecorderTuple{}, 84 }, 85 ctx: ctx, 86 logger: logger, 87 loader: loader, 88 } 89 } 90 91 // enableRecorder initializes the main recorder infrastructure once upon agent 92 // bootstrap for tracking tuple insertions and masks that need to be pushed 93 // down into the BPF datapath. Given we currently do not support restore 94 // functionality, it also flushes prior existing recorder objects from the 95 // BPF maps. 96 func (r *Recorder) enableRecorder() error { 97 maps := []*bpf.Map{} 98 if option.Config.EnableIPv4 { 99 t := &recorder.CaptureWcard4{} 100 maps = append(maps, t.Map()) 101 } 102 if option.Config.EnableIPv6 { 103 t := &recorder.CaptureWcard6{} 104 maps = append(maps, t.Map()) 105 } 106 for _, m := range maps { 107 if err := m.OpenOrCreate(); err != nil { 108 return err 109 } 110 if err := m.DeleteAll(); err != nil { 111 return err 112 } 113 } 114 115 return nil 116 } 117 118 func convertTupleToMask(t RecorderTuple) RecorderMask { 119 m := RecorderMask{ 120 srcMask: make([]byte, len(t.SrcPrefix.Mask)), 121 dstMask: make([]byte, len(t.DstPrefix.Mask)), 122 } 123 if t.SrcPort != 0 { 124 m.srcPort = 0xffff 125 } 126 if t.DstPort != 0 { 127 m.dstPort = 0xffff 128 } 129 if t.Proto != 0 { 130 m.proto = 0xff 131 } 132 copy(m.srcMask, t.SrcPrefix.Mask) 133 copy(m.dstMask, t.DstPrefix.Mask) 134 return m 135 } 136 137 func countMaskOnes(m RecorderMask) int { 138 ones := 0 139 onesSrc, _ := m.srcMask.Size() 140 onesDst, _ := m.dstMask.Size() 141 ones += onesSrc + onesDst 142 if m.srcPort == 0xffff { 143 ones += 16 144 } 145 if m.dstPort == 0xffff { 146 ones += 16 147 } 148 if m.proto == 0xff { 149 ones += 8 150 } 151 return ones 152 } 153 154 func hashMask(x *RecorderMask) string { 155 return fmt.Sprintf("%s/%s/%x/%x/%x", 156 x.srcMask.String(), x.dstMask.String(), 157 int(x.srcPort), int(x.dstPort), int(x.proto)) 158 } 159 160 func hashTuple(x *RecorderTuple) string { 161 return fmt.Sprintf("%s/%s/%x/%x/%x", 162 x.SrcPrefix.String(), x.DstPrefix.String(), 163 int(x.SrcPort), int(x.DstPort), int(x.Proto)) 164 } 165 166 func (t *RecorderTuple) isIPv4() bool { 167 _, bits := t.SrcPrefix.Mask.Size() 168 return bits == 32 169 } 170 171 func (m *RecorderMask) isIPv4() bool { 172 _, bits := m.srcMask.Size() 173 return bits == 32 174 } 175 176 func (m *RecorderMask) genMacroSpec() string { 177 onesSrc, _ := m.srcMask.Size() 178 onesDst, _ := m.dstMask.Size() 179 180 spec := "{" 181 if m.isIPv4() { 182 spec += fmt.Sprintf(".daddr=__constant_htonl(0x%s),", m.dstMask.String()) 183 spec += fmt.Sprintf(".saddr=__constant_htonl(0x%s),", m.srcMask.String()) 184 } else { 185 spec += fmt.Sprintf(".daddr={.addr={%s}},", common.GoArray2C(m.dstMask)) 186 spec += fmt.Sprintf(".saddr={.addr={%s}},", common.GoArray2C(m.srcMask)) 187 } 188 spec += fmt.Sprintf(".dmask=%d,", onesDst) 189 spec += fmt.Sprintf(".smask=%d,", onesSrc) 190 spec += fmt.Sprintf(".dport=%#x,", m.dstPort) 191 spec += fmt.Sprintf(".sport=%#x,", m.srcPort) 192 spec += fmt.Sprintf(".nexthdr=%#x,", uint8(m.proto)) 193 spec += "}," 194 return spec 195 } 196 197 func (r *Recorder) orderedMaskSets() ([]*RecMask, []*RecMask) { 198 ordered4 := []*RecMask{} 199 ordered6 := []*RecMask{} 200 for _, m := range r.recMask { 201 if m.mask.isIPv4() { 202 ordered4 = append(ordered4, m) 203 } else { 204 ordered6 = append(ordered6, m) 205 } 206 } 207 sort.Slice(ordered4, func(i, j int) bool { 208 return ordered4[i].prio > ordered4[j].prio 209 }) 210 sort.Slice(ordered6, func(i, j int) bool { 211 return ordered6[i].prio > ordered6[j].prio 212 }) 213 return ordered4, ordered6 214 } 215 216 func (r *Recorder) triggerDatapathRegenerate() error { 217 var masks4, masks6 string 218 extraCArgs := []string{} 219 if len(r.recMask) == 0 { 220 extraCArgs = append(extraCArgs, "-Dcapture_enabled=0") 221 } else { 222 extraCArgs = append(extraCArgs, "-Dcapture_enabled=1") 223 ordered4, ordered6 := r.orderedMaskSets() 224 if option.Config.EnableIPv4 { 225 masks4 = "-DPREFIX_MASKS4=" 226 if len(ordered4) == 0 { 227 masks4 += " " 228 } else { 229 for _, m := range ordered4 { 230 masks4 += m.mask.genMacroSpec() 231 } 232 } 233 extraCArgs = append(extraCArgs, masks4) 234 } 235 if option.Config.EnableIPv6 { 236 masks6 = "-DPREFIX_MASKS6=" 237 if len(ordered6) == 0 { 238 masks6 += " " 239 } else { 240 for _, m := range ordered6 { 241 masks6 += m.mask.genMacroSpec() 242 } 243 } 244 extraCArgs = append(extraCArgs, masks6) 245 } 246 } 247 err := r.loader.ReinitializeXDP(r.ctx, extraCArgs) 248 if err != nil { 249 r.logger.WithError(err).Warnf("Failed to regenerate datapath with masks: %s / %s", 250 masks4, masks6) 251 } 252 return err 253 } 254 255 func recorderTupleToMapTuple4(ri *RecInfo, t *RecorderTuple) (*recorder.CaptureWcard4, *recorder.CaptureRule4) { 256 onesSrc, _ := t.SrcPrefix.Mask.Size() 257 onesDst, _ := t.DstPrefix.Mask.Size() 258 259 k := &recorder.CaptureWcard4{ 260 NextHdr: uint8(t.Proto), 261 DestMask: uint8(onesDst), 262 SrcMask: uint8(onesSrc), 263 } 264 k.DestPort = byteorder.HostToNetwork16(t.DstPort) 265 k.SrcPort = byteorder.HostToNetwork16(t.SrcPort) 266 copy(k.DestAddr[:], t.DstPrefix.IP.To4()[:]) 267 copy(k.SrcAddr[:], t.SrcPrefix.IP.To4()[:]) 268 v := &recorder.CaptureRule4{ 269 RuleId: uint16(ri.ID), 270 CapLen: uint32(ri.CapLen), 271 } 272 return k, v 273 } 274 275 func recorderTupleToMapTuple6(ri *RecInfo, t *RecorderTuple) (*recorder.CaptureWcard6, *recorder.CaptureRule6) { 276 onesSrc, _ := t.SrcPrefix.Mask.Size() 277 onesDst, _ := t.DstPrefix.Mask.Size() 278 279 k := &recorder.CaptureWcard6{ 280 NextHdr: uint8(t.Proto), 281 DestMask: uint8(onesDst), 282 SrcMask: uint8(onesSrc), 283 } 284 k.DestPort = byteorder.HostToNetwork16(t.DstPort) 285 k.SrcPort = byteorder.HostToNetwork16(t.SrcPort) 286 copy(k.DestAddr[:], t.DstPrefix.IP.To16()[:]) 287 copy(k.SrcAddr[:], t.SrcPrefix.IP.To16()[:]) 288 v := &recorder.CaptureRule6{ 289 RuleId: uint16(ri.ID), 290 CapLen: uint32(ri.CapLen), 291 } 292 return k, v 293 } 294 295 func recorderTupleToMapTuple(ri *RecInfo, t *RecorderTuple) (recorder.RecorderKey, recorder.RecorderEntry) { 296 var k recorder.RecorderKey 297 var v recorder.RecorderEntry 298 if t.isIPv4() { 299 k, v = recorderTupleToMapTuple4(ri, t) 300 } else { 301 k, v = recorderTupleToMapTuple6(ri, t) 302 } 303 return k, v 304 } 305 306 func (r *Recorder) triggerMapUpsert(ri *RecInfo, t *RecorderTuple) error { 307 k, v := recorderTupleToMapTuple(ri, t) 308 return k.Map().Update(k, v) 309 } 310 311 func (r *Recorder) triggerMapDelete(ri *RecInfo, t *RecorderTuple) error { 312 k, _ := recorderTupleToMapTuple(ri, t) 313 return k.Map().Delete(k) 314 } 315 316 func (r *Recorder) applyDatapath(regen bool) error { 317 for _, e := range r.queue.add { 318 r.triggerMapUpsert(r.queue.ri, e) 319 } 320 r.queue.add = []*RecorderTuple{} 321 for _, e := range r.queue.del { 322 r.triggerMapDelete(r.queue.ri, e) 323 } 324 r.queue.del = []*RecorderTuple{} 325 r.queue.ri = nil 326 if regen { 327 r.logger.Debugf("Recorder Masks: %v", r.recMask) 328 // If datapath masks did not change, then there is of course 329 // also no need to trigger a regeneration since map updates 330 // suffice (which is also much faster). 331 return r.triggerDatapathRegenerate() 332 } 333 return nil 334 } 335 336 func queuePurge(q []*RecorderTuple, i int) []*RecorderTuple { 337 q[i] = q[len(q)-1] 338 q[len(q)-1] = nil 339 return q[:len(q)-1] 340 } 341 342 func (r *Recorder) queueAddDatapathFilter(ri *RecInfo, i int) { 343 if r.queue.ri == nil { 344 r.queue.ri = ri 345 } 346 r.queue.add = append(r.queue.add, &ri.Filters[i]) 347 } 348 349 func (r *Recorder) queueDelDatapathFilter(ri *RecInfo, i int) { 350 if r.queue.ri == nil { 351 r.queue.ri = ri 352 } 353 filter := &ri.Filters[i] 354 hash := hashTuple(filter) 355 // If the recorder updated an existing filter element which sits 356 // in both queues, then we do not need any change in the BPF data 357 // path, and can avoid temporary recorder disruption. Hence, add/del 358 // queues strictly only ever contain entries that need change. 359 for i, e := range r.queue.add { 360 if hashTuple(e) == hash { 361 if r.queue.ri.CapLen == ri.CapLen { 362 r.queue.add = queuePurge(r.queue.add, i) 363 } 364 return 365 } 366 } 367 r.queue.del = append(r.queue.del, filter) 368 } 369 370 func (r *Recorder) deleteRecInfoLocked(ri *RecInfo, withID bool) bool { 371 triggerRegen := false 372 for i, filter := range ri.Filters { 373 mask := convertTupleToMask(filter) 374 maskHash := hashMask(&mask) 375 if rm, found := r.recMask[maskHash]; found { 376 rm.users-- 377 if rm.users == 0 { 378 delete(r.recMask, maskHash) 379 triggerRegen = true 380 } 381 } 382 r.queueDelDatapathFilter(ri, i) 383 } 384 if withID { 385 delete(r.recByID, ri.ID) 386 } 387 return triggerRegen 388 } 389 390 // DeleteRecorder will delete an existing recorder object based on its unique 391 // identifier. If needed, it will also update datapath maps to purge the 392 // recorder filters from the BPF maps as well as triggering a reinitialization 393 // of the XDP datapath if the mask set has changed. 394 func (r *Recorder) DeleteRecorder(id ID) (bool, error) { 395 r.Lock() 396 defer r.Unlock() 397 if recInfo, found := r.recByID[id]; found { 398 return true, r.applyDatapath(r.deleteRecInfoLocked(recInfo, true)) 399 } 400 return false, nil 401 } 402 403 func (r *Recorder) createRecInfoLocked(ri *RecInfo, withID bool) bool { 404 if withID { 405 r.recByID[ri.ID] = ri 406 } 407 triggerRegen := false 408 for i, filter := range ri.Filters { 409 mask := convertTupleToMask(filter) 410 maskHash := hashMask(&mask) 411 if rm, found := r.recMask[maskHash]; found { 412 rm.users++ 413 } else { 414 ones := countMaskOnes(mask) 415 rm := &RecMask{ 416 users: 1, 417 mask: mask, 418 prio: ones, 419 } 420 r.recMask[maskHash] = rm 421 triggerRegen = true 422 } 423 r.queueAddDatapathFilter(ri, i) 424 } 425 return triggerRegen 426 } 427 428 func (r *Recorder) updateRecInfoLocked(riNew, riOld *RecInfo) error { 429 triggerRegen := false 430 if r.createRecInfoLocked(riNew, true) { 431 triggerRegen = true 432 } 433 if r.deleteRecInfoLocked(riOld, false) { 434 triggerRegen = true 435 } 436 return r.applyDatapath(triggerRegen) 437 } 438 439 // UpsertRecorder will create a new or update an existing recorder object 440 // based on its unique identifier. If needed, it will also update datapath 441 // maps to insert new or remove obsolete recorder filters from the BPF maps 442 // as well as triggering a reinitialization of the XDP datapath if the mask 443 // set has changed. 444 func (r *Recorder) UpsertRecorder(recInfoNew *RecInfo) (bool, error) { 445 if !option.Config.EnableRecorder { 446 return false, fmt.Errorf("Ignoring recorder request due to --%s being disabled in agent", 447 option.EnableRecorder) 448 } 449 recInfoCpy := recInfoNew.DeepCopy() 450 r.Lock() 451 defer r.Unlock() 452 if recInfoCur, found := r.recByID[recInfoCpy.ID]; found { 453 return false, r.updateRecInfoLocked(recInfoCpy, recInfoCur) 454 } else { 455 return true, r.applyDatapath(r.createRecInfoLocked(recInfoCpy, true)) 456 } 457 } 458 459 func (r *Recorder) retrieveRecorderLocked(id ID) (*RecInfo, error) { 460 if recInfo, found := r.recByID[id]; found { 461 return recInfo.DeepCopy(), nil 462 } else { 463 return nil, fmt.Errorf("Recorder id %d not found", int(id)) 464 } 465 } 466 467 // RetrieveRecorder will return an existing recorder object based on its 468 // unique identifier. The returned object is a deep copy of the original 469 // one tracked by the recorder infrastructure, so it can be freely changed 470 // without affecting the original recorder object. 471 func (r *Recorder) RetrieveRecorder(id ID) (*RecInfo, error) { 472 r.RLock() 473 defer r.RUnlock() 474 return r.retrieveRecorderLocked(id) 475 } 476 477 // RetrieveRecorderSet will return a list of all existing recorder objects. 478 func (r *Recorder) RetrieveRecorderSet() []*RecInfo { 479 recList := make([]*RecInfo, 0, len(r.recByID)) 480 r.RLock() 481 defer r.RUnlock() 482 for id := range r.recByID { 483 rec, _ := r.retrieveRecorderLocked(id) 484 recList = append(recList, rec) 485 } 486 return recList 487 } 488 489 // RetrieveRecorderMaskSet will return a list of all existing recorder masks. 490 func (r *Recorder) RetrieveRecorderMaskSet() []*RecMask { 491 recMaskList := make([]*RecMask, 0, len(r.recMask)) 492 r.RLock() 493 defer r.RUnlock() 494 for _, mask := range r.recMask { 495 maskCpy := mask.DeepCopy() 496 recMaskList = append(recMaskList, maskCpy) 497 } 498 return recMaskList 499 } 500 501 func ModelToRecorder(mo *models.RecorderSpec) (*RecInfo, error) { 502 if mo.ID == nil { 503 return nil, fmt.Errorf("Recorder model ID must be defined") 504 } 505 ri := &RecInfo{ 506 ID: ID(*mo.ID), 507 CapLen: uint16(mo.CaptureLength), 508 Filters: []RecorderTuple{}, 509 } 510 for _, mf := range mo.Filters { 511 f := RecorderTuple{} 512 ipDst, prefix, err := net.ParseCIDR(mf.DstPrefix) 513 if err != nil { 514 return nil, err 515 } 516 f.DstPrefix = *cidr.NewCIDR(prefix) 517 ipSrc, prefix, err := net.ParseCIDR(mf.SrcPrefix) 518 if err != nil { 519 return nil, err 520 } 521 f.SrcPrefix = *cidr.NewCIDR(prefix) 522 if (ipDst.To4() == nil) != (ipSrc.To4() == nil) { 523 return nil, fmt.Errorf("Recorder source (%s) and destination (%s) prefix must be same protocol version", 524 f.SrcPrefix, f.DstPrefix) 525 } 526 if !option.Config.EnableIPv4 && ipDst.To4() != nil || 527 !option.Config.EnableIPv6 && ipDst.To4() == nil { 528 return nil, fmt.Errorf("Recorder source (%s) and destination (%s) prefix not supported by agent config", 529 f.SrcPrefix, f.DstPrefix) 530 } 531 port, err := strconv.ParseUint(mf.DstPort, 10, 16) 532 if err != nil { 533 return nil, err 534 } 535 f.DstPort = uint16(port) 536 port, err = strconv.ParseUint(mf.SrcPort, 10, 16) 537 if err != nil { 538 return nil, err 539 } 540 f.SrcPort = uint16(port) 541 switch mf.Protocol { 542 case models.RecorderFilterProtocolTCP: 543 f.Proto = u8proto.TCP 544 case models.RecorderFilterProtocolUDP: 545 f.Proto = u8proto.UDP 546 case models.RecorderFilterProtocolSCTP: 547 f.Proto = u8proto.SCTP 548 case models.RecorderFilterProtocolANY: 549 f.Proto = u8proto.ANY 550 default: 551 return nil, fmt.Errorf("Recorder protocol %s not supported by backend", 552 mf.Protocol) 553 } 554 ri.Filters = append(ri.Filters, f) 555 } 556 return ri, nil 557 } 558 559 func RecorderToModel(ri *RecInfo) (*models.RecorderSpec, error) { 560 id := int64(ri.ID) 561 mo := &models.RecorderSpec{ 562 ID: &id, 563 Filters: []*models.RecorderFilter{}, 564 CaptureLength: int64(ri.CapLen), 565 } 566 for _, rf := range ri.Filters { 567 mf := &models.RecorderFilter{} 568 mf.DstPrefix = rf.DstPrefix.String() 569 mf.SrcPrefix = rf.SrcPrefix.String() 570 mf.DstPort = fmt.Sprintf("%d", int(rf.DstPort)) 571 mf.SrcPort = fmt.Sprintf("%d", int(rf.SrcPort)) 572 switch rf.Proto { 573 case u8proto.TCP: 574 mf.Protocol = models.RecorderFilterProtocolTCP 575 case u8proto.UDP: 576 mf.Protocol = models.RecorderFilterProtocolUDP 577 case u8proto.SCTP: 578 mf.Protocol = models.RecorderFilterProtocolSCTP 579 case u8proto.ANY: 580 mf.Protocol = models.RecorderFilterProtocolANY 581 default: 582 return nil, fmt.Errorf("Recorder protocol %d not supported by model", 583 int(rf.Proto)) 584 } 585 mo.Filters = append(mo.Filters, mf) 586 } 587 return mo, nil 588 } 589 590 func RecorderMaskToModel(rm *RecMask) *models.RecorderMaskSpec { 591 mo := &models.RecorderMaskSpec{ 592 Users: int64(rm.users), 593 Priority: int64(rm.prio), 594 } 595 mo.DstPrefixMask = rm.mask.dstMask.String() 596 mo.SrcPrefixMask = rm.mask.srcMask.String() 597 mo.DstPortMask = fmt.Sprintf("%x", int(rm.mask.dstPort)) 598 mo.SrcPortMask = fmt.Sprintf("%x", int(rm.mask.srcPort)) 599 mo.ProtocolMask = fmt.Sprintf("%x", int(rm.mask.proto)) 600 return mo 601 }