From 875dde494d5f82a4f85034e281946285940fc55f Mon Sep 17 00:00:00 2001 From: Aryan Bakliwal Date: Mon, 23 Dec 2024 00:26:31 +0530 Subject: [PATCH 1/7] add bidirection flag Signed-off-by: Aryan Bakliwal --- pkg/apis/crd/v1alpha1/types.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index 82b2b422d13..1ce5b449f57 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -439,6 +439,8 @@ type PacketCaptureSpec struct { // for a capture session, and at least one `Pod` should be present either in the source or the destination. Source Source `json:"source"` Destination Destination `json:"destination"` + // Bidirection specifies whether to capture the return (response) traffic from the destination back to the source. + Bidirection bool `json:"bidirection"` // Packet defines what kind of traffic we want to capture between the source and destination. If not specified, // all kinds of traffic will count. Packet *Packet `json:"packet,omitempty"` From 1307c175edc32f944273fb09073fc6dd4bd27718 Mon Sep 17 00:00:00 2001 From: Aryan Bakliwal Date: Mon, 23 Dec 2024 09:39:16 +0530 Subject: [PATCH 2/7] add bidirectional capture Signed-off-by: Aryan Bakliwal --- build/yamls/antrea.yml | 4 +- .../packetcapture/packetcapture_controller.go | 42 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index 0580bbfb76b..afae6ceeb15 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -3035,7 +3035,9 @@ spec: type: integer minimum: 1 maximum: 65535 - + bidirection: + type: boolean + default: false timeout: type: integer minimum: 1 diff --git a/pkg/agent/packetcapture/packetcapture_controller.go b/pkg/agent/packetcapture/packetcapture_controller.go index c799ffd2179..7a6b1c388a5 100644 --- a/pkg/agent/packetcapture/packetcapture_controller.go +++ b/pkg/agent/packetcapture/packetcapture_controller.go @@ -464,10 +464,22 @@ func (c *Controller) performCapture( } defer pcapngWriter.Flush() updateRateLimiter := rate.NewLimiter(rate.Every(captureStatusUpdatePeriod), 1) - packets, err := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet) - if err != nil { - return false, err + + var packets chan gopacket.Packet + if pc.Spec.Bidirection { + reqpackets, err1 := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet) + respackets, err2 := c.captureInterface.Capture(ctx, device, snapLen, dstIP, srcIP, pc.Spec.Packet) + if err1 != nil || err2 != nil { + return false, fmt.Errorf("failed to set up bidirectional capture: %v, %v", err1, err2) + } + packets = c.mergeChannels(reqpackets, respackets) + } else { + packets, err = c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet) + if err != nil { + return false, err + } } + // Track whether any packet is captured. capturedAny := false for { @@ -503,6 +515,30 @@ func (c *Controller) performCapture( } } +func (c *Controller) mergeChannels(ch1, ch2 chan gopacket.Packet) chan gopacket.Packet { + merged := make(chan gopacket.Packet) + go func() { + defer close(merged) + for ch1 != nil || ch2 != nil { + select { + case pkt, ok := <-ch1: + if ok { + merged <- pkt + } else { + ch1 = nil + } + case pkt, ok := <-ch2: + if ok { + merged <- pkt + } else { + ch2 = nil + } + } + } + }() + return merged +} + func (c *Controller) getPodIP(ctx context.Context, podRef *crdv1alpha1.PodReference) (net.IP, error) { podInterfaces := c.interfaceStore.GetContainerInterfacesByPod(podRef.Name, podRef.Namespace) var podIP net.IP From 25d581b43dbcb1623cd4fc838dc41bf926e3f6f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:12:45 -0800 Subject: [PATCH 3/7] Bump golang.org/x/net from 0.32.0 to 0.33.0 in the golang-org-x group (#6877) Bumps the golang-org-x group with 1 update: [golang.org/x/net](https://github.com/golang/net). Updates `golang.org/x/net` from 0.32.0 to 0.33.0 - [Commits](https://github.com/golang/net/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor dependency-group: golang-org-x ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06ebc1989f6..0c57b1a312b 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( go.uber.org/mock v0.5.0 golang.org/x/crypto v0.31.0 golang.org/x/mod v0.22.0 - golang.org/x/net v0.32.0 + golang.org/x/net v0.33.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/time v0.8.0 diff --git a/go.sum b/go.sum index 9d8aa866ac2..62a8f5ee32c 100644 --- a/go.sum +++ b/go.sum @@ -900,8 +900,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 60d3e7e1d29342621d0627f9f8b516617b048c6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:12:58 -0800 Subject: [PATCH 4/7] Bump github.com/onsi/ginkgo/v2 from 2.22.0 to 2.22.1 in the ginkgo group (#6880) Bumps the ginkgo group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo). Updates `github.com/onsi/ginkgo/v2` from 2.22.0 to 2.22.1 - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.22.0...v2.22.1) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: ginkgo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0c57b1a312b..f0029179595 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/mdlayher/packet v1.1.2 github.com/miekg/dns v1.1.62 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 - github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/ginkgo/v2 v2.22.1 github.com/onsi/gomega v1.36.1 github.com/osrg/gobgp/v3 v3.32.0 github.com/pkg/sftp v1.13.7 @@ -147,7 +147,7 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect diff --git a/go.sum b/go.sum index 62a8f5ee32c..e75e093e386 100644 --- a/go.sum +++ b/go.sum @@ -367,8 +367,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -588,8 +588,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= +github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= From 79a26c0d2e0f6d8b1af75476442cdc7a882e13b2 Mon Sep 17 00:00:00 2001 From: Aryan Bakliwal Date: Wed, 1 Jan 2025 18:36:22 +0530 Subject: [PATCH 5/7] update code and tests Signed-off-by: Aryan Bakliwal --- pkg/agent/packetcapture/capture/bpf.go | 103 ++++++++++++++---- pkg/agent/packetcapture/capture/bpf_test.go | 11 +- pkg/agent/packetcapture/capture/pcap_linux.go | 4 +- pkg/agent/packetcapture/capture_interface.go | 2 +- .../packetcapture/packetcapture_controller.go | 42 +------ .../packetcapture_controller_test.go | 2 +- pkg/apis/crd/v1alpha1/types.go | 1 + 7 files changed, 98 insertions(+), 67 deletions(-) diff --git a/pkg/agent/packetcapture/capture/bpf.go b/pkg/agent/packetcapture/capture/bpf.go index 841b721a25c..e42227f1344 100644 --- a/pkg/agent/packetcapture/capture/bpf.go +++ b/pkg/agent/packetcapture/capture/bpf.go @@ -75,17 +75,17 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction // compilePacketFilter compiles the CRD spec to bpf instructions. For now, we only focus on // ipv4 traffic. Compared to the raw BPF filter supported by libpcap, we only need to support // limited use cases, so an expression parser is not needed. -func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []bpf.Instruction { - size := uint8(calculateInstructionsSize(packetSpec)) +func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, bidirection bool) []bpf.Instruction { + size := uint8(calculateInstructionsSize(packetSpec, bidirection)) // ipv4 check - inst := []bpf.Instruction{loadEtherKind} + inst := []bpf.Instruction{loadEtherKind} //(000) // skip means how many instructions we need to skip if the compare fails. // for example, for now we have 2 instructions, and the total size is 17, if ipv4 // check failed, we need to jump to the end (ret #0), skip 17-3=14 instructions. // if check succeed, skipTrue means we jump to the next instruction. Here 3 means we // have 3 instructions so far. - inst = append(inst, compareProtocolIP4(0, size-3)) + inst = append(inst, compareProtocolIP4(0, size-3)) //(001) if packetSpec != nil { if packetSpec.Protocol != nil { @@ -96,24 +96,27 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) [] proto = ProtocolMap[strings.ToUpper(packetSpec.Protocol.StrVal)] } - inst = append(inst, loadIPv4Protocol) - inst = append(inst, compareProtocol(proto, 0, size-5)) + inst = append(inst, loadIPv4Protocol) //(002) + inst = append(inst, compareProtocol(proto, 0, size-5)) //(003) 27-5=22 } } // source ip if srcIP != nil { - inst = append(inst, loadIPv4SourceAddress) + inst = append(inst, loadIPv4SourceAddress) //(004) addrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:]) // from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions. - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - + if bidirection { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 13}) //(005) 27-5-13=9 + } else { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(005) 17-5-2=10 + } } // dst ip if dstIP != nil { - inst = append(inst, loadIPv4DestinationAddress) + inst = append(inst, loadIPv4DestinationAddress) //(006) addrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:]) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(007) 18, 8 } // ports @@ -136,21 +139,48 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) [] if srcPort > 0 || dstPort > 0 { skipTrue := size - uint8(len(inst)) - 3 - inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) + inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) //(008), (009), (010) if srcPort > 0 { - inst = append(inst, loadIPv4SourcePort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, loadIPv4SourcePort) //(011) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(012) } if dstPort > 0 { - inst = append(inst, loadIPv4DestinationPort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, loadIPv4DestinationPort) //(013) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(014) + } + } + + if bidirection { + // src ip (return traffic) + if dstIP != nil { + addrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:]) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(015) + } + + // dst ip (return traffic) + if srcIP != nil { + inst = append(inst, loadIPv4SourceAddress) //(016) + addrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:]) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(017) } + if srcPort > 0 || dstPort > 0 { + skipTrue := size - uint8(len(inst)) - 3 + inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) //(018), (019), (020) + if dstPort > 0 { + inst = append(inst, loadIPv4SourcePort) //(021) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(022) + } + if srcPort > 0 { + inst = append(inst, loadIPv4DestinationPort) //(023) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(024) + } + } } // return - inst = append(inst, returnKeep) - inst = append(inst, returnDrop) + inst = append(inst, returnKeep) //(015), (025) + inst = append(inst, returnDrop) //(016), (026) return inst @@ -178,7 +208,38 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) [] // (015) ret #262144 # MATCH // (016) ret #0 # NOMATCH -func calculateInstructionsSize(packet *crdv1alpha1.Packet) int { +// When capturing return traffic also (i.e., both src -> dst and dst -> src), the filter might look like this: +// 'ip proto 6 and ((src host 10.244.1.2 and dst host 10.244.1.3 and src port 123 and dst port 124) or (src host 10.244.1.3 and dst host 10.244.1.2 and src port 124 and dst port 123))' +// And using `tcpdump -i '' -d` will generate the following BPF instructions: +// (000) ldh [12] # Load 2B at 12 (Ethertype) +// (001) jeq #0x800 jt 2 jf 26 # Ethertype: If IPv4, goto #2, else #26 +// (002) ldb [23] # Load 1B at 23 (IPv4 Protocol) +// (003) jeq #0x6 jt 4 jf 26 # IPv4 Protocol: If TCP, goto #4, #26 +// (004) ld [26] # Load 4B at 26 (source address) +// (005) jeq #0xaf40102 jt 6 jf 15 # If bytes match(10.244.0.2), goto #6, else #15 +// (006) ld [30] # Load 4B at 30 (dest address) +// (007) jeq #0xaf40103 jt 8 jf 26 # If bytes match(10.244.0.3), goto #8, else #26 +// (008) ldh [20] # Load 2B at 20 (13b Fragment Offset) +// (009) jset #0x1fff jt 26 jf 10 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #26 +// (010) ldxb 4*([14]&0xf) # x = IP header length +// (011) ldh [x + 14] # Load 2B at x+14 (TCP Source Port) +// (012) jeq #0x7b jt 13 jf 26 # TCP Source Port: If 123, goto #13, else #26 +// (013) ldh [x + 16] # Load 2B at x+16 (TCP dst port) +// (014) jeq #0x7c jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 +// (015) jeq #0xaf40103 jt 16 jf 26 # If bytes match(10.244.0.3), goto #16, else #26 +// (016) ld [30] # Load 4B at 30 (return traffic dest address) +// (017) jeq #0xaf40102 jt 18 jf 26 # If bytes match(10.244.0.2), goto #18, else #26 +// (018) ldh [20] # Load 2B at 20 (13b Fragment Offset) +// (019) jset #0x1fff jt 26 jf 20 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #20, else #26 +// (020) ldxb 4*([14]&0xf) # x = IP header length +// (021) ldh [x + 14] # Load 2B at x+14 (TCP Source Port) +// (022) jeq #0x7c jt 23 jf 26 # TCP Source Port: If 124, goto #23, else #26 +// (023) ldh [x + 16] # Load 2B at x+16 (TCP dst port) +// (024) jeq #0x7b jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 +// (025) ret #262144 # MATCH +// (026) ret #0 # NOMATCH + +func calculateInstructionsSize(packet *crdv1alpha1.Packet, bidirection bool) int { count := 0 // load ethertype count++ @@ -214,6 +275,10 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int { // src and dst ip count += 4 + if bidirection { + count += 10 + } + // ret command count += 2 return count diff --git a/pkg/agent/packetcapture/capture/bpf_test.go b/pkg/agent/packetcapture/capture/bpf_test.go index 1f911135a52..de3e7ab1800 100644 --- a/pkg/agent/packetcapture/capture/bpf_test.go +++ b/pkg/agent/packetcapture/capture/bpf_test.go @@ -34,9 +34,10 @@ var ( func TestCalculateInstructionsSize(t *testing.T) { tt := []struct { - name string - packet *crdv1alpha1.Packet - count int + name string + packet *crdv1alpha1.Packet + count int + bidirection bool }{ { name: "proto and host and port", @@ -92,7 +93,7 @@ func TestCalculateInstructionsSize(t *testing.T) { for _, item := range tt { t.Run(item.name, func(t *testing.T) { - assert.Equal(t, item.count, calculateInstructionsSize(item.packet)) + assert.Equal(t, item.count, calculateInstructionsSize(item.packet, item.bidirection)) }) } } @@ -177,7 +178,7 @@ func TestPacketCaptureCompileBPF(t *testing.T) { for _, item := range tt { t.Run(item.name, func(t *testing.T) { - result := compilePacketFilter(item.spec.Packet, item.srcIP, item.dstIP) + result := compilePacketFilter(item.spec.Packet, item.srcIP, item.dstIP, item.spec.Bidirection) assert.Equal(t, item.inst, result) }) } diff --git a/pkg/agent/packetcapture/capture/pcap_linux.go b/pkg/agent/packetcapture/capture/pcap_linux.go index 8bbd4845ab8..2f178d08c23 100644 --- a/pkg/agent/packetcapture/capture/pcap_linux.go +++ b/pkg/agent/packetcapture/capture/pcap_linux.go @@ -41,9 +41,9 @@ func zeroFilter() []bpf.Instruction { return []bpf.Instruction{returnDrop} } -func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet) (chan gopacket.Packet, error) { +func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) { // Compile the BPF filter in advance to reduce the time window between starting the capture and applying the filter. - inst := compilePacketFilter(packet, srcIP, dstIP) + inst := compilePacketFilter(packet, srcIP, dstIP, bidirection) klog.V(5).InfoS("Generated bpf instructions for PacketCapture", "device", device, "srcIP", srcIP, "dstIP", dstIP, "packetSpec", packet, "bpf", inst) rawInst, err := bpf.Assemble(inst) if err != nil { diff --git a/pkg/agent/packetcapture/capture_interface.go b/pkg/agent/packetcapture/capture_interface.go index c607cb32c30..8c37df3ad72 100644 --- a/pkg/agent/packetcapture/capture_interface.go +++ b/pkg/agent/packetcapture/capture_interface.go @@ -24,5 +24,5 @@ import ( ) type PacketCapturer interface { - Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet) (chan gopacket.Packet, error) + Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) } diff --git a/pkg/agent/packetcapture/packetcapture_controller.go b/pkg/agent/packetcapture/packetcapture_controller.go index 7a6b1c388a5..3074dde8306 100644 --- a/pkg/agent/packetcapture/packetcapture_controller.go +++ b/pkg/agent/packetcapture/packetcapture_controller.go @@ -464,22 +464,10 @@ func (c *Controller) performCapture( } defer pcapngWriter.Flush() updateRateLimiter := rate.NewLimiter(rate.Every(captureStatusUpdatePeriod), 1) - - var packets chan gopacket.Packet - if pc.Spec.Bidirection { - reqpackets, err1 := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet) - respackets, err2 := c.captureInterface.Capture(ctx, device, snapLen, dstIP, srcIP, pc.Spec.Packet) - if err1 != nil || err2 != nil { - return false, fmt.Errorf("failed to set up bidirectional capture: %v, %v", err1, err2) - } - packets = c.mergeChannels(reqpackets, respackets) - } else { - packets, err = c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet) - if err != nil { - return false, err - } + packets, err := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet, pc.Spec.Bidirection) + if err != nil { + return false, err } - // Track whether any packet is captured. capturedAny := false for { @@ -515,30 +503,6 @@ func (c *Controller) performCapture( } } -func (c *Controller) mergeChannels(ch1, ch2 chan gopacket.Packet) chan gopacket.Packet { - merged := make(chan gopacket.Packet) - go func() { - defer close(merged) - for ch1 != nil || ch2 != nil { - select { - case pkt, ok := <-ch1: - if ok { - merged <- pkt - } else { - ch1 = nil - } - case pkt, ok := <-ch2: - if ok { - merged <- pkt - } else { - ch2 = nil - } - } - } - }() - return merged -} - func (c *Controller) getPodIP(ctx context.Context, podRef *crdv1alpha1.PodReference) (net.IP, error) { podInterfaces := c.interfaceStore.GetContainerInterfacesByPod(podRef.Name, podRef.Namespace) var podIP net.IP diff --git a/pkg/agent/packetcapture/packetcapture_controller_test.go b/pkg/agent/packetcapture/packetcapture_controller_test.go index 903c7815482..1b7e1b23236 100644 --- a/pkg/agent/packetcapture/packetcapture_controller_test.go +++ b/pkg/agent/packetcapture/packetcapture_controller_test.go @@ -193,7 +193,7 @@ func craftTestPacket() gopacket.Packet { type testCapture struct { } -func (p *testCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet) (chan gopacket.Packet, error) { +func (p *testCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) { ch := make(chan gopacket.Packet, testCaptureNum) for i := 0; i < 15; i++ { ch <- craftTestPacket() diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index 1ce5b449f57..11d3461731c 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -440,6 +440,7 @@ type PacketCaptureSpec struct { Source Source `json:"source"` Destination Destination `json:"destination"` // Bidirection specifies whether to capture the return (response) traffic from the destination back to the source. + // If not specified, defaults to false. Bidirection bool `json:"bidirection"` // Packet defines what kind of traffic we want to capture between the source and destination. If not specified, // all kinds of traffic will count. From e5ef8429e1be83857eb8aa71eaf65f686c961461 Mon Sep 17 00:00:00 2001 From: Aryan Bakliwal Date: Sun, 5 Jan 2025 23:35:28 +0530 Subject: [PATCH 6/7] add enum, tests and docs Signed-off-by: Aryan Bakliwal --- build/yamls/antrea-aks.yml | 5 +- build/yamls/antrea-eks.yml | 5 +- build/yamls/antrea-gke.yml | 5 +- build/yamls/antrea-ipsec.yml | 5 +- build/yamls/antrea.yml | 7 +- docs/packetcapture-guide.md | 3 + pkg/agent/packetcapture/capture/bpf.go | 149 ++++++++++++------ pkg/agent/packetcapture/capture/bpf_test.go | 120 +++++++++++++- pkg/agent/packetcapture/capture/pcap_linux.go | 4 +- .../packetcapture/capture/pcap_unsupported.go | 2 +- pkg/agent/packetcapture/capture_interface.go | 2 +- .../packetcapture/packetcapture_controller.go | 2 +- .../packetcapture_controller_test.go | 2 +- pkg/apis/crd/v1alpha1/types.go | 14 +- 14 files changed, 257 insertions(+), 68 deletions(-) diff --git a/build/yamls/antrea-aks.yml b/build/yamls/antrea-aks.yml index c7c7798c1dc..c2964bacd9a 100644 --- a/build/yamls/antrea-aks.yml +++ b/build/yamls/antrea-aks.yml @@ -3035,7 +3035,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/build/yamls/antrea-eks.yml b/build/yamls/antrea-eks.yml index c2428bf8d85..c7e4532d8f3 100644 --- a/build/yamls/antrea-eks.yml +++ b/build/yamls/antrea-eks.yml @@ -3035,7 +3035,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/build/yamls/antrea-gke.yml b/build/yamls/antrea-gke.yml index e1f07fc564c..9abb421fbca 100644 --- a/build/yamls/antrea-gke.yml +++ b/build/yamls/antrea-gke.yml @@ -3035,7 +3035,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/build/yamls/antrea-ipsec.yml b/build/yamls/antrea-ipsec.yml index dbb2119becb..3b87d174af4 100644 --- a/build/yamls/antrea-ipsec.yml +++ b/build/yamls/antrea-ipsec.yml @@ -3035,7 +3035,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/build/yamls/antrea.yml b/build/yamls/antrea.yml index afae6ceeb15..cb6b5f2b16a 100644 --- a/build/yamls/antrea.yml +++ b/build/yamls/antrea.yml @@ -3035,9 +3035,10 @@ spec: type: integer minimum: 1 maximum: 65535 - bidirection: - type: boolean - default: false + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/docs/packetcapture-guide.md b/docs/packetcapture-guide.md index d46379c40cc..e98b5ea04ba 100644 --- a/docs/packetcapture-guide.md +++ b/docs/packetcapture-guide.md @@ -32,6 +32,7 @@ the target traffic flow: * Destination Pod, or IP address * Transport protocol (TCP/UDP/ICMP) * Transport ports +* Direction (SourceToDestination/DestinationToSource/Both) You can start a new packet capture by creating a `PacketCapture` CR. An optional `fileServer` field can be specified to store the generated packets file. Before that, @@ -74,6 +75,8 @@ spec: pod: namespace: default name: backend + # Available options for direction: `SourceToDestination` (default), `DestinationToSource` or `Both`. + direction: SourceToDestination # optional to specify packet: ipFamily: IPv4 protocol: TCP # support arbitrary number values and string values in [TCP,UDP,ICMP] (case insensitive) diff --git a/pkg/agent/packetcapture/capture/bpf.go b/pkg/agent/packetcapture/capture/bpf.go index e42227f1344..02efe5d6469 100644 --- a/pkg/agent/packetcapture/capture/bpf.go +++ b/pkg/agent/packetcapture/capture/bpf.go @@ -75,17 +75,17 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction // compilePacketFilter compiles the CRD spec to bpf instructions. For now, we only focus on // ipv4 traffic. Compared to the raw BPF filter supported by libpcap, we only need to support // limited use cases, so an expression parser is not needed. -func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, bidirection bool) []bpf.Instruction { - size := uint8(calculateInstructionsSize(packetSpec, bidirection)) +func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, direction crdv1alpha1.CaptureDirection) []bpf.Instruction { + size := uint8(calculateInstructionsSize(packetSpec, direction)) // ipv4 check - inst := []bpf.Instruction{loadEtherKind} //(000) + inst := []bpf.Instruction{loadEtherKind} // skip means how many instructions we need to skip if the compare fails. // for example, for now we have 2 instructions, and the total size is 17, if ipv4 // check failed, we need to jump to the end (ret #0), skip 17-3=14 instructions. // if check succeed, skipTrue means we jump to the next instruction. Here 3 means we // have 3 instructions so far. - inst = append(inst, compareProtocolIP4(0, size-3)) //(001) + inst = append(inst, compareProtocolIP4(0, size-3)) if packetSpec != nil { if packetSpec.Protocol != nil { @@ -96,28 +96,13 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, bi proto = ProtocolMap[strings.ToUpper(packetSpec.Protocol.StrVal)] } - inst = append(inst, loadIPv4Protocol) //(002) - inst = append(inst, compareProtocol(proto, 0, size-5)) //(003) 27-5=22 + inst = append(inst, loadIPv4Protocol) + inst = append(inst, compareProtocol(proto, 0, size-5)) } } - // source ip - if srcIP != nil { - inst = append(inst, loadIPv4SourceAddress) //(004) - addrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:]) - // from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions. - if bidirection { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 13}) //(005) 27-5-13=9 - } else { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(005) 17-5-2=10 - } - } - // dst ip - if dstIP != nil { - inst = append(inst, loadIPv4DestinationAddress) //(006) - addrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:]) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(007) 18, 8 - } + srcAddrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:]) + dstAddrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:]) // ports var srcPort, dstPort uint16 @@ -137,50 +122,101 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, bi } } + // source ip + if srcIP != nil { + inst = append(inst, loadIPv4SourceAddress) + // from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions. + if direction == crdv1alpha1.Both { + if srcPort > 0 && dstPort > 0 { //TCP or UDP (both ports) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 13}) + } else if (srcPort == 0 && dstPort > 0) || (srcPort > 0 && dstPort == 0) { // TCP or UDP (only one port) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 11}) + } else { //ICMP + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 6}) + } + } else if direction == crdv1alpha1.DestinationToSource { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } else { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } + } + // dst ip + if dstIP != nil { + inst = append(inst, loadIPv4DestinationAddress) + if direction == crdv1alpha1.Both { + if srcPort > 0 || dstPort > 0 { //TCP or UDP + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } else { // ICMP + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: size - uint8(len(inst)) - 3, SkipFalse: size - uint8(len(inst)) - 2}) + } + } else if direction == crdv1alpha1.DestinationToSource { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } else { + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } + } + if srcPort > 0 || dstPort > 0 { skipTrue := size - uint8(len(inst)) - 3 - inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) //(008), (009), (010) - if srcPort > 0 { - inst = append(inst, loadIPv4SourcePort) //(011) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(012) + inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) + if direction == crdv1alpha1.DestinationToSource { + if dstPort > 0 { + inst = append(inst, loadIPv4SourcePort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } + } else { + if srcPort > 0 { + inst = append(inst, loadIPv4SourcePort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } } - if dstPort > 0 { - inst = append(inst, loadIPv4DestinationPort) //(013) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(014) + + if direction == crdv1alpha1.Both { + if dstPort > 0 { + inst = append(inst, loadIPv4DestinationPort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: size - uint8(len(inst)) - 3, SkipFalse: size - uint8(len(inst)) - 2}) + } + } else if direction == crdv1alpha1.DestinationToSource { + if srcPort > 0 { + inst = append(inst, loadIPv4DestinationPort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + } + } else { + inst = append(inst, loadIPv4DestinationPort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) } } - if bidirection { + if direction == crdv1alpha1.Both { // src ip (return traffic) if dstIP != nil { - addrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:]) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(015) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) } // dst ip (return traffic) if srcIP != nil { - inst = append(inst, loadIPv4SourceAddress) //(016) - addrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:]) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: addrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(017) + inst = append(inst, loadIPv4DestinationAddress) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) } + // return traffic ports if srcPort > 0 || dstPort > 0 { skipTrue := size - uint8(len(inst)) - 3 - inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) //(018), (019), (020) + inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) if dstPort > 0 { - inst = append(inst, loadIPv4SourcePort) //(021) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(022) + inst = append(inst, loadIPv4SourcePort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) } if srcPort > 0 { - inst = append(inst, loadIPv4DestinationPort) //(023) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) //(024) + inst = append(inst, loadIPv4DestinationPort) + inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) } } } // return - inst = append(inst, returnKeep) //(015), (025) - inst = append(inst, returnDrop) //(016), (026) + inst = append(inst, returnKeep) + inst = append(inst, returnDrop) return inst @@ -239,7 +275,7 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, bi // (025) ret #262144 # MATCH // (026) ret #0 # NOMATCH -func calculateInstructionsSize(packet *crdv1alpha1.Packet, bidirection bool) int { +func calculateInstructionsSize(packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) int { count := 0 // load ethertype count++ @@ -275,8 +311,29 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet, bidirection bool) int // src and dst ip count += 4 - if bidirection { - count += 10 + if direction == crdv1alpha1.Both { + count += 3 + + transPort := packet.TransportHeader + if transPort.TCP != nil { + // load Fragment Offset + count += 3 + if transPort.TCP.SrcPort != nil { + count += 2 + } + if transPort.TCP.DstPort != nil { + count += 2 + } + + } else if transPort.UDP != nil { + count += 3 + if transPort.UDP.SrcPort != nil { + count += 2 + } + if transPort.UDP.DstPort != nil { + count += 2 + } + } } // ret command diff --git a/pkg/agent/packetcapture/capture/bpf_test.go b/pkg/agent/packetcapture/capture/bpf_test.go index de3e7ab1800..9c014f8b3ca 100644 --- a/pkg/agent/packetcapture/capture/bpf_test.go +++ b/pkg/agent/packetcapture/capture/bpf_test.go @@ -34,10 +34,10 @@ var ( func TestCalculateInstructionsSize(t *testing.T) { tt := []struct { - name string - packet *crdv1alpha1.Packet - count int - bidirection bool + name string + packet *crdv1alpha1.Packet + count int + direction crdv1alpha1.CaptureDirection }{ { name: "proto and host and port", @@ -52,6 +52,34 @@ func TestCalculateInstructionsSize(t *testing.T) { }, count: 17, }, + { + name: "proto and host and port and DestinationToSource", + packet: &crdv1alpha1.Packet{ + Protocol: &testTCPProtocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + SrcPort: &testSrcPort, + DstPort: &testDstPort, + }, + }, + }, + count: 17, + direction: crdv1alpha1.DestinationToSource, + }, + { + name: "proto and host to port and Both", + packet: &crdv1alpha1.Packet{ + Protocol: &testTCPProtocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + SrcPort: &testSrcPort, + DstPort: &testDstPort, + }, + }, + }, + count: 27, + direction: crdv1alpha1.Both, + }, { name: "proto with host", packet: &crdv1alpha1.Packet{ @@ -93,7 +121,7 @@ func TestCalculateInstructionsSize(t *testing.T) { for _, item := range tt { t.Run(item.name, func(t *testing.T) { - assert.Equal(t, item.count, calculateInstructionsSize(item.packet, item.bidirection)) + assert.Equal(t, item.count, calculateInstructionsSize(item.packet, item.direction)) }) } } @@ -174,11 +202,91 @@ func TestPacketCaptureCompileBPF(t *testing.T) { bpf.RetConstant{Val: 0}, }, }, + { + name: "with-proto-port-DestinationToSource", + srcIP: net.ParseIP("127.0.0.1"), + dstIP: net.ParseIP("127.0.0.2"), + spec: &crdv1alpha1.PacketCaptureSpec{ + Packet: &crdv1alpha1.Packet{ + Protocol: &testTCPProtocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + SrcPort: &testSrcPort, + DstPort: &testDstPort, + }}, + }, + Direction: crdv1alpha1.DestinationToSource, + }, + inst: []bpf.Instruction{ + bpf.LoadAbsolute{Off: 12, Size: 2}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x800, SkipFalse: 14}, + bpf.LoadAbsolute{Off: 23, Size: 1}, // ip protocol + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipFalse: 12}, // tcp + bpf.LoadAbsolute{Off: 26, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 10}, + bpf.LoadAbsolute{Off: 30, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 8}, + bpf.LoadAbsolute{Off: 20, Size: 2}, // flags+fragment offset, since we need to calc where the src/dst port is + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 6}, // do we have an L4 header? + bpf.LoadMemShift{Off: 14}, // calculate size of IP header + bpf.LoadIndirect{Off: 14, Size: 2}, // src port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 3}, // port 23 + bpf.LoadIndirect{Off: 16, Size: 2}, // dst port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1}, // port 23 + bpf.RetConstant{Val: 262144}, + bpf.RetConstant{Val: 0}, + }, + }, + { + name: "with-proto-port-and-Both", + srcIP: net.ParseIP("127.0.0.1"), + dstIP: net.ParseIP("127.0.0.2"), + spec: &crdv1alpha1.PacketCaptureSpec{ + Packet: &crdv1alpha1.Packet{ + Protocol: &testTCPProtocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + SrcPort: &testSrcPort, + DstPort: &testDstPort, + }}, + }, + Direction: crdv1alpha1.Both, + }, + inst: []bpf.Instruction{ + bpf.LoadAbsolute{Off: 12, Size: 2}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x800, SkipFalse: 24}, + bpf.LoadAbsolute{Off: 23, Size: 1}, // ip protocol + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipFalse: 22}, // tcp + bpf.LoadAbsolute{Off: 26, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 9}, + bpf.LoadAbsolute{Off: 30, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 18}, + bpf.LoadAbsolute{Off: 20, Size: 2}, // flags+fragment offset, since we need to calc where the src/dst port is + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 16}, // do we have an L4 header? + bpf.LoadMemShift{Off: 14}, // calculate size of IP header + bpf.LoadIndirect{Off: 14, Size: 2}, // src port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 13}, // port 23 + bpf.LoadIndirect{Off: 16, Size: 2}, // dst port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipTrue: 10, SkipFalse: 11}, // port 23 + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 10}, + bpf.LoadAbsolute{Off: 30, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 8}, + bpf.LoadAbsolute{Off: 20, Size: 2}, // flags+fragment offset, since we need to calc where the src/dst port is + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 6}, // do we have an L4 header? + bpf.LoadMemShift{Off: 14}, // calculate size of IP header + bpf.LoadIndirect{Off: 14, Size: 2}, // src port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 3}, // port 23 + bpf.LoadIndirect{Off: 16, Size: 2}, // dst port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1}, // port 23 + bpf.RetConstant{Val: 262144}, + bpf.RetConstant{Val: 0}, + }, + }, } for _, item := range tt { t.Run(item.name, func(t *testing.T) { - result := compilePacketFilter(item.spec.Packet, item.srcIP, item.dstIP, item.spec.Bidirection) + result := compilePacketFilter(item.spec.Packet, item.srcIP, item.dstIP, item.spec.Direction) assert.Equal(t, item.inst, result) }) } diff --git a/pkg/agent/packetcapture/capture/pcap_linux.go b/pkg/agent/packetcapture/capture/pcap_linux.go index 2f178d08c23..d6aa2562566 100644 --- a/pkg/agent/packetcapture/capture/pcap_linux.go +++ b/pkg/agent/packetcapture/capture/pcap_linux.go @@ -41,9 +41,9 @@ func zeroFilter() []bpf.Instruction { return []bpf.Instruction{returnDrop} } -func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) { +func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) (chan gopacket.Packet, error) { // Compile the BPF filter in advance to reduce the time window between starting the capture and applying the filter. - inst := compilePacketFilter(packet, srcIP, dstIP, bidirection) + inst := compilePacketFilter(packet, srcIP, dstIP, direction) klog.V(5).InfoS("Generated bpf instructions for PacketCapture", "device", device, "srcIP", srcIP, "dstIP", dstIP, "packetSpec", packet, "bpf", inst) rawInst, err := bpf.Assemble(inst) if err != nil { diff --git a/pkg/agent/packetcapture/capture/pcap_unsupported.go b/pkg/agent/packetcapture/capture/pcap_unsupported.go index ef2cbfbcd01..acdb56fac0a 100644 --- a/pkg/agent/packetcapture/capture/pcap_unsupported.go +++ b/pkg/agent/packetcapture/capture/pcap_unsupported.go @@ -34,6 +34,6 @@ func NewPcapCapture() (*pcapCapture, error) { return nil, errors.New("PacketCapture is not implemented") } -func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet) (chan gopacket.Packet, error) { +func (p *pcapCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) (chan gopacket.Packet, error) { return nil, errors.New("PacketCapture is not implemented") } diff --git a/pkg/agent/packetcapture/capture_interface.go b/pkg/agent/packetcapture/capture_interface.go index 8c37df3ad72..231be21f29f 100644 --- a/pkg/agent/packetcapture/capture_interface.go +++ b/pkg/agent/packetcapture/capture_interface.go @@ -24,5 +24,5 @@ import ( ) type PacketCapturer interface { - Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) + Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) (chan gopacket.Packet, error) } diff --git a/pkg/agent/packetcapture/packetcapture_controller.go b/pkg/agent/packetcapture/packetcapture_controller.go index 3074dde8306..6787861481b 100644 --- a/pkg/agent/packetcapture/packetcapture_controller.go +++ b/pkg/agent/packetcapture/packetcapture_controller.go @@ -464,7 +464,7 @@ func (c *Controller) performCapture( } defer pcapngWriter.Flush() updateRateLimiter := rate.NewLimiter(rate.Every(captureStatusUpdatePeriod), 1) - packets, err := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet, pc.Spec.Bidirection) + packets, err := c.captureInterface.Capture(ctx, device, snapLen, srcIP, dstIP, pc.Spec.Packet, pc.Spec.Direction) if err != nil { return false, err } diff --git a/pkg/agent/packetcapture/packetcapture_controller_test.go b/pkg/agent/packetcapture/packetcapture_controller_test.go index 1b7e1b23236..895a4aca9b9 100644 --- a/pkg/agent/packetcapture/packetcapture_controller_test.go +++ b/pkg/agent/packetcapture/packetcapture_controller_test.go @@ -193,7 +193,7 @@ func craftTestPacket() gopacket.Packet { type testCapture struct { } -func (p *testCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, bidirection bool) (chan gopacket.Packet, error) { +func (p *testCapture) Capture(ctx context.Context, device string, snapLen int, srcIP, dstIP net.IP, packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) (chan gopacket.Packet, error) { ch := make(chan gopacket.Packet, testCaptureNum) for i := 0; i < 15; i++ { ch <- craftTestPacket() diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index 11d3461731c..f56ee0dc01f 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -431,6 +431,14 @@ type PacketCaptureFileServer struct { HostPublicKey []byte `json:"hostPublicKey,omitempty"` } +type CaptureDirection string + +const ( + SourceToDestination CaptureDirection = "SourceToDestination" + DestinationToSource CaptureDirection = "DestinationToSource" + Both CaptureDirection = "Both" +) + type PacketCaptureSpec struct { // Timeout is the timeout for this capture session. If not specified, defaults to 60s. Timeout *int32 `json:"timeout,omitempty"` @@ -439,9 +447,9 @@ type PacketCaptureSpec struct { // for a capture session, and at least one `Pod` should be present either in the source or the destination. Source Source `json:"source"` Destination Destination `json:"destination"` - // Bidirection specifies whether to capture the return (response) traffic from the destination back to the source. - // If not specified, defaults to false. - Bidirection bool `json:"bidirection"` + // Direction specifies which packets to capture (source -> destination, destination -> source or both). + // If not specified, defaults to SourceToDestination. + Direction CaptureDirection `json:"direction,omitempty"` // Packet defines what kind of traffic we want to capture between the source and destination. If not specified, // all kinds of traffic will count. Packet *Packet `json:"packet,omitempty"` From 7eb34dea4009adcace685d319d5fd92bb39ef0ec Mon Sep 17 00:00:00 2001 From: Aryan Bakliwal Date: Mon, 20 Jan 2025 03:19:32 +0530 Subject: [PATCH 7/7] refactor and add e2e test Signed-off-by: Aryan Bakliwal --- build/charts/antrea/crds/packetcapture.yaml | 5 +- build/yamls/antrea-crds.yml | 5 +- pkg/agent/packetcapture/capture/bpf.go | 135 +++++++++++--------- pkg/agent/packetcapture/capture/bpf_test.go | 48 ++++++- pkg/apis/crd/v1alpha1/types.go | 6 +- test/e2e/packetcapture_test.go | 51 +++++++- 6 files changed, 182 insertions(+), 68 deletions(-) diff --git a/build/charts/antrea/crds/packetcapture.yaml b/build/charts/antrea/crds/packetcapture.yaml index 62aea5bbaea..aee36ce85c1 100644 --- a/build/charts/antrea/crds/packetcapture.yaml +++ b/build/charts/antrea/crds/packetcapture.yaml @@ -135,7 +135,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/build/yamls/antrea-crds.yml b/build/yamls/antrea-crds.yml index 9300377ee62..ea14fff54ba 100644 --- a/build/yamls/antrea-crds.yml +++ b/build/yamls/antrea-crds.yml @@ -3008,7 +3008,10 @@ spec: type: integer minimum: 1 maximum: 65535 - + direction: + type: string + enum: ["SourceToDestination", "DestinationToSource", "Both"] + default: "SourceToDestination" timeout: type: integer minimum: 1 diff --git a/pkg/agent/packetcapture/capture/bpf.go b/pkg/agent/packetcapture/capture/bpf.go index 02efe5d6469..7f0d325ace5 100644 --- a/pkg/agent/packetcapture/capture/bpf.go +++ b/pkg/agent/packetcapture/capture/bpf.go @@ -72,6 +72,62 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse} } +func calculateSkipTrue(size, instlen uint8, direction crdv1alpha1.CaptureDirection, srcPort, dstPort uint16, isSrc bool) uint8 { + if direction == crdv1alpha1.CaptureDirectionBoth { + if isSrc && (srcPort > 0 && dstPort == 0) { + return size - instlen - 3 + } else if !isSrc { + return size - instlen - 3 + } + } + return 0 +} + +func calculateSkipFalse(size, instlen uint8, srcPort, dstPort uint16, direction crdv1alpha1.CaptureDirection) uint8 { + if direction == crdv1alpha1.CaptureDirectionBoth { + if srcPort > 0 && dstPort > 0 { + return size - instlen - 13 + } else if srcPort > 0 || dstPort > 0 { + return size - instlen - 11 + } else { + return size - instlen - 6 + } + } + return size - instlen - 2 +} + +func compareIP(direction crdv1alpha1.CaptureDirection, skipTrue, skipFalse uint8, isSrc bool, srcAddrVal, dstAddrVal uint32) bpf.Instruction { + if isSrc { + if direction == crdv1alpha1.CaptureDirectionDestinationToSource { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: skipTrue, SkipFalse: skipFalse} + } else { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: skipTrue, SkipFalse: skipFalse} + } + } else { + if direction == crdv1alpha1.CaptureDirectionDestinationToSource { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: skipTrue, SkipFalse: skipFalse} + } else { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: skipTrue, SkipFalse: skipFalse} + } + } +} + +func comparePort(direction crdv1alpha1.CaptureDirection, skipTrue, skipFalse uint8, isSrc bool, srcPort, dstPort uint16) bpf.Instruction { + if isSrc { + if direction == crdv1alpha1.CaptureDirectionDestinationToSource { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: skipTrue, SkipFalse: skipFalse} + } else { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: skipTrue, SkipFalse: skipFalse} + } + } else { + if direction == crdv1alpha1.CaptureDirectionDestinationToSource { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: skipTrue, SkipFalse: skipFalse} + } else { + return bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: skipTrue, SkipFalse: skipFalse} + } + } +} + // compilePacketFilter compiles the CRD spec to bpf instructions. For now, we only focus on // ipv4 traffic. Compared to the raw BPF filter supported by libpcap, we only need to support // limited use cases, so an expression parser is not needed. @@ -126,77 +182,42 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, di if srcIP != nil { inst = append(inst, loadIPv4SourceAddress) // from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions. - if direction == crdv1alpha1.Both { - if srcPort > 0 && dstPort > 0 { //TCP or UDP (both ports) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 13}) - } else if (srcPort == 0 && dstPort > 0) || (srcPort > 0 && dstPort == 0) { // TCP or UDP (only one port) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 11}) - } else { //ICMP - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 6}) - } - } else if direction == crdv1alpha1.DestinationToSource { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } else { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } + inst = append(inst, compareIP(direction, 0, calculateSkipFalse(size, uint8(len(inst)), srcPort, dstPort, direction), true, srcAddrVal, dstAddrVal)) } // dst ip if dstIP != nil { inst = append(inst, loadIPv4DestinationAddress) - if direction == crdv1alpha1.Both { - if srcPort > 0 || dstPort > 0 { //TCP or UDP - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } else { // ICMP - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: size - uint8(len(inst)) - 3, SkipFalse: size - uint8(len(inst)) - 2}) - } - } else if direction == crdv1alpha1.DestinationToSource { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + if direction == crdv1alpha1.CaptureDirectionBoth && (srcPort == 0 && dstPort == 0) { + inst = append(inst, compareIP(direction, size-uint8(len(inst))-3, size-uint8(len(inst))-2, false, srcAddrVal, dstAddrVal)) } else { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, compareIP(direction, 0, size-uint8(len(inst))-2, false, srcAddrVal, dstAddrVal)) } } if srcPort > 0 || dstPort > 0 { skipTrue := size - uint8(len(inst)) - 3 inst = append(inst, loadIPv4HeaderOffset(skipTrue)...) - if direction == crdv1alpha1.DestinationToSource { - if dstPort > 0 { - inst = append(inst, loadIPv4SourcePort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } - } else { - if srcPort > 0 { - inst = append(inst, loadIPv4SourcePort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } + if (direction != crdv1alpha1.CaptureDirectionDestinationToSource && srcPort > 0) || (direction == crdv1alpha1.CaptureDirectionDestinationToSource && dstPort > 0) { + inst = append(inst, loadIPv4SourcePort) + inst = append(inst, comparePort(direction, calculateSkipTrue(size, uint8(len(inst)), direction, srcPort, dstPort, true), size-uint8(len(inst))-2, true, srcPort, dstPort)) } - if direction == crdv1alpha1.Both { - if dstPort > 0 { - inst = append(inst, loadIPv4DestinationPort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: size - uint8(len(inst)) - 3, SkipFalse: size - uint8(len(inst)) - 2}) - } - } else if direction == crdv1alpha1.DestinationToSource { - if srcPort > 0 { - inst = append(inst, loadIPv4DestinationPort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) - } - } else { + if (direction != crdv1alpha1.CaptureDirectionDestinationToSource && dstPort > 0) || (direction == crdv1alpha1.CaptureDirectionDestinationToSource && srcPort > 0) { inst = append(inst, loadIPv4DestinationPort) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, comparePort(direction, calculateSkipTrue(size, uint8(len(inst)), direction, srcPort, dstPort, false), size-uint8(len(inst))-2, false, srcPort, dstPort)) } } - if direction == crdv1alpha1.Both { + if direction == crdv1alpha1.CaptureDirectionBoth { // src ip (return traffic) if dstIP != nil { - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, compareIP(direction, 0, size-uint8(len(inst))-2, false, srcAddrVal, dstAddrVal)) } // dst ip (return traffic) if srcIP != nil { inst = append(inst, loadIPv4DestinationAddress) - inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2}) + inst = append(inst, compareIP(direction, 0, size-uint8(len(inst))-2, true, srcAddrVal, dstAddrVal)) } // return traffic ports @@ -235,12 +256,12 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, di // (006) ld [30] # Load 4B at 30 (dest address) // (007) jeq #0x7f000001 jt 8 jf 16 # If bytes match(127.0.0.1), goto #8, else #16 // (008) ldh [20] # Load 2B at 20 (13b Fragment Offset) -// (009) jset #0x1fff jt 16 jf 10 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #16 +// (009) jset #0x1fff jt 16 jf 10 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #16 // (010) ldxb 4*([14]&0xf) # x = IP header length // (011) ldh [x + 14] # Load 2B at x+14 (TCP Source Port) -// (012) jeq #0x7b jt 13 jf 16 # TCP Source Port: If 123, goto #13, else #16 +// (012) jeq #0x7b jt 13 jf 16 # TCP Source Port: If 123, goto #13, else #16 // (013) ldh [x + 16] # Load 2B at x+16 (TCP dst port) -// (014) jeq #0x7c jt 15 jf 16 # TCP dst port: If 123, goto $15, else #16 +// (014) jeq #0x7c jt 15 jf 16 # TCP dst port: If 123, goto $15, else #16 // (015) ret #262144 # MATCH // (016) ret #0 # NOMATCH @@ -256,22 +277,22 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP, di // (006) ld [30] # Load 4B at 30 (dest address) // (007) jeq #0xaf40103 jt 8 jf 26 # If bytes match(10.244.0.3), goto #8, else #26 // (008) ldh [20] # Load 2B at 20 (13b Fragment Offset) -// (009) jset #0x1fff jt 26 jf 10 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #26 +// (009) jset #0x1fff jt 26 jf 10 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #26 // (010) ldxb 4*([14]&0xf) # x = IP header length // (011) ldh [x + 14] # Load 2B at x+14 (TCP Source Port) -// (012) jeq #0x7b jt 13 jf 26 # TCP Source Port: If 123, goto #13, else #26 +// (012) jeq #0x7b jt 13 jf 26 # TCP Source Port: If 123, goto #13, else #26 // (013) ldh [x + 16] # Load 2B at x+16 (TCP dst port) -// (014) jeq #0x7c jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 +// (014) jeq #0x7c jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 // (015) jeq #0xaf40103 jt 16 jf 26 # If bytes match(10.244.0.3), goto #16, else #26 // (016) ld [30] # Load 4B at 30 (return traffic dest address) // (017) jeq #0xaf40102 jt 18 jf 26 # If bytes match(10.244.0.2), goto #18, else #26 // (018) ldh [20] # Load 2B at 20 (13b Fragment Offset) -// (019) jset #0x1fff jt 26 jf 20 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #20, else #26 +// (019) jset #0x1fff jt 26 jf 20 # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #20, else #26 // (020) ldxb 4*([14]&0xf) # x = IP header length // (021) ldh [x + 14] # Load 2B at x+14 (TCP Source Port) -// (022) jeq #0x7c jt 23 jf 26 # TCP Source Port: If 124, goto #23, else #26 +// (022) jeq #0x7c jt 23 jf 26 # TCP Source Port: If 124, goto #23, else #26 // (023) ldh [x + 16] # Load 2B at x+16 (TCP dst port) -// (024) jeq #0x7b jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 +// (024) jeq #0x7b jt 25 jf 26 # TCP dst port: If 123, goto #25, else #26 // (025) ret #262144 # MATCH // (026) ret #0 # NOMATCH @@ -311,7 +332,7 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet, direction crdv1alpha1 // src and dst ip count += 4 - if direction == crdv1alpha1.Both { + if direction == crdv1alpha1.CaptureDirectionBoth { count += 3 transPort := packet.TransportHeader diff --git a/pkg/agent/packetcapture/capture/bpf_test.go b/pkg/agent/packetcapture/capture/bpf_test.go index 9c014f8b3ca..95728688508 100644 --- a/pkg/agent/packetcapture/capture/bpf_test.go +++ b/pkg/agent/packetcapture/capture/bpf_test.go @@ -64,7 +64,7 @@ func TestCalculateInstructionsSize(t *testing.T) { }, }, count: 17, - direction: crdv1alpha1.DestinationToSource, + direction: crdv1alpha1.CaptureDirectionDestinationToSource, }, { name: "proto and host to port and Both", @@ -78,7 +78,7 @@ func TestCalculateInstructionsSize(t *testing.T) { }, }, count: 27, - direction: crdv1alpha1.Both, + direction: crdv1alpha1.CaptureDirectionBoth, }, { name: "proto with host", @@ -215,7 +215,7 @@ func TestPacketCaptureCompileBPF(t *testing.T) { DstPort: &testDstPort, }}, }, - Direction: crdv1alpha1.DestinationToSource, + Direction: crdv1alpha1.CaptureDirectionDestinationToSource, }, inst: []bpf.Instruction{ bpf.LoadAbsolute{Off: 12, Size: 2}, @@ -237,6 +237,46 @@ func TestPacketCaptureCompileBPF(t *testing.T) { bpf.RetConstant{Val: 0}, }, }, + { + name: "with-proto-dstPort-and-Both", + srcIP: net.ParseIP("127.0.0.1"), + dstIP: net.ParseIP("127.0.0.2"), + spec: &crdv1alpha1.PacketCaptureSpec{ + Packet: &crdv1alpha1.Packet{ + Protocol: &testTCPProtocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + DstPort: &testDstPort, + }}, + }, + Direction: crdv1alpha1.CaptureDirectionBoth, + }, + inst: []bpf.Instruction{ + bpf.LoadAbsolute{Off: 12, Size: 2}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x800, SkipFalse: 20}, + bpf.LoadAbsolute{Off: 23, Size: 1}, // ip protocol + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipFalse: 18}, // tcp + bpf.LoadAbsolute{Off: 26, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 7}, + bpf.LoadAbsolute{Off: 30, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 14}, + bpf.LoadAbsolute{Off: 20, Size: 2}, // flags+fragment offset, since we need to calc where the src/dst port is + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 12}, // do we have an L4 header? + bpf.LoadMemShift{Off: 14}, // calculate size of IP header + bpf.LoadIndirect{Off: 16, Size: 2}, // dst port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipTrue: 8, SkipFalse: 9}, // port 23 + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 8}, + bpf.LoadAbsolute{Off: 30, Size: 4}, + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 6}, + bpf.LoadAbsolute{Off: 20, Size: 2}, // flags+fragment offset, since we need to calc where the src/dst port is + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 4}, // do we have an L4 header? + bpf.LoadMemShift{Off: 14}, // calculate size of IP header + bpf.LoadIndirect{Off: 14, Size: 2}, // src port + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1}, // port 23 + bpf.RetConstant{Val: 262144}, + bpf.RetConstant{Val: 0}, + }, + }, { name: "with-proto-port-and-Both", srcIP: net.ParseIP("127.0.0.1"), @@ -250,7 +290,7 @@ func TestPacketCaptureCompileBPF(t *testing.T) { DstPort: &testDstPort, }}, }, - Direction: crdv1alpha1.Both, + Direction: crdv1alpha1.CaptureDirectionBoth, }, inst: []bpf.Instruction{ bpf.LoadAbsolute{Off: 12, Size: 2}, diff --git a/pkg/apis/crd/v1alpha1/types.go b/pkg/apis/crd/v1alpha1/types.go index f56ee0dc01f..4d89e1b1d48 100644 --- a/pkg/apis/crd/v1alpha1/types.go +++ b/pkg/apis/crd/v1alpha1/types.go @@ -434,9 +434,9 @@ type PacketCaptureFileServer struct { type CaptureDirection string const ( - SourceToDestination CaptureDirection = "SourceToDestination" - DestinationToSource CaptureDirection = "DestinationToSource" - Both CaptureDirection = "Both" + CaptureDirectionSourceToDestination CaptureDirection = "SourceToDestination" + CaptureDirectionDestinationToSource CaptureDirection = "DestinationToSource" + CaptureDirectionBoth CaptureDirection = "Both" ) type PacketCaptureSpec struct { diff --git a/test/e2e/packetcapture_test.go b/test/e2e/packetcapture_test.go index 6ac6670f342..44f66c23e73 100644 --- a/test/e2e/packetcapture_test.go +++ b/test/e2e/packetcapture_test.go @@ -174,7 +174,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p return p } - getPacketCaptureCR := func(name string, destinationPodName string, packet *crdv1alpha1.Packet, options ...packetCaptureOption) *crdv1alpha1.PacketCapture { + getPacketCaptureCR := func(name string, destinationPodName string, packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection, options ...packetCaptureOption) *crdv1alpha1.PacketCapture { pc := &crdv1alpha1.PacketCapture{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -200,7 +200,8 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p FileServer: &crdv1alpha1.PacketCaptureFileServer{ URL: sftpURL, }, - Packet: packet, + Packet: packet, + Direction: direction, }, } for _, option := range options { @@ -220,6 +221,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p Protocol: &icmpProto, IPFamily: v1.IPv4Protocol, }, + crdv1alpha1.CaptureDirectionSourceToDestination, packetCaptureTimeout(ptr.To[int32](15)), packetCaptureFirstN(500), ), @@ -253,6 +255,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p nonExistingPodName, nonExistingPodName, nil, + crdv1alpha1.CaptureDirectionSourceToDestination, ), expectedStatus: crdv1alpha1.PacketCaptureStatus{ Conditions: []crdv1alpha1.PacketCaptureCondition{ @@ -285,6 +288,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p }, }, }, + crdv1alpha1.CaptureDirectionSourceToDestination, packetCaptureHostPublicKey(pubKey1), ), expectedStatus: crdv1alpha1.PacketCaptureStatus{ @@ -324,6 +328,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p }, }, }, + crdv1alpha1.CaptureDirectionSourceToDestination, packetCaptureHostPublicKey(pubKey2), ), expectedStatus: crdv1alpha1.PacketCaptureStatus{ @@ -358,6 +363,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p Protocol: &icmpProto, IPFamily: v1.IPv4Protocol, }, + crdv1alpha1.CaptureDirectionSourceToDestination, ), expectedStatus: crdv1alpha1.PacketCaptureStatus{ NumberCaptured: 5, @@ -392,6 +398,7 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p Protocol: &icmpProto, IPFamily: v1.IPv4Protocol, }, + crdv1alpha1.CaptureDirectionSourceToDestination, packetCaptureHostPublicKey(invalidPubKey.Marshal()), ), expectedStatus: crdv1alpha1.PacketCaptureStatus{ @@ -417,6 +424,46 @@ func testPacketCaptureBasic(t *testing.T, data *TestData, sftpServerIP string, p }, }, }, + { + name: "ipv4-tcp-both", + ipVersion: 4, + pc: getPacketCaptureCR( + "ipv4-tcp-both", + tcpServerPodName, + &crdv1alpha1.Packet{ + Protocol: &tcpProto, + IPFamily: v1.IPv4Protocol, + TransportHeader: crdv1alpha1.TransportHeader{ + TCP: &crdv1alpha1.TCPHeader{ + DstPort: ptr.To(serverPodPort), + }, + }, + }, + crdv1alpha1.CaptureDirectionBoth, + packetCaptureHostPublicKey(pubKey1), + ), + expectedStatus: crdv1alpha1.PacketCaptureStatus{ + NumberCaptured: 10, + FilePath: getPcapURL("ipv4-tcp"), + Conditions: []crdv1alpha1.PacketCaptureCondition{ + { + Type: crdv1alpha1.PacketCaptureStarted, + Status: metav1.ConditionStatus(v1.ConditionTrue), + Reason: "Started", + }, + { + Type: crdv1alpha1.PacketCaptureComplete, + Status: metav1.ConditionStatus(v1.ConditionTrue), + Reason: "Succeed", + }, + { + Type: crdv1alpha1.PacketCaptureFileUploaded, + Status: metav1.ConditionStatus(v1.ConditionTrue), + Reason: "Succeed", + }, + }, + }, + }, } t.Run("testPacketCaptureBasic", func(t *testing.T) { for _, tc := range testcases {