Repository: antrea-io/antrea
Pull request #6882: Add bidirectional packet capture
Author: login: AryanBakliwal
Labels: name: action/release-note
description: Indicates a PR that should be included in release notes.
name: area/ops/packetcapture
description: Issues or PRs related to the PacketCapture feature
Created: 2024-12-23T04:18:38Z | Merged: 2025-03-10T07:35:19Z

fixes: #6862 

A new `direction` field is added to the PacketCapture CRD.
Packets can now be captured from source to destination (default),
destination to source, or in both directions.

Screenshot of the `.pcapng` output file (both directions).
![image](https://github.com/user-attachments/assets/34262988-987f-43ba-870c-ca82cc02089d)



Diff:
diff --git a/build/charts/antrea/crds/packetcapture.yaml b/build/charts/antrea/crds/packetcapture.yaml
index f834345b3e6..ee5288e1ddd 100644
--- a/build/charts/antrea/crds/packetcapture.yaml
+++ b/build/charts/antrea/crds/packetcapture.yaml
@@ -152,7 +152,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-aks.yml b/build/yamls/antrea-aks.yml
index b42e0574983..b039d50b769 100644
--- a/build/yamls/antrea-aks.yml
+++ b/build/yamls/antrea-aks.yml
@@ -3067,7 +3067,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 352079ea497..4fa419048be 100644
--- a/build/yamls/antrea-crds.yml
+++ b/build/yamls/antrea-crds.yml
@@ -3040,7 +3040,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 57074e9eeda..aa728788bdd 100644
--- a/build/yamls/antrea-eks.yml
+++ b/build/yamls/antrea-eks.yml
@@ -3067,7 +3067,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 f8fa64ad881..e086038a335 100644
--- a/build/yamls/antrea-gke.yml
+++ b/build/yamls/antrea-gke.yml
@@ -3067,7 +3067,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 6e70431db1d..f7a3b4bce7a 100644
--- a/build/yamls/antrea-ipsec.yml
+++ b/build/yamls/antrea-ipsec.yml
@@ -3067,7 +3067,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 d11446e0749..ad078160bb4 100644
--- a/build/yamls/antrea.yml
+++ b/build/yamls/antrea.yml
@@ -3067,7 +3067,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/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 841b721a25c..532fcb70010 100644
--- a/pkg/agent/packetcapture/capture/bpf.go
+++ b/pkg/agent/packetcapture/capture/bpf.go
@@ -72,11 +72,76 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, needsOtherTrafficDirectionCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+
+	// calculate skip size to jump to the final instruction (NO MATCH)
+	skipToEnd := func() uint8 {
+		return size - curLen - uint8(len(inst)) - 2
+	}
+
+	// needsOtherTrafficDirectionCheck indicates if we need to check whether the packet belongs to the return traffic flow when source IP from the
+	// packet spec and packet header don't match and we are capturing packets in both direction. If true, we calculate skipFalse to jump to the
+	// instruction that compares the destination IP from the packet spec with the loaded source IP from the packet header.
+	if needsOtherTrafficDirectionCheck {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: calculateSkipFalse(srcPort, dstPort)})
+	} else {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: skipToEnd()})
+	}
+
+	// dst ip
+	inst = append(inst, loadIPv4DestinationAddress)
+	inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: skipToEnd()})
+
+	if srcPort > 0 || dstPort > 0 {
+		skipTrue := skipToEnd() - 1
+		inst = append(inst, loadIPv4HeaderOffset(skipTrue)...)
+		if srcPort > 0 {
+			inst = append(inst, loadIPv4SourcePort)
+			inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: skipToEnd()})
+		}
+		if dstPort > 0 {
+			inst = append(inst, loadIPv4DestinationPort)
+			inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: skipToEnd()})
+		}
+	}
+
+	// return (accept)
+	inst = append(inst, returnKeep)
+
+	return inst
+}
+
 // 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, direction crdv1alpha1.CaptureDirection) []bpf.Instruction {
+	size := uint8(calculateInstructionsSize(packetSpec, direction))
 
 	// ipv4 check
 	inst := []bpf.Instruction{loadEtherKind}
@@ -101,20 +166,8 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
-	// source ip
-	if srcIP != nil {
-		inst = append(inst, loadIPv4SourceAddress)
-		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})
-
-	}
-	// dst ip
-	if dstIP != nil {
-		inst = append(inst, loadIPv4DestinationAddress)
-		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})
-	}
+	srcAddrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:])
+	dstAddrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:])
 
 	// ports
 	var srcPort, dstPort uint16
@@ -134,22 +187,18 @@ 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)...)
-		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)
-			inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - uint8(len(inst)) - 2})
-		}
+	inst = append(inst, loadIPv4SourceAddress)
 
+	if direction == crdv1alpha1.CaptureDirectionSourceToDestination {
+		inst = append(inst, compileIPPortFilter(srcAddrVal, dstAddrVal, size, uint8(len(inst)), srcPort, dstPort, false)...)
+	} else if direction == crdv1alpha1.CaptureDirectionDestinationToSource {
+		inst = append(inst, compileIPPortFilter(dstAddrVal, srcAddrVal, size, uint8(len(inst)), dstPort, srcPort, false)...)
+	} else {
+		inst = append(inst, compileIPPortFilter(srcAddrVal, dstAddrVal, size, uint8(len(inst)), srcPort, dstPort, true)...)
+		inst = append(inst, compileIPPortFilter(dstAddrVal, srcAddrVal, size, uint8(len(inst)), dstPort, srcPort, false)...)
 	}
 
-	// return
-	inst = append(inst, returnKeep)
+	// return (drop)
 	inst = append(inst, returnDrop)
 
 	return inst
@@ -169,50 +218,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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.1.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.1.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.1.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.1.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
+
+// For simpler code generation in 'Both' direction, an extra instruction to accept the packet is added after instruction 014.
+// The final instruction set looks like this:
+// (000) ldh      [12]									   # Load 2B at 12 (Ethertype)
+// (001) jeq      #0x800           jt 2	jf 27			   # Ethertype: If IPv4, goto #2, else #27
+// (002) ldb      [23]									   # Load 1B at 23 (IPv4 Protocol)
+// (003) jeq      #0x6             jt 4	jf 27			   # IPv4 Protocol: If TCP, goto #4, #27
+// (004) ld       [26]									   # Load 4B at 26 (source address)
+// (005) jeq      #0xaf40102       jt 6	jf 16			   # If bytes match(10.244.1.2), goto #6, else #16
+// (006) ld       [30]									   # Load 4B at 30 (dest address)
+// (007) jeq      #0xaf40103       jt 8	jf 27			   # If bytes match(10.244.1.3), goto #8, else #27
+// (008) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (009) jset     #0x1fff          jt 27	jf 10		   # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #27
+// (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 27		   # TCP Source Port: If 123, goto #13, else #27
+// (013) ldh      [x + 16]								   # Load 2B at x+16 (TCP dst port)
+// (014) jeq      #0x7c            jt 15	jf 27		   # TCP dst port: If 123, goto #15, else #27
+// (015) ret      #262144								   # MATCH
+// (016) jeq      #0xaf40103       jt 17	jf 27		   # If bytes match(10.244.1.3), goto #17, else #27
+// (017) ld       [30]									   # Load 4B at 30 (return traffic dest address)
+// (018) jeq      #0xaf40102       jt 19	jf 27		   # If bytes match(10.244.1.2), goto #19, else #27
+// (019) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (020) jset     #0x1fff          jt 27	jf 21		   # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #21, else #27
+// (021) ldxb     4*([14]&0xf)							   # x = IP header length
+// (022) ldh      [x + 14]								   # Load 2B at x+14 (TCP Source Port)
+// (023) jeq      #0x7c            jt 24	jf 27		   # TCP Source Port: If 124, goto #24, else #27
+// (024) ldh      [x + 16]								   # Load 2B at x+16 (TCP dst port)
+// (025) jeq      #0x7b            jt 26	jf 27		   # TCP dst port: If 123, goto #26, else #27
+// (026) ret      #262144								   # MATCH
+// (027) ret      #0									   # NOMATCH
+
+func calculateInstructionsSize(packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) int {
 	count := 0
 	// load ethertype
 	count++
 	// ip check
 	count++
 
+	// src and dst ip
+	count += 4
+
 	if packet != nil {
 		// protocol check
 		if packet.Protocol != nil {
 			count += 2
 		}
-		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
+		transport := packet.TransportHeader
+		portFiltersSize := func() int {
+			count := 0
+			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
+				}
 			}
+			return count
+		}()
+
+		count += portFiltersSize
 
-		} else if transPort.UDP != nil {
+		if direction == crdv1alpha1.CaptureDirectionBoth {
+
+			// extra returnKeep
+			count++
+
+			// src and dst ip (return traffic)
 			count += 3
-			if transPort.UDP.SrcPort != nil {
-				count += 2
-			}
-			if transPort.UDP.DstPort != nil {
-				count += 2
-			}
+
+			count += portFiltersSize
+
 		}
 	}
-	// src and dst ip
-	count += 4
 
 	// ret command
 	count += 2
diff --git a/pkg/agent/packetcapture/capture/bpf_test.go b/pkg/agent/packetcapture/capture/bpf_test.go
index 1f911135a52..c6ba76756a2 100644
--- a/pkg/agent/packetcapture/capture/bpf_test.go
+++ b/pkg/agent/packetcapture/capture/bpf_test.go
@@ -28,15 +28,16 @@ import (
 var (
 	testTCPProtocol       = intstr.FromString("TCP")
 	testUDPProtocol       = intstr.FromString("UDP")
-	testSrcPort     int32 = 80
+	testSrcPort     int32 = 12345
 	testDstPort     int32 = 80
 )
 
 func TestCalculateInstructionsSize(t *testing.T) {
 	tt := []struct {
-		name   string
-		packet *crdv1alpha1.Packet
-		count  int
+		name      string
+		packet    *crdv1alpha1.Packet
+		count     int
+		direction crdv1alpha1.CaptureDirection
 	}{
 		{
 			name: "proto and host and port",
@@ -49,14 +50,44 @@ func TestCalculateInstructionsSize(t *testing.T) {
 					},
 				},
 			},
-			count: 17,
+			count:     17,
+			direction: crdv1alpha1.CaptureDirectionSourceToDestination,
+		},
+		{
+			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.CaptureDirectionDestinationToSource,
+		},
+		{
+			name: "proto and host to port and Both",
+			packet: &crdv1alpha1.Packet{
+				Protocol: &testTCPProtocol,
+				TransportHeader: crdv1alpha1.TransportHeader{
+					TCP: &crdv1alpha1.TCPHeader{
+						SrcPort: &testSrcPort,
+						DstPort: &testDstPort,
+					},
+				},
+			},
+			count:     28,
+			direction: crdv1alpha1.CaptureDirectionBoth,
 		},
 		{
 			name: "proto with host",
 			packet: &crdv1alpha1.Packet{
 				Protocol: &testTCPProtocol,
 			},
-			count: 10,
+			count:     10,
+			direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 		},
 		{
 			name: "proto with src port",
@@ -68,7 +99,8 @@ func TestCalculateInstructionsSize(t *testing.T) {
 					},
 				},
 			},
-			count: 15,
+			count:     15,
+			direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 		},
 		{
 			name: "proto with dst port",
@@ -80,19 +112,21 @@ func TestCalculateInstructionsSize(t *testing.T) {
 					},
 				},
 			},
-			count: 15,
+			count:     15,
+			direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 		},
 
 		{
-			name:   "any proto",
-			packet: &crdv1alpha1.Packet{},
-			count:  8,
+			name:      "any proto",
+			packet:    &crdv1alpha1.Packet{},
+			count:     8,
+			direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 		},
 	}
 
 	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.direction))
 		})
 	}
 }
@@ -118,6 +152,7 @@ func TestPacketCaptureCompileBPF(t *testing.T) {
 							DstPort: &testDstPort,
 						}},
 				},
+				Direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 			},
 			inst: []bpf.Instruction{
 				bpf.LoadAbsolute{Off: 12, Size: 2},
@@ -132,9 +167,9 @@ func TestPacketCaptureCompileBPF(t *testing.T) {
 				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.JumpIf{Cond: bpf.JumpEqual, Val: 0x3039, SkipFalse: 3},  // port 12345
 				bpf.LoadIndirect{Off: 16, Size: 2},                          // dst port
-				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1},    // port 23
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1},    // port 80
 				bpf.RetConstant{Val: 262144},
 				bpf.RetConstant{Val: 0},
 			},
@@ -152,6 +187,7 @@ func TestPacketCaptureCompileBPF(t *testing.T) {
 							DstPort: &testDstPort,
 						}},
 				},
+				Direction: crdv1alpha1.CaptureDirectionSourceToDestination,
 			},
 			inst: []bpf.Instruction{
 				bpf.LoadAbsolute{Off: 12, Size: 2},
@@ -166,9 +202,131 @@ func TestPacketCaptureCompileBPF(t *testing.T) {
 				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.JumpIf{Cond: bpf.JumpEqual, Val: 0x3039, SkipFalse: 3},  // port 12345
+				bpf.LoadIndirect{Off: 16, Size: 2},                          // dst port
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1},    // port 80
+				bpf.RetConstant{Val: 262144},
+				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.CaptureDirectionDestinationToSource,
+			},
+			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 80
+				bpf.LoadIndirect{Off: 16, Size: 2},                          // dst port
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x3039, SkipFalse: 1},  // port 12345
+				bpf.RetConstant{Val: 262144},
+				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: 21},
+				bpf.LoadAbsolute{Off: 23, Size: 1},                       // ip protocol
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipFalse: 19}, // tcp
+				bpf.LoadAbsolute{Off: 26, Size: 4},
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 8},
+				bpf.LoadAbsolute{Off: 30, Size: 4},
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 15},
+				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: 13},           // 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: 0, SkipFalse: 10}, // port 80
+				bpf.RetConstant{Val: 262144},
+				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 80
+				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.CaptureDirectionBoth,
+			},
+			inst: []bpf.Instruction{
+				bpf.LoadAbsolute{Off: 12, Size: 2},
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x800, SkipFalse: 25},
+				bpf.LoadAbsolute{Off: 23, Size: 1},                       // ip protocol
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipFalse: 23}, // tcp
+				bpf.LoadAbsolute{Off: 26, Size: 4},
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000001, SkipTrue: 0, SkipFalse: 10},
+				bpf.LoadAbsolute{Off: 30, Size: 4},
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x7f000002, SkipTrue: 0, SkipFalse: 19},
+				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: 17},           // 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: 0x3039, SkipFalse: 14},            // port 12345
+				bpf.LoadIndirect{Off: 16, Size: 2},                                     // dst port
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipTrue: 0, SkipFalse: 12}, // port 80
+				bpf.RetConstant{Val: 262144},
+				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 80
 				bpf.LoadIndirect{Off: 16, Size: 2},                          // dst port
-				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x50, SkipFalse: 1},    // port 23
+				bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x3039, SkipFalse: 1},  // port 12345
 				bpf.RetConstant{Val: 262144},
 				bpf.RetConstant{Val: 0},
 			},
@@ -177,7 +335,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.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 8bbd4845ab8..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) (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)
+	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 c607cb32c30..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) (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 c799ffd2179..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)
+	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 903c7815482..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) (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 e83418ef858..0c55af81bdf 100644
--- a/pkg/apis/crd/v1alpha1/types.go
+++ b/pkg/apis/crd/v1alpha1/types.go
@@ -442,6 +442,14 @@ type PacketCaptureFileServer struct {
 	HostPublicKey []byte `json:"hostPublicKey,omitempty"`
 }
 
+type CaptureDirection string
+
+const (
+	CaptureDirectionSourceToDestination CaptureDirection = "SourceToDestination"
+	CaptureDirectionDestinationToSource CaptureDirection = "DestinationToSource"
+	CaptureDirectionBoth                CaptureDirection = "Both"
+)
+
 type PacketCaptureSpec struct {
 	// Timeout is the timeout for this capture session. If not specified, defaults to 60s.
 	Timeout       *int32        `json:"timeout,omitempty"`
@@ -450,6 +458,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"`
+	// 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"`
diff --git a/test/e2e/packetcapture_test.go b/test/e2e/packetcapture_test.go
index 6ac6670f342..387bf3f4321 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: 5,
+				FilePath:       getPcapURL("ipv4-tcp-both"),
+				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 {
@@ -649,8 +696,13 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 		ipLayer := packet.Layer(layers.LayerTypeIPv4)
 		require.NotNil(t, ipLayer)
 		ip, _ := ipLayer.(*layers.IPv4)
-		assert.Equal(t, srcIP.String(), ip.SrcIP.String())
-		assert.Equal(t, dstIP.String(), ip.DstIP.String())
+		if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.SrcIP.String())
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.DstIP.String())
+		} else {
+			assert.Equal(t, srcIP.String(), ip.SrcIP.String())
+			assert.Equal(t, dstIP.String(), ip.DstIP.String())
+		}
 
 		if pc.Spec.Packet == nil {
 			continue
@@ -667,11 +719,20 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 			tcp, _ := tcpLayer.(*layers.TCP)
 			if packetSpec.TransportHeader.TCP != nil {
 				ports := packetSpec.TransportHeader.TCP
-				if ports.DstPort != nil {
-					assert.Equal(t, *ports.DstPort, int32(tcp.DstPort))
-				}
-				if ports.SrcPort != nil {
-					assert.Equal(t, *ports.SrcPort, int32(tcp.SrcPort))
+				if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+					if ports.DstPort != nil {
+						assert.Contains(t, []int32{int32(tcp.SrcPort), int32(tcp.DstPort)}, *ports.DstPort)
+					}
+					if ports.SrcPort != nil {
+						assert.Contains(t, []int32{int32(tcp.SrcPort), int32(tcp.DstPort)}, *ports.SrcPort)
+					}
+				} else {
+					if ports.DstPort != nil {
+						assert.Equal(t, *ports.DstPort, int32(tcp.DstPort))
+					}
+					if ports.SrcPort != nil {
+						assert.Equal(t, *ports.SrcPort, int32(tcp.SrcPort))
+					}
 				}
 			}
 		} else if strings.ToUpper(proto.StrVal) == "UDP" || proto.IntVal == 17 {
@@ -680,11 +741,20 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 			udp, _ := udpLayer.(*layers.UDP)
 			if packetSpec.TransportHeader.UDP != nil {
 				ports := packetSpec.TransportHeader.UDP
-				if ports.DstPort != nil {
-					assert.Equal(t, *ports.DstPort, int32(udp.DstPort))
-				}
-				if ports.SrcPort != nil {
-					assert.Equal(t, *ports.SrcPort, int32(udp.SrcPort))
+				if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+					if ports.DstPort != nil {
+						assert.Contains(t, []int32{int32(udp.SrcPort), int32(udp.DstPort)}, *ports.DstPort)
+					}
+					if ports.SrcPort != nil {
+						assert.Contains(t, []int32{int32(udp.SrcPort), int32(udp.DstPort)}, *ports.SrcPort)
+					}
+				} else {
+					if ports.DstPort != nil {
+						assert.Equal(t, *ports.DstPort, int32(udp.DstPort))
+					}
+					if ports.SrcPort != nil {
+						assert.Equal(t, *ports.SrcPort, int32(udp.SrcPort))
+					}
 				}
 			}
 		} else if strings.ToUpper(proto.StrVal) == "ICMP" || proto.IntVal == 1 {


Reviews:
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-12-23T18:24:57Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-12-24T03:18:45Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-02T02:45:00Z
user: login: tnqn
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-03T14:57:56Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-04T19:20:27Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-04T19:36:34Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-08T09:08:48Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-08T15:25:48Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-08T15:27:04Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-08T15:39:56Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-08T15:53:49Z
user: login: antoninbas
body: could we add an e2e test with the `Both` direction?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-08T21:52:45Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-09T03:48:24Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-09T20:39:20Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-16T18:32:55Z
user: login: antoninbas
body: thanks for your ongoing work on this
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-21T21:55:55Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-26T19:20:04Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-26T19:31:55Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-26T19:55:17Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-26T20:10:16Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-28T05:46:32Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-28T05:58:08Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-30T04:30:30Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-30T04:30:45Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T18:52:51Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-01T06:17:07Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-01T06:20:56Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-03T21:51:10Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-04T14:18:56Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-05T08:23:04Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T13:30:24Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T13:33:55Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-07T08:37:27Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-07T08:47:02Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-07T09:48:39Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-07T09:51:51Z
user: login: hangyan
body: Thanks for your work
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-02-17T03:57:05Z
user: login: antoninbas
body: Some small comments but overall LGTM. Thanks for all the work and patience.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T19:51:51Z
user: login: hangyan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-26T03:34:56Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T01:35:55Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T01:36:43Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-27T05:24:03Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T21:22:06Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T03:06:33Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T03:10:37Z
user: login: antoninbas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T00:03:05Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T06:05:55Z
user: login: antoninbas
body: LGTM, thanks for this great contribution and for your patience during the review process

@hangyan do you want to take another look?
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T08:58:49Z
user: login: antoninbas
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T07:33:11Z
user: login: AryanBakliwal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T08:09:44Z

Review comments:
diff_hunk: @@ -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)
path: pkg/agent/packetcapture/packetcapture_controller.go
user: login: antoninbas
body: @hangyan do we need 2 separate capture instances, or a single instance that can capture traffic in both directions?
created_at: 2024-12-23T18:23:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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"`
path: pkg/apis/crd/v1alpha1/types.go
user: login: antoninbas
body: maybe we could have an "enum" with such values: `SourceToDestination`, `DestinationToSource`, `Both` (and name the field `direction`).

What do you think?
cc @tnqn @hongliangl 
created_at: 2024-12-23T18:24:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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)
path: pkg/agent/packetcapture/packetcapture_controller.go
user: login: hangyan
body: one is enough. The bpf code should be updated to support bidirection capture.

@AryanBakliwal You can use tcpdump to check the bpf coded it generated and update ours corresponding
created_at: 2024-12-24T03:18:45Z
author_association: MEMBER
diff_hunk: @@ -214,6 +275,10 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if bidirection {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: this part seems incorrect. If we didn't filter on ports and only on IP, only 4 extra instructions is needed. So it's better to break it down. I'm guessing the error is mostly like caused by the invalid jump distance. 
created_at: 2025-01-02T02:45:00Z
author_association: MEMBER
diff_hunk: @@ -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"`
path: pkg/apis/crd/v1alpha1/types.go
user: login: tnqn
body: Sounds good to me
created_at: 2025-01-03T14:57:55Z
author_association: MEMBER
diff_hunk: @@ -214,6 +275,10 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if bidirection {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Thanks for pointing this out, after breaking it and doing some more changes in `compilePacketFilter`, it now works as expected.
created_at: 2025-01-04T19:20:27Z
author_association: MEMBER
diff_hunk: @@ -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"`
path: pkg/apis/crd/v1alpha1/types.go
user: login: AryanBakliwal
body: Got it, I’ll make the required changes
created_at: 2025-01-04T19:36:33Z
author_association: MEMBER
diff_hunk: @@ -178,7 +244,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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: maybe align the comments after `#`.
created_at: 2025-01-08T08:28:58Z
author_association: MEMBER
diff_hunk: @@ -134,18 +122,96 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
+	// source ip
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: In the future we may also support tcp flags and other layer4 configs, if that happens, we should consider make the current code structure more modularized, or it would be extremely hard to extend this function .  This won't be easy but i suggest to review this part and see if we can do better. 

not sure if we can separate the ip section and ports section apart, call  sub functions to calculate their instruments size and sums up. 
created_at: 2025-01-08T09:02:13Z
author_association: MEMBER
diff_hunk: @@ -173,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"),
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: hangyan
body: better to add a test case for only have `dstPort` and `Both`, that's the real world case as client port is always random.
created_at: 2025-01-08T09:06:56Z
author_association: MEMBER
diff_hunk: @@ -439,6 +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"`
+	// Direction specifies which packets to capture (source -> destination, destination -> source or both).
path: pkg/apis/crd/v1alpha1/types.go
user: login: hangyan
body: you may need to run `make codegen`
created_at: 2025-01-08T09:08:39Z
author_association: MEMBER
diff_hunk: @@ -178,7 +244,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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: sure
created_at: 2025-01-08T15:25:48Z
author_association: MEMBER
diff_hunk: @@ -173,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"),
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: AryanBakliwal
body: makes sense, will add this test case
created_at: 2025-01-08T15:27:04Z
author_association: MEMBER
diff_hunk: @@ -439,6 +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"`
+	// Direction specifies which packets to capture (source -> destination, destination -> source or both).
path: pkg/apis/crd/v1alpha1/types.go
user: login: AryanBakliwal
body: I did ran `make codegen` but it did not make any changes in the `zz_generated.deepcopy.go` file, maybe because it is not a pointer in `PacketCaptureSpec`. 
created_at: 2025-01-08T15:39:56Z
author_association: MEMBER
diff_hunk: @@ -134,18 +122,96 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
+	// source ip
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: I'll try my best to improve the code structure and explore separating the IP and port sections as suggested
created_at: 2025-01-08T15:53:49Z
author_association: MEMBER
diff_hunk: @@ -101,20 +101,8 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
-	// source ip
-	if srcIP != nil {
-		inst = append(inst, loadIPv4SourceAddress)
-		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})
-
-	}
-	// dst ip
-	if dstIP != nil {
-		inst = append(inst, loadIPv4DestinationAddress)
-		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})
-	}
+	srcAddrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:])
+	dstAddrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:])
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: I am not sure I understand this change. Isn't it possible for `srcIP` and `dstIP` to be nil?
created_at: 2025-01-08T21:22:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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"
+)
path: pkg/apis/crd/v1alpha1/types.go
user: login: antoninbas
body: ```suggestion
const (
	CaptureDirectionSourceToDestination CaptureDirection = "SourceToDestination"
	CaptureDirectionDestinationToSource CaptureDirection = "DestinationToSource"
	CaptureDirectionBoth                CaptureDirection = "Both"
)
```
created_at: 2025-01-08T21:29:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3035,7 +3035,10 @@ spec:
                               type: integer
                               minimum: 1
                               maximum: 65535
-
+                direction:
+                  type: string
+                  enum: ["SourceToDestination", "DestinationToSource", "Both"]
+                  default: "SourceToDestination"
path: build/yamls/antrea.yml
user: login: antoninbas
body: you seem to have made that change manually, which is not correct
the source of truth is `build/charts/antrea/crds/packetcapture.yaml`. You need to edit that file and run `make manifest`.
created_at: 2025-01-08T21:33:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -439,6 +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"`
+	// Direction specifies which packets to capture (source -> destination, destination -> source or both).
path: pkg/apis/crd/v1alpha1/types.go
user: login: antoninbas
body: Yes, I think @AryanBakliwal is correct here. A shallow-copy is sufficient for a string, and the `*out = *in` statement in `func (in *PacketCaptureSpec) DeepCopyInto(out *PacketCaptureSpec)` will take care of copying the field value.

The CI job that checks for code generation is also passing right now.
created_at: 2025-01-08T21:42:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,20 +101,8 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
-	// source ip
-	if srcIP != nil {
-		inst = append(inst, loadIPv4SourceAddress)
-		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})
-
-	}
-	// dst ip
-	if dstIP != nil {
-		inst = append(inst, loadIPv4DestinationAddress)
-		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})
-	}
+	srcAddrVal := binary.BigEndian.Uint32(srcIP[len(srcIP)-4:])
+	dstAddrVal := binary.BigEndian.Uint32(dstIP[len(dstIP)-4:])
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: I think the current spec enforced `srcIP` and `dstIP` both cannot be nil, because either Pod or IP will present.  It cloud be pretty easy to remove the restrictions in the crd spec and do a little tweak to make this work.(no the target of the PR)
created_at: 2025-01-09T03:48:24Z
author_association: MEMBER
diff_hunk: @@ -3035,7 +3035,10 @@ spec:
                               type: integer
                               minimum: 1
                               maximum: 65535
-
+                direction:
+                  type: string
+                  enum: ["SourceToDestination", "DestinationToSource", "Both"]
+                  default: "SourceToDestination"
path: build/yamls/antrea.yml
user: login: AryanBakliwal
body: Thanks for the clarification, I'll make the changes in the correct files and regenerate the manifests.
created_at: 2025-01-09T20:39:20Z
author_association: MEMBER
diff_hunk: @@ -134,18 +122,96 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
+	// source ip
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @hangyan I am thinking of refactoring the code as you suggested by modularizing the logic for IP and port comparison by adding helper functions for taking decisions based on direction and other parameters.
I’ve created a function `compareIP`  to handle the conditional logic for building the instruction to compare IP address depending on the direction and which IP address is loaded for comparison (the boolean `isSrc` tells if I have loaded the source or destination IP address)

```
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}
		}
	}
}
```
Similarly a function `comparePort` builds instruction for comparing port
Additional functions to calculate number of `SkipTrue` and `SkipFalse` instructions dynamically

Let me know if this looks better or if you have any suggestions for further improvements.
created_at: 2025-01-16T18:32:55Z
author_association: MEMBER
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		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
+			}
+		}
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: correct me if I'm wrong but this code looks like a perfect duplicate of the code above
you could do something like this:
```go
transport := packet.TransportHeader
portFiltersSize := func() int {
                count := 0
		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
			}
		}
		return count
}
```

Note that I have changed the spelling of `transPort` to `transport`, as the current version makes no sense.
created_at: 2025-01-21T21:41:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		count += 3
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: can you add a comment for this one?

based on your function comment, in the `Both` direction case, we have to repeat the source host and destination host filters. Is that what this is for? This seems "small" to me, as I see the following above:
```
	// src and dst ip
	count += 4
```
but I assume you validated it in your unit tests
created_at: 2025-01-21T21:43:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		count += 3
+
+		transPort := packet.TransportHeader
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: I notice that you are not checking for `packet != nil` here, which doesn't match the above code?
created_at: 2025-01-21T21:44:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -134,18 +178,61 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
+	// 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.
+		inst = append(inst, compareIP(direction, 0, calculateSkipFalse(size, uint8(len(inst)), srcPort, dstPort, direction), true, srcAddrVal, dstAddrVal))
+	}
+	// dst ip
+	if dstIP != nil {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: the code above assumes that `srcIP` and `dstIP` are never nil. There was a discussion about it, which was marked as resolved. We should be consistent. If this is a precondition for the function, then we should not include these nil checks.
created_at: 2025-01-21T21:46:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,67 @@ 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 {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: the way these helper functions are written doesn't make a lot of sense to me. The functions have a generic name, yet they have very specialized implementation that is dependent on a bunch of parameters. To me, the fact that `direction` is a parameter to this function is a bit of a red flag.

overall, I would expect the implementation to be more streamlined. There should be a helper function to generate IP + port matching. We can call the function for the `SourceToDestination` direction. We can also call the function for the `DestinationToSource` direction by swapping src / dst function parameters. For the `Both` direction, the function should just be called twice, and the caller should add the glue to make it work (`or` operator). Am I missing something? Does it have to be much more complex than that?
created_at: 2025-01-21T21:54:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		count += 3
+
+		transPort := packet.TransportHeader
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Thanks for pointing this out, I overlooked it. Will add this check.
created_at: 2025-01-26T19:20:04Z
author_association: MEMBER
diff_hunk: @@ -134,18 +178,61 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 		}
 	}
 
+	// 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.
+		inst = append(inst, compareIP(direction, 0, calculateSkipFalse(size, uint8(len(inst)), srcPort, dstPort, direction), true, srcAddrVal, dstAddrVal))
+	}
+	// dst ip
+	if dstIP != nil {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: You're right, I will remove these nil checks.
created_at: 2025-01-26T19:31:55Z
author_association: MEMBER
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		count += 3
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Yes, this is for the IP check instructions. We only need 3 more in the `Both` case because the source IP address is already loaded and 
```
// (004) ld       [26]                                     # Load 4B at 26 (source address)
```
we jump to compare it with the destination IP address.

I will add a comment for this.
created_at: 2025-01-26T19:55:17Z
author_association: MEMBER
diff_hunk: @@ -214,6 +332,31 @@ func calculateInstructionsSize(packet *crdv1alpha1.Packet) int {
 	// src and dst ip
 	count += 4
 
+	if direction == crdv1alpha1.CaptureDirectionBoth {
+		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
+			}
+		}
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: You're correct, it is indeed a copy. I'll replace it with this function to remove duplication.
Thanks for sharing this.
created_at: 2025-01-26T20:10:16Z
author_association: MEMBER
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: nit: The IP addresses don't match the hex value and the human-readable filter that you have above (should be 10.244.1.2 and 10.244.1.3)
created_at: 2025-01-28T05:27:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: would the code be easier to generate if we jumped to (015) in case this instruction (007) was not a match?

the fact that we know we can skip the second `or` operand in this case is an optimization. Not saying it's not a good optimization, but if using a "fixed" offset makes code generation much easier, maybe we should do it for now.

I'm thinking about the discussion we just had during the community meeting. That being said, I can see why generating the same code as tcpdump is valuable.
created_at: 2025-01-28T05:30:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: @AryanBakliwal Please take a look at this ^

Taking a concrete example:
```
tcpdump -d "ip proto 6 and ((src host 10.244.1.2 and dst host 10.244.1.3) or (src host 10.244.1.3 and dst host 10.244.1.2))"
```
This is the code generated by tcpdump:
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 12
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 12
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 8
(006) ld       [30]
(007) jeq      #0xaf40103       jt 11	jf 12
(008) jeq      #0xaf40103       jt 9	jf 12
(009) ld       [30]
(010) jeq      #0xaf40102       jt 11	jf 12
(011) ret      #262144
(012) ret      #0
```

If we generate this code instead, I believe it will be simpler:
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 12
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 12
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 8
(006) ld       [30]
(007) jeq      #0xaf40103       jt 11	jf 8   <- less efficient than tcpdump's version but easier to implement?
(008) jeq      #0xaf40103       jt 9	jf 12
(009) ld       [30]
(010) jeq      #0xaf40102       jt 11	jf 12
(011) ret      #262144
(012) ret      #0
```
created_at: 2025-01-28T05:58:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @antoninbas Actually, the real problem is the calculation of `skipTrue` (for which I created the function `calculateSkipTrue`) when we want to accept the packet. But `calculateSkipTrue` depends on many parameters [`srcPort`, `dstPort`, `direction`, which port is loaded in the register (`srcPort` or `dstPort`) and if we are calling this function from the first half (instructions that check request) or the second half (instructions that check response)] to calculate the number of instructions to skip to jump to the `returnKeep` instruction. This makes the code generation very complex if generating instruction set exactly like tcpdump.

Taking each case:

1. ICMP (both `srcPort` and `dstPort` are 0)
```
tcpdump -d "ip proto 6 and ((src host 10.244.1.2 and dst host 10.244.1.3) or (src host 10.244.1.3 and dst host 10.244.1.2))"
```
Code generated by tcpdump:
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 12
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 12
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 8
(006) ld       [30]
(007) jeq      #0xaf40103       jt 11	jf 12    <- need to jump to 11 from 7, difference to calculate = 3
(008) jeq      #0xaf40103       jt 9	jf 12
(009) ld       [30]
(010) jeq      #0xaf40102       jt 11	jf 12    <- skipTrue = 0
(011) ret      #262144
(012) ret      #0
```

2. TCP (`srcPort` = 0 and `dstPort` > 0)
```
tcpdump -d "ip proto 6 and ((src host 10.244.1.2 and dst host 10.244.1.3 and dst port 80) or (src host 10.244.1.3 and dst host 10.244.1.2 and src port 80))"
```
Code generated by tcpdump:
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 22
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 22
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 13
(006) ld       [30]
(007) jeq      #0xaf40103       jt 8	jf 22    <- jump to next instruction since port(s) not zero
(008) ldh      [20]
(009) jset     #0x1fff          jt 22	jf 10
(010) ldxb     4*([14]&0xf)
(011) ldh      [x + 16]
(012) jeq      #0x50            jt 21	jf 22    <- need to jump to 21 from 12, difference to calculate = 8
(013) jeq      #0xaf40103       jt 14	jf 22
(014) ld       [30]
(015) jeq      #0xaf40102       jt 16	jf 22
(016) ldh      [20]
(017) jset     #0x1fff          jt 22	jf 18
(018) ldxb     4*([14]&0xf)
(019) ldh      [x + 14]
(020) jeq      #0x50            jt 21	jf 22    <- skipTrue = 0
(021) ret      #262144
(022) ret      #0								 
```
Note that this is similar to when (`srcPort` > 0 and `dstPort` = 0) and (`srcPort` > 0 and `dstPort` > 0).

Since the problem is to jump to the `returnKeep` instruction, why not add it again to mitigate this complex calculation and jump to it easily. For example:

1. ICMP:
Code that can be generated more simply
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 12
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 12
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 8
(006) ld       [30]
(007) jeq      #0xaf40103       jt 8	jf 13    <- just skip to next instruction if true
(008) ret      #262144
(009) jeq      #0xaf40103       jt 10	jf 13
(010) ld       [30]
(011) jeq      #0xaf40102       jt 12	jf 13
(012) ret      #262144
(013) ret      #0
```

2. TCP (`srcPort` = 0 and `dstPort` > 0)
Code that can be generated more simply
```
(000) ldh      [12]
(001) jeq      #0x800           jt 2	jf 23
(002) ldb      [23]
(003) jeq      #0x6             jt 4	jf 23
(004) ld       [26]
(005) jeq      #0xaf40102       jt 6	jf 13
(006) ld       [30]
(007) jeq      #0xaf40103       jt 8	jf 23
(008) ldh      [20]
(009) jset     #0x1fff          jt 23	jf 10
(010) ldxb     4*([14]&0xf)
(011) ldh      [x + 16]
(012) jeq      #0x50            jt 13	jf 23    <- just skip to next instruction if true
(013) ret      #262144
(014) jeq      #0xaf40103       jt 15	jf 23
(015) ld       [30]
(016) jeq      #0xaf40102       jt 17	jf 23
(017) ldh      [20]
(018) jset     #0x1fff          jt 23	jf 19
(019) ldxb     4*([14]&0xf)
(020) ldh      [x + 14]
(021) jeq      #0x50            jt 22	jf 23
(022) ret      #262144
(023) ret      #0								 
```

Let me know what you think about this approach
created_at: 2025-01-30T04:30:30Z
author_association: MEMBER
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Thanks for catching that.
created_at: 2025-01-30T04:30:45Z
author_association: MEMBER
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: Adding an extra return instruction sounds fine to me.

I think what we are going for is some code that looks like this in the end:
```go
if direction == SourceToDest {
        generateFilters(offset, srcIP, dstIP, srcPort, dstPort)
} else if direction == DestToSource {
        generateFilters(offset, dstIP, srcIP, dstPort, srcPort)
} else {
        generateFilters(offset, srcIP, dstIP, srcPort, dstPort)
        // ...
        generateFilters(offset, dstIP, srcIP, dstPort, srcPort)
}
```
So if we can get there and have some generate code that looks reasonable and works, that would be good.
created_at: 2025-01-31T18:52:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,67 @@ 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 {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: I have refactored the code to contain the logic to compare IP and ports in a single function that can be called by interchanging function parameters for `SourceToDestination` and `DestinationToSource`; and called twice for `Both`.
created_at: 2025-02-01T06:17:07Z
author_association: MEMBER
diff_hunk: @@ -169,16 +256,47 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @antoninbas I have made the requested changes and refactored the code as you suggested and as per our discussion. PTAL and let me know if there is anything else to update or add. 
created_at: 2025-02-01T06:20:56Z
author_association: MEMBER
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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.1.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.1.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.1.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.1.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
+
+// For simpler code generation in 'Both' direction, an extra instruction to accept the packet is added after instruction 014.
+// The final instruction set looks like this:
+// (000) ldh      [12]									   # Load 2B at 12 (Ethertype)
+// (001) jeq      #0x800           jt 2	jf 27			   # Ethertype: If IPv4, goto #2, else #26
+// (002) ldb      [23]									   # Load 1B at 23 (IPv4 Protocol)
+// (003) jeq      #0x6             jt 4	jf 27			   # IPv4 Protocol: If TCP, goto #4, #26
+// (004) ld       [26]									   # Load 4B at 26 (source address)
+// (005) jeq      #0xaf40102       jt 6	jf 16			   # If bytes match(10.244.1.2), goto #6, else #15
+// (006) ld       [30]									   # Load 4B at 30 (dest address)
+// (007) jeq      #0xaf40103       jt 8	jf 27			   # If bytes match(10.244.1.3), goto #8, else #26
+// (008) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (009) jset     #0x1fff          jt 27	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 27		   # 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 15	jf 27		   # TCP dst port: If 123, goto #15, else #26
+// (015) ret      #262144								   # MATCH
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: Maybe a stupid question, but if this is the only change we have, it doesn't seem worth it? Why can't we calculate the jump size using `size - curOffset - 1` given that:
1) we always compute the full size up front
2) we always know the current offset ("where we are")
3) the instruction before last is always a `MATCH` return instruction 

What am I missing?
created_at: 2025-02-03T21:51:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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.1.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.1.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.1.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.1.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
+
+// For simpler code generation in 'Both' direction, an extra instruction to accept the packet is added after instruction 014.
+// The final instruction set looks like this:
+// (000) ldh      [12]									   # Load 2B at 12 (Ethertype)
+// (001) jeq      #0x800           jt 2	jf 27			   # Ethertype: If IPv4, goto #2, else #26
+// (002) ldb      [23]									   # Load 1B at 23 (IPv4 Protocol)
+// (003) jeq      #0x6             jt 4	jf 27			   # IPv4 Protocol: If TCP, goto #4, #26
+// (004) ld       [26]									   # Load 4B at 26 (source address)
+// (005) jeq      #0xaf40102       jt 6	jf 16			   # If bytes match(10.244.1.2), goto #6, else #15
+// (006) ld       [30]									   # Load 4B at 30 (dest address)
+// (007) jeq      #0xaf40103       jt 8	jf 27			   # If bytes match(10.244.1.3), goto #8, else #26
+// (008) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (009) jset     #0x1fff          jt 27	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 27		   # 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 15	jf 27		   # TCP dst port: If 123, goto #15, else #26
+// (015) ret      #262144								   # MATCH
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @antoninbas You're right, the jump size can indeed be calculated easily with the available information (full size and current offset). 
The main challenge, however, isn't the calculation itself but rather when to perform it, meaning in which instructions do we need `skipTrue` set to `size - curOffset - 1`. 
And if a helper function is used for calculation, then it depends on which instruction is calling the helper function, what is the direction and what is the value of ports. Adding this extra instruction helps simplify the instruction set generation and avoids unnecessary complexity.

### The challenges I faced:
For request packets in `Both` direction, there are 3 cases (assuming the 3 instructions - dstIP. srcPort and srcPort comparison instructions are calling a helper function to get jump size/`skipTrue` value):
- When both ports are 0, the destination IP comparison instruction performs the calculation to jump to `MATCH`.
- When only one port is 0, the destination IP comparison sets skipTrue to 0, and the comparison for the defined port performs the calculation to jump to `MATCH`.
- When neither port is 0, all comparisons set skipTrue to 0, except for the destination port comparison, which performs the calculation to jump to `MATCH`. 

In `SourceToDestination`, `DestinationToSource` and response packets in `Both`, no calculations are needed, and these 3 instructions (dstIP, srcPort and dstPort comparison) jump to `MATCH` step with `skipTrue` set to 0. 

The helper function that calculates the jump size or returns the value of `skipTrue` requires additional context which is a bunch of parameters such as the calling instruction, direction, ports, size and current offset. Adding this logic in the code makes it difficult to extend in the future if we filter traffic on more parameters and also increases complexity.

Hope that this explanation makes sense and adding an extra `MATCH` instruction seems reasonable. Any suggestions on how we could simplify or approach this differently? I'm open to any ideas you might have for improving the implementation of calculation of `skipTrue` value.
created_at: 2025-02-04T14:18:56Z
author_association: MEMBER
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: in comment section, `else #26` should be `else $27`？
created_at: 2025-02-05T07:10:52Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,67 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+func compareIPPort(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, useSkipFalse bool) []bpf.Instruction {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: this function name is not very straightforward, can you use a new one? we may also need some comments.

Also i think the param `useSkipFalse` maybe misleading, we always `use skip false`, the difference is skipFalse to ret `#0` or skipFalse to some intermediate instructions. 
created_at: 2025-02-05T08:05:59Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,67 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+func compareIPPort(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, useSkipFalse bool) []bpf.Instruction {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @hangyan For function name, I think `generateFilter` or `generateInstructions` can be used. For the bool param, we can use something like `jumpToResponseCheck` or `skipRequestCheck`?
created_at: 2025-02-06T13:30:24Z
author_association: MEMBER
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: This is the example instruction set generated by tcpdump in which we skip to 26. Example of our implementation is below, in which we jump to 27.
created_at: 2025-02-06T13:33:55Z
author_association: MEMBER
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: ok, can you change the comment in that part to `#27`,  or the comment will be misleading.
created_at: 2025-02-07T08:37:27Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,67 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+func compareIPPort(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, useSkipFalse bool) []bpf.Instruction {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: hangyan
body: `compilePacketFilter` call `compareIPPort`, so `compileIPPortFilter` or `genereateIPPortFilterInstructions` should be fine. (compile not compare)

For `useSkipFalse`, seems either one need detailed comment for  documentation. 



created_at: 2025-02-07T08:47:02Z
author_association: MEMBER
diff_hunk: @@ -169,50 +210,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: I'll change this and also check if any other comments need correction 
created_at: 2025-02-07T09:48:39Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,67 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+func compareIPPort(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, useSkipFalse bool) []bpf.Instruction {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: I will change the function and param name, and also add a detailed comment for it.
created_at: 2025-02-07T09:51:50Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: is there a better name than `skipRequestCheck`? Even after reading the comment, the name still doesn't make sense to me.
created_at: 2025-02-25T19:39:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: calculateSkipFalse(srcPort, dstPort)})
+	} else {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
+	}
+
+	// dst ip
+	inst = append(inst, loadIPv4DestinationAddress)
+	inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: dstAddrVal, SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
+
+	if srcPort > 0 || dstPort > 0 {
+		skipTrue := size - curLen - uint8(len(inst)) - 3
+		inst = append(inst, loadIPv4HeaderOffset(skipTrue)...)
+		if srcPort > 0 {
+			inst = append(inst, loadIPv4SourcePort)
+			inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(srcPort), SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
+		}
+		if dstPort > 0 {
+			inst = append(inst, loadIPv4DestinationPort)
+			inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(dstPort), SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
+		}
+
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: remove extra empty line
created_at: 2025-02-25T19:39:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,50 +216,131 @@ func compilePacketFilter(packetSpec *crdv1alpha1.Packet, srcIP, dstIP net.IP) []
 // (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
 
-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 <device> '<filter>' -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.1.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.1.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.1.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.1.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
+
+// For simpler code generation in 'Both' direction, an extra instruction to accept the packet is added after instruction 014.
+// The final instruction set looks like this:
+// (000) ldh      [12]									   # Load 2B at 12 (Ethertype)
+// (001) jeq      #0x800           jt 2	jf 27			   # Ethertype: If IPv4, goto #2, else #27
+// (002) ldb      [23]									   # Load 1B at 23 (IPv4 Protocol)
+// (003) jeq      #0x6             jt 4	jf 27			   # IPv4 Protocol: If TCP, goto #4, #27
+// (004) ld       [26]									   # Load 4B at 26 (source address)
+// (005) jeq      #0xaf40102       jt 6	jf 16			   # If bytes match(10.244.1.2), goto #6, else #16
+// (006) ld       [30]									   # Load 4B at 30 (dest address)
+// (007) jeq      #0xaf40103       jt 8	jf 27			   # If bytes match(10.244.1.3), goto #8, else #27
+// (008) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (009) jset     #0x1fff          jt 27	jf 10		   # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #10, else #27
+// (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 27		   # TCP Source Port: If 123, goto #13, else #27
+// (013) ldh      [x + 16]								   # Load 2B at x+16 (TCP dst port)
+// (014) jeq      #0x7c            jt 15	jf 27		   # TCP dst port: If 123, goto #15, else #27
+// (015) ret      #262144								   # MATCH
+// (016) jeq      #0xaf40103       jt 17	jf 27		   # If bytes match(10.244.1.3), goto #17, else #27
+// (017) ld       [30]									   # Load 4B at 30 (return traffic dest address)
+// (018) jeq      #0xaf40102       jt 19	jf 27		   # If bytes match(10.244.1.2), goto #19, else #27
+// (019) ldh      [20]									   # Load 2B at 20 (13b Fragment Offset)
+// (020) jset     #0x1fff          jt 27	jf 21		   # Use 0x1fff as a mask for fragment offset; If fragment offset != 0, #21, else #27
+// (021) ldxb     4*([14]&0xf)							   # x = IP header length
+// (022) ldh      [x + 14]								   # Load 2B at x+14 (TCP Source Port)
+// (023) jeq      #0x7c            jt 24	jf 27		   # TCP Source Port: If 124, goto #24, else #27
+// (024) ldh      [x + 16]								   # Load 2B at x+16 (TCP dst port)
+// (025) jeq      #0x7b            jt 26	jf 27		   # TCP dst port: If 123, goto #26, else #27
+// (026) ret      #262144								   # MATCH
+// (027) ret      #0									   # NOMATCH
+
+func calculateInstructionsSize(packet *crdv1alpha1.Packet, direction crdv1alpha1.CaptureDirection) int {
 	count := 0
 	// load ethertype
 	count++
 	// ip check
 	count++
 
+	// src and dst ip
+	count += 4
+
 	if packet != nil {
 		// protocol check
 		if packet.Protocol != nil {
 			count += 2
 		}
-		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
+		transport := packet.TransportHeader
+		portFiltersSize := func() int {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: the way you wrote it, there is no need to call the function twice I think. So maybe use it like this:
```go
portFiltersSize := func() int {
    // ...
}()
// ...
count += portFiltersSize
```
created_at: 2025-02-25T19:46:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,11 +209,133 @@ 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,
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: antoninbas
body: not introduced by you, but do you think you could change the test code so that `testSrcPort` and `testDstPort` use 2 different values? Maybe 12345 for the source and 80 for the destination.
created_at: 2025-02-25T19:48:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,11 +209,133 @@ 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.CaptureDirectionDestinationToSource,
+			},
+			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
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: antoninbas
body: again, this was copied and paster from existing code, but what does the comment "port 23" mean? The port value is not 23, is it referring to the jump destination (but there are less than 23 instructions...). @hangyan is that your comment?
created_at: 2025-02-25T19:50:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,11 +209,133 @@ 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.CaptureDirectionDestinationToSource,
+			},
+			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
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: hangyan
body: yeah i think so. comments should be updated.
created_at: 2025-02-26T03:34:56Z
author_association: MEMBER
diff_hunk: @@ -173,11 +209,133 @@ 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,
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: AryanBakliwal
body: Sure, I will change the code for tests.
created_at: 2025-02-27T01:35:55Z
author_association: MEMBER
diff_hunk: @@ -173,11 +209,133 @@ 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.CaptureDirectionDestinationToSource,
+			},
+			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
path: pkg/agent/packetcapture/capture/bpf_test.go
user: login: AryanBakliwal
body: I will update the comments.
created_at: 2025-02-27T01:36:43Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: @antoninbas Since it tells if we need to jump to return traffic check part of the filter, how about `checkIfResponse` or `jumpToResponseCheck` ?
created_at: 2025-02-27T05:24:03Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: calculateSkipFalse(srcPort, dstPort)})
+	} else {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: `len(inst)` is actually 0 here, so we could remove it?

or maybe we could define this function:
```go
// calculate skip size to jump to the final instruction (NO MATCH)
skipToEnd := func() uint8 {
        return size - curLen - uint8(len(inst)) - 2
}
```

then use `SkipFalse: skipToEnd()` everywhere in this function. I think it would make the code even more readable. What do you think?
created_at: 2025-02-27T21:11:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: IMO the current variable name is the "opposite" of what it should be almost.
Maybe I would go with `needsOtherTrafficDirectionCheck` as a drop-in replacement for `skipRequestCheck`?
created_at: 2025-02-27T21:22:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: calculateSkipFalse(srcPort, dstPort)})
+	} else {
+		inst = append(inst, bpf.JumpIf{Cond: bpf.JumpEqual, Val: srcAddrVal, SkipTrue: 0, SkipFalse: size - curLen - uint8(len(inst)) - 2})
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Using this function is better, I will make this change.
created_at: 2025-02-28T03:06:33Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: `needsOtherTrafficDirectionCheck` makes sense. Or maybe `needsReturnTrafficDirectionCheck` to be more specific?
created_at: 2025-02-28T03:10:37Z
author_association: MEMBER
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: antoninbas
body: I wouldn't assume that we check one direction before the other. That's why I would recommend sticking with "other direction".
created_at: 2025-03-01T00:03:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,11 +72,74 @@ func compareProtocol(protocol uint32, skipTrue, skipFalse uint8) bpf.Instruction
 	return bpf.JumpIf{Cond: bpf.JumpEqual, Val: protocol, SkipTrue: skipTrue, SkipFalse: skipFalse}
 }
 
+func calculateSkipFalse(srcPort, dstPort uint16) uint8 {
+	var count uint8
+	// load dstIP and compare
+	count += 2
+
+	if srcPort > 0 || dstPort > 0 {
+		// load fragment offset
+		count += 3
+
+		if srcPort > 0 {
+			count += 2
+		}
+		if dstPort > 0 {
+			count += 2
+		}
+	}
+	// ret keep
+	count += 1
+
+	return count
+}
+
+// Generates IP address and port matching instructions
+func compileIPPortFilter(srcAddrVal, dstAddrVal uint32, size, curLen uint8, srcPort, dstPort uint16, skipRequestCheck bool) []bpf.Instruction {
+	inst := []bpf.Instruction{}
+
+	// from here we need to check the inst length to calculate skipFalse. If no protocol is set, there will be no related bpf instructions.
+	
+	// In the previous instruction, we load the packet's source IP. We then compare it with the source IP from the packet spec to determine if 
+	// the packet is a request (from source to destination). When capturing packets in Both directions, if the source IPs do not match, 
+	// we need to check if the packet is a response (from destination to source). In this case, we skip to the instruction where we compare the 
+	// loaded source IP with the destination IP from the packet spec. The skipRequestCheck flag indicates whether we need to call calculateSkipFalse 
+	// to determine how many instructions to skip before checking for response packets or just skip to the last instruction (drop packet).
+	if skipRequestCheck {
path: pkg/agent/packetcapture/capture/bpf.go
user: login: AryanBakliwal
body: Changed the name and also rephrased the comment to better reflect its purpose. 
created_at: 2025-03-01T06:05:55Z
author_association: MEMBER
diff_hunk: @@ -649,8 +696,13 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 		ipLayer := packet.Layer(layers.LayerTypeIPv4)
 		require.NotNil(t, ipLayer)
 		ip, _ := ipLayer.(*layers.IPv4)
-		assert.Equal(t, srcIP.String(), ip.SrcIP.String())
-		assert.Equal(t, dstIP.String(), ip.DstIP.String())
+		if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.SrcIP.String())
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.DstIP.String())
+		} else {
path: test/e2e/packetcapture_test.go
user: login: antoninbas
body: this code would be wrong for the `DestinationToSource` direction. Could you add a single e2e test case for the `DestinationToSource` direction as a follow-up PR, and update this verification function accordingly?
created_at: 2025-03-10T07:32:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -667,11 +719,20 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 			tcp, _ := tcpLayer.(*layers.TCP)
 			if packetSpec.TransportHeader.TCP != nil {
 				ports := packetSpec.TransportHeader.TCP
-				if ports.DstPort != nil {
-					assert.Equal(t, *ports.DstPort, int32(tcp.DstPort))
-				}
-				if ports.SrcPort != nil {
-					assert.Equal(t, *ports.SrcPort, int32(tcp.SrcPort))
+				if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+					if ports.DstPort != nil {
+						assert.Contains(t, []int32{int32(tcp.SrcPort), int32(tcp.DstPort)}, *ports.DstPort)
+					}
+					if ports.SrcPort != nil {
+						assert.Contains(t, []int32{int32(tcp.SrcPort), int32(tcp.DstPort)}, *ports.SrcPort)
+					}
+				} else {
path: test/e2e/packetcapture_test.go
user: login: antoninbas
body: ditto
created_at: 2025-03-10T07:32:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -649,8 +696,13 @@ func verifyPacketFile(t *testing.T, pc *crdv1alpha1.PacketCapture, reader io.Rea
 		ipLayer := packet.Layer(layers.LayerTypeIPv4)
 		require.NotNil(t, ipLayer)
 		ip, _ := ipLayer.(*layers.IPv4)
-		assert.Equal(t, srcIP.String(), ip.SrcIP.String())
-		assert.Equal(t, dstIP.String(), ip.DstIP.String())
+		if pc.Spec.Direction == crdv1alpha1.CaptureDirectionBoth {
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.SrcIP.String())
+			assert.Contains(t, []string{srcIP.String(), dstIP.String()}, ip.DstIP.String())
+		} else {
path: test/e2e/packetcapture_test.go
user: login: AryanBakliwal
body: Sure, will add it
created_at: 2025-03-10T08:09:44Z
author_association: MEMBER

PR comments:
user: login: AryanBakliwal
created_at: 2024-12-23T04:20:04Z
body: @hangyan please let me know what you think about this
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-01-01T13:13:56Z
body: Hey @hangyan @antoninbas, I made the changes in the bpf code according to the one generated by tcpdump. 

When I try to test the bidirectional packet capture, it fails with this log
```
E1229 11:44:58.723258       1 packetcapture_controller.go:424] "PacketCapture failed capturing packets" err="invalid argument" name="pc-test"
```

Could you please take a look and help me identify what might be going wrong?

Also, golangci-lint is giving me this error even though I have changed the `Capture` method everywhere.
```
===> Running golangci (linux) <===
===> Running golangci (windows) <===
cmd/antrea-agent/agent.go:64:2: could not import antrea.io/antrea/pkg/agent/packetcapture (-: # antrea.io/antrea/pkg/agent/packetcapture
pkg/agent/packetcapture/packetcapture_controller.go:170:23: cannot use capture (variable of type *capture.pcapCapture) as PacketCapturer value in assignment: *capture.pcapCapture does not implement PacketCapturer (wrong type for method Capture)
                have Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet) (chan gopacket.Packet, error)
                want Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet, bool) (chan gopacket.Packet, error)) (typecheck)
        "antrea.io/antrea/pkg/agent/packetcapture"
        ^
pkg/agent/packetcapture/capture_interface.go:1: : # antrea.io/antrea/pkg/agent/packetcapture [antrea.io/antrea/pkg/agent/packetcapture.test]
pkg/agent/packetcapture/packetcapture_controller.go:170:23: cannot use capture (variable of type *capture.pcapCapture) as PacketCapturer value in assignment: *capture.pcapCapture does not implement PacketCapturer (wrong type for method Capture)
                have Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet) (chan gopacket.Packet, error)
                want Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet, bool) (chan gopacket.Packet, error) (typecheck)
// Copyright 2024 Antrea Authors.
make: *** [Makefile:329: golangci] Error 1
```
author_association: MEMBER
user: login: hangyan
created_at: 2025-01-02T02:29:30Z
body: > Hey @hangyan @antoninbas, I made the changes in the bpf code according to the one generated by tcpdump.
> 
> When I try to test the bidirectional packet capture, it fails with this log
> 
> ```
> E1229 11:44:58.723258       1 packetcapture_controller.go:424] "PacketCapture failed capturing packets" err="invalid argument" name="pc-test"
> ```
> 
> Could you please take a look and help me identify what might be going wrong?
> 
> Also, golangci-lint is giving me this error even though I have changed the `Capture` method everywhere.
> 
> ```
> ===> Running golangci (linux) <===
> ===> Running golangci (windows) <===
> cmd/antrea-agent/agent.go:64:2: could not import antrea.io/antrea/pkg/agent/packetcapture (-: # antrea.io/antrea/pkg/agent/packetcapture
> pkg/agent/packetcapture/packetcapture_controller.go:170:23: cannot use capture (variable of type *capture.pcapCapture) as PacketCapturer value in assignment: *capture.pcapCapture does not implement PacketCapturer (wrong type for method Capture)
>                 have Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet) (chan gopacket.Packet, error)
>                 want Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet, bool) (chan gopacket.Packet, error)) (typecheck)
>         "antrea.io/antrea/pkg/agent/packetcapture"
>         ^
> pkg/agent/packetcapture/capture_interface.go:1: : # antrea.io/antrea/pkg/agent/packetcapture [antrea.io/antrea/pkg/agent/packetcapture.test]
> pkg/agent/packetcapture/packetcapture_controller.go:170:23: cannot use capture (variable of type *capture.pcapCapture) as PacketCapturer value in assignment: *capture.pcapCapture does not implement PacketCapturer (wrong type for method Capture)
>                 have Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet) (chan gopacket.Packet, error)
>                 want Capture(context.Context, string, int, "net".IP, "net".IP, *"antrea.io/antrea/pkg/apis/crd/v1alpha1".Packet, bool) (chan gopacket.Packet, error) (typecheck)
> // Copyright 2024 Antrea Authors.
> make: *** [Makefile:329: golangci] Error 1
> ```

this error was reported by golangci on windows, we have a `pcap_unsupported.go` for windows/... interface, it's an non-op since we didn't support this on windows yet, you should change that too.

author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-01-05T18:52:56Z
body: I’ve added the `direction` field with enum values (`SourceToDestination`, `DestinationToSource`, and `Both`) and set the default to `SourceToDestination`. Additionally, I’ve updated the YAML manifests, packet capture guide and tests accordingly.

Please let me know if any adjustments are needed or if there's anything else I should update or add.

### Captured packets
ICMP (SourceToDestination)
![Screenshot from 2025-01-06 00-07-02](https://github.com/user-attachments/assets/4c0f6253-6bf8-486b-b731-96f98d6db5f1)

ICMP (DestinationToSource)
![Screenshot from 2025-01-06 00-14-28](https://github.com/user-attachments/assets/6dae03cc-4679-4afa-ad78-81ec5207eb0d)

ICMP (Both)
![Screenshot from 2025-01-06 00-17-58](https://github.com/user-attachments/assets/9de5934f-55a3-401e-93b7-30d7dd144295)

TCP (SourceToDestination)
![image](https://github.com/user-attachments/assets/cfd1e30d-5112-4f7f-84f0-4a08cd028b80)

TCP (DestinationToSource)
![image](https://github.com/user-attachments/assets/d625a4a1-6c09-443c-998d-1b2c1999de82)

TCP (Both)
![image](https://github.com/user-attachments/assets/2cad9977-fa85-4e7b-b6db-74a8618cf8e1)


author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-01-19T22:21:13Z
body: @antoninbas @hangyan I have refactored the `compilePacketFilter` function, added a test case for `dstPort` and `Both` in `bpf_test.go`, generated the manifests by running `make manifest` and added an e2e test with the `Both` direction. 
Could you please review the changes? Let me know if there’s anything else I can improve.
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-02-07T20:26:59Z
body: @antoninbas @hangyan Changed the function and param name, and added some comments. Please let me know if there’s anything else I should take care of.
author_association: MEMBER
user: login: luolanzone
created_at: 2025-02-25T14:40:42Z
body: Hi @AryanBakliwal please re-base to the latest main branch and squash all your changes into one commit. Thanks.
author_association: CONTRIBUTOR
user: login: antoninbas
created_at: 2025-03-06T09:00:09Z
body: @AryanBakliwal I just started the Github workflows, let's see if there is any test or linting failure
author_association: CONTRIBUTOR
user: login: hangyan
created_at: 2025-03-06T09:00:24Z
body: > LGTM, thanks for this great contribution and for your patience during the review process
> 
> @hangyan do you want to take another look?

sure.  will do it now and give my feedback soon
author_association: MEMBER
user: login: hangyan
created_at: 2025-03-06T09:12:14Z
body: LGTM.

Thanks @AryanBakliwal 
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-03-06T09:13:43Z
body: Thank you @antoninbas and @hangyan for guiding me through this. Your support made it much easier :) 
author_association: MEMBER
user: login: antoninbas
created_at: 2025-03-06T12:22:03Z
body: /test-all
author_association: CONTRIBUTOR
user: login: antoninbas
created_at: 2025-03-06T12:24:30Z
body: @AryanBakliwal One if your new e2e tests has failed in CI. I'm copying the logs below for convenience. PTAL.

<details>
<summary>Logs for failed test</summary>

```
2025-03-06T09:55:13.0928191Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.5665773Z     packetcapture_test.go:581: CR status not match, actual: {NumberCaptured:5 FilePath:sftp://10.96.206.31:22/upload/ipv4-tcp-both.pcapng Conditions:[{Type:PacketCaptureStarted Status:True LastTransitionTime:2025-03-06 09:55:13 +0000 UTC Reason:Started Message:} {Type:PacketCaptureComplete Status:True LastTransitionTime:2025-03-06 09:55:14 +0000 UTC Reason:Succeed Message:} {Type:PacketCaptureFileUploaded Status:True LastTransitionTime:2025-03-06 09:55:14 +0000 UTC Reason:Succeed Message:}]}, expected: {NumberCaptured:10 FilePath:sftp://10.96.206.31:22/upload/ipv4-tcp.pcapng Conditions:[{Type:PacketCaptureStarted Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Started Message:} {Type:PacketCaptureComplete Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Succeed Message:} {Type:PacketCaptureFileUploaded Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Succeed Message:}]}
2025-03-06T09:55:14.6059211Z I0306 09:55:14.605566   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-tcp-both.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:14.6060486Z     packetcapture_test.go:699: 
2025-03-06T09:55:14.6061597Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:699
2025-03-06T09:55:14.6063285Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6065129Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6066262Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6067020Z         	            	expected: "10.244.2.52"
2025-03-06T09:55:14.6067893Z         	            	actual  : "10.244.0.115"
2025-03-06T09:55:14.6068216Z         	            	
2025-03-06T09:55:14.6068671Z         	            	Diff:
2025-03-06T09:55:14.6069069Z         	            	--- Expected
2025-03-06T09:55:14.6069508Z         	            	+++ Actual
2025-03-06T09:55:14.6069859Z         	            	@@ -1 +1 @@
2025-03-06T09:55:14.6070217Z         	            	-10.244.2.52
2025-03-06T09:55:14.6070573Z         	            	+10.244.0.115
2025-03-06T09:55:14.6071187Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6071826Z     packetcapture_test.go:700: 
2025-03-06T09:55:14.6072579Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:700
2025-03-06T09:55:14.6074019Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6075207Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6075904Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6076481Z         	            	expected: "10.244.0.115"
2025-03-06T09:55:14.6077124Z         	            	actual  : "10.244.2.52"
2025-03-06T09:55:14.6077868Z         	            	
2025-03-06T09:55:14.6078287Z         	            	Diff:
2025-03-06T09:55:14.6078637Z         	            	--- Expected
2025-03-06T09:55:14.6079211Z         	            	+++ Actual
2025-03-06T09:55:14.6079690Z         	            	@@ -1 +1 @@
2025-03-06T09:55:14.6080143Z         	            	-10.244.0.115
2025-03-06T09:55:14.6080645Z         	            	+10.244.2.52
2025-03-06T09:55:14.6081466Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6081961Z     packetcapture_test.go:718: 
2025-03-06T09:55:14.6082648Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:718
2025-03-06T09:55:14.6083801Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6084893Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6085317Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6085765Z         	            	expected: 80
2025-03-06T09:55:14.6086399Z         	            	actual  : 49834
2025-03-06T09:55:14.6087969Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6088858Z     packetcapture_test.go:699: 
2025-03-06T09:55:14.6089458Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:699
2025-03-06T09:55:14.6090442Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6091540Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6092163Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6092598Z         	            	expected: "10.244.2.52"
2025-03-06T09:55:14.6093046Z         	            	actual  : "10.244.0.115"
2025-03-06T09:55:14.6093488Z         	            	
2025-03-06T09:55:14.6093773Z         	            	Diff:
2025-03-06T09:55:14.6094116Z         	            	--- Expected
2025-03-06T09:55:14.6094462Z         	            	+++ Actual
2025-03-06T09:55:14.6094907Z         	            	@@ -1 +1 @@
2025-03-06T09:55:14.6095256Z         	            	-10.244.2.52
2025-03-06T09:55:14.6095599Z         	            	+10.244.0.115
2025-03-06T09:55:14.6096205Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6096819Z     packetcapture_test.go:700: 
2025-03-06T09:55:14.6097693Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:700
2025-03-06T09:55:14.6099330Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6101419Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6102254Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6102985Z         	            	expected: "10.244.0.115"
2025-03-06T09:55:14.6103580Z         	            	actual  : "10.244.2.52"
2025-03-06T09:55:14.6103895Z         	            	
2025-03-06T09:55:14.6104310Z         	            	Diff:
2025-03-06T09:55:14.6104693Z         	            	--- Expected
2025-03-06T09:55:14.6105254Z         	            	+++ Actual
2025-03-06T09:55:14.6105776Z         	            	@@ -1 +1 @@
2025-03-06T09:55:14.6106253Z         	            	-10.244.0.115
2025-03-06T09:55:14.6106793Z         	            	+10.244.2.52
2025-03-06T09:55:14.6108147Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6109011Z     packetcapture_test.go:718: 
2025-03-06T09:55:14.6110100Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:718
2025-03-06T09:55:14.6111940Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6113729Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6114483Z         	Error:      	Not equal: 
2025-03-06T09:55:14.6115090Z         	            	expected: 80
2025-03-06T09:55:14.6115957Z         	            	actual  : 49834
2025-03-06T09:55:14.6116999Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6119002Z     packetcapture_test.go:598: 
2025-03-06T09:55:14.6120589Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
2025-03-06T09:55:14.6122658Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
2025-03-06T09:55:14.6123398Z         	Error:      	Received unexpected error:
2025-03-06T09:55:14.6124333Z         	            	EOF
2025-03-06T09:55:14.6126047Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
2025-03-06T09:55:14.6127161Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp
2025-03-06T09:55:16.1084962Z I0306 09:55:16.108202   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-tcp.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:16.1149248Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/non-existing-pod
2025-03-06T09:55:17.1262610Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/invalid-host-public-key
2025-03-06T09:55:23.4110071Z I0306 09:55:23.410656   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-icmp.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:28.4360245Z I0306 09:55:28.435537   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-icmp-timeout.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:34.6221684Z I0306 09:55:34.621816   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-udp.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:39.4440890Z I0306 09:55:39.443728   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/invalid-host-public-key.pcapng" from Pod kube-system/antrea-agent-9rqsg
2025-03-06T09:55:52.4711478Z === NAME  TestPacketCapture
2025-03-06T09:55:52.4713224Z     fixtures.go:352: Exporting test logs to '/home/runner/work/antrea/antrea/log/TestPacketCapture/beforeTeardown.Mar06-09-55-52'
2025-03-06T09:55:55.2982465Z     fixtures.go:523: Deleting 'testpacketcapture-1m63oz7o' K8s Namespace
2025-03-06T09:55:55.3026064Z I0306 09:55:55.302338   26834 framework.go:882] Deleting Namespace testpacketcapture-1m63oz7o took 4.302301ms
2025-03-06T09:55:55.3031550Z --- FAIL: TestPacketCapture (52.69s)
2025-03-06T09:55:55.3033334Z     --- FAIL: TestPacketCapture/testPacketCaptureBasic (42.40s)
2025-03-06T09:55:55.3034117Z         --- FAIL: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic (0.00s)
2025-03-06T09:55:55.3035120Z             --- FAIL: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both (1.52s)
2025-03-06T09:55:55.3036220Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp (1.50s)
2025-03-06T09:55:55.3038200Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/non-existing-pod (1.01s)
2025-03-06T09:55:55.3039872Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-icmp (10.32s)
2025-03-06T09:55:55.3041535Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-icmp-timeout (15.35s)
2025-03-06T09:55:55.3043277Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-udp (21.54s)
2025-03-06T09:55:55.3045166Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/invalid-host-public-key (22.32s)
```

</details>
author_association: CONTRIBUTOR
user: login: AryanBakliwal
created_at: 2025-03-06T12:36:33Z
body: > @AryanBakliwal One if your new e2e tests has failed in CI. I'm copying the logs below for convenience. PTAL.
> 
> 
> 
> <details>
> 
> <summary>Logs for failed test</summary>
> 
> 
> 
> ```
> 
> 2025-03-06T09:55:13.0928191Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.5665773Z     packetcapture_test.go:581: CR status not match, actual: {NumberCaptured:5 FilePath:sftp://10.96.206.31:22/upload/ipv4-tcp-both.pcapng Conditions:[{Type:PacketCaptureStarted Status:True LastTransitionTime:2025-03-06 09:55:13 +0000 UTC Reason:Started Message:} {Type:PacketCaptureComplete Status:True LastTransitionTime:2025-03-06 09:55:14 +0000 UTC Reason:Succeed Message:} {Type:PacketCaptureFileUploaded Status:True LastTransitionTime:2025-03-06 09:55:14 +0000 UTC Reason:Succeed Message:}]}, expected: {NumberCaptured:10 FilePath:sftp://10.96.206.31:22/upload/ipv4-tcp.pcapng Conditions:[{Type:PacketCaptureStarted Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Started Message:} {Type:PacketCaptureComplete Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Succeed Message:} {Type:PacketCaptureFileUploaded Status:True LastTransitionTime:0001-01-01 00:00:00 +0000 UTC Reason:Succeed Message:}]}
> 
> 2025-03-06T09:55:14.6059211Z I0306 09:55:14.605566   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-tcp-both.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:14.6060486Z     packetcapture_test.go:699: 
> 
> 2025-03-06T09:55:14.6061597Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:699
> 
> 2025-03-06T09:55:14.6063285Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6065129Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6066262Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6067020Z         	            	expected: "10.244.2.52"
> 
> 2025-03-06T09:55:14.6067893Z         	            	actual  : "10.244.0.115"
> 
> 2025-03-06T09:55:14.6068216Z         	            	
> 
> 2025-03-06T09:55:14.6068671Z         	            	Diff:
> 
> 2025-03-06T09:55:14.6069069Z         	            	--- Expected
> 
> 2025-03-06T09:55:14.6069508Z         	            	+++ Actual
> 
> 2025-03-06T09:55:14.6069859Z         	            	@@ -1 +1 @@
> 
> 2025-03-06T09:55:14.6070217Z         	            	-10.244.2.52
> 
> 2025-03-06T09:55:14.6070573Z         	            	+10.244.0.115
> 
> 2025-03-06T09:55:14.6071187Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6071826Z     packetcapture_test.go:700: 
> 
> 2025-03-06T09:55:14.6072579Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:700
> 
> 2025-03-06T09:55:14.6074019Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6075207Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6075904Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6076481Z         	            	expected: "10.244.0.115"
> 
> 2025-03-06T09:55:14.6077124Z         	            	actual  : "10.244.2.52"
> 
> 2025-03-06T09:55:14.6077868Z         	            	
> 
> 2025-03-06T09:55:14.6078287Z         	            	Diff:
> 
> 2025-03-06T09:55:14.6078637Z         	            	--- Expected
> 
> 2025-03-06T09:55:14.6079211Z         	            	+++ Actual
> 
> 2025-03-06T09:55:14.6079690Z         	            	@@ -1 +1 @@
> 
> 2025-03-06T09:55:14.6080143Z         	            	-10.244.0.115
> 
> 2025-03-06T09:55:14.6080645Z         	            	+10.244.2.52
> 
> 2025-03-06T09:55:14.6081466Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6081961Z     packetcapture_test.go:718: 
> 
> 2025-03-06T09:55:14.6082648Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:718
> 
> 2025-03-06T09:55:14.6083801Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6084893Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6085317Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6085765Z         	            	expected: 80
> 
> 2025-03-06T09:55:14.6086399Z         	            	actual  : 49834
> 
> 2025-03-06T09:55:14.6087969Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6088858Z     packetcapture_test.go:699: 
> 
> 2025-03-06T09:55:14.6089458Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:699
> 
> 2025-03-06T09:55:14.6090442Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6091540Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6092163Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6092598Z         	            	expected: "10.244.2.52"
> 
> 2025-03-06T09:55:14.6093046Z         	            	actual  : "10.244.0.115"
> 
> 2025-03-06T09:55:14.6093488Z         	            	
> 
> 2025-03-06T09:55:14.6093773Z         	            	Diff:
> 
> 2025-03-06T09:55:14.6094116Z         	            	--- Expected
> 
> 2025-03-06T09:55:14.6094462Z         	            	+++ Actual
> 
> 2025-03-06T09:55:14.6094907Z         	            	@@ -1 +1 @@
> 
> 2025-03-06T09:55:14.6095256Z         	            	-10.244.2.52
> 
> 2025-03-06T09:55:14.6095599Z         	            	+10.244.0.115
> 
> 2025-03-06T09:55:14.6096205Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6096819Z     packetcapture_test.go:700: 
> 
> 2025-03-06T09:55:14.6097693Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:700
> 
> 2025-03-06T09:55:14.6099330Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6101419Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6102254Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6102985Z         	            	expected: "10.244.0.115"
> 
> 2025-03-06T09:55:14.6103580Z         	            	actual  : "10.244.2.52"
> 
> 2025-03-06T09:55:14.6103895Z         	            	
> 
> 2025-03-06T09:55:14.6104310Z         	            	Diff:
> 
> 2025-03-06T09:55:14.6104693Z         	            	--- Expected
> 
> 2025-03-06T09:55:14.6105254Z         	            	+++ Actual
> 
> 2025-03-06T09:55:14.6105776Z         	            	@@ -1 +1 @@
> 
> 2025-03-06T09:55:14.6106253Z         	            	-10.244.0.115
> 
> 2025-03-06T09:55:14.6106793Z         	            	+10.244.2.52
> 
> 2025-03-06T09:55:14.6108147Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6109011Z     packetcapture_test.go:718: 
> 
> 2025-03-06T09:55:14.6110100Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:718
> 
> 2025-03-06T09:55:14.6111940Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6113729Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6114483Z         	Error:      	Not equal: 
> 
> 2025-03-06T09:55:14.6115090Z         	            	expected: 80
> 
> 2025-03-06T09:55:14.6115957Z         	            	actual  : 49834
> 
> 2025-03-06T09:55:14.6116999Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6119002Z     packetcapture_test.go:598: 
> 
> 2025-03-06T09:55:14.6120589Z         	Error Trace:	/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:598
> 
> 2025-03-06T09:55:14.6122658Z         	            				/home/runner/work/antrea/antrea/test/e2e/packetcapture_test.go:473
> 
> 2025-03-06T09:55:14.6123398Z         	Error:      	Received unexpected error:
> 
> 2025-03-06T09:55:14.6124333Z         	            	EOF
> 
> 2025-03-06T09:55:14.6126047Z         	Test:       	TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both
> 
> 2025-03-06T09:55:14.6127161Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp
> 
> 2025-03-06T09:55:16.1084962Z I0306 09:55:16.108202   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-tcp.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:16.1149248Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/non-existing-pod
> 
> 2025-03-06T09:55:17.1262610Z === CONT  TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/invalid-host-public-key
> 
> 2025-03-06T09:55:23.4110071Z I0306 09:55:23.410656   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-icmp.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:28.4360245Z I0306 09:55:28.435537   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-icmp-timeout.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:34.6221684Z I0306 09:55:34.621816   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/ipv4-udp.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:39.4440890Z I0306 09:55:39.443728   26834 framework.go:2950] Copying file "/tmp/antrea/packetcapture/packets/invalid-host-public-key.pcapng" from Pod kube-system/antrea-agent-9rqsg
> 
> 2025-03-06T09:55:52.4711478Z === NAME  TestPacketCapture
> 
> 2025-03-06T09:55:52.4713224Z     fixtures.go:352: Exporting test logs to '/home/runner/work/antrea/antrea/log/TestPacketCapture/beforeTeardown.Mar06-09-55-52'
> 
> 2025-03-06T09:55:55.2982465Z     fixtures.go:523: Deleting 'testpacketcapture-1m63oz7o' K8s Namespace
> 
> 2025-03-06T09:55:55.3026064Z I0306 09:55:55.302338   26834 framework.go:882] Deleting Namespace testpacketcapture-1m63oz7o took 4.302301ms
> 
> 2025-03-06T09:55:55.3031550Z --- FAIL: TestPacketCapture (52.69s)
> 
> 2025-03-06T09:55:55.3033334Z     --- FAIL: TestPacketCapture/testPacketCaptureBasic (42.40s)
> 
> 2025-03-06T09:55:55.3034117Z         --- FAIL: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic (0.00s)
> 
> 2025-03-06T09:55:55.3035120Z             --- FAIL: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp-both (1.52s)
> 
> 2025-03-06T09:55:55.3036220Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-tcp (1.50s)
> 
> 2025-03-06T09:55:55.3038200Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/non-existing-pod (1.01s)
> 
> 2025-03-06T09:55:55.3039872Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-icmp (10.32s)
> 
> 2025-03-06T09:55:55.3041535Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-icmp-timeout (15.35s)
> 
> 2025-03-06T09:55:55.3043277Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/ipv4-udp (21.54s)
> 
> 2025-03-06T09:55:55.3045166Z             --- PASS: TestPacketCapture/testPacketCaptureBasic/testPacketCaptureBasic/invalid-host-public-key (22.32s)
> 
> ```
> 
> 
> 
> </details>

I'll look into it.
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-03-07T14:34:27Z
body: /test-all
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-03-08T11:55:30Z
body: /test-e2e
author_association: MEMBER
user: login: AryanBakliwal
created_at: 2025-03-08T12:48:29Z
body: @antoninbas the e2e test is passing now
author_association: MEMBER
user: login: antoninbas
created_at: 2025-03-09T21:15:31Z
body: /test-all
author_association: CONTRIBUTOR